claude-nomad 0.65.0 → 0.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/README.md +3 -2
- package/dist/nomad.mjs +1424 -1051
- package/package.json +1 -1
package/dist/nomad.mjs
CHANGED
|
@@ -51,19 +51,34 @@ function isSecretFileName(name) {
|
|
|
51
51
|
const stripped = stripTrailingDotsAndWhitespace(name);
|
|
52
52
|
return SECRET_FILE_PATTERNS.some((re) => re.test(stripped));
|
|
53
53
|
}
|
|
54
|
-
function
|
|
54
|
+
function matchDeniedName(blockSet, name) {
|
|
55
55
|
const stripped = stripTrailingDotsAndWhitespace(name);
|
|
56
|
-
|
|
56
|
+
for (const candidate of [name, name.toLowerCase(), stripped, stripped.toLowerCase()]) {
|
|
57
|
+
if (blockSet.has(candidate)) return { axis: "name", entry: candidate };
|
|
58
|
+
}
|
|
59
|
+
return isSecretFileName(name) ? { axis: "shape" } : null;
|
|
60
|
+
}
|
|
61
|
+
function isDeniedName(blockSet, name) {
|
|
62
|
+
return matchDeniedName(blockSet, name) !== null;
|
|
57
63
|
}
|
|
58
64
|
function isClaudeExtraName(name) {
|
|
59
65
|
return stripTrailingDotsAndWhitespace(name).toLowerCase() === ".claude";
|
|
60
66
|
}
|
|
67
|
+
function regionKey(segment) {
|
|
68
|
+
return stripTrailingDotsAndWhitespace(segment).toLowerCase();
|
|
69
|
+
}
|
|
61
70
|
function isExtrasScoped(segments) {
|
|
62
|
-
return segments[0] === "shared" && segments[1] === "extras";
|
|
71
|
+
return segments[0] === "shared" && segments.length > 1 && regionKey(segments[1]) === "extras";
|
|
72
|
+
}
|
|
73
|
+
function isSharedNameScoped(segments) {
|
|
74
|
+
return segments[0] === "shared" && segments.length > 1 && !UNFILTERED_SHARED_REGIONS.has(regionKey(segments[1]));
|
|
63
75
|
}
|
|
64
76
|
function blockSetFor(segments) {
|
|
65
|
-
if (
|
|
66
|
-
|
|
77
|
+
if (isExtrasScoped(segments)) {
|
|
78
|
+
return isClaudeExtraName(segments[3] ?? "") ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
79
|
+
}
|
|
80
|
+
if (isSharedNameScoped(segments)) return ALWAYS_NEVER_SYNC;
|
|
81
|
+
return NEVER_SYNC;
|
|
67
82
|
}
|
|
68
83
|
function deniedSegmentFor(path) {
|
|
69
84
|
const segments = path.split("/");
|
|
@@ -77,7 +92,7 @@ function deniedSegmentFor(path) {
|
|
|
77
92
|
function isNeverSync(path) {
|
|
78
93
|
return deniedSegmentFor(path) !== null;
|
|
79
94
|
}
|
|
80
|
-
var NEVER_SYNC, CLAUDE_EXTRA_NEVER_SYNC, ALWAYS_NEVER_SYNC, SECRET_FILE_PATTERNS;
|
|
95
|
+
var NEVER_SYNC, CLAUDE_EXTRA_NEVER_SYNC, ALWAYS_NEVER_SYNC, SECRET_FILE_PATTERNS, UNFILTERED_SHARED_REGIONS;
|
|
81
96
|
var init_config_never_sync = __esm({
|
|
82
97
|
"src/config.never-sync.ts"() {
|
|
83
98
|
"use strict";
|
|
@@ -128,6 +143,7 @@ var init_config_never_sync = __esm({
|
|
|
128
143
|
/^\.git-credentials$/i,
|
|
129
144
|
/^credentials$/i
|
|
130
145
|
];
|
|
146
|
+
UNFILTERED_SHARED_REGIONS = /* @__PURE__ */ new Set(["extras", "projects"]);
|
|
131
147
|
}
|
|
132
148
|
});
|
|
133
149
|
|
|
@@ -787,15 +803,16 @@ import {
|
|
|
787
803
|
cpSync as cpSync3,
|
|
788
804
|
existsSync as existsSync3,
|
|
789
805
|
fsyncSync,
|
|
790
|
-
lstatSync as
|
|
806
|
+
lstatSync as lstatSync4,
|
|
791
807
|
mkdirSync,
|
|
792
808
|
openSync,
|
|
793
809
|
renameSync,
|
|
810
|
+
rmdirSync,
|
|
794
811
|
statSync,
|
|
795
812
|
symlinkSync,
|
|
796
813
|
writeFileSync
|
|
797
814
|
} from "node:fs";
|
|
798
|
-
import { dirname, join as
|
|
815
|
+
import { dirname, join as join5, relative as relative3, sep as sep2 } from "node:path";
|
|
799
816
|
function isRetryableRenameCode(code) {
|
|
800
817
|
return code === "EPERM" || code === "EBUSY";
|
|
801
818
|
}
|
|
@@ -846,14 +863,20 @@ function nowTimestamp() {
|
|
|
846
863
|
}
|
|
847
864
|
function freshBackupTs(backupRoot) {
|
|
848
865
|
const base = nowTimestamp();
|
|
849
|
-
if (!existsSync3(
|
|
866
|
+
if (!existsSync3(join5(backupRoot, base))) return base;
|
|
850
867
|
let n = 1;
|
|
851
|
-
while (existsSync3(
|
|
868
|
+
while (existsSync3(join5(backupRoot, `${base}-${n}`))) n++;
|
|
852
869
|
return `${base}-${n}`;
|
|
853
870
|
}
|
|
871
|
+
function discardEmptyBackupDir(backupRoot) {
|
|
872
|
+
try {
|
|
873
|
+
rmdirSync(backupRoot);
|
|
874
|
+
} catch {
|
|
875
|
+
}
|
|
876
|
+
}
|
|
854
877
|
function ensureSymlink(linkPath, target) {
|
|
855
878
|
if (existsSync3(linkPath)) {
|
|
856
|
-
if (
|
|
879
|
+
if (lstatSync4(linkPath).isSymbolicLink()) return;
|
|
857
880
|
die(`${linkPath} exists and is not a symlink. Move it aside first.`);
|
|
858
881
|
}
|
|
859
882
|
mkdirSync(dirname(linkPath), { recursive: true });
|
|
@@ -861,21 +884,22 @@ function ensureSymlink(linkPath, target) {
|
|
|
861
884
|
log(`linked ${linkPath} -> ${target}`);
|
|
862
885
|
}
|
|
863
886
|
function backupUnder(absPath, anchor, destRoot) {
|
|
864
|
-
if (!existsSync3(absPath)) return;
|
|
865
|
-
const rel =
|
|
866
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${sep2}`)) return;
|
|
867
|
-
const dst =
|
|
887
|
+
if (!existsSync3(absPath)) return false;
|
|
888
|
+
const rel = relative3(anchor, absPath);
|
|
889
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep2}`)) return false;
|
|
890
|
+
const dst = join5(destRoot, rel);
|
|
868
891
|
mkdirSync(dirname(dst), { recursive: true });
|
|
869
892
|
cpSync3(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
893
|
+
return true;
|
|
870
894
|
}
|
|
871
895
|
function backupBeforeWrite(absPath, ts) {
|
|
872
|
-
backupUnder(absPath, claudeHome(),
|
|
896
|
+
return backupUnder(absPath, claudeHome(), join5(backupBase(), ts));
|
|
873
897
|
}
|
|
874
898
|
function backupRepoWrite(absPath, ts, repoHome2) {
|
|
875
|
-
backupUnder(absPath, repoHome2,
|
|
899
|
+
backupUnder(absPath, repoHome2, join5(backupBase(), ts, "repo"));
|
|
876
900
|
}
|
|
877
901
|
function backupExtrasWrite(absPath, ts, projectRoot) {
|
|
878
|
-
backupUnder(absPath, projectRoot,
|
|
902
|
+
backupUnder(absPath, projectRoot, join5(backupBase(), ts, "extras", encodePath(projectRoot)));
|
|
879
903
|
}
|
|
880
904
|
var RENAME_RETRY_MAX_ATTEMPTS, RENAME_RETRY_BACKOFF_MS;
|
|
881
905
|
var init_utils_fs = __esm({
|
|
@@ -891,12 +915,12 @@ var init_utils_fs = __esm({
|
|
|
891
915
|
|
|
892
916
|
// src/commands.pull.wedge.ts
|
|
893
917
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
894
|
-
import { existsSync as
|
|
895
|
-
import { join as
|
|
918
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
919
|
+
import { join as join20 } from "node:path";
|
|
896
920
|
function detectWedge(repo) {
|
|
897
|
-
const g =
|
|
898
|
-
if (
|
|
899
|
-
if (
|
|
921
|
+
const g = join20(repo, ".git");
|
|
922
|
+
if (existsSync16(join20(g, "rebase-merge")) || existsSync16(join20(g, "rebase-apply"))) return "rebase";
|
|
923
|
+
if (existsSync16(join20(g, "MERGE_HEAD"))) return "merge";
|
|
900
924
|
return null;
|
|
901
925
|
}
|
|
902
926
|
function probeUnmergedIndex(repo) {
|
|
@@ -966,15 +990,15 @@ var init_commands_pull_wedge = __esm({
|
|
|
966
990
|
});
|
|
967
991
|
|
|
968
992
|
// src/push-gitleaks.config.ts
|
|
969
|
-
import { existsSync as
|
|
993
|
+
import { existsSync as existsSync17, mkdtempSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
970
994
|
import { tmpdir } from "node:os";
|
|
971
|
-
import { join as
|
|
995
|
+
import { join as join21 } from "node:path";
|
|
972
996
|
import { fileURLToPath } from "node:url";
|
|
973
997
|
function resolveTomlPath(repo = repoHome()) {
|
|
974
|
-
const repoToml =
|
|
975
|
-
if (
|
|
998
|
+
const repoToml = join21(repo, ".gitleaks.toml");
|
|
999
|
+
if (existsSync17(repoToml)) return repoToml;
|
|
976
1000
|
const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
|
|
977
|
-
return
|
|
1001
|
+
return existsSync17(bundled) ? bundled : null;
|
|
978
1002
|
}
|
|
979
1003
|
function assertOverlayAllowlistsScoped(overlayBody) {
|
|
980
1004
|
if (OVERLAY_INLINE_ALLOWLIST_RE.test(overlayBody)) {
|
|
@@ -1012,17 +1036,17 @@ function buildOverlayTempConfig(overlayBody, bundled) {
|
|
|
1012
1036
|
path = ${JSON.stringify(bundled)}
|
|
1013
1037
|
|
|
1014
1038
|
${overlayBody}`;
|
|
1015
|
-
const tempPath = mkdtempSync(
|
|
1016
|
-
const configPath =
|
|
1039
|
+
const tempPath = mkdtempSync(join21(tmpdir(), "nomad-gitleaks-cfg-"));
|
|
1040
|
+
const configPath = join21(tempPath, "config.toml");
|
|
1017
1041
|
writeFileSync3(configPath, tempBody, { mode: 384, flag: "wx" });
|
|
1018
1042
|
return { configPath, tempPath };
|
|
1019
1043
|
}
|
|
1020
1044
|
function resolveTomlConfig() {
|
|
1021
1045
|
const repo = repoHome();
|
|
1022
|
-
const overlayPath =
|
|
1023
|
-
const repoToml =
|
|
1046
|
+
const overlayPath = join21(repo, ".gitleaks.overlay.toml");
|
|
1047
|
+
const repoToml = join21(repo, ".gitleaks.toml");
|
|
1024
1048
|
const bundled = resolveTomlPath(repo);
|
|
1025
|
-
if (!
|
|
1049
|
+
if (!existsSync17(overlayPath)) {
|
|
1026
1050
|
return { path: bundled, tempPath: null };
|
|
1027
1051
|
}
|
|
1028
1052
|
if (bundled === repoToml) {
|
|
@@ -1108,9 +1132,9 @@ var init_autostash_guard = __esm({
|
|
|
1108
1132
|
|
|
1109
1133
|
// src/push-checks.ts
|
|
1110
1134
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
1111
|
-
import { readdirSync as
|
|
1135
|
+
import { readdirSync as readdirSync8, rmSync as rmSync7 } from "node:fs";
|
|
1112
1136
|
import { homedir as homedir2, platform } from "node:os";
|
|
1113
|
-
import { delimiter, join as
|
|
1137
|
+
import { delimiter, join as join22 } from "node:path";
|
|
1114
1138
|
function gitleaksInstallHint() {
|
|
1115
1139
|
const head = "gitleaks not on PATH (required for nomad push). Install:";
|
|
1116
1140
|
const plat = platform();
|
|
@@ -1150,23 +1174,23 @@ function gitleaksInstallHint() {
|
|
|
1150
1174
|
}
|
|
1151
1175
|
function findGitlinks(dir) {
|
|
1152
1176
|
const hits = [];
|
|
1153
|
-
function
|
|
1177
|
+
function walk2(current) {
|
|
1154
1178
|
let entries;
|
|
1155
1179
|
try {
|
|
1156
|
-
entries =
|
|
1180
|
+
entries = readdirSync8(current, { withFileTypes: true });
|
|
1157
1181
|
} catch {
|
|
1158
1182
|
return;
|
|
1159
1183
|
}
|
|
1160
1184
|
for (const e of entries) {
|
|
1161
|
-
const p =
|
|
1185
|
+
const p = join22(current, e.name);
|
|
1162
1186
|
if (e.name === ".git") {
|
|
1163
1187
|
hits.push(p);
|
|
1164
1188
|
continue;
|
|
1165
1189
|
}
|
|
1166
|
-
if (e.isDirectory())
|
|
1190
|
+
if (e.isDirectory()) walk2(p);
|
|
1167
1191
|
}
|
|
1168
1192
|
}
|
|
1169
|
-
|
|
1193
|
+
walk2(dir);
|
|
1170
1194
|
return hits;
|
|
1171
1195
|
}
|
|
1172
1196
|
function probeGitleaks() {
|
|
@@ -1222,7 +1246,7 @@ var init_push_checks = __esm({
|
|
|
1222
1246
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
1223
1247
|
import { mkdirSync as mkdirSync3, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, rmSync as rmSync8 } from "node:fs";
|
|
1224
1248
|
import { homedir as homedir3 } from "node:os";
|
|
1225
|
-
import { join as
|
|
1249
|
+
import { join as join26 } from "node:path";
|
|
1226
1250
|
function toFinding(entry) {
|
|
1227
1251
|
if (typeof entry !== "object" || entry === null) return null;
|
|
1228
1252
|
const e = entry;
|
|
@@ -1270,9 +1294,9 @@ function resolveScanFailure(reportPath, e, forwardStreams) {
|
|
|
1270
1294
|
return report;
|
|
1271
1295
|
}
|
|
1272
1296
|
function scanStagedTree(repoDir, forwardStreams = false) {
|
|
1273
|
-
const cacheDir =
|
|
1297
|
+
const cacheDir = join26(homedir3(), ".cache", "claude-nomad");
|
|
1274
1298
|
mkdirSync3(cacheDir, { recursive: true });
|
|
1275
|
-
const reportPath =
|
|
1299
|
+
const reportPath = join26(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
|
|
1276
1300
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
1277
1301
|
const args = [
|
|
1278
1302
|
"protect",
|
|
@@ -1300,14 +1324,14 @@ function scanStagedTree(repoDir, forwardStreams = false) {
|
|
|
1300
1324
|
}
|
|
1301
1325
|
}
|
|
1302
1326
|
function scanFile(filePath, forwardStreams = false, timeoutMs = GITLEAKS_SCAN_TIMEOUT_MS) {
|
|
1303
|
-
const cacheDir =
|
|
1327
|
+
const cacheDir = join26(homedir3(), ".cache", "claude-nomad");
|
|
1304
1328
|
mkdirSync3(cacheDir, { recursive: true });
|
|
1305
1329
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
1306
1330
|
let reportDir;
|
|
1307
1331
|
let reportPath;
|
|
1308
1332
|
try {
|
|
1309
|
-
reportDir = mkdtempSync2(
|
|
1310
|
-
reportPath =
|
|
1333
|
+
reportDir = mkdtempSync2(join26(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}-`));
|
|
1334
|
+
reportPath = join26(reportDir, "report.json");
|
|
1311
1335
|
const args = [
|
|
1312
1336
|
"detect",
|
|
1313
1337
|
"--no-git",
|
|
@@ -1531,16 +1555,314 @@ var init_push_leak_verdict = __esm({
|
|
|
1531
1555
|
});
|
|
1532
1556
|
|
|
1533
1557
|
// src/commands.adopt.ts
|
|
1558
|
+
import { existsSync as existsSync5, lstatSync as lstatSync7 } from "node:fs";
|
|
1559
|
+
import { join as join9 } from "node:path";
|
|
1560
|
+
|
|
1561
|
+
// src/commands.adopt.recover.ts
|
|
1562
|
+
import { lstatSync as lstatSync6, rmSync as rmSync4 } from "node:fs";
|
|
1563
|
+
import { dirname as dirname2, join as join7, resolve as resolve2 } from "node:path";
|
|
1564
|
+
|
|
1565
|
+
// src/commands.adopt.scan.ts
|
|
1534
1566
|
init_config();
|
|
1535
|
-
init_config_sharedDirs_guard();
|
|
1536
1567
|
init_exit_codes();
|
|
1537
|
-
|
|
1538
|
-
import {
|
|
1568
|
+
init_utils();
|
|
1569
|
+
import { lstatSync, readdirSync } from "node:fs";
|
|
1570
|
+
import { join as join2, relative } from "node:path";
|
|
1571
|
+
function toForwardSlash(p) {
|
|
1572
|
+
return p.replaceAll("\\", "/");
|
|
1573
|
+
}
|
|
1574
|
+
function walk(root, dir, out) {
|
|
1575
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1576
|
+
const match = matchDeniedName(ALWAYS_NEVER_SYNC, entry.name);
|
|
1577
|
+
if (match !== null) {
|
|
1578
|
+
out.push({
|
|
1579
|
+
path: toForwardSlash(relative(root, join2(dir, entry.name))),
|
|
1580
|
+
matched: match.axis === "name" ? match.entry : null
|
|
1581
|
+
});
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
if (entry.isDirectory()) walk(root, join2(dir, entry.name), out);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
function scanDeniedEntries(root) {
|
|
1588
|
+
const stat = lstatSync(root, { throwIfNoEntry: false });
|
|
1589
|
+
if (!stat?.isDirectory()) return [];
|
|
1590
|
+
const out = [];
|
|
1591
|
+
walk(root, root, out);
|
|
1592
|
+
out.sort((a, b) => a.path.localeCompare(b.path));
|
|
1593
|
+
return out;
|
|
1594
|
+
}
|
|
1595
|
+
function scanOrFatal(name, root, state) {
|
|
1596
|
+
try {
|
|
1597
|
+
return scanDeniedEntries(root);
|
|
1598
|
+
} catch (err) {
|
|
1599
|
+
const message = err?.message;
|
|
1600
|
+
const text = typeof message === "string" ? message : String(err);
|
|
1601
|
+
throw new NomadFatal(
|
|
1602
|
+
`cannot adopt ${name}: could not scan ${root} for never-sync content (${text}). ${state()} Check that it is readable and not being written to, then run \`nomad adopt ${name}\` again.`,
|
|
1603
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
function describeMatch(hit) {
|
|
1608
|
+
return hit.matched === null ? "matches a credential filename shape" : `matches the never-sync name "${hit.matched}"`;
|
|
1609
|
+
}
|
|
1610
|
+
function remedies(hits, root, name) {
|
|
1611
|
+
const subject = hits.length === 1 ? "that path" : "those paths";
|
|
1612
|
+
let out = ` Move ${subject} out of ${root} and run \`nomad adopt ${name}\` again.`;
|
|
1613
|
+
if (hits.some((hit) => hit.matched !== null)) {
|
|
1614
|
+
out += " A never-sync name is matched by spelling alone and never by content, so renaming a path listed above as a never-sync name clears the refusal just as well.";
|
|
1615
|
+
}
|
|
1616
|
+
if (hits.some((hit) => hit.matched === null)) {
|
|
1617
|
+
out += " A credential filename shape is matched by the extension or by the whole filename, so renaming a path listed above as a credential filename shape clears the refusal only if the new name falls outside that shape too.";
|
|
1618
|
+
}
|
|
1619
|
+
return out;
|
|
1620
|
+
}
|
|
1621
|
+
function deniedEntriesRefusal(name, root, hits, clauses) {
|
|
1622
|
+
const lines = hits.map((hit) => ` ${hit.path} (${describeMatch(hit)})`).join("\n");
|
|
1623
|
+
return new NomadFatal(
|
|
1624
|
+
`cannot adopt ${name}: ${clauses.found}:
|
|
1625
|
+
${lines}
|
|
1626
|
+
${clauses.state}${remedies(hits, root, name)}`,
|
|
1627
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
function refuseDeniedEntries(name, root) {
|
|
1631
|
+
const nothingChanged = "Nothing was changed.";
|
|
1632
|
+
const hits = scanOrFatal(name, root, () => nothingChanged);
|
|
1633
|
+
if (hits.length === 0) return;
|
|
1634
|
+
throw deniedEntriesRefusal(name, root, hits, {
|
|
1635
|
+
found: `${root} contains never-sync content`,
|
|
1636
|
+
state: nothingChanged
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// src/commands.adopt.recover.ts
|
|
1641
|
+
init_config();
|
|
1642
|
+
init_exit_codes();
|
|
1643
|
+
|
|
1644
|
+
// src/extras-sync.core.ts
|
|
1645
|
+
init_config();
|
|
1646
|
+
import { cpSync as cpSync2, existsSync as existsSync2, lstatSync as lstatSync3, readdirSync as readdirSync3, readFileSync as readFileSync2, rmSync as rmSync2 } from "node:fs";
|
|
1647
|
+
import { basename as basename2, join as join4, relative as relative2 } from "node:path";
|
|
1648
|
+
|
|
1649
|
+
// src/extras-sync.collision.ts
|
|
1650
|
+
init_utils();
|
|
1651
|
+
import { cpSync, existsSync, lstatSync as lstatSync2, readdirSync as readdirSync2, rmSync } from "node:fs";
|
|
1652
|
+
import { basename, join as join3 } from "node:path";
|
|
1653
|
+
function quoted(p) {
|
|
1654
|
+
return `"${p}"`;
|
|
1655
|
+
}
|
|
1656
|
+
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
1657
|
+
if (!existsSync(dst)) return;
|
|
1658
|
+
for (const name of readdirSync2(src)) {
|
|
1659
|
+
if (isExcluded(name)) continue;
|
|
1660
|
+
const dstPath = join3(dst, name);
|
|
1661
|
+
const dstStat = lstatSync2(dstPath, { throwIfNoEntry: false });
|
|
1662
|
+
if (dstStat === void 0) continue;
|
|
1663
|
+
if (dstStat.isSymbolicLink()) {
|
|
1664
|
+
rmSync(dstPath, { recursive: true, force: true });
|
|
1665
|
+
} else if (dstStat.isDirectory() && lstatSync2(join3(src, name)).isDirectory()) {
|
|
1666
|
+
stripCollidingDstSymlinks(join3(src, name), dstPath, isExcluded);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
function cpSyncGuarded(src, dst, filter, label) {
|
|
1671
|
+
try {
|
|
1672
|
+
cpSync(src, dst, { recursive: true, force: true, verbatimSymlinks: true, filter });
|
|
1673
|
+
} catch (err) {
|
|
1674
|
+
const e = err;
|
|
1675
|
+
if (e.code === "EINVAL" || e.code === "ENOTEMPTY" || e.code === "ERR_FS_CP_NON_DIR_TO_DIR" || e.code === "ERR_FS_CP_DIR_TO_NON_DIR" || process.platform === "win32" && e.errno === 145) {
|
|
1676
|
+
throw new NomadFatal(
|
|
1677
|
+
`${label}: type collision copying ${quoted(src)} -> ${quoted(dst)} (${e.path ?? "unknown path"}): a file/directory type changed upstream; run nomad pull --force-remote to recover`
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
throw err;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
function prunePreservingBy(src, dst, isPreserved, isRootPreserved) {
|
|
1684
|
+
for (const name of readdirSync2(dst)) {
|
|
1685
|
+
if (isPreserved(name) || isRootPreserved?.(name) === true) continue;
|
|
1686
|
+
const dstPath = join3(dst, name);
|
|
1687
|
+
const srcStat = lstatSync2(join3(src, name), { throwIfNoEntry: false });
|
|
1688
|
+
if (srcStat === void 0) {
|
|
1689
|
+
rmSync(dstPath, { recursive: true, force: true });
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
const dstStat = lstatSync2(dstPath);
|
|
1693
|
+
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
1694
|
+
prunePreservingBy(join3(src, name), dstPath, isPreserved);
|
|
1695
|
+
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
1696
|
+
rmSync(dstPath, { recursive: true, force: true });
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
function copyExtrasFilteredPreservingBy(src, dst, isPreserved, isRootPreserved) {
|
|
1701
|
+
const dstStat = lstatSync2(dst, { throwIfNoEntry: false });
|
|
1702
|
+
if (dstStat !== void 0) {
|
|
1703
|
+
if (dstStat.isDirectory()) {
|
|
1704
|
+
const srcStat = lstatSync2(src, { throwIfNoEntry: false });
|
|
1705
|
+
if (srcStat !== void 0 && !srcStat.isDirectory()) {
|
|
1706
|
+
throw new NomadFatal(
|
|
1707
|
+
`copyExtrasFilteredPreservingBy: type mismatch copying ${quoted(src)} -> ${quoted(dst)}: the repo entry is a file but the local entry is a directory; run nomad pull --force-remote to recover`
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
prunePreservingBy(src, dst, isPreserved, isRootPreserved);
|
|
1711
|
+
} else {
|
|
1712
|
+
rmSync(dst, { recursive: true, force: true });
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
stripCollidingDstSymlinks(src, dst, isPreserved);
|
|
1716
|
+
cpSync(src, dst, {
|
|
1717
|
+
recursive: true,
|
|
1718
|
+
force: true,
|
|
1719
|
+
verbatimSymlinks: true,
|
|
1720
|
+
filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
// src/extras-sync.guards.ts
|
|
1725
|
+
init_utils();
|
|
1726
|
+
init_config_sharedDirs_guard();
|
|
1727
|
+
import { isAbsolute, normalize, sep } from "node:path";
|
|
1728
|
+
function assertSafeLocalRoot(localRoot, logical) {
|
|
1729
|
+
if (!isAbsolute(localRoot)) {
|
|
1730
|
+
throw new NomadFatal(
|
|
1731
|
+
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be absolute)`
|
|
1732
|
+
);
|
|
1733
|
+
}
|
|
1734
|
+
const canonical = process.platform === "win32" ? localRoot.split("/").join(sep) : localRoot;
|
|
1735
|
+
if (canonical !== normalize(canonical)) {
|
|
1736
|
+
throw new NomadFatal(
|
|
1737
|
+
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be already-normalized; no '..' or redundant segments)`
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// src/extras-sync.core.ts
|
|
1743
|
+
init_utils();
|
|
1744
|
+
init_utils_json();
|
|
1745
|
+
function loadValidatedExtras(opts) {
|
|
1746
|
+
const repo = repoHome();
|
|
1747
|
+
const mapPath = join4(repo, "path-map.json");
|
|
1748
|
+
const repoExtras = join4(repo, "shared", "extras");
|
|
1749
|
+
if (!existsSync2(mapPath) || opts.requireRepoExtras === true && !existsSync2(repoExtras)) {
|
|
1750
|
+
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
1751
|
+
return null;
|
|
1752
|
+
}
|
|
1753
|
+
const map = readPathMap(mapPath);
|
|
1754
|
+
const extrasMap = map.extras ?? {};
|
|
1755
|
+
if (Object.keys(extrasMap).length === 0) return null;
|
|
1756
|
+
for (const logical of Object.keys(extrasMap)) {
|
|
1757
|
+
assertSafeLogical(logical);
|
|
1758
|
+
const localRoot = map.projects[logical]?.[HOST];
|
|
1759
|
+
if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
|
|
1760
|
+
}
|
|
1761
|
+
return { map, extrasMap };
|
|
1762
|
+
}
|
|
1763
|
+
function* eachExtrasTarget(v, counts) {
|
|
1764
|
+
const whitelist = SUPPORTED_EXTRAS;
|
|
1765
|
+
for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
|
|
1766
|
+
const localRoot = v.map.projects[logical]?.[HOST];
|
|
1767
|
+
if (!localRoot || localRoot === "TBD") {
|
|
1768
|
+
counts.unmapped++;
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
for (const dirname15 of dirnames) {
|
|
1772
|
+
if (!whitelist.includes(dirname15)) {
|
|
1773
|
+
counts.skipped++;
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
yield { logical, localRoot, dirname: dirname15 };
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
function copyExtrasOverlayFiltered(src, dst, blockSet) {
|
|
1781
|
+
const dstStat = lstatSync3(dst, { throwIfNoEntry: false });
|
|
1782
|
+
if (dstStat !== void 0 && !dstStat.isDirectory()) {
|
|
1783
|
+
rmSync2(dst, { recursive: true, force: true });
|
|
1784
|
+
}
|
|
1785
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
1786
|
+
cpSyncGuarded(
|
|
1787
|
+
src,
|
|
1788
|
+
dst,
|
|
1789
|
+
(srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry)),
|
|
1790
|
+
"copyExtrasOverlayFiltered"
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
function copyExtrasOverlaySkipDiverged(src, dst, blockSet, divergedSet) {
|
|
1794
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
1795
|
+
cpSyncGuarded(
|
|
1796
|
+
src,
|
|
1797
|
+
dst,
|
|
1798
|
+
(srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry)) && !divergedSet.has(relative2(src, srcEntry)),
|
|
1799
|
+
"copyExtrasOverlaySkipDiverged"
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
function copyExtras(src, dst) {
|
|
1803
|
+
rmSync2(dst, { recursive: true, force: true });
|
|
1804
|
+
cpSync2(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
1805
|
+
}
|
|
1806
|
+
function copyExtrasFileSkipDiverged(src, dst) {
|
|
1807
|
+
if (existsSync2(dst)) {
|
|
1808
|
+
let dstBuf;
|
|
1809
|
+
try {
|
|
1810
|
+
dstBuf = readFileSync2(dst);
|
|
1811
|
+
} catch {
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
if (!readFileSync2(src).equals(dstBuf)) return;
|
|
1815
|
+
}
|
|
1816
|
+
copyExtras(src, dst);
|
|
1817
|
+
}
|
|
1818
|
+
function extrasDenySet(dirname15) {
|
|
1819
|
+
return isClaudeExtraName(dirname15) ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
1820
|
+
}
|
|
1821
|
+
function copyExtrasFiltered(src, dst, blockSet) {
|
|
1822
|
+
rmSync2(dst, { recursive: true, force: true });
|
|
1823
|
+
cpSync2(src, dst, {
|
|
1824
|
+
recursive: true,
|
|
1825
|
+
force: true,
|
|
1826
|
+
verbatimSymlinks: true,
|
|
1827
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry))
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
function prunePreservingDenied(src, dst, blockSet) {
|
|
1831
|
+
for (const name of readdirSync3(dst)) {
|
|
1832
|
+
if (isDeniedName(blockSet, name)) continue;
|
|
1833
|
+
const dstPath = join4(dst, name);
|
|
1834
|
+
const srcStat = lstatSync3(join4(src, name), { throwIfNoEntry: false });
|
|
1835
|
+
if (srcStat === void 0) {
|
|
1836
|
+
rmSync2(dstPath, { recursive: true, force: true });
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
const dstStat = lstatSync3(dstPath);
|
|
1840
|
+
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
1841
|
+
prunePreservingDenied(join4(src, name), dstPath, blockSet);
|
|
1842
|
+
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
1843
|
+
rmSync2(dstPath, { recursive: true, force: true });
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
1848
|
+
const dstStat = lstatSync3(dst, { throwIfNoEntry: false });
|
|
1849
|
+
if (dstStat !== void 0) {
|
|
1850
|
+
if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
|
|
1851
|
+
else rmSync2(dst, { recursive: true, force: true });
|
|
1852
|
+
}
|
|
1853
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
1854
|
+
cpSync2(src, dst, {
|
|
1855
|
+
recursive: true,
|
|
1856
|
+
force: true,
|
|
1857
|
+
verbatimSymlinks: true,
|
|
1858
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry))
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1539
1861
|
|
|
1540
1862
|
// src/links.ts
|
|
1541
1863
|
init_config();
|
|
1542
|
-
import { existsSync as existsSync4, lstatSync as
|
|
1543
|
-
import { join as
|
|
1864
|
+
import { existsSync as existsSync4, lstatSync as lstatSync5, rmSync as rmSync3 } from "node:fs";
|
|
1865
|
+
import { join as join6 } from "node:path";
|
|
1544
1866
|
|
|
1545
1867
|
// src/hooks-filter.ts
|
|
1546
1868
|
init_config();
|
|
@@ -1829,225 +2151,7 @@ function buildCaptureSubset(merged, settings, opts) {
|
|
|
1829
2151
|
return out;
|
|
1830
2152
|
}
|
|
1831
2153
|
|
|
1832
|
-
// src/
|
|
1833
|
-
init_config();
|
|
1834
|
-
import { cpSync as cpSync2, existsSync as existsSync2, lstatSync as lstatSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2 } from "node:fs";
|
|
1835
|
-
import { basename as basename2, join as join3, relative } from "node:path";
|
|
1836
|
-
|
|
1837
|
-
// src/extras-sync.collision.ts
|
|
1838
|
-
init_utils();
|
|
1839
|
-
import { cpSync, existsSync, lstatSync, readdirSync, rmSync } from "node:fs";
|
|
1840
|
-
import { basename, join as join2 } from "node:path";
|
|
1841
|
-
function quoted(p) {
|
|
1842
|
-
return `"${p}"`;
|
|
1843
|
-
}
|
|
1844
|
-
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
1845
|
-
if (!existsSync(dst)) return;
|
|
1846
|
-
for (const name of readdirSync(src)) {
|
|
1847
|
-
if (isExcluded(name)) continue;
|
|
1848
|
-
const dstPath = join2(dst, name);
|
|
1849
|
-
const dstStat = lstatSync(dstPath, { throwIfNoEntry: false });
|
|
1850
|
-
if (dstStat === void 0) continue;
|
|
1851
|
-
if (dstStat.isSymbolicLink()) {
|
|
1852
|
-
rmSync(dstPath, { recursive: true, force: true });
|
|
1853
|
-
} else if (dstStat.isDirectory() && lstatSync(join2(src, name)).isDirectory()) {
|
|
1854
|
-
stripCollidingDstSymlinks(join2(src, name), dstPath, isExcluded);
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
function cpSyncGuarded(src, dst, filter, label) {
|
|
1859
|
-
try {
|
|
1860
|
-
cpSync(src, dst, { recursive: true, force: true, verbatimSymlinks: true, filter });
|
|
1861
|
-
} catch (err) {
|
|
1862
|
-
const e = err;
|
|
1863
|
-
if (e.code === "EINVAL" || e.code === "ENOTEMPTY" || e.code === "ERR_FS_CP_NON_DIR_TO_DIR" || e.code === "ERR_FS_CP_DIR_TO_NON_DIR" || process.platform === "win32" && e.errno === 145) {
|
|
1864
|
-
throw new NomadFatal(
|
|
1865
|
-
`${label}: type collision copying ${quoted(src)} -> ${quoted(dst)} (${e.path ?? "unknown path"}): a file/directory type changed upstream; run nomad pull --force-remote to recover`
|
|
1866
|
-
);
|
|
1867
|
-
}
|
|
1868
|
-
throw err;
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
function prunePreservingBy(src, dst, isPreserved, isRootPreserved) {
|
|
1872
|
-
for (const name of readdirSync(dst)) {
|
|
1873
|
-
if (isPreserved(name) || isRootPreserved?.(name) === true) continue;
|
|
1874
|
-
const dstPath = join2(dst, name);
|
|
1875
|
-
const srcStat = lstatSync(join2(src, name), { throwIfNoEntry: false });
|
|
1876
|
-
if (srcStat === void 0) {
|
|
1877
|
-
rmSync(dstPath, { recursive: true, force: true });
|
|
1878
|
-
continue;
|
|
1879
|
-
}
|
|
1880
|
-
const dstStat = lstatSync(dstPath);
|
|
1881
|
-
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
1882
|
-
prunePreservingBy(join2(src, name), dstPath, isPreserved);
|
|
1883
|
-
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
1884
|
-
rmSync(dstPath, { recursive: true, force: true });
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
function copyExtrasFilteredPreservingBy(src, dst, isPreserved, isRootPreserved) {
|
|
1889
|
-
const dstStat = lstatSync(dst, { throwIfNoEntry: false });
|
|
1890
|
-
if (dstStat !== void 0) {
|
|
1891
|
-
if (dstStat.isDirectory()) {
|
|
1892
|
-
const srcStat = lstatSync(src, { throwIfNoEntry: false });
|
|
1893
|
-
if (srcStat !== void 0 && !srcStat.isDirectory()) {
|
|
1894
|
-
throw new NomadFatal(
|
|
1895
|
-
`copyExtrasFilteredPreservingBy: type mismatch copying ${quoted(src)} -> ${quoted(dst)}: the repo entry is a file but the local entry is a directory; run nomad pull --force-remote to recover`
|
|
1896
|
-
);
|
|
1897
|
-
}
|
|
1898
|
-
prunePreservingBy(src, dst, isPreserved, isRootPreserved);
|
|
1899
|
-
} else {
|
|
1900
|
-
rmSync(dst, { recursive: true, force: true });
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
stripCollidingDstSymlinks(src, dst, isPreserved);
|
|
1904
|
-
cpSync(src, dst, {
|
|
1905
|
-
recursive: true,
|
|
1906
|
-
force: true,
|
|
1907
|
-
verbatimSymlinks: true,
|
|
1908
|
-
filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
|
|
1909
|
-
});
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
// src/extras-sync.guards.ts
|
|
1913
|
-
init_utils();
|
|
1914
|
-
init_config_sharedDirs_guard();
|
|
1915
|
-
import { isAbsolute, normalize, sep } from "node:path";
|
|
1916
|
-
function assertSafeLocalRoot(localRoot, logical) {
|
|
1917
|
-
if (!isAbsolute(localRoot)) {
|
|
1918
|
-
throw new NomadFatal(
|
|
1919
|
-
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be absolute)`
|
|
1920
|
-
);
|
|
1921
|
-
}
|
|
1922
|
-
const canonical = process.platform === "win32" ? localRoot.split("/").join(sep) : localRoot;
|
|
1923
|
-
if (canonical !== normalize(canonical)) {
|
|
1924
|
-
throw new NomadFatal(
|
|
1925
|
-
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be already-normalized; no '..' or redundant segments)`
|
|
1926
|
-
);
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
// src/extras-sync.core.ts
|
|
1931
|
-
init_utils();
|
|
1932
|
-
init_utils_json();
|
|
1933
|
-
function loadValidatedExtras(opts) {
|
|
1934
|
-
const repo = repoHome();
|
|
1935
|
-
const mapPath = join3(repo, "path-map.json");
|
|
1936
|
-
const repoExtras = join3(repo, "shared", "extras");
|
|
1937
|
-
if (!existsSync2(mapPath) || opts.requireRepoExtras === true && !existsSync2(repoExtras)) {
|
|
1938
|
-
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
1939
|
-
return null;
|
|
1940
|
-
}
|
|
1941
|
-
const map = readPathMap(mapPath);
|
|
1942
|
-
const extrasMap = map.extras ?? {};
|
|
1943
|
-
if (Object.keys(extrasMap).length === 0) return null;
|
|
1944
|
-
for (const logical of Object.keys(extrasMap)) {
|
|
1945
|
-
assertSafeLogical(logical);
|
|
1946
|
-
const localRoot = map.projects[logical]?.[HOST];
|
|
1947
|
-
if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
|
|
1948
|
-
}
|
|
1949
|
-
return { map, extrasMap };
|
|
1950
|
-
}
|
|
1951
|
-
function* eachExtrasTarget(v, counts) {
|
|
1952
|
-
const whitelist = SUPPORTED_EXTRAS;
|
|
1953
|
-
for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
|
|
1954
|
-
const localRoot = v.map.projects[logical]?.[HOST];
|
|
1955
|
-
if (!localRoot || localRoot === "TBD") {
|
|
1956
|
-
counts.unmapped++;
|
|
1957
|
-
continue;
|
|
1958
|
-
}
|
|
1959
|
-
for (const dirname14 of dirnames) {
|
|
1960
|
-
if (!whitelist.includes(dirname14)) {
|
|
1961
|
-
counts.skipped++;
|
|
1962
|
-
continue;
|
|
1963
|
-
}
|
|
1964
|
-
yield { logical, localRoot, dirname: dirname14 };
|
|
1965
|
-
}
|
|
1966
|
-
}
|
|
1967
|
-
}
|
|
1968
|
-
function copyExtrasOverlayFiltered(src, dst, blockSet) {
|
|
1969
|
-
const dstStat = lstatSync2(dst, { throwIfNoEntry: false });
|
|
1970
|
-
if (dstStat !== void 0 && !dstStat.isDirectory()) {
|
|
1971
|
-
rmSync2(dst, { recursive: true, force: true });
|
|
1972
|
-
}
|
|
1973
|
-
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
1974
|
-
cpSyncGuarded(
|
|
1975
|
-
src,
|
|
1976
|
-
dst,
|
|
1977
|
-
(srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry)),
|
|
1978
|
-
"copyExtrasOverlayFiltered"
|
|
1979
|
-
);
|
|
1980
|
-
}
|
|
1981
|
-
function copyExtrasOverlaySkipDiverged(src, dst, blockSet, divergedSet) {
|
|
1982
|
-
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
1983
|
-
cpSyncGuarded(
|
|
1984
|
-
src,
|
|
1985
|
-
dst,
|
|
1986
|
-
(srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry)) && !divergedSet.has(relative(src, srcEntry)),
|
|
1987
|
-
"copyExtrasOverlaySkipDiverged"
|
|
1988
|
-
);
|
|
1989
|
-
}
|
|
1990
|
-
function copyExtras(src, dst) {
|
|
1991
|
-
rmSync2(dst, { recursive: true, force: true });
|
|
1992
|
-
cpSync2(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
1993
|
-
}
|
|
1994
|
-
function copyExtrasFileSkipDiverged(src, dst) {
|
|
1995
|
-
if (existsSync2(dst)) {
|
|
1996
|
-
let dstBuf;
|
|
1997
|
-
try {
|
|
1998
|
-
dstBuf = readFileSync2(dst);
|
|
1999
|
-
} catch {
|
|
2000
|
-
return;
|
|
2001
|
-
}
|
|
2002
|
-
if (!readFileSync2(src).equals(dstBuf)) return;
|
|
2003
|
-
}
|
|
2004
|
-
copyExtras(src, dst);
|
|
2005
|
-
}
|
|
2006
|
-
function extrasDenySet(dirname14) {
|
|
2007
|
-
return isClaudeExtraName(dirname14) ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
2008
|
-
}
|
|
2009
|
-
function copyExtrasFiltered(src, dst, blockSet) {
|
|
2010
|
-
rmSync2(dst, { recursive: true, force: true });
|
|
2011
|
-
cpSync2(src, dst, {
|
|
2012
|
-
recursive: true,
|
|
2013
|
-
force: true,
|
|
2014
|
-
verbatimSymlinks: true,
|
|
2015
|
-
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry))
|
|
2016
|
-
});
|
|
2017
|
-
}
|
|
2018
|
-
function prunePreservingDenied(src, dst, blockSet) {
|
|
2019
|
-
for (const name of readdirSync2(dst)) {
|
|
2020
|
-
if (isDeniedName(blockSet, name)) continue;
|
|
2021
|
-
const dstPath = join3(dst, name);
|
|
2022
|
-
const srcStat = lstatSync2(join3(src, name), { throwIfNoEntry: false });
|
|
2023
|
-
if (srcStat === void 0) {
|
|
2024
|
-
rmSync2(dstPath, { recursive: true, force: true });
|
|
2025
|
-
continue;
|
|
2026
|
-
}
|
|
2027
|
-
const dstStat = lstatSync2(dstPath);
|
|
2028
|
-
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
2029
|
-
prunePreservingDenied(join3(src, name), dstPath, blockSet);
|
|
2030
|
-
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
2031
|
-
rmSync2(dstPath, { recursive: true, force: true });
|
|
2032
|
-
}
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
2036
|
-
const dstStat = lstatSync2(dst, { throwIfNoEntry: false });
|
|
2037
|
-
if (dstStat !== void 0) {
|
|
2038
|
-
if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
|
|
2039
|
-
else rmSync2(dst, { recursive: true, force: true });
|
|
2040
|
-
}
|
|
2041
|
-
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
2042
|
-
cpSync2(src, dst, {
|
|
2043
|
-
recursive: true,
|
|
2044
|
-
force: true,
|
|
2045
|
-
verbatimSymlinks: true,
|
|
2046
|
-
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry))
|
|
2047
|
-
});
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
// src/links.ts
|
|
2154
|
+
// src/links.ts
|
|
2051
2155
|
init_utils();
|
|
2052
2156
|
init_utils_fs();
|
|
2053
2157
|
init_utils_json();
|
|
@@ -2073,14 +2177,14 @@ function emitCopy(onPreview, from, to) {
|
|
|
2073
2177
|
}
|
|
2074
2178
|
}
|
|
2075
2179
|
function isAlreadySymlink(linkPath) {
|
|
2076
|
-
return existsSync4(linkPath) &&
|
|
2180
|
+
return existsSync4(linkPath) && lstatSync5(linkPath).isSymbolicLink();
|
|
2077
2181
|
}
|
|
2078
2182
|
function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
|
|
2079
2183
|
for (const name of linkNames) {
|
|
2080
|
-
const linkPath =
|
|
2081
|
-
const target =
|
|
2184
|
+
const linkPath = join6(claude, name);
|
|
2185
|
+
const target = join6(repo, "shared", name);
|
|
2082
2186
|
if (!existsSync4(linkPath)) continue;
|
|
2083
|
-
if (
|
|
2187
|
+
if (lstatSync5(linkPath).isSymbolicLink()) continue;
|
|
2084
2188
|
if (!existsSync4(target)) continue;
|
|
2085
2189
|
if (dryRun) {
|
|
2086
2190
|
emitAutoMove(onPreview, linkPath, ts, name);
|
|
@@ -2093,23 +2197,58 @@ function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
|
|
|
2093
2197
|
function copySharedLinkPull(src, dst) {
|
|
2094
2198
|
copyExtrasFilteredPreservingBy(src, dst, (name) => isDeniedName(ALWAYS_NEVER_SYNC, name));
|
|
2095
2199
|
}
|
|
2200
|
+
function stillOccupied(abs) {
|
|
2201
|
+
try {
|
|
2202
|
+
return lstatSync5(abs, { throwIfNoEntry: false }) !== void 0;
|
|
2203
|
+
} catch {
|
|
2204
|
+
return true;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
function snapshotBeforeWin32Copy(linkPath, ts) {
|
|
2208
|
+
try {
|
|
2209
|
+
return backupBeforeWrite(linkPath, ts) ? "snapshotted" : "nothing-to-snapshot";
|
|
2210
|
+
} catch (err) {
|
|
2211
|
+
warn(
|
|
2212
|
+
`could not snapshot ${linkPath} before updating it (${err.message}), so it was left as it is. The rest of the pull continues`
|
|
2213
|
+
);
|
|
2214
|
+
return "failed";
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
function warnWin32ApplyFailed(linkPath, ts, err, snapshotted) {
|
|
2218
|
+
const state = stillOccupied(linkPath) ? "it may be unchanged, or partly updated" : "nothing is at that path now";
|
|
2219
|
+
const recover = snapshotted ? ` A copy of what it held before this pull is under backup/${ts}/.` : "";
|
|
2220
|
+
warn(
|
|
2221
|
+
`${linkPath} could not be updated (${err.message}), so ${state}.${recover} The rest of the pull continues. Check its permissions, or whether another program has it open, then run 'nomad pull' again to update it`
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
function applyOneSharedLinkWin32(target, linkPath, ts) {
|
|
2225
|
+
let snapshotted = false;
|
|
2226
|
+
try {
|
|
2227
|
+
const stat = lstatSync5(linkPath, { throwIfNoEntry: false });
|
|
2228
|
+
if (stat !== void 0) {
|
|
2229
|
+
const snapshot = snapshotBeforeWin32Copy(linkPath, ts);
|
|
2230
|
+
if (snapshot === "failed") return;
|
|
2231
|
+
snapshotted = snapshot === "snapshotted";
|
|
2232
|
+
if (stat.isSymbolicLink()) {
|
|
2233
|
+
rmSync3(linkPath, { recursive: true, force: true });
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
copySharedLinkPull(target, linkPath);
|
|
2237
|
+
} catch (err) {
|
|
2238
|
+
if (err instanceof NomadFatal) throw err;
|
|
2239
|
+
warnWin32ApplyFailed(linkPath, ts, err, snapshotted);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2096
2242
|
function applySharedLinksWin32(linkNames, claude, repo, ts, dryRun, onPreview) {
|
|
2097
2243
|
for (const name of linkNames) {
|
|
2098
|
-
const target =
|
|
2244
|
+
const target = join6(repo, "shared", name);
|
|
2099
2245
|
if (!existsSync4(target)) continue;
|
|
2100
|
-
const linkPath =
|
|
2246
|
+
const linkPath = join6(claude, name);
|
|
2101
2247
|
if (dryRun) {
|
|
2102
2248
|
emitCopy(onPreview, linkPath, target);
|
|
2103
2249
|
continue;
|
|
2104
2250
|
}
|
|
2105
|
-
|
|
2106
|
-
if (stat !== void 0) {
|
|
2107
|
-
backupBeforeWrite(linkPath, ts);
|
|
2108
|
-
if (stat.isSymbolicLink()) {
|
|
2109
|
-
rmSync3(linkPath, { recursive: true, force: true });
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
copySharedLinkPull(target, linkPath);
|
|
2251
|
+
applyOneSharedLinkWin32(target, linkPath, ts);
|
|
2113
2252
|
}
|
|
2114
2253
|
}
|
|
2115
2254
|
function applySharedLinks(ts, map, opts = {}) {
|
|
@@ -2123,9 +2262,9 @@ function applySharedLinks(ts, map, opts = {}) {
|
|
|
2123
2262
|
}
|
|
2124
2263
|
runAutoMovePasses(linkNames, claude, repo, ts, dryRun, opts.onPreview);
|
|
2125
2264
|
for (const name of linkNames) {
|
|
2126
|
-
const target =
|
|
2265
|
+
const target = join6(repo, "shared", name);
|
|
2127
2266
|
if (!existsSync4(target)) continue;
|
|
2128
|
-
const linkPath =
|
|
2267
|
+
const linkPath = join6(claude, name);
|
|
2129
2268
|
if (isAlreadySymlink(linkPath)) continue;
|
|
2130
2269
|
if (dryRun) {
|
|
2131
2270
|
emitCreate(opts.onPreview, linkPath, target);
|
|
@@ -2167,8 +2306,8 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
2167
2306
|
const suppressDriftWarn = opts.suppressDriftWarn === true;
|
|
2168
2307
|
const repo = repoHome();
|
|
2169
2308
|
const claude = claudeHome();
|
|
2170
|
-
const basePath =
|
|
2171
|
-
const hostPath =
|
|
2309
|
+
const basePath = join6(repo, "shared", "settings.base.json");
|
|
2310
|
+
const hostPath = join6(repo, "hosts", `${HOST}.json`);
|
|
2172
2311
|
if (!existsSync4(basePath)) {
|
|
2173
2312
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
2174
2313
|
}
|
|
@@ -2176,7 +2315,7 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
2176
2315
|
const hasOverrides = existsSync4(hostPath);
|
|
2177
2316
|
const overrides = hasOverrides ? readJson(hostPath) : {};
|
|
2178
2317
|
const merged = deepMerge(base, overrides);
|
|
2179
|
-
const settingsPath =
|
|
2318
|
+
const settingsPath = join6(claude, "settings.json");
|
|
2180
2319
|
const { existing, present, malformed } = readExistingSettings(settingsPath);
|
|
2181
2320
|
if (!suppressDriftWarn && present) {
|
|
2182
2321
|
if (malformed) {
|
|
@@ -2198,56 +2337,318 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
2198
2337
|
return { label: overrideLabel };
|
|
2199
2338
|
}
|
|
2200
2339
|
|
|
2201
|
-
// src/commands.adopt.ts
|
|
2340
|
+
// src/commands.adopt.recover.ts
|
|
2202
2341
|
init_utils();
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2342
|
+
function errorText(err) {
|
|
2343
|
+
const message = err?.message;
|
|
2344
|
+
return typeof message === "string" ? message : String(err);
|
|
2345
|
+
}
|
|
2206
2346
|
function lexists(p) {
|
|
2207
2347
|
try {
|
|
2208
|
-
|
|
2348
|
+
lstatSync6(p);
|
|
2209
2349
|
return true;
|
|
2210
2350
|
} catch {
|
|
2211
2351
|
return false;
|
|
2212
2352
|
}
|
|
2213
2353
|
}
|
|
2214
|
-
function
|
|
2215
|
-
|
|
2216
|
-
return existsSync5(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
2354
|
+
function isDirectChildOf(root, abs) {
|
|
2355
|
+
return dirname2(resolve2(abs)) === resolve2(root);
|
|
2217
2356
|
}
|
|
2218
|
-
function
|
|
2219
|
-
return
|
|
2357
|
+
function clearPartialCopy(linkPath) {
|
|
2358
|
+
return clearIfDirectChild(claudeHome(), linkPath);
|
|
2220
2359
|
}
|
|
2221
|
-
function
|
|
2222
|
-
|
|
2223
|
-
return isValidSharedDir(name);
|
|
2360
|
+
function clearPartialShared(sharedTarget, repo) {
|
|
2361
|
+
return clearIfDirectChild(join7(repo, "shared"), sharedTarget);
|
|
2224
2362
|
}
|
|
2225
|
-
function
|
|
2226
|
-
const
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
} else {
|
|
2233
|
-
ensureSymlink(linkPath, sharedTarget);
|
|
2363
|
+
function clearIfDirectChild(root, target) {
|
|
2364
|
+
const resolved = resolve2(target);
|
|
2365
|
+
if (!isDirectChildOf(root, resolved)) return false;
|
|
2366
|
+
try {
|
|
2367
|
+
rmSync4(resolved, { recursive: true, force: true });
|
|
2368
|
+
} catch {
|
|
2369
|
+
return false;
|
|
2234
2370
|
}
|
|
2235
|
-
|
|
2236
|
-
gitOrFatal(["add", "--", rel], `git add shared/${name}`, repo);
|
|
2237
|
-
log(`adopted ${name}; ${ADOPT_PUSH_HINT}`);
|
|
2371
|
+
return !lexists(resolved);
|
|
2238
2372
|
}
|
|
2239
|
-
function
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2373
|
+
function stageOrReport(stage) {
|
|
2374
|
+
try {
|
|
2375
|
+
stage();
|
|
2376
|
+
return "";
|
|
2377
|
+
} catch (err) {
|
|
2378
|
+
return ` Staging it failed too (${errorText(err)}), so it is in the repo but not staged.`;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
function describeLeftoverAt(name, linkPath, sourceRemoved) {
|
|
2382
|
+
if (!sourceRemoved) {
|
|
2383
|
+
return ` The original is still at ${linkPath}, because removing it failed too (warned above), so this host keeps a complete local copy and a later \`nomad push\` publishes it.`;
|
|
2384
|
+
}
|
|
2385
|
+
if (clearPartialCopy(linkPath)) return "";
|
|
2386
|
+
return ` A partial copy may still be at that path, so do NOT run \`nomad push\` or \`nomad sync\` yet: either one copies that path back over shared/${name}, and \`nomad sync\` pushes in the same run. Pull first, and check it does not warn about ${name} again, because a pull that cannot read the path warns and still exits 0.`;
|
|
2387
|
+
}
|
|
2388
|
+
function restoreWin32LocalCopy(name, linkPath, sharedTarget, ts, stage, opts) {
|
|
2389
|
+
try {
|
|
2390
|
+
copySharedLinkPull(sharedTarget, linkPath);
|
|
2391
|
+
} catch (err) {
|
|
2392
|
+
if (err instanceof NomadFatal) {
|
|
2393
|
+
const fatalStageFailure = stageOrReport(stage);
|
|
2394
|
+
if (fatalStageFailure !== "") warn(`${name}:${fatalStageFailure}`);
|
|
2395
|
+
throw err;
|
|
2396
|
+
}
|
|
2397
|
+
const leftover = describeLeftoverAt(name, linkPath, opts.sourceRemoved);
|
|
2398
|
+
const stageFailure = stageOrReport(stage);
|
|
2399
|
+
const staged = stageFailure === "" ? " and staged." : `.${stageFailure}`;
|
|
2400
|
+
const recover = opts.snapshotted ? ` A copy of what it held before is under backup/${ts}/.` : "";
|
|
2243
2401
|
throw new NomadFatal(
|
|
2244
|
-
`
|
|
2402
|
+
`adopted ${name} into shared/${name}, but could not restore the local copy at ${linkPath} (${errorText(err)}). The content is safe in the repo${staged}${recover} Check its permissions, or whether another program has it open, then run \`nomad pull\` to recreate the local copy before your next \`nomad push\`.${leftover}`,
|
|
2245
2403
|
{ code: EXIT.GENERIC_FAILURE }
|
|
2246
2404
|
);
|
|
2247
2405
|
}
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2406
|
+
}
|
|
2407
|
+
function describePartialShared(name, linkPath) {
|
|
2408
|
+
if (process.platform === "win32") {
|
|
2409
|
+
return ` A partial shared/${name} may still be in the repo. Remove it before re-running: adopt reports any name whose shared/${name} already exists as adopted and does nothing, and the \`nomad pull\` it suggests would copy that fragment over ${linkPath}, which is still whole.`;
|
|
2410
|
+
}
|
|
2411
|
+
return ` A partial shared/${name} may still be in the repo; remove it first, because adopt refuses to run while it is there.`;
|
|
2412
|
+
}
|
|
2413
|
+
function copyIntoSharedOrFatal(name, linkPath, sharedTarget, repo) {
|
|
2414
|
+
try {
|
|
2415
|
+
copyExtrasFiltered(linkPath, sharedTarget, ALWAYS_NEVER_SYNC);
|
|
2416
|
+
} catch (err) {
|
|
2417
|
+
const leftover = clearPartialShared(sharedTarget, repo) ? "" : describePartialShared(name, linkPath);
|
|
2418
|
+
throw new NomadFatal(
|
|
2419
|
+
`could not copy ${linkPath} into shared/${name} (${errorText(err)}). Nothing was removed from ${linkPath}. Check its permissions, or whether another program has it open, then run \`nomad adopt ${name}\` again.${leftover}`,
|
|
2420
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
function refuseLateDeniedEntries(name, linkPath, sharedTarget, repo, opts) {
|
|
2425
|
+
const snapshot = opts.snapshotted ? ` A plain snapshot of ${linkPath} taken before the copy is under backup/${opts.ts}/; it is unfiltered, so any never-sync entry under that path is in it too.` : "";
|
|
2426
|
+
const stateNow = () => clearPartialShared(sharedTarget, repo) ? `shared/${name} was cleared, and nothing was removed from ${linkPath}.${snapshot}` : `Nothing was removed from ${linkPath}.${describePartialShared(name, linkPath)}${snapshot}`;
|
|
2427
|
+
const late = scanOrFatal(name, linkPath, stateNow);
|
|
2428
|
+
if (late.length === 0) return;
|
|
2429
|
+
throw deniedEntriesRefusal(name, linkPath, late, {
|
|
2430
|
+
found: `never-sync content appeared under ${linkPath} after the preflight scan`,
|
|
2431
|
+
state: stateNow()
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
function removeAdoptSource(linkPath) {
|
|
2435
|
+
const resolved = resolve2(linkPath);
|
|
2436
|
+
if (!isDirectChildOf(claudeHome(), resolved)) {
|
|
2437
|
+
return { ok: false, message: "it is not a direct child of the configured Claude home" };
|
|
2438
|
+
}
|
|
2439
|
+
try {
|
|
2440
|
+
rmSync4(resolved, { recursive: true, force: true });
|
|
2441
|
+
} catch (err) {
|
|
2442
|
+
return { ok: false, message: errorText(err) };
|
|
2443
|
+
}
|
|
2444
|
+
if (lexists(resolved)) {
|
|
2445
|
+
return { ok: false, message: "the delete was accepted but the entry is still there" };
|
|
2446
|
+
}
|
|
2447
|
+
return { ok: true };
|
|
2448
|
+
}
|
|
2449
|
+
function reportSourceRemovalFailure(name, linkPath, message, stage) {
|
|
2450
|
+
if (process.platform === "win32") {
|
|
2451
|
+
warn(
|
|
2452
|
+
`${name}: could not remove ${linkPath} (${message}); refreshing it from shared/${name} instead, which leaves the same result`
|
|
2453
|
+
);
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
const stageFailure = stageOrReport(stage);
|
|
2457
|
+
const staged = stageFailure === "" ? " and staged." : `.${stageFailure}`;
|
|
2458
|
+
throw new NomadFatal(
|
|
2459
|
+
`adopted ${name} into shared/${name}, but could not remove the original at ${linkPath} (${message}). It is a real directory where a symlink belongs, so nothing links to shared/${name} yet. The content is safe in the repo${staged} Check its permissions, or whether another program has it open, then run \`nomad pull\`, which backs that directory up and replaces it with the symlink. Remove it by hand only if the same problem blocks the pull.`,
|
|
2460
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
// src/commands.adopt.ts
|
|
2465
|
+
init_config();
|
|
2466
|
+
init_config_sharedDirs_guard();
|
|
2467
|
+
init_exit_codes();
|
|
2468
|
+
init_utils();
|
|
2469
|
+
init_utils_fs();
|
|
2470
|
+
|
|
2471
|
+
// src/utils.lockfile.ts
|
|
2472
|
+
init_config();
|
|
2473
|
+
init_utils();
|
|
2474
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2475
|
+
import { dirname as dirname3, join as join8 } from "node:path";
|
|
2476
|
+
function lockFilePath() {
|
|
2477
|
+
return join8(home(), ".cache", "claude-nomad", "nomad.lock");
|
|
2478
|
+
}
|
|
2479
|
+
function acquireLock(verb) {
|
|
2480
|
+
const lp = lockFilePath();
|
|
2481
|
+
mkdirSync2(dirname3(lp), { recursive: true });
|
|
2482
|
+
try {
|
|
2483
|
+
const fd = openSync2(lp, "wx");
|
|
2484
|
+
try {
|
|
2485
|
+
writeFileSync2(fd, String(process.pid));
|
|
2486
|
+
} catch (writeErr) {
|
|
2487
|
+
try {
|
|
2488
|
+
closeSync2(fd);
|
|
2489
|
+
} catch {
|
|
2490
|
+
}
|
|
2491
|
+
try {
|
|
2492
|
+
unlinkSync(lp);
|
|
2493
|
+
} catch {
|
|
2494
|
+
}
|
|
2495
|
+
throw writeErr;
|
|
2496
|
+
}
|
|
2497
|
+
return { fd, path: lp };
|
|
2498
|
+
} catch (err) {
|
|
2499
|
+
const code = err.code;
|
|
2500
|
+
if (code !== "EEXIST") throw err;
|
|
2501
|
+
return checkStaleAndRetry(verb, lp);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
function releaseLock(handle) {
|
|
2505
|
+
if (handle === null) return;
|
|
2506
|
+
const lp = handle.path;
|
|
2507
|
+
try {
|
|
2508
|
+
closeSync2(handle.fd);
|
|
2509
|
+
} catch {
|
|
2510
|
+
}
|
|
2511
|
+
try {
|
|
2512
|
+
unlinkSync(lp);
|
|
2513
|
+
} catch (err) {
|
|
2514
|
+
if (err.code !== "ENOENT") throw err;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
function unlinkIfSamePid(expectedPidStr, lp) {
|
|
2518
|
+
let current;
|
|
2519
|
+
try {
|
|
2520
|
+
current = readFileSync3(lp, "utf8").trim();
|
|
2521
|
+
} catch {
|
|
2522
|
+
return false;
|
|
2523
|
+
}
|
|
2524
|
+
if (current !== expectedPidStr) return false;
|
|
2525
|
+
try {
|
|
2526
|
+
unlinkSync(lp);
|
|
2527
|
+
return true;
|
|
2528
|
+
} catch {
|
|
2529
|
+
return false;
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
function checkStaleAndRetry(verb, lp) {
|
|
2533
|
+
let pidStr;
|
|
2534
|
+
try {
|
|
2535
|
+
pidStr = readFileSync3(lp, "utf8").trim();
|
|
2536
|
+
} catch {
|
|
2537
|
+
pidStr = "";
|
|
2538
|
+
}
|
|
2539
|
+
const pid = Number.parseInt(pidStr, 10);
|
|
2540
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
2541
|
+
if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
|
|
2542
|
+
warn(`another nomad ${verb} running, skipping`);
|
|
2543
|
+
return null;
|
|
2544
|
+
}
|
|
2545
|
+
try {
|
|
2546
|
+
process.kill(pid, 0);
|
|
2547
|
+
warn(`another nomad ${verb} running, skipping`);
|
|
2548
|
+
return null;
|
|
2549
|
+
} catch (err) {
|
|
2550
|
+
const code = err.code;
|
|
2551
|
+
if (code === "ESRCH") {
|
|
2552
|
+
if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
|
|
2553
|
+
warn(`another nomad ${verb} running, skipping`);
|
|
2554
|
+
return null;
|
|
2555
|
+
}
|
|
2556
|
+
warn(`another nomad ${verb} running, skipping`);
|
|
2557
|
+
return null;
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
function retryOnce(verb, lp) {
|
|
2561
|
+
try {
|
|
2562
|
+
const fd = openSync2(lp, "wx");
|
|
2563
|
+
try {
|
|
2564
|
+
writeFileSync2(fd, String(process.pid));
|
|
2565
|
+
} catch {
|
|
2566
|
+
try {
|
|
2567
|
+
closeSync2(fd);
|
|
2568
|
+
} catch {
|
|
2569
|
+
}
|
|
2570
|
+
try {
|
|
2571
|
+
unlinkSync(lp);
|
|
2572
|
+
} catch {
|
|
2573
|
+
}
|
|
2574
|
+
warn(`another nomad ${verb} running, skipping`);
|
|
2575
|
+
return null;
|
|
2576
|
+
}
|
|
2577
|
+
return { fd, path: lp };
|
|
2578
|
+
} catch {
|
|
2579
|
+
warn(`another nomad ${verb} running, skipping`);
|
|
2580
|
+
return null;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
// src/commands.adopt.ts
|
|
2585
|
+
init_utils_json();
|
|
2586
|
+
var ADOPT_PUSH_HINT = "run `nomad push` to share with other hosts";
|
|
2587
|
+
function readMapIfPresent(repoHome2) {
|
|
2588
|
+
const mapPath = join9(repoHome2, "path-map.json");
|
|
2589
|
+
return existsSync5(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
2590
|
+
}
|
|
2591
|
+
function isConfiguredTarget(name, map) {
|
|
2592
|
+
return SHARED_LINKS.includes(name) || sharedDirEntries(map).includes(name);
|
|
2593
|
+
}
|
|
2594
|
+
function isValidAdoptName(name) {
|
|
2595
|
+
if (SHARED_LINKS.includes(name)) return true;
|
|
2596
|
+
return isValidSharedDir(name);
|
|
2597
|
+
}
|
|
2598
|
+
function reportWin32AlreadyAdopted(name, sharedTarget) {
|
|
2599
|
+
if (process.platform !== "win32" || !lexists(sharedTarget)) return false;
|
|
2600
|
+
log(`${name}: already adopted (win32 copy-sync); run \`nomad pull\` to refresh the local copy`);
|
|
2601
|
+
return true;
|
|
2602
|
+
}
|
|
2603
|
+
function adoptStopsEarly(name, linkPath, sharedTarget) {
|
|
2604
|
+
if (!existsSync5(linkPath)) {
|
|
2605
|
+
if (reportWin32AlreadyAdopted(name, sharedTarget)) return true;
|
|
2606
|
+
log(`${name}: nothing to adopt (not present in ~/.claude/)`);
|
|
2607
|
+
return true;
|
|
2608
|
+
}
|
|
2609
|
+
if (lstatSync7(linkPath).isSymbolicLink()) {
|
|
2610
|
+
log(`${name}: already adopted (already a symlink)`);
|
|
2611
|
+
return true;
|
|
2612
|
+
}
|
|
2613
|
+
if (reportWin32AlreadyAdopted(name, sharedTarget)) return true;
|
|
2614
|
+
if (lexists(sharedTarget)) {
|
|
2615
|
+
fail(`${name}: shared/${name} already exists; would clobber. Remove it first.`);
|
|
2616
|
+
process.exit(1);
|
|
2617
|
+
}
|
|
2618
|
+
return false;
|
|
2619
|
+
}
|
|
2620
|
+
function performAdoptMove(name, linkPath, sharedTarget, repo, backup) {
|
|
2621
|
+
const ts = freshBackupTs(backup);
|
|
2622
|
+
const snapshotted = backupBeforeWrite(linkPath, ts);
|
|
2623
|
+
const rel = join9("shared", name);
|
|
2624
|
+
const stage = () => gitOrFatal(["add", "--", rel], `git add shared/${name}`, repo);
|
|
2625
|
+
copyIntoSharedOrFatal(name, linkPath, sharedTarget, repo);
|
|
2626
|
+
refuseLateDeniedEntries(name, linkPath, sharedTarget, repo, { snapshotted, ts });
|
|
2627
|
+
const removal = removeAdoptSource(linkPath);
|
|
2628
|
+
if (!removal.ok) reportSourceRemovalFailure(name, linkPath, removal.message, stage);
|
|
2629
|
+
if (process.platform === "win32") {
|
|
2630
|
+
restoreWin32LocalCopy(name, linkPath, sharedTarget, ts, stage, {
|
|
2631
|
+
snapshotted,
|
|
2632
|
+
sourceRemoved: removal.ok
|
|
2633
|
+
});
|
|
2634
|
+
} else {
|
|
2635
|
+
ensureSymlink(linkPath, sharedTarget);
|
|
2636
|
+
}
|
|
2637
|
+
stage();
|
|
2638
|
+
log(`adopted ${name}; ${ADOPT_PUSH_HINT}`);
|
|
2639
|
+
}
|
|
2640
|
+
function cmdAdopt(name, opts = {}) {
|
|
2641
|
+
const dryRun = opts.dryRun === true;
|
|
2642
|
+
const rejection = validateSharedDirEntry(name);
|
|
2643
|
+
if (rejection !== null && rejection.reason === "secret-shaped") {
|
|
2644
|
+
throw new NomadFatal(
|
|
2645
|
+
`cannot adopt ${JSON.stringify(name)}: ${rejection.message}. If it is listed in sharedDirs in path-map.json, remove it there too.`,
|
|
2646
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
if (!isValidAdoptName(name)) {
|
|
2650
|
+
fail(`invalid name: ${JSON.stringify(name)}`);
|
|
2651
|
+
process.exit(1);
|
|
2251
2652
|
}
|
|
2252
2653
|
const repo = repoHome();
|
|
2253
2654
|
const claude = claudeHome();
|
|
@@ -2259,24 +2660,23 @@ function cmdAdopt(name, opts = {}) {
|
|
|
2259
2660
|
);
|
|
2260
2661
|
process.exit(1);
|
|
2261
2662
|
}
|
|
2262
|
-
const linkPath =
|
|
2263
|
-
const sharedTarget =
|
|
2264
|
-
if (
|
|
2265
|
-
|
|
2266
|
-
return;
|
|
2267
|
-
}
|
|
2268
|
-
if (lstatSync5(linkPath).isSymbolicLink()) {
|
|
2269
|
-
log(`${name}: already adopted (already a symlink)`);
|
|
2270
|
-
return;
|
|
2271
|
-
}
|
|
2272
|
-
if (process.platform === "win32" && lexists(sharedTarget)) {
|
|
2273
|
-
log(`${name}: already adopted (win32 copy-sync); run \`nomad pull\` to refresh the local copy`);
|
|
2663
|
+
const linkPath = join9(claude, name);
|
|
2664
|
+
const sharedTarget = join9(repo, "shared", name);
|
|
2665
|
+
if (dryRun) {
|
|
2666
|
+
adoptPreflightAndMove(name, linkPath, sharedTarget, repo, backup, true);
|
|
2274
2667
|
return;
|
|
2275
2668
|
}
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2669
|
+
const handle = acquireLock("adopt");
|
|
2670
|
+
if (handle === null) process.exit(EXIT.SUCCESS);
|
|
2671
|
+
try {
|
|
2672
|
+
adoptPreflightAndMove(name, linkPath, sharedTarget, repo, backup, false);
|
|
2673
|
+
} finally {
|
|
2674
|
+
releaseLock(handle);
|
|
2279
2675
|
}
|
|
2676
|
+
}
|
|
2677
|
+
function adoptPreflightAndMove(name, linkPath, sharedTarget, repo, backup, dryRun) {
|
|
2678
|
+
if (adoptStopsEarly(name, linkPath, sharedTarget)) return;
|
|
2679
|
+
refuseDeniedEntries(name, linkPath);
|
|
2280
2680
|
if (dryRun) {
|
|
2281
2681
|
const ts = freshBackupTs(backup);
|
|
2282
2682
|
log(`would backup: ${linkPath} -> backup/${ts}/${name}`);
|
|
@@ -2301,8 +2701,8 @@ init_config();
|
|
|
2301
2701
|
import { existsSync as existsSync6 } from "node:fs";
|
|
2302
2702
|
|
|
2303
2703
|
// src/commands.redact.core.ts
|
|
2304
|
-
import { appendFileSync, readFileSync as
|
|
2305
|
-
import { join as
|
|
2704
|
+
import { appendFileSync, readFileSync as readFileSync4 } from "node:fs";
|
|
2705
|
+
import { join as join10 } from "node:path";
|
|
2306
2706
|
function collectMatchIntervals(content, findings) {
|
|
2307
2707
|
const intervals = [];
|
|
2308
2708
|
for (const f of findings) {
|
|
@@ -2340,182 +2740,67 @@ function applyRedactions(content, findings) {
|
|
|
2340
2740
|
const merged = mergeIntervals(raw);
|
|
2341
2741
|
let result = content;
|
|
2342
2742
|
for (let i = merged.length - 1; i >= 0; i--) {
|
|
2343
|
-
const { start, end, ruleId } = merged[i];
|
|
2344
|
-
result = result.slice(0, start) + `[REDACTED:${ruleId}]` + result.slice(end);
|
|
2345
|
-
}
|
|
2346
|
-
return result;
|
|
2347
|
-
}
|
|
2348
|
-
function formatFingerprint(fingerprint) {
|
|
2349
|
-
return fingerprint.replace(/[\r\n]/g, "") + "\n";
|
|
2350
|
-
}
|
|
2351
|
-
function isRecentlyModified(mtimeMs, nowMs, thresholdMs = 5 * 60 * 1e3) {
|
|
2352
|
-
return nowMs - mtimeMs < thresholdMs;
|
|
2353
|
-
}
|
|
2354
|
-
function isValidFingerprint(fingerprint) {
|
|
2355
|
-
return fingerprint.trim().length > 0 && fingerprint.length <= 512 && /^[^\r\n]+$/.test(fingerprint);
|
|
2356
|
-
}
|
|
2357
|
-
function isAlreadyPresent(line, lines) {
|
|
2358
|
-
return lines.includes(line);
|
|
2359
|
-
}
|
|
2360
|
-
function appendGitleaksIgnore(fingerprint, repo) {
|
|
2361
|
-
const sanitized = fingerprint.replace(/[\r\n]/g, "").trim();
|
|
2362
|
-
if (sanitized.length === 0) return;
|
|
2363
|
-
const ignPath = join7(repo, ".gitleaksignore");
|
|
2364
|
-
let raw;
|
|
2365
|
-
try {
|
|
2366
|
-
raw = readFileSync3(ignPath, "utf8");
|
|
2367
|
-
} catch {
|
|
2368
|
-
raw = "";
|
|
2369
|
-
}
|
|
2370
|
-
const existing = raw.split("\n").filter((l) => l.length > 0);
|
|
2371
|
-
if (isAlreadyPresent(sanitized, existing)) return;
|
|
2372
|
-
const needsLeadingNewline = raw.length > 0 && !raw.endsWith("\n");
|
|
2373
|
-
const prefix = needsLeadingNewline ? "\n" : "";
|
|
2374
|
-
appendFileSync(ignPath, prefix + formatFingerprint(sanitized), "utf8");
|
|
2375
|
-
}
|
|
2376
|
-
|
|
2377
|
-
// src/commands.allow.ts
|
|
2378
|
-
init_exit_codes();
|
|
2379
|
-
init_utils();
|
|
2380
|
-
function cmdAllow(fingerprints) {
|
|
2381
|
-
const repo = repoHome();
|
|
2382
|
-
if (!existsSync6(repo)) die(`repo not cloned at ${repo}`);
|
|
2383
|
-
for (const fp of fingerprints) {
|
|
2384
|
-
if (!isValidFingerprint(fp)) {
|
|
2385
|
-
const shown = fp.replaceAll("\r", String.raw`\r`).replaceAll("\n", String.raw`\n`);
|
|
2386
|
-
fail(`invalid fingerprint: ${shown}`);
|
|
2387
|
-
process.exit(EXIT.USAGE);
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
for (const fp of fingerprints) {
|
|
2391
|
-
appendGitleaksIgnore(fp, repo);
|
|
2392
|
-
item(`allowed: ${fp}`);
|
|
2393
|
-
}
|
|
2394
|
-
log(`allowed ${fingerprints.length} fingerprint(s)`);
|
|
2395
|
-
}
|
|
2396
|
-
|
|
2397
|
-
// src/commands.capture-settings.ts
|
|
2398
|
-
init_config();
|
|
2399
|
-
import { existsSync as existsSync7 } from "node:fs";
|
|
2400
|
-
import { join as join9 } from "node:path";
|
|
2401
|
-
import { createInterface } from "node:readline/promises";
|
|
2402
|
-
init_utils_fs();
|
|
2403
|
-
init_utils_json();
|
|
2404
|
-
|
|
2405
|
-
// src/utils.lockfile.ts
|
|
2406
|
-
init_config();
|
|
2407
|
-
init_utils();
|
|
2408
|
-
import { closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2409
|
-
import { dirname as dirname2, join as join8 } from "node:path";
|
|
2410
|
-
function lockFilePath() {
|
|
2411
|
-
return join8(home(), ".cache", "claude-nomad", "nomad.lock");
|
|
2412
|
-
}
|
|
2413
|
-
function acquireLock(verb) {
|
|
2414
|
-
const lp = lockFilePath();
|
|
2415
|
-
mkdirSync2(dirname2(lp), { recursive: true });
|
|
2416
|
-
try {
|
|
2417
|
-
const fd = openSync2(lp, "wx");
|
|
2418
|
-
try {
|
|
2419
|
-
writeFileSync2(fd, String(process.pid));
|
|
2420
|
-
} catch (writeErr) {
|
|
2421
|
-
try {
|
|
2422
|
-
closeSync2(fd);
|
|
2423
|
-
} catch {
|
|
2424
|
-
}
|
|
2425
|
-
try {
|
|
2426
|
-
unlinkSync(lp);
|
|
2427
|
-
} catch {
|
|
2428
|
-
}
|
|
2429
|
-
throw writeErr;
|
|
2430
|
-
}
|
|
2431
|
-
return { fd, path: lp };
|
|
2432
|
-
} catch (err) {
|
|
2433
|
-
const code = err.code;
|
|
2434
|
-
if (code !== "EEXIST") throw err;
|
|
2435
|
-
return checkStaleAndRetry(verb, lp);
|
|
2436
|
-
}
|
|
2437
|
-
}
|
|
2438
|
-
function releaseLock(handle) {
|
|
2439
|
-
if (handle === null) return;
|
|
2440
|
-
const lp = handle.path;
|
|
2441
|
-
try {
|
|
2442
|
-
closeSync2(handle.fd);
|
|
2443
|
-
} catch {
|
|
2444
|
-
}
|
|
2445
|
-
try {
|
|
2446
|
-
unlinkSync(lp);
|
|
2447
|
-
} catch (err) {
|
|
2448
|
-
if (err.code !== "ENOENT") throw err;
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
function unlinkIfSamePid(expectedPidStr, lp) {
|
|
2452
|
-
let current;
|
|
2453
|
-
try {
|
|
2454
|
-
current = readFileSync4(lp, "utf8").trim();
|
|
2455
|
-
} catch {
|
|
2456
|
-
return false;
|
|
2457
|
-
}
|
|
2458
|
-
if (current !== expectedPidStr) return false;
|
|
2459
|
-
try {
|
|
2460
|
-
unlinkSync(lp);
|
|
2461
|
-
return true;
|
|
2462
|
-
} catch {
|
|
2463
|
-
return false;
|
|
2743
|
+
const { start, end, ruleId } = merged[i];
|
|
2744
|
+
result = result.slice(0, start) + `[REDACTED:${ruleId}]` + result.slice(end);
|
|
2464
2745
|
}
|
|
2746
|
+
return result;
|
|
2465
2747
|
}
|
|
2466
|
-
function
|
|
2467
|
-
|
|
2748
|
+
function formatFingerprint(fingerprint) {
|
|
2749
|
+
return fingerprint.replace(/[\r\n]/g, "") + "\n";
|
|
2750
|
+
}
|
|
2751
|
+
function isRecentlyModified(mtimeMs, nowMs, thresholdMs = 5 * 60 * 1e3) {
|
|
2752
|
+
return nowMs - mtimeMs < thresholdMs;
|
|
2753
|
+
}
|
|
2754
|
+
function isValidFingerprint(fingerprint) {
|
|
2755
|
+
return fingerprint.trim().length > 0 && fingerprint.length <= 512 && /^[^\r\n]+$/.test(fingerprint);
|
|
2756
|
+
}
|
|
2757
|
+
function isAlreadyPresent(line, lines) {
|
|
2758
|
+
return lines.includes(line);
|
|
2759
|
+
}
|
|
2760
|
+
function appendGitleaksIgnore(fingerprint, repo) {
|
|
2761
|
+
const sanitized = fingerprint.replace(/[\r\n]/g, "").trim();
|
|
2762
|
+
if (sanitized.length === 0) return;
|
|
2763
|
+
const ignPath = join10(repo, ".gitleaksignore");
|
|
2764
|
+
let raw;
|
|
2468
2765
|
try {
|
|
2469
|
-
|
|
2766
|
+
raw = readFileSync4(ignPath, "utf8");
|
|
2470
2767
|
} catch {
|
|
2471
|
-
|
|
2472
|
-
}
|
|
2473
|
-
const pid = Number.parseInt(pidStr, 10);
|
|
2474
|
-
if (!Number.isFinite(pid) || pid <= 0) {
|
|
2475
|
-
if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
|
|
2476
|
-
warn(`another nomad ${verb} running, skipping`);
|
|
2477
|
-
return null;
|
|
2478
|
-
}
|
|
2479
|
-
try {
|
|
2480
|
-
process.kill(pid, 0);
|
|
2481
|
-
warn(`another nomad ${verb} running, skipping`);
|
|
2482
|
-
return null;
|
|
2483
|
-
} catch (err) {
|
|
2484
|
-
const code = err.code;
|
|
2485
|
-
if (code === "ESRCH") {
|
|
2486
|
-
if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
|
|
2487
|
-
warn(`another nomad ${verb} running, skipping`);
|
|
2488
|
-
return null;
|
|
2489
|
-
}
|
|
2490
|
-
warn(`another nomad ${verb} running, skipping`);
|
|
2491
|
-
return null;
|
|
2768
|
+
raw = "";
|
|
2492
2769
|
}
|
|
2770
|
+
const existing = raw.split("\n").filter((l) => l.length > 0);
|
|
2771
|
+
if (isAlreadyPresent(sanitized, existing)) return;
|
|
2772
|
+
const needsLeadingNewline = raw.length > 0 && !raw.endsWith("\n");
|
|
2773
|
+
const prefix = needsLeadingNewline ? "\n" : "";
|
|
2774
|
+
appendFileSync(ignPath, prefix + formatFingerprint(sanitized), "utf8");
|
|
2493
2775
|
}
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
} catch {
|
|
2507
|
-
}
|
|
2508
|
-
warn(`another nomad ${verb} running, skipping`);
|
|
2509
|
-
return null;
|
|
2776
|
+
|
|
2777
|
+
// src/commands.allow.ts
|
|
2778
|
+
init_exit_codes();
|
|
2779
|
+
init_utils();
|
|
2780
|
+
function cmdAllow(fingerprints) {
|
|
2781
|
+
const repo = repoHome();
|
|
2782
|
+
if (!existsSync6(repo)) die(`repo not cloned at ${repo}`);
|
|
2783
|
+
for (const fp of fingerprints) {
|
|
2784
|
+
if (!isValidFingerprint(fp)) {
|
|
2785
|
+
const shown = fp.replaceAll("\r", String.raw`\r`).replaceAll("\n", String.raw`\n`);
|
|
2786
|
+
fail(`invalid fingerprint: ${shown}`);
|
|
2787
|
+
process.exit(EXIT.USAGE);
|
|
2510
2788
|
}
|
|
2511
|
-
return { fd, path: lp };
|
|
2512
|
-
} catch {
|
|
2513
|
-
warn(`another nomad ${verb} running, skipping`);
|
|
2514
|
-
return null;
|
|
2515
2789
|
}
|
|
2790
|
+
for (const fp of fingerprints) {
|
|
2791
|
+
appendGitleaksIgnore(fp, repo);
|
|
2792
|
+
item(`allowed: ${fp}`);
|
|
2793
|
+
}
|
|
2794
|
+
log(`allowed ${fingerprints.length} fingerprint(s)`);
|
|
2516
2795
|
}
|
|
2517
2796
|
|
|
2518
2797
|
// src/commands.capture-settings.ts
|
|
2798
|
+
init_config();
|
|
2799
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
2800
|
+
import { join as join11 } from "node:path";
|
|
2801
|
+
import { createInterface } from "node:readline/promises";
|
|
2802
|
+
init_utils_fs();
|
|
2803
|
+
init_utils_json();
|
|
2519
2804
|
init_utils();
|
|
2520
2805
|
async function confirmCapture(destLabel, keys) {
|
|
2521
2806
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
@@ -2534,7 +2819,7 @@ async function confirmCapture(destLabel, keys) {
|
|
|
2534
2819
|
}
|
|
2535
2820
|
}
|
|
2536
2821
|
function resolveCaptureDestination(repo, useHost) {
|
|
2537
|
-
const destPath = useHost ?
|
|
2822
|
+
const destPath = useHost ? join11(repo, "hosts", `${HOST}.json`) : join11(repo, "shared", "settings.base.json");
|
|
2538
2823
|
const existing = existsSync7(destPath) ? readJson(destPath) : {};
|
|
2539
2824
|
return { destPath, existing };
|
|
2540
2825
|
}
|
|
@@ -2546,17 +2831,17 @@ async function cmdCaptureSettings(opts) {
|
|
|
2546
2831
|
if (handle === null) process.exit(0);
|
|
2547
2832
|
try {
|
|
2548
2833
|
const claude = claudeHome();
|
|
2549
|
-
const basePath =
|
|
2834
|
+
const basePath = join11(repo, "shared", "settings.base.json");
|
|
2550
2835
|
if (!existsSync7(basePath)) {
|
|
2551
2836
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
2552
2837
|
}
|
|
2553
|
-
const settingsPath =
|
|
2838
|
+
const settingsPath = join11(claude, "settings.json");
|
|
2554
2839
|
if (!existsSync7(settingsPath)) {
|
|
2555
2840
|
log("no ~/.claude/settings.json found; nothing to capture");
|
|
2556
2841
|
return;
|
|
2557
2842
|
}
|
|
2558
2843
|
const base = readJson(basePath);
|
|
2559
|
-
const hostPath =
|
|
2844
|
+
const hostPath = join11(repo, "hosts", `${HOST}.json`);
|
|
2560
2845
|
const overrides = existsSync7(hostPath) ? readJson(hostPath) : {};
|
|
2561
2846
|
const merged = deepMerge(base, overrides);
|
|
2562
2847
|
const settings = readJson(settingsPath);
|
|
@@ -2595,12 +2880,15 @@ async function cmdCaptureSettings(opts) {
|
|
|
2595
2880
|
init_config();
|
|
2596
2881
|
init_exit_codes();
|
|
2597
2882
|
init_utils();
|
|
2598
|
-
import { existsSync as existsSync8, lstatSync as
|
|
2599
|
-
import { join as
|
|
2883
|
+
import { existsSync as existsSync8, lstatSync as lstatSync8, readdirSync as readdirSync4, rmSync as rmSync5, statSync as statSync2 } from "node:fs";
|
|
2884
|
+
import { join as join12 } from "node:path";
|
|
2600
2885
|
var TS_SHAPE = /^\d{8}-\d{6}(-\d+)?$/;
|
|
2601
2886
|
var DURATION_RE = /^(\d+)([dhm])$/;
|
|
2602
2887
|
var UNIT_MS = { d: 864e5, h: 36e5, m: 6e4 };
|
|
2603
2888
|
var CLEAN_DEFAULT_OLDER_THAN_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
2889
|
+
function nameOf(d) {
|
|
2890
|
+
return d.name;
|
|
2891
|
+
}
|
|
2604
2892
|
function isTsDir(name) {
|
|
2605
2893
|
return TS_SHAPE.test(name);
|
|
2606
2894
|
}
|
|
@@ -2611,7 +2899,20 @@ function parseDuration(s) {
|
|
|
2611
2899
|
}
|
|
2612
2900
|
function listBackupDirs(backupBase2) {
|
|
2613
2901
|
if (!existsSync8(backupBase2)) return [];
|
|
2614
|
-
return
|
|
2902
|
+
return readdirSync4(backupBase2).filter(isTsDir).map((name) => ({ name, mtimeMs: statSync2(join12(backupBase2, name)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2903
|
+
}
|
|
2904
|
+
function holdsNoContent(dir) {
|
|
2905
|
+
let entries;
|
|
2906
|
+
try {
|
|
2907
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
2908
|
+
} catch {
|
|
2909
|
+
return false;
|
|
2910
|
+
}
|
|
2911
|
+
for (const entry of entries) {
|
|
2912
|
+
if (!entry.isDirectory()) return false;
|
|
2913
|
+
if (!holdsNoContent(join12(dir, entry.name))) return false;
|
|
2914
|
+
}
|
|
2915
|
+
return true;
|
|
2615
2916
|
}
|
|
2616
2917
|
function prunableByAge(dirs, olderThanMs, nowMs) {
|
|
2617
2918
|
return dirs.filter((d) => nowMs - d.mtimeMs > olderThanMs).map((d) => d.name);
|
|
@@ -2621,8 +2922,8 @@ function prunableByCount(dirs, keep) {
|
|
|
2621
2922
|
}
|
|
2622
2923
|
function safeDelete(backupBase2, name) {
|
|
2623
2924
|
if (!isTsDir(name)) return;
|
|
2624
|
-
const full =
|
|
2625
|
-
const st =
|
|
2925
|
+
const full = join12(backupBase2, name);
|
|
2926
|
+
const st = lstatSync8(full, { throwIfNoEntry: false });
|
|
2626
2927
|
if (!st || st.isSymbolicLink() || !st.isDirectory()) return;
|
|
2627
2928
|
rmSync5(full, { recursive: true, force: true });
|
|
2628
2929
|
}
|
|
@@ -2645,8 +2946,30 @@ function cmdClean(opts, backupBase2 = backupBase()) {
|
|
|
2645
2946
|
}
|
|
2646
2947
|
olderThanMs = parsed;
|
|
2647
2948
|
}
|
|
2949
|
+
if (dryRun) {
|
|
2950
|
+
pruneBackups({ dryRun, olderThanMs, keep }, backupBase2);
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
const handle = acquireLock("clean");
|
|
2954
|
+
if (handle === null) process.exit(EXIT.SUCCESS);
|
|
2955
|
+
try {
|
|
2956
|
+
pruneBackups({ dryRun, olderThanMs, keep }, backupBase2);
|
|
2957
|
+
} finally {
|
|
2958
|
+
releaseLock(handle);
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
function pruneBackups(opts, backupBase2) {
|
|
2962
|
+
const { dryRun, olderThanMs, keep } = opts;
|
|
2648
2963
|
const dirs = listBackupDirs(backupBase2);
|
|
2649
|
-
const
|
|
2964
|
+
const empty = new Set(dirs.filter((d) => holdsNoContent(join12(backupBase2, d.name))).map(nameOf));
|
|
2965
|
+
const aged = new Set(
|
|
2966
|
+
resolveTargets(
|
|
2967
|
+
dirs.filter((d) => !empty.has(d.name)),
|
|
2968
|
+
olderThanMs,
|
|
2969
|
+
keep
|
|
2970
|
+
)
|
|
2971
|
+
);
|
|
2972
|
+
const targets = dirs.filter((d) => empty.has(d.name) || aged.has(d.name)).map(nameOf);
|
|
2650
2973
|
if (dryRun) {
|
|
2651
2974
|
for (const name of targets) item(name);
|
|
2652
2975
|
log(`dry-run: ${targets.length} backup(s) would be removed`);
|
|
@@ -2662,8 +2985,8 @@ init_config_sharedDirs_guard();
|
|
|
2662
2985
|
init_utils();
|
|
2663
2986
|
init_utils_fs();
|
|
2664
2987
|
init_utils_json();
|
|
2665
|
-
import { cpSync as
|
|
2666
|
-
import { join as
|
|
2988
|
+
import { cpSync as cpSync4, existsSync as existsSync9, lstatSync as lstatSync9, realpathSync, rmSync as rmSync6 } from "node:fs";
|
|
2989
|
+
import { join as join13, sep as sep3 } from "node:path";
|
|
2667
2990
|
function ejectChecklist() {
|
|
2668
2991
|
return [
|
|
2669
2992
|
"Manual steps remaining to finish leaving claude-nomad on this host:",
|
|
@@ -2679,28 +3002,28 @@ function errMessage(err) {
|
|
|
2679
3002
|
}
|
|
2680
3003
|
function lexists2(p) {
|
|
2681
3004
|
try {
|
|
2682
|
-
|
|
3005
|
+
lstatSync9(p);
|
|
2683
3006
|
return true;
|
|
2684
3007
|
} catch {
|
|
2685
3008
|
return false;
|
|
2686
3009
|
}
|
|
2687
3010
|
}
|
|
2688
3011
|
function readMapIfPresent2(repoHome2) {
|
|
2689
|
-
const mapPath =
|
|
3012
|
+
const mapPath = join13(repoHome2, "path-map.json");
|
|
2690
3013
|
return existsSync9(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
2691
3014
|
}
|
|
2692
3015
|
function classifyName(linkPath) {
|
|
2693
3016
|
if (!lexists2(linkPath)) return "absent";
|
|
2694
|
-
if (!
|
|
3017
|
+
if (!lstatSync9(linkPath).isSymbolicLink()) return "skip-real";
|
|
2695
3018
|
if (!existsSync9(linkPath)) return "dangling";
|
|
2696
3019
|
return "materialize";
|
|
2697
3020
|
}
|
|
2698
3021
|
function resolveSharedRoot(repoHome2) {
|
|
2699
3022
|
try {
|
|
2700
|
-
return realpathSync(
|
|
3023
|
+
return realpathSync(join13(repoHome2, "shared"));
|
|
2701
3024
|
} catch {
|
|
2702
3025
|
return die(
|
|
2703
|
-
`cannot resolve ${
|
|
3026
|
+
`cannot resolve ${join13(repoHome2, "shared")} (repo checkout incomplete). run \`nomad pull\` first, then re-run \`nomad eject\``
|
|
2704
3027
|
);
|
|
2705
3028
|
}
|
|
2706
3029
|
}
|
|
@@ -2716,7 +3039,7 @@ function materializeOne(name, linkPath, sharedRoot) {
|
|
|
2716
3039
|
const tmp = `${linkPath}.eject.tmp.${process.pid}.${Date.now()}`;
|
|
2717
3040
|
try {
|
|
2718
3041
|
rmSync6(tmp, { recursive: true, force: true });
|
|
2719
|
-
|
|
3042
|
+
cpSync4(target, tmp, {
|
|
2720
3043
|
recursive: true,
|
|
2721
3044
|
force: true,
|
|
2722
3045
|
dereference: true,
|
|
@@ -2740,7 +3063,7 @@ function skipRealMessage(name) {
|
|
|
2740
3063
|
function previewDryRun(names, classifications, claudeHome2, sharedRoot) {
|
|
2741
3064
|
for (const name of names) {
|
|
2742
3065
|
const cls = classifications.get(name);
|
|
2743
|
-
const linkPath =
|
|
3066
|
+
const linkPath = join13(claudeHome2, name);
|
|
2744
3067
|
if (cls === "absent") {
|
|
2745
3068
|
item(`skipped (absent): ${name}`);
|
|
2746
3069
|
} else if (cls === "skip-real") {
|
|
@@ -2770,7 +3093,7 @@ function runLiveEject(names, classifications, claudeHome2, sharedRoot) {
|
|
|
2770
3093
|
let skipped = 0;
|
|
2771
3094
|
for (const name of names) {
|
|
2772
3095
|
const cls = classifications.get(name);
|
|
2773
|
-
const linkPath =
|
|
3096
|
+
const linkPath = join13(claudeHome2, name);
|
|
2774
3097
|
if (cls === "absent") {
|
|
2775
3098
|
item(`skipped (absent): ${name}`);
|
|
2776
3099
|
skipped++;
|
|
@@ -2819,7 +3142,7 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2819
3142
|
const names = ejectNames(map);
|
|
2820
3143
|
const classifications = /* @__PURE__ */ new Map();
|
|
2821
3144
|
for (const name of names) {
|
|
2822
|
-
classifications.set(name, classifyName(
|
|
3145
|
+
classifications.set(name, classifyName(join13(claudeHome2, name)));
|
|
2823
3146
|
}
|
|
2824
3147
|
const base = new Set(allSharedLinks(map, { quiet: true }));
|
|
2825
3148
|
for (const name of names) {
|
|
@@ -2857,14 +3180,14 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2857
3180
|
}
|
|
2858
3181
|
|
|
2859
3182
|
// src/commands.doctor.ts
|
|
2860
|
-
import { existsSync as
|
|
2861
|
-
import { join as
|
|
3183
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
3184
|
+
import { join as join48 } from "node:path";
|
|
2862
3185
|
|
|
2863
3186
|
// src/commands.doctor.checks.repo.ts
|
|
2864
3187
|
init_color();
|
|
2865
3188
|
init_config();
|
|
2866
|
-
import { existsSync as
|
|
2867
|
-
import { join as
|
|
3189
|
+
import { existsSync as existsSync12, lstatSync as lstatSync10, readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
|
|
3190
|
+
import { join as join16 } from "node:path";
|
|
2868
3191
|
|
|
2869
3192
|
// src/commands.doctor.format.ts
|
|
2870
3193
|
init_color();
|
|
@@ -2939,10 +3262,16 @@ function readJsonSafe(path, label, section2) {
|
|
|
2939
3262
|
}
|
|
2940
3263
|
}
|
|
2941
3264
|
|
|
3265
|
+
// src/commands.doctor.checks.repo.win32.ts
|
|
3266
|
+
init_color();
|
|
3267
|
+
init_config();
|
|
3268
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
3269
|
+
import { join as join14, win32 as win32Path } from "node:path";
|
|
3270
|
+
|
|
2942
3271
|
// src/extras-sync.diff.ts
|
|
2943
3272
|
init_utils();
|
|
2944
3273
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2945
|
-
import { relative as
|
|
3274
|
+
import { relative as relative4 } from "node:path";
|
|
2946
3275
|
var NAME_STATUS_DIFF_TIMEOUT_MS = 3e3;
|
|
2947
3276
|
function parseNameStatus(stdout) {
|
|
2948
3277
|
const fields = stdout.split("\0").filter((f) => f.length > 0);
|
|
@@ -2961,7 +3290,7 @@ function parseDiffOutput(stdout) {
|
|
|
2961
3290
|
return parseNameStatus(stdout).map(labelEntry);
|
|
2962
3291
|
}
|
|
2963
3292
|
function parseModifiedPaths(stdout, a) {
|
|
2964
|
-
return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) =>
|
|
3293
|
+
return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative4(a, entry.path));
|
|
2965
3294
|
}
|
|
2966
3295
|
function runNameStatusDiff(a, b, parse) {
|
|
2967
3296
|
try {
|
|
@@ -2994,18 +3323,56 @@ function listDivergingModified(a, b) {
|
|
|
2994
3323
|
return runNameStatusDiff(a, b, (stdout) => parseModifiedPaths(stdout, a));
|
|
2995
3324
|
}
|
|
2996
3325
|
|
|
3326
|
+
// src/commands.doctor.checks.repo.win32.ts
|
|
3327
|
+
function win32CopyOkRow(name, exempt = 0) {
|
|
3328
|
+
const note = exempt > 0 ? dim(` (${exempt} never-synced path(s) not compared)`) : "";
|
|
3329
|
+
return { line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)${note}`, fail: false };
|
|
3330
|
+
}
|
|
3331
|
+
function win32CopyUnpublishedRow(name) {
|
|
3332
|
+
return {
|
|
3333
|
+
line: `${dim(infoGlyph)} ${name}: real local copy, not published (run \`nomad adopt ${name}\` to share it)`,
|
|
3334
|
+
fail: false
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
function relativeUnder(root, path) {
|
|
3338
|
+
const rel = win32Path.relative(root, path);
|
|
3339
|
+
const segments = rel.split(win32Path.sep);
|
|
3340
|
+
if (rel === "" || segments[0] === ".." || win32Path.isAbsolute(rel)) return null;
|
|
3341
|
+
return segments.join("/");
|
|
3342
|
+
}
|
|
3343
|
+
function divergingRepoPath(line, name, local, shared) {
|
|
3344
|
+
const path = line.replace(/ \((?:local|repo) only\)$/, "");
|
|
3345
|
+
const rel = relativeUnder(local, path) ?? relativeUnder(shared, path);
|
|
3346
|
+
return rel === null ? `shared/${name}` : `shared/${name}/${rel}`;
|
|
3347
|
+
}
|
|
3348
|
+
function classifyWin32Copy(name, p) {
|
|
3349
|
+
const sharedPath = join14(repoHome(), "shared", name);
|
|
3350
|
+
if (!existsSync10(sharedPath)) return win32CopyUnpublishedRow(name);
|
|
3351
|
+
const compared = listDivergingFiles(p, sharedPath);
|
|
3352
|
+
const diverging = compared.filter(
|
|
3353
|
+
(line) => deniedSegmentFor(divergingRepoPath(line, name, p, sharedPath)) === null
|
|
3354
|
+
);
|
|
3355
|
+
const exempt = compared.length - diverging.length;
|
|
3356
|
+
if (diverging.length === 0) return win32CopyOkRow(name, exempt);
|
|
3357
|
+
return {
|
|
3358
|
+
line: `${yellow(warnGlyph)} ${name}: ${diverging.length} file(s) diverge from shared/${name}`,
|
|
3359
|
+
fail: false,
|
|
3360
|
+
children: diverging
|
|
3361
|
+
};
|
|
3362
|
+
}
|
|
3363
|
+
|
|
2997
3364
|
// src/init.classify.ts
|
|
2998
3365
|
init_config();
|
|
2999
3366
|
init_utils_json();
|
|
3000
|
-
import { existsSync as
|
|
3001
|
-
import { join as
|
|
3367
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
3368
|
+
import { join as join15 } from "node:path";
|
|
3002
3369
|
function classifyRepoState(repoHome2, host) {
|
|
3003
|
-
const basePath =
|
|
3004
|
-
const mapPath =
|
|
3005
|
-
const hostPath =
|
|
3006
|
-
const hasBase =
|
|
3007
|
-
const hasMap =
|
|
3008
|
-
const hasHost =
|
|
3370
|
+
const basePath = join15(repoHome2, "shared", "settings.base.json");
|
|
3371
|
+
const mapPath = join15(repoHome2, "path-map.json");
|
|
3372
|
+
const hostPath = join15(repoHome2, "hosts", `${host}.json`);
|
|
3373
|
+
const hasBase = existsSync11(basePath);
|
|
3374
|
+
const hasMap = existsSync11(mapPath);
|
|
3375
|
+
const hasHost = existsSync11(hostPath);
|
|
3009
3376
|
let mapEntryCount = 0;
|
|
3010
3377
|
if (hasMap) {
|
|
3011
3378
|
try {
|
|
@@ -3020,11 +3387,11 @@ function classifyRepoState(repoHome2, host) {
|
|
|
3020
3387
|
return "partial";
|
|
3021
3388
|
}
|
|
3022
3389
|
function reasonForPartial(repoHome2, host) {
|
|
3023
|
-
const basePath =
|
|
3024
|
-
const mapPath =
|
|
3025
|
-
const hostPath =
|
|
3026
|
-
if (!
|
|
3027
|
-
if (!
|
|
3390
|
+
const basePath = join15(repoHome2, "shared", "settings.base.json");
|
|
3391
|
+
const mapPath = join15(repoHome2, "path-map.json");
|
|
3392
|
+
const hostPath = join15(repoHome2, "hosts", `${host}.json`);
|
|
3393
|
+
if (!existsSync11(basePath)) return "- shared/settings.base.json missing";
|
|
3394
|
+
if (!existsSync11(mapPath)) return "- path-map.json missing";
|
|
3028
3395
|
let mapEntryCount;
|
|
3029
3396
|
try {
|
|
3030
3397
|
const map = readJson(mapPath);
|
|
@@ -3033,7 +3400,7 @@ function reasonForPartial(repoHome2, host) {
|
|
|
3033
3400
|
mapEntryCount = 0;
|
|
3034
3401
|
}
|
|
3035
3402
|
if (mapEntryCount === 0) return "- path-map.json.projects has no entries";
|
|
3036
|
-
if (!
|
|
3403
|
+
if (!existsSync11(hostPath)) return `- hosts/${host}.json missing`;
|
|
3037
3404
|
return "- partial state (unknown gap)";
|
|
3038
3405
|
}
|
|
3039
3406
|
|
|
@@ -3050,15 +3417,15 @@ function reportHostAndPaths(section2) {
|
|
|
3050
3417
|
if (isOverrideActive()) {
|
|
3051
3418
|
addItem(section2, `${dim(infoGlyph)} NOMAD_REPO: ${blue(repo)}`);
|
|
3052
3419
|
}
|
|
3053
|
-
addItem(section2, `${
|
|
3420
|
+
addItem(section2, `${existsSync12(repo) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(repo)}`);
|
|
3054
3421
|
addItem(
|
|
3055
3422
|
section2,
|
|
3056
|
-
`${
|
|
3423
|
+
`${existsSync12(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
|
|
3057
3424
|
);
|
|
3058
3425
|
}
|
|
3059
3426
|
function pathMapHostKeys() {
|
|
3060
|
-
const mapPath =
|
|
3061
|
-
if (!
|
|
3427
|
+
const mapPath = join16(repoHome(), "path-map.json");
|
|
3428
|
+
if (!existsSync12(mapPath)) return /* @__PURE__ */ new Set();
|
|
3062
3429
|
let raw;
|
|
3063
3430
|
try {
|
|
3064
3431
|
raw = readJson(mapPath);
|
|
@@ -3075,7 +3442,7 @@ function pathMapHostKeys() {
|
|
|
3075
3442
|
function hostOverrideLabels() {
|
|
3076
3443
|
let entries;
|
|
3077
3444
|
try {
|
|
3078
|
-
entries =
|
|
3445
|
+
entries = readdirSync5(join16(repoHome(), "hosts"));
|
|
3079
3446
|
} catch {
|
|
3080
3447
|
return /* @__PURE__ */ new Set();
|
|
3081
3448
|
}
|
|
@@ -3116,42 +3483,12 @@ function reportRepoState(section2) {
|
|
|
3116
3483
|
}
|
|
3117
3484
|
}
|
|
3118
3485
|
function repoHasSharedSource(name) {
|
|
3119
|
-
return
|
|
3120
|
-
}
|
|
3121
|
-
function win32CopyOkRow(name, exempt = 0) {
|
|
3122
|
-
const note = exempt > 0 ? dim(` (${exempt} never-synced path(s) not compared)`) : "";
|
|
3123
|
-
return { line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)${note}`, fail: false };
|
|
3124
|
-
}
|
|
3125
|
-
function relativeUnder(root, path) {
|
|
3126
|
-
const rel = win32Path.relative(root, path);
|
|
3127
|
-
const segments = rel.split(win32Path.sep);
|
|
3128
|
-
if (rel === "" || segments[0] === ".." || win32Path.isAbsolute(rel)) return null;
|
|
3129
|
-
return segments.join("/");
|
|
3130
|
-
}
|
|
3131
|
-
function divergingRepoPath(line, name, local, shared) {
|
|
3132
|
-
const path = line.replace(/ \((?:local|repo) only\)$/, "");
|
|
3133
|
-
const rel = relativeUnder(local, path) ?? relativeUnder(shared, path);
|
|
3134
|
-
return rel === null ? `shared/${name}` : `shared/${name}/${rel}`;
|
|
3135
|
-
}
|
|
3136
|
-
function classifyWin32Copy(name, p) {
|
|
3137
|
-
const sharedPath = join13(repoHome(), "shared", name);
|
|
3138
|
-
if (!existsSync11(sharedPath)) return win32CopyOkRow(name);
|
|
3139
|
-
const compared = listDivergingFiles(p, sharedPath);
|
|
3140
|
-
const diverging = compared.filter(
|
|
3141
|
-
(line) => deniedSegmentFor(divergingRepoPath(line, name, p, sharedPath)) === null
|
|
3142
|
-
);
|
|
3143
|
-
const exempt = compared.length - diverging.length;
|
|
3144
|
-
if (diverging.length === 0) return win32CopyOkRow(name, exempt);
|
|
3145
|
-
return {
|
|
3146
|
-
line: `${yellow(warnGlyph)} ${name}: ${diverging.length} file(s) diverge from shared/${name}`,
|
|
3147
|
-
fail: false,
|
|
3148
|
-
children: diverging
|
|
3149
|
-
};
|
|
3486
|
+
return existsSync12(join16(repoHome(), "shared", name));
|
|
3150
3487
|
}
|
|
3151
3488
|
function classifySharedLink(name, p) {
|
|
3152
3489
|
let stat;
|
|
3153
3490
|
try {
|
|
3154
|
-
stat =
|
|
3491
|
+
stat = lstatSync10(p);
|
|
3155
3492
|
} catch (err) {
|
|
3156
3493
|
const code = err.code;
|
|
3157
3494
|
if (code === "ENOENT") {
|
|
@@ -3197,7 +3534,7 @@ function classifySymlinkTarget(name, p) {
|
|
|
3197
3534
|
function reportSharedLinks(section2, map) {
|
|
3198
3535
|
const claude = claudeHome();
|
|
3199
3536
|
for (const name of allSharedLinks(map, { quiet: true })) {
|
|
3200
|
-
const p =
|
|
3537
|
+
const p = join16(claude, name);
|
|
3201
3538
|
const { line, fail: fail2, children } = classifySharedLink(name, p);
|
|
3202
3539
|
addItem(section2, line);
|
|
3203
3540
|
if (fail2) process.exitCode = 1;
|
|
@@ -3209,10 +3546,10 @@ function reportSharedLinks(section2, map) {
|
|
|
3209
3546
|
function reportDroppedNamesMigration(section2) {
|
|
3210
3547
|
const claude = claudeHome();
|
|
3211
3548
|
for (const name of GSD_DROPPED_NAMES) {
|
|
3212
|
-
const p =
|
|
3549
|
+
const p = join16(claude, name);
|
|
3213
3550
|
let stat;
|
|
3214
3551
|
try {
|
|
3215
|
-
stat =
|
|
3552
|
+
stat = lstatSync10(p);
|
|
3216
3553
|
} catch {
|
|
3217
3554
|
continue;
|
|
3218
3555
|
}
|
|
@@ -3227,11 +3564,11 @@ function reportDroppedNamesMigration(section2) {
|
|
|
3227
3564
|
// src/commands.doctor.checks.settings.ts
|
|
3228
3565
|
init_color();
|
|
3229
3566
|
init_config();
|
|
3230
|
-
import { existsSync as
|
|
3231
|
-
import { join as
|
|
3567
|
+
import { existsSync as existsSync13, readdirSync as readdirSync6 } from "node:fs";
|
|
3568
|
+
import { join as join17 } from "node:path";
|
|
3232
3569
|
function loadBaseSettings(section2) {
|
|
3233
|
-
const basePath =
|
|
3234
|
-
if (!
|
|
3570
|
+
const basePath = join17(repoHome(), "shared", "settings.base.json");
|
|
3571
|
+
if (!existsSync13(basePath)) {
|
|
3235
3572
|
addItem(section2, `${red(failGlyph)} shared/settings.base.json missing at ${blue(basePath)}`);
|
|
3236
3573
|
process.exitCode = 1;
|
|
3237
3574
|
return null;
|
|
@@ -3239,8 +3576,8 @@ function loadBaseSettings(section2) {
|
|
|
3239
3576
|
return readJsonSafe(basePath, basePath, section2);
|
|
3240
3577
|
}
|
|
3241
3578
|
function loadAndReportSettings(section2) {
|
|
3242
|
-
const settingsPath =
|
|
3243
|
-
if (!
|
|
3579
|
+
const settingsPath = join17(claudeHome(), "settings.json");
|
|
3580
|
+
if (!existsSync13(settingsPath)) return null;
|
|
3244
3581
|
const settings = readJsonSafe(settingsPath, settingsPath, section2);
|
|
3245
3582
|
if (settings === null) return null;
|
|
3246
3583
|
const unknownKeys = Object.keys(settings).filter((k) => !KNOWN_SETTINGS_KEYS.has(k));
|
|
@@ -3256,13 +3593,13 @@ function loadAndReportSettings(section2) {
|
|
|
3256
3593
|
}
|
|
3257
3594
|
function reportHostOverrides(section2, base, settings) {
|
|
3258
3595
|
const repo = repoHome();
|
|
3259
|
-
const hostFile =
|
|
3596
|
+
const hostFile = join17(repo, "hosts", `${HOST}.json`);
|
|
3260
3597
|
let drift = [];
|
|
3261
3598
|
if (base !== null && settings !== null) {
|
|
3262
3599
|
const baseKeys = new Set(Object.keys(base));
|
|
3263
3600
|
drift = Object.keys(settings).filter((k) => !baseKeys.has(k));
|
|
3264
3601
|
}
|
|
3265
|
-
if (
|
|
3602
|
+
if (existsSync13(hostFile)) {
|
|
3266
3603
|
if (readJsonSafe(hostFile, hostFile, section2) !== null) {
|
|
3267
3604
|
addItem(section2, `${green(okGlyph)} host overrides: ${blue(hostFile)}`);
|
|
3268
3605
|
}
|
|
@@ -3271,9 +3608,9 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
3271
3608
|
section2,
|
|
3272
3609
|
`${red(failGlyph)} no hosts/${HOST}.json AND settings.json has unbased keys ${JSON.stringify(drift)}`
|
|
3273
3610
|
);
|
|
3274
|
-
const hostsDir =
|
|
3275
|
-
if (
|
|
3276
|
-
const cands =
|
|
3611
|
+
const hostsDir = join17(repo, "hosts");
|
|
3612
|
+
if (existsSync13(hostsDir)) {
|
|
3613
|
+
const cands = readdirSync6(hostsDir).filter((f) => f.endsWith(".json"));
|
|
3277
3614
|
if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
|
|
3278
3615
|
}
|
|
3279
3616
|
process.exitCode = 1;
|
|
@@ -3289,8 +3626,8 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
3289
3626
|
init_color();
|
|
3290
3627
|
init_config();
|
|
3291
3628
|
init_config_sharedDirs_guard();
|
|
3292
|
-
import { existsSync as
|
|
3293
|
-
import { join as
|
|
3629
|
+
import { existsSync as existsSync14, lstatSync as lstatSync11, readdirSync as readdirSync7, realpathSync as realpathSync2 } from "node:fs";
|
|
3630
|
+
import { join as join18, sep as sep4 } from "node:path";
|
|
3294
3631
|
init_utils_json();
|
|
3295
3632
|
function reportMappedProjects(section2, map) {
|
|
3296
3633
|
const mapped = Object.entries(map.projects).filter(([, hosts]) => hosts[HOST]);
|
|
@@ -3300,11 +3637,11 @@ function reportMappedProjects(section2, map) {
|
|
|
3300
3637
|
}
|
|
3301
3638
|
}
|
|
3302
3639
|
function reportUnmappedProjects(section2, map) {
|
|
3303
|
-
const localProjects =
|
|
3304
|
-
if (!
|
|
3640
|
+
const localProjects = join18(claudeHome(), "projects");
|
|
3641
|
+
if (!existsSync14(localProjects)) return;
|
|
3305
3642
|
let localDirs;
|
|
3306
3643
|
try {
|
|
3307
|
-
localDirs =
|
|
3644
|
+
localDirs = readdirSync7(localProjects);
|
|
3308
3645
|
} catch {
|
|
3309
3646
|
return;
|
|
3310
3647
|
}
|
|
@@ -3322,7 +3659,7 @@ function reportCurrentHostPathsMissing(section2, map) {
|
|
|
3322
3659
|
for (const [name, hosts] of Object.entries(map.projects)) {
|
|
3323
3660
|
const abspath = hosts[HOST];
|
|
3324
3661
|
if (!abspath || abspath === "TBD") continue;
|
|
3325
|
-
if (!
|
|
3662
|
+
if (!existsSync14(abspath)) {
|
|
3326
3663
|
addItem(
|
|
3327
3664
|
section2,
|
|
3328
3665
|
`${yellow(warnGlyph)} path-map: ${name} local path missing on ${HOST}: ${blue(abspath)}`
|
|
@@ -3332,7 +3669,7 @@ function reportCurrentHostPathsMissing(section2, map) {
|
|
|
3332
3669
|
}
|
|
3333
3670
|
function hasSharedLeftover(entry) {
|
|
3334
3671
|
try {
|
|
3335
|
-
return
|
|
3672
|
+
return lstatSync11(join18(repoHome(), "shared", entry), { throwIfNoEntry: false }) !== void 0;
|
|
3336
3673
|
} catch {
|
|
3337
3674
|
return false;
|
|
3338
3675
|
}
|
|
@@ -3342,10 +3679,10 @@ var PROBEABLE_REASONS = /* @__PURE__ */ new Set([
|
|
|
3342
3679
|
"secret-shaped"
|
|
3343
3680
|
]);
|
|
3344
3681
|
function classifyLocalLink(entry) {
|
|
3345
|
-
const linkPath =
|
|
3682
|
+
const linkPath = join18(claudeHome(), entry);
|
|
3346
3683
|
let stat;
|
|
3347
3684
|
try {
|
|
3348
|
-
stat =
|
|
3685
|
+
stat = lstatSync11(linkPath, { throwIfNoEntry: false });
|
|
3349
3686
|
} catch {
|
|
3350
3687
|
return "absent";
|
|
3351
3688
|
}
|
|
@@ -3358,7 +3695,7 @@ function classifyLocalLink(entry) {
|
|
|
3358
3695
|
}
|
|
3359
3696
|
let root;
|
|
3360
3697
|
try {
|
|
3361
|
-
root = realpathSync2(
|
|
3698
|
+
root = realpathSync2(join18(repoHome(), "shared"));
|
|
3362
3699
|
} catch {
|
|
3363
3700
|
return "foreign";
|
|
3364
3701
|
}
|
|
@@ -3442,8 +3779,8 @@ function reportPathCollisions(section2, map) {
|
|
|
3442
3779
|
else addItem(section2, `${green(okGlyph)} path-encoding: no collisions`);
|
|
3443
3780
|
}
|
|
3444
3781
|
function reportPathMap(section2) {
|
|
3445
|
-
const mapPath =
|
|
3446
|
-
if (!
|
|
3782
|
+
const mapPath = join18(repoHome(), "path-map.json");
|
|
3783
|
+
if (!existsSync14(mapPath)) {
|
|
3447
3784
|
addItem(section2, `${red(failGlyph)} path-map.json missing at ${blue(mapPath)}`);
|
|
3448
3785
|
process.exitCode = 1;
|
|
3449
3786
|
return;
|
|
@@ -3473,8 +3810,8 @@ function reportNeverSync(section2) {
|
|
|
3473
3810
|
|
|
3474
3811
|
// src/commands.doctor.checks.skills.ts
|
|
3475
3812
|
init_color();
|
|
3476
|
-
import { existsSync as
|
|
3477
|
-
import { join as
|
|
3813
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
3814
|
+
import { join as join19 } from "node:path";
|
|
3478
3815
|
init_config();
|
|
3479
3816
|
function stripSideIndicator(line) {
|
|
3480
3817
|
if (line.endsWith(" (local only)")) return line.slice(0, -" (local only)".length);
|
|
@@ -3483,24 +3820,24 @@ function stripSideIndicator(line) {
|
|
|
3483
3820
|
}
|
|
3484
3821
|
function isGsdDiffLine(line, localBase, sharedBase) {
|
|
3485
3822
|
const bare = stripSideIndicator(line);
|
|
3486
|
-
let
|
|
3823
|
+
let relative12;
|
|
3487
3824
|
if (bare.startsWith(localBase + "/")) {
|
|
3488
|
-
|
|
3825
|
+
relative12 = bare.slice(localBase.length + 1);
|
|
3489
3826
|
} else if (bare.startsWith(sharedBase + "/")) {
|
|
3490
|
-
|
|
3827
|
+
relative12 = bare.slice(sharedBase.length + 1);
|
|
3491
3828
|
} else {
|
|
3492
|
-
|
|
3829
|
+
relative12 = bare;
|
|
3493
3830
|
}
|
|
3494
|
-
return
|
|
3831
|
+
return relative12.split("/")[0].startsWith(GSD_PREFIX);
|
|
3495
3832
|
}
|
|
3496
3833
|
function reportSkillsDivergence(section2) {
|
|
3497
|
-
const sharedSkills =
|
|
3498
|
-
const localSkills =
|
|
3499
|
-
if (!
|
|
3834
|
+
const sharedSkills = join19(repoHome(), "shared", "skills");
|
|
3835
|
+
const localSkills = join19(claudeHome(), "skills");
|
|
3836
|
+
if (!existsSync15(sharedSkills)) {
|
|
3500
3837
|
addItem(section2, `${dim(infoGlyph)} skills: no shared/skills/ to compare`);
|
|
3501
3838
|
return;
|
|
3502
3839
|
}
|
|
3503
|
-
if (!
|
|
3840
|
+
if (!existsSync15(localSkills)) {
|
|
3504
3841
|
addItem(section2, `${dim(infoGlyph)} skills: no local skills/ to compare`);
|
|
3505
3842
|
return;
|
|
3506
3843
|
}
|
|
@@ -3523,8 +3860,8 @@ function reportSkillsDivergence(section2) {
|
|
|
3523
3860
|
init_color();
|
|
3524
3861
|
init_config();
|
|
3525
3862
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
3526
|
-
import { existsSync as
|
|
3527
|
-
import { join as
|
|
3863
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
3864
|
+
import { join as join23, relative as relative5 } from "node:path";
|
|
3528
3865
|
init_commands_pull_wedge();
|
|
3529
3866
|
init_push_checks();
|
|
3530
3867
|
init_utils();
|
|
@@ -3547,11 +3884,11 @@ function reportGitleaksProbe(section2) {
|
|
|
3547
3884
|
}
|
|
3548
3885
|
function reportGitlinks(section2) {
|
|
3549
3886
|
const repo = repoHome();
|
|
3550
|
-
const sharedDir =
|
|
3551
|
-
if (
|
|
3887
|
+
const sharedDir = join23(repo, "shared");
|
|
3888
|
+
if (existsSync18(sharedDir)) {
|
|
3552
3889
|
const gitlinks = findGitlinks(sharedDir);
|
|
3553
3890
|
for (const p of gitlinks) {
|
|
3554
|
-
const rel =
|
|
3891
|
+
const rel = relative5(repo, p).replaceAll("\\", "/");
|
|
3555
3892
|
addItem(
|
|
3556
3893
|
section2,
|
|
3557
3894
|
`${red(failGlyph)} gitlink: ${blue(rel)} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`
|
|
@@ -3646,13 +3983,13 @@ function reportGitIdentity(section2) {
|
|
|
3646
3983
|
|
|
3647
3984
|
// src/commands.doctor.checks.backups.ts
|
|
3648
3985
|
init_color();
|
|
3649
|
-
import { existsSync as
|
|
3650
|
-
import { join as
|
|
3986
|
+
import { existsSync as existsSync19, lstatSync as lstatSync12, readdirSync as readdirSync9 } from "node:fs";
|
|
3987
|
+
import { join as join24 } from "node:path";
|
|
3651
3988
|
init_config();
|
|
3652
3989
|
var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
|
|
3653
3990
|
function safeReaddir(dir) {
|
|
3654
3991
|
try {
|
|
3655
|
-
return
|
|
3992
|
+
return readdirSync9(dir);
|
|
3656
3993
|
} catch {
|
|
3657
3994
|
return [];
|
|
3658
3995
|
}
|
|
@@ -3663,8 +4000,8 @@ var BYTES_PER_MB = 1024 * 1024;
|
|
|
3663
4000
|
function dirSizeBytes(dir) {
|
|
3664
4001
|
let bytes = 0;
|
|
3665
4002
|
for (const entry of safeReaddir(dir)) {
|
|
3666
|
-
const full =
|
|
3667
|
-
const st =
|
|
4003
|
+
const full = join24(dir, entry);
|
|
4004
|
+
const st = lstatSync12(full, { throwIfNoEntry: false });
|
|
3668
4005
|
if (!st) continue;
|
|
3669
4006
|
if (st.isSymbolicLink()) continue;
|
|
3670
4007
|
if (st.isDirectory()) bytes += dirSizeBytes(full);
|
|
@@ -3674,18 +4011,18 @@ function dirSizeBytes(dir) {
|
|
|
3674
4011
|
}
|
|
3675
4012
|
function totalSizeMb(backupBase2, dirs) {
|
|
3676
4013
|
let bytes = 0;
|
|
3677
|
-
for (const name of dirs) bytes += dirSizeBytes(
|
|
4014
|
+
for (const name of dirs) bytes += dirSizeBytes(join24(backupBase2, name));
|
|
3678
4015
|
return bytes / BYTES_PER_MB;
|
|
3679
4016
|
}
|
|
3680
4017
|
function reportBackupsCheck(section2, backupBase2 = backupBase()) {
|
|
3681
|
-
if (!
|
|
4018
|
+
if (!existsSync19(backupBase2)) return;
|
|
3682
4019
|
const dirs = safeReaddir(backupBase2).filter((n) => TS_SHAPE2.test(n));
|
|
3683
4020
|
const count = dirs.length;
|
|
3684
4021
|
const sizeMb = totalSizeMb(backupBase2, dirs);
|
|
3685
4022
|
if (count > DOCTOR_BACKUP_COUNT_WARN || sizeMb > DOCTOR_BACKUP_SIZE_WARN_MB) {
|
|
3686
4023
|
addItem(
|
|
3687
4024
|
section2,
|
|
3688
|
-
`${yellow(warnGlyph)} backups: ${count} dirs / ${sizeMb.toFixed(1)} MB (run 'nomad clean --backups --keep <N>'; bare --backups
|
|
4025
|
+
`${yellow(warnGlyph)} backups: ${count} dirs / ${sizeMb.toFixed(1)} MB (run 'nomad clean --backups --keep <N>'; bare --backups prunes empty dirs plus those older than 14d)`
|
|
3689
4026
|
);
|
|
3690
4027
|
}
|
|
3691
4028
|
}
|
|
@@ -3759,8 +4096,8 @@ function reportCheckRemote(section2) {
|
|
|
3759
4096
|
|
|
3760
4097
|
// src/commands.doctor.check-schema.ts
|
|
3761
4098
|
init_color();
|
|
3762
|
-
import { existsSync as
|
|
3763
|
-
import { join as
|
|
4099
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
4100
|
+
import { join as join25 } from "node:path";
|
|
3764
4101
|
init_config();
|
|
3765
4102
|
|
|
3766
4103
|
// src/http-fetch.ts
|
|
@@ -3797,8 +4134,8 @@ function fetchSchemaKeys() {
|
|
|
3797
4134
|
}
|
|
3798
4135
|
}
|
|
3799
4136
|
function reportCheckSchema(section2) {
|
|
3800
|
-
const settingsPath =
|
|
3801
|
-
if (!
|
|
4137
|
+
const settingsPath = join25(claudeHome(), "settings.json");
|
|
4138
|
+
if (!existsSync20(settingsPath)) {
|
|
3802
4139
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
|
|
3803
4140
|
return;
|
|
3804
4141
|
}
|
|
@@ -3828,9 +4165,9 @@ function reportCheckSchema(section2) {
|
|
|
3828
4165
|
init_color();
|
|
3829
4166
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
3830
4167
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
3831
|
-
import { existsSync as
|
|
4168
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync8, readdirSync as readdirSync12, rmSync as rmSync12 } from "node:fs";
|
|
3832
4169
|
import { homedir as homedir6 } from "node:os";
|
|
3833
|
-
import { join as
|
|
4170
|
+
import { join as join32 } from "node:path";
|
|
3834
4171
|
|
|
3835
4172
|
// src/commands.doctor.check-shared.memory.ts
|
|
3836
4173
|
init_color();
|
|
@@ -3838,7 +4175,7 @@ import { execFileSync as execFileSync9 } from "node:child_process";
|
|
|
3838
4175
|
import { randomBytes } from "node:crypto";
|
|
3839
4176
|
import { mkdirSync as mkdirSync4, rmSync as rmSync9, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3840
4177
|
import { homedir as homedir4 } from "node:os";
|
|
3841
|
-
import { dirname as
|
|
4178
|
+
import { dirname as dirname4, join as join27 } from "node:path";
|
|
3842
4179
|
init_config();
|
|
3843
4180
|
init_push_gitleaks();
|
|
3844
4181
|
init_utils_fs();
|
|
@@ -3871,8 +4208,8 @@ function buildMemoryScanTree(tmpRoot) {
|
|
|
3871
4208
|
incomplete = true;
|
|
3872
4209
|
continue;
|
|
3873
4210
|
}
|
|
3874
|
-
const dest =
|
|
3875
|
-
mkdirSync4(
|
|
4211
|
+
const dest = join27(tmpRoot, rel);
|
|
4212
|
+
mkdirSync4(dirname4(dest), { recursive: true });
|
|
3876
4213
|
writeFileSync4(dest, blob);
|
|
3877
4214
|
logicals.add(m[1]);
|
|
3878
4215
|
}
|
|
@@ -3890,10 +4227,10 @@ function reportMemoryFindings(section2, findings) {
|
|
|
3890
4227
|
function reportCommittedMemory(section2) {
|
|
3891
4228
|
let tmpRoot;
|
|
3892
4229
|
try {
|
|
3893
|
-
const cacheDir =
|
|
4230
|
+
const cacheDir = join27(homedir4(), ".cache", "claude-nomad");
|
|
3894
4231
|
mkdirSync4(cacheDir, { recursive: true });
|
|
3895
4232
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
3896
|
-
tmpRoot =
|
|
4233
|
+
tmpRoot = join27(cacheDir, `check-shared-memory-tree-${stamp}`);
|
|
3897
4234
|
mkdirSync4(tmpRoot, { recursive: true, mode: 448 });
|
|
3898
4235
|
const { staged, incomplete } = buildMemoryScanTree(tmpRoot);
|
|
3899
4236
|
if (incomplete) {
|
|
@@ -3940,12 +4277,12 @@ function reportCommittedMemory(section2) {
|
|
|
3940
4277
|
|
|
3941
4278
|
// src/commands.doctor.check-shared.scan.ts
|
|
3942
4279
|
init_color();
|
|
3943
|
-
import { join as
|
|
4280
|
+
import { join as join28 } from "node:path";
|
|
3944
4281
|
init_config();
|
|
3945
4282
|
init_push_gitleaks();
|
|
3946
4283
|
function scrubPath(logical, sid, logicalToEncoded) {
|
|
3947
4284
|
const encoded = logicalToEncoded.get(logical) ?? logical;
|
|
3948
|
-
return
|
|
4285
|
+
return join28(claudeHome(), "projects", encoded, `${sid}.jsonl`);
|
|
3949
4286
|
}
|
|
3950
4287
|
function reportSessionFindings(section2, bySession) {
|
|
3951
4288
|
for (const [sid, counts] of bySession) {
|
|
@@ -4034,7 +4371,7 @@ import { execFileSync as execFileSync10 } from "node:child_process";
|
|
|
4034
4371
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
4035
4372
|
import { mkdirSync as mkdirSync5, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4036
4373
|
import { homedir as homedir5 } from "node:os";
|
|
4037
|
-
import { dirname as
|
|
4374
|
+
import { dirname as dirname5, join as join29 } from "node:path";
|
|
4038
4375
|
init_config();
|
|
4039
4376
|
init_push_gitleaks();
|
|
4040
4377
|
init_utils_fs();
|
|
@@ -4067,8 +4404,8 @@ function buildSkillScanTree(tmpRoot) {
|
|
|
4067
4404
|
incomplete = true;
|
|
4068
4405
|
continue;
|
|
4069
4406
|
}
|
|
4070
|
-
const dest =
|
|
4071
|
-
mkdirSync5(
|
|
4407
|
+
const dest = join29(tmpRoot, rel);
|
|
4408
|
+
mkdirSync5(dirname5(dest), { recursive: true });
|
|
4072
4409
|
writeFileSync5(dest, blob);
|
|
4073
4410
|
names.add(m[1]);
|
|
4074
4411
|
}
|
|
@@ -4090,10 +4427,10 @@ function reportSkillFindings(section2, findings) {
|
|
|
4090
4427
|
function reportCommittedSkills(section2) {
|
|
4091
4428
|
let tmpRoot;
|
|
4092
4429
|
try {
|
|
4093
|
-
const cacheDir =
|
|
4430
|
+
const cacheDir = join29(homedir5(), ".cache", "claude-nomad");
|
|
4094
4431
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4095
4432
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
4096
|
-
tmpRoot =
|
|
4433
|
+
tmpRoot = join29(cacheDir, `check-shared-skills-tree-${stamp}`);
|
|
4097
4434
|
mkdirSync5(tmpRoot, { recursive: true, mode: 448 });
|
|
4098
4435
|
const { staged, incomplete } = buildSkillScanTree(tmpRoot);
|
|
4099
4436
|
if (incomplete) {
|
|
@@ -4143,8 +4480,8 @@ init_config();
|
|
|
4143
4480
|
|
|
4144
4481
|
// src/remap.ts
|
|
4145
4482
|
init_config_sharedDirs_guard();
|
|
4146
|
-
import { cpSync as
|
|
4147
|
-
import { dirname as
|
|
4483
|
+
import { cpSync as cpSync5, existsSync as existsSync22, lstatSync as lstatSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync11, rmSync as rmSync11, statSync as statSync5 } from "node:fs";
|
|
4484
|
+
import { dirname as dirname7, join as join31, relative as relative6, sep as sep5 } from "node:path";
|
|
4148
4485
|
init_config();
|
|
4149
4486
|
|
|
4150
4487
|
// src/push-manifest.ts
|
|
@@ -4152,8 +4489,8 @@ init_config();
|
|
|
4152
4489
|
init_push_gitleaks_config();
|
|
4153
4490
|
init_utils_fs();
|
|
4154
4491
|
import { createHash } from "node:crypto";
|
|
4155
|
-
import { existsSync as
|
|
4156
|
-
import { dirname as
|
|
4492
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync6, readdirSync as readdirSync10, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
|
|
4493
|
+
import { dirname as dirname6, join as join30 } from "node:path";
|
|
4157
4494
|
function isChanged(prev, cur, hash) {
|
|
4158
4495
|
if (prev === void 0) return true;
|
|
4159
4496
|
if (prev.size !== cur.size) return true;
|
|
@@ -4193,8 +4530,8 @@ function hashFile(absPath) {
|
|
|
4193
4530
|
return createHash("sha256").update(readFileSync7(absPath)).digest("hex");
|
|
4194
4531
|
}
|
|
4195
4532
|
function enumerateDir(dir, results) {
|
|
4196
|
-
for (const entry of
|
|
4197
|
-
const fullPath =
|
|
4533
|
+
for (const entry of readdirSync10(dir)) {
|
|
4534
|
+
const fullPath = join30(dir, entry);
|
|
4198
4535
|
const st = statSync4(fullPath);
|
|
4199
4536
|
if (st.isDirectory()) {
|
|
4200
4537
|
enumerateDir(fullPath, results);
|
|
@@ -4205,8 +4542,8 @@ function enumerateDir(dir, results) {
|
|
|
4205
4542
|
}
|
|
4206
4543
|
function enumerateSourceFiles(projectDir) {
|
|
4207
4544
|
const results = [];
|
|
4208
|
-
for (const entry of
|
|
4209
|
-
const fullPath =
|
|
4545
|
+
for (const entry of readdirSync10(projectDir)) {
|
|
4546
|
+
const fullPath = join30(projectDir, entry);
|
|
4210
4547
|
const st = statSync4(fullPath);
|
|
4211
4548
|
if (st.isDirectory()) {
|
|
4212
4549
|
enumerateDir(fullPath, results);
|
|
@@ -4218,15 +4555,15 @@ function enumerateSourceFiles(projectDir) {
|
|
|
4218
4555
|
}
|
|
4219
4556
|
function fileIdentity(p) {
|
|
4220
4557
|
if (p === null) return "none::absent";
|
|
4221
|
-
if (!
|
|
4558
|
+
if (!existsSync21(p)) return `${p}::absent`;
|
|
4222
4559
|
return `${p}::${createHash("sha256").update(readFileSync7(p)).digest("hex")}`;
|
|
4223
4560
|
}
|
|
4224
4561
|
function computeConfigHash() {
|
|
4225
4562
|
const repo = repoHome();
|
|
4226
4563
|
const parts = [
|
|
4227
4564
|
fileIdentity(resolveTomlPath(repo)),
|
|
4228
|
-
fileIdentity(
|
|
4229
|
-
fileIdentity(
|
|
4565
|
+
fileIdentity(join30(repo, ".gitleaks.overlay.toml")),
|
|
4566
|
+
fileIdentity(join30(repo, ".gitleaksignore"))
|
|
4230
4567
|
];
|
|
4231
4568
|
return createHash("sha256").update(parts.join("\n")).digest("hex");
|
|
4232
4569
|
}
|
|
@@ -4246,7 +4583,7 @@ function readManifest(path) {
|
|
|
4246
4583
|
}
|
|
4247
4584
|
}
|
|
4248
4585
|
function writeManifest(path, manifest) {
|
|
4249
|
-
mkdirSync6(
|
|
4586
|
+
mkdirSync6(dirname6(path), { recursive: true });
|
|
4250
4587
|
writeJsonAtomic(path, manifest);
|
|
4251
4588
|
}
|
|
4252
4589
|
function buildManifest(files, scannerVersion, configHash) {
|
|
@@ -4261,7 +4598,7 @@ var TMP_SUFFIX = ".nomad-tmp";
|
|
|
4261
4598
|
function atomicMirror(src, dst, options) {
|
|
4262
4599
|
const tmp = `${dst}${TMP_SUFFIX}`;
|
|
4263
4600
|
rmSync11(tmp, { recursive: true, force: true });
|
|
4264
|
-
|
|
4601
|
+
cpSync5(src, tmp, options);
|
|
4265
4602
|
rmSync11(dst, { recursive: true, force: true });
|
|
4266
4603
|
renameAtomicRetry(tmp, dst);
|
|
4267
4604
|
}
|
|
@@ -4270,9 +4607,9 @@ function overlaySessionDir(src, dst) {
|
|
|
4270
4607
|
cpSyncGuarded(src, dst, void 0, "overlaySessionDir");
|
|
4271
4608
|
}
|
|
4272
4609
|
function copyFileAtomic(src, dst) {
|
|
4273
|
-
mkdirSync7(
|
|
4610
|
+
mkdirSync7(dirname7(dst), { recursive: true });
|
|
4274
4611
|
const tmp = `${dst}.tmp.${process.pid}`;
|
|
4275
|
-
|
|
4612
|
+
cpSync5(src, tmp, { force: true, preserveTimestamps: true });
|
|
4276
4613
|
renameAtomicRetry(tmp, dst);
|
|
4277
4614
|
}
|
|
4278
4615
|
function hasDeltaForDir(sel, localDir) {
|
|
@@ -4288,12 +4625,12 @@ function applySelective(sel, localDir, repoDst) {
|
|
|
4288
4625
|
const prefix = `${localDir}${sep5}`;
|
|
4289
4626
|
for (const src of sel.changed) {
|
|
4290
4627
|
if (!src.startsWith(prefix)) continue;
|
|
4291
|
-
copyFileAtomic(src,
|
|
4628
|
+
copyFileAtomic(src, join31(repoDst, relative6(localDir, src)));
|
|
4292
4629
|
}
|
|
4293
4630
|
for (const src of sel.deleted) {
|
|
4294
4631
|
if (!src.startsWith(prefix)) continue;
|
|
4295
|
-
const dst =
|
|
4296
|
-
if (
|
|
4632
|
+
const dst = join31(repoDst, relative6(localDir, src));
|
|
4633
|
+
if (existsSync22(dst)) rmSync11(dst);
|
|
4297
4634
|
}
|
|
4298
4635
|
}
|
|
4299
4636
|
function copyDirJsonlOnly(src, dst) {
|
|
@@ -4301,7 +4638,7 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
4301
4638
|
recursive: true,
|
|
4302
4639
|
force: true,
|
|
4303
4640
|
filter: (srcPath) => {
|
|
4304
|
-
const rel =
|
|
4641
|
+
const rel = relative6(src, srcPath);
|
|
4305
4642
|
if (rel === "") return true;
|
|
4306
4643
|
if (rel.split(sep5).length > 1) return true;
|
|
4307
4644
|
if (statSync5(srcPath).isDirectory()) return true;
|
|
@@ -4322,15 +4659,15 @@ function remapPull(ts, opts = {}) {
|
|
|
4322
4659
|
const wouldPull = [];
|
|
4323
4660
|
const repo = repoHome();
|
|
4324
4661
|
const claude = claudeHome();
|
|
4325
|
-
const mapPath =
|
|
4326
|
-
const repoProjects =
|
|
4327
|
-
if (!
|
|
4662
|
+
const mapPath = join31(repo, "path-map.json");
|
|
4663
|
+
const repoProjects = join31(repo, "shared", "projects");
|
|
4664
|
+
if (!existsSync22(mapPath) || !existsSync22(repoProjects)) {
|
|
4328
4665
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
4329
4666
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
4330
4667
|
return { unmapped: 0, pulled, wouldPull };
|
|
4331
4668
|
}
|
|
4332
4669
|
const map = readPathMap(mapPath);
|
|
4333
|
-
const localProjects =
|
|
4670
|
+
const localProjects = join31(claude, "projects");
|
|
4334
4671
|
if (!dryRun) mkdirSync7(localProjects, { recursive: true });
|
|
4335
4672
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
4336
4673
|
assertSafeLogical(logical);
|
|
@@ -4340,9 +4677,9 @@ function remapPull(ts, opts = {}) {
|
|
|
4340
4677
|
continue;
|
|
4341
4678
|
}
|
|
4342
4679
|
assertSafeLocalRoot(localPath, logical);
|
|
4343
|
-
const src =
|
|
4344
|
-
if (!
|
|
4345
|
-
const dst =
|
|
4680
|
+
const src = join31(repoProjects, logical);
|
|
4681
|
+
if (!existsSync22(src)) continue;
|
|
4682
|
+
const dst = join31(localProjects, encodePath(localPath));
|
|
4346
4683
|
if (dryRun) {
|
|
4347
4684
|
wouldPull.push(logical);
|
|
4348
4685
|
emitPreview(
|
|
@@ -4360,12 +4697,12 @@ function remapPull(ts, opts = {}) {
|
|
|
4360
4697
|
}
|
|
4361
4698
|
function countLocalOnly(src, dst) {
|
|
4362
4699
|
let count = 0;
|
|
4363
|
-
for (const name of
|
|
4364
|
-
const dstPath =
|
|
4365
|
-
const srcPath =
|
|
4366
|
-
if (
|
|
4700
|
+
for (const name of readdirSync11(dst)) {
|
|
4701
|
+
const dstPath = join31(dst, name);
|
|
4702
|
+
const srcPath = join31(src, name);
|
|
4703
|
+
if (lstatSync13(dstPath).isDirectory()) {
|
|
4367
4704
|
count += countLocalOnly(srcPath, dstPath);
|
|
4368
|
-
} else if (
|
|
4705
|
+
} else if (lstatSync13(srcPath, { throwIfNoEntry: false }) === void 0) {
|
|
4369
4706
|
count++;
|
|
4370
4707
|
}
|
|
4371
4708
|
}
|
|
@@ -4374,20 +4711,20 @@ function countLocalOnly(src, dst) {
|
|
|
4374
4711
|
function scanLocalOnly() {
|
|
4375
4712
|
const repo = repoHome();
|
|
4376
4713
|
const claude = claudeHome();
|
|
4377
|
-
const mapPath =
|
|
4378
|
-
const repoProjects =
|
|
4379
|
-
if (!
|
|
4714
|
+
const mapPath = join31(repo, "path-map.json");
|
|
4715
|
+
const repoProjects = join31(repo, "shared", "projects");
|
|
4716
|
+
if (!existsSync22(mapPath) || !existsSync22(repoProjects)) return 0;
|
|
4380
4717
|
const map = readPathMap(mapPath);
|
|
4381
|
-
const localProjects =
|
|
4718
|
+
const localProjects = join31(claude, "projects");
|
|
4382
4719
|
let count = 0;
|
|
4383
4720
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
4384
4721
|
assertSafeLogical(logical);
|
|
4385
4722
|
const localPath = hosts[HOST];
|
|
4386
4723
|
if (!localPath || localPath === "TBD") continue;
|
|
4387
4724
|
assertSafeLocalRoot(localPath, logical);
|
|
4388
|
-
const dst =
|
|
4389
|
-
if (!
|
|
4390
|
-
count += countLocalOnly(
|
|
4725
|
+
const dst = join31(localProjects, encodePath(localPath));
|
|
4726
|
+
if (!existsSync22(dst)) continue;
|
|
4727
|
+
count += countLocalOnly(join31(repoProjects, logical), dst);
|
|
4391
4728
|
}
|
|
4392
4729
|
return count;
|
|
4393
4730
|
}
|
|
@@ -4422,26 +4759,26 @@ function remapPush(ts, opts = {}) {
|
|
|
4422
4759
|
const wouldPush = [];
|
|
4423
4760
|
const repo = repoHome();
|
|
4424
4761
|
const claude = claudeHome();
|
|
4425
|
-
const mapPath =
|
|
4426
|
-
if (!
|
|
4762
|
+
const mapPath = join31(repo, "path-map.json");
|
|
4763
|
+
if (!existsSync22(mapPath)) {
|
|
4427
4764
|
log("no path-map.json; skipping session export");
|
|
4428
4765
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
4429
4766
|
}
|
|
4430
4767
|
const map = readPathMap(mapPath);
|
|
4431
|
-
const localProjects =
|
|
4432
|
-
const repoProjects =
|
|
4768
|
+
const localProjects = join31(claude, "projects");
|
|
4769
|
+
const repoProjects = join31(repo, "shared", "projects");
|
|
4433
4770
|
const reverse = buildReverseMap(map);
|
|
4434
|
-
if (!
|
|
4771
|
+
if (!existsSync22(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
4435
4772
|
if (!dryRun) mkdirSync7(repoProjects, { recursive: true });
|
|
4436
|
-
for (const dir of
|
|
4773
|
+
for (const dir of readdirSync11(localProjects)) {
|
|
4437
4774
|
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
4438
4775
|
const logical = reverse.get(dir);
|
|
4439
4776
|
if (!logical) {
|
|
4440
4777
|
unmapped++;
|
|
4441
4778
|
continue;
|
|
4442
4779
|
}
|
|
4443
|
-
const localDir =
|
|
4444
|
-
const repoDst =
|
|
4780
|
+
const localDir = join31(localProjects, dir);
|
|
4781
|
+
const repoDst = join31(repoProjects, logical);
|
|
4445
4782
|
if (skipForSelection(opts.selection, localDir)) continue;
|
|
4446
4783
|
if (dryRun) {
|
|
4447
4784
|
wouldPush.push(logical);
|
|
@@ -4465,8 +4802,8 @@ function buildScanTree(tmpRoot) {
|
|
|
4465
4802
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
4466
4803
|
let staged = 0;
|
|
4467
4804
|
const repo = repoHome();
|
|
4468
|
-
const mapPath =
|
|
4469
|
-
if (!
|
|
4805
|
+
const mapPath = join32(repo, "path-map.json");
|
|
4806
|
+
if (!existsSync23(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
4470
4807
|
let map;
|
|
4471
4808
|
try {
|
|
4472
4809
|
map = readJson(mapPath);
|
|
@@ -4483,12 +4820,12 @@ function buildScanTree(tmpRoot) {
|
|
|
4483
4820
|
if (!p || p === "TBD") continue;
|
|
4484
4821
|
reverse.set(encodePath(p), logical);
|
|
4485
4822
|
}
|
|
4486
|
-
const localProjects =
|
|
4487
|
-
if (!
|
|
4488
|
-
for (const dir of
|
|
4823
|
+
const localProjects = join32(claudeHome(), "projects");
|
|
4824
|
+
if (!existsSync23(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
4825
|
+
for (const dir of readdirSync12(localProjects)) {
|
|
4489
4826
|
const logical = reverse.get(dir);
|
|
4490
4827
|
if (!logical) continue;
|
|
4491
|
-
copyDirJsonlOnly(
|
|
4828
|
+
copyDirJsonlOnly(join32(localProjects, dir), join32(tmpRoot, "shared", "projects", logical));
|
|
4492
4829
|
logicalToEncoded.set(logical, dir);
|
|
4493
4830
|
staged++;
|
|
4494
4831
|
}
|
|
@@ -4518,11 +4855,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
4518
4855
|
return true;
|
|
4519
4856
|
}
|
|
4520
4857
|
function runLocalPreviewScan(section2) {
|
|
4521
|
-
const cacheDir =
|
|
4858
|
+
const cacheDir = join32(homedir6(), ".cache", "claude-nomad");
|
|
4522
4859
|
mkdirSync8(cacheDir, { recursive: true });
|
|
4523
4860
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes3(4).toString("hex")}`;
|
|
4524
|
-
const reportPath =
|
|
4525
|
-
const tmpRoot =
|
|
4861
|
+
const reportPath = join32(cacheDir, `check-shared-${stamp}.json`);
|
|
4862
|
+
const tmpRoot = join32(cacheDir, `check-shared-tree-${stamp}`);
|
|
4526
4863
|
try {
|
|
4527
4864
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
4528
4865
|
if (malformed) {
|
|
@@ -4555,8 +4892,8 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
4555
4892
|
|
|
4556
4893
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
4557
4894
|
init_color();
|
|
4558
|
-
import { existsSync as
|
|
4559
|
-
import { dirname as
|
|
4895
|
+
import { existsSync as existsSync24, readFileSync as readFileSync8, readdirSync as readdirSync13, realpathSync as realpathSync3 } from "node:fs";
|
|
4896
|
+
import { dirname as dirname8, extname, join as join33 } from "node:path";
|
|
4560
4897
|
init_config();
|
|
4561
4898
|
function typeFromPackageJson(pkgPath) {
|
|
4562
4899
|
try {
|
|
@@ -4576,11 +4913,11 @@ function effectiveType(hookPath) {
|
|
|
4576
4913
|
} catch {
|
|
4577
4914
|
return null;
|
|
4578
4915
|
}
|
|
4579
|
-
let dir =
|
|
4916
|
+
let dir = dirname8(real);
|
|
4580
4917
|
for (; ; ) {
|
|
4581
|
-
const pkg =
|
|
4582
|
-
if (
|
|
4583
|
-
const parent =
|
|
4918
|
+
const pkg = join33(dir, "package.json");
|
|
4919
|
+
if (existsSync24(pkg)) return typeFromPackageJson(pkg);
|
|
4920
|
+
const parent = dirname8(dir);
|
|
4584
4921
|
if (parent === dir) return "cjs";
|
|
4585
4922
|
dir = parent;
|
|
4586
4923
|
}
|
|
@@ -4608,7 +4945,7 @@ function remedy(family) {
|
|
|
4608
4945
|
}
|
|
4609
4946
|
function safeReaddir2(dir) {
|
|
4610
4947
|
try {
|
|
4611
|
-
return
|
|
4948
|
+
return readdirSync13(dir);
|
|
4612
4949
|
} catch {
|
|
4613
4950
|
return [];
|
|
4614
4951
|
}
|
|
@@ -4621,15 +4958,15 @@ function safeRead(path) {
|
|
|
4621
4958
|
}
|
|
4622
4959
|
}
|
|
4623
4960
|
function reportHookScopeCheck(section2) {
|
|
4624
|
-
const hooksDir =
|
|
4625
|
-
if (!
|
|
4961
|
+
const hooksDir = join33(claudeHome(), "hooks");
|
|
4962
|
+
if (!existsSync24(hooksDir)) {
|
|
4626
4963
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
4627
4964
|
return;
|
|
4628
4965
|
}
|
|
4629
4966
|
let anyWarn = false;
|
|
4630
4967
|
for (const name of safeReaddir2(hooksDir)) {
|
|
4631
4968
|
if (extname(name) !== ".js") continue;
|
|
4632
|
-
const abs =
|
|
4969
|
+
const abs = join33(hooksDir, name);
|
|
4633
4970
|
const eff = effectiveType(abs);
|
|
4634
4971
|
if (eff === null) continue;
|
|
4635
4972
|
const src = safeRead(abs);
|
|
@@ -4649,8 +4986,8 @@ function reportHookScopeCheck(section2) {
|
|
|
4649
4986
|
|
|
4650
4987
|
// src/commands.doctor.checks.hooks.ts
|
|
4651
4988
|
init_color();
|
|
4652
|
-
import { existsSync as
|
|
4653
|
-
import { join as
|
|
4989
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
4990
|
+
import { join as join34 } from "node:path";
|
|
4654
4991
|
init_config();
|
|
4655
4992
|
function expandHome(token) {
|
|
4656
4993
|
const h2 = home();
|
|
@@ -4693,7 +5030,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
4693
5030
|
for (const group of groups) {
|
|
4694
5031
|
for (const cmd of commandsFromGroup(group)) {
|
|
4695
5032
|
for (const resolved of claudePathsIn(cmd)) {
|
|
4696
|
-
if (
|
|
5033
|
+
if (existsSync25(resolved)) continue;
|
|
4697
5034
|
addItem(
|
|
4698
5035
|
section2,
|
|
4699
5036
|
`${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
|
|
@@ -4706,8 +5043,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
4706
5043
|
return anyFail;
|
|
4707
5044
|
}
|
|
4708
5045
|
function reportHooksTargetCheck(section2) {
|
|
4709
|
-
const settingsPath =
|
|
4710
|
-
if (!
|
|
5046
|
+
const settingsPath = join34(claudeHome(), "settings.json");
|
|
5047
|
+
if (!existsSync25(settingsPath)) {
|
|
4711
5048
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
4712
5049
|
return;
|
|
4713
5050
|
}
|
|
@@ -4730,12 +5067,12 @@ function reportHooksTargetCheck(section2) {
|
|
|
4730
5067
|
|
|
4731
5068
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
4732
5069
|
init_color();
|
|
4733
|
-
import { existsSync as
|
|
4734
|
-
import { join as
|
|
5070
|
+
import { existsSync as existsSync27, readFileSync as readFileSync9 } from "node:fs";
|
|
5071
|
+
import { join as join36 } from "node:path";
|
|
4735
5072
|
|
|
4736
5073
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
4737
|
-
import { closeSync as closeSync3, existsSync as
|
|
4738
|
-
import { dirname as
|
|
5074
|
+
import { closeSync as closeSync3, existsSync as existsSync26, openSync as openSync3, readSync, realpathSync as realpathSync4 } from "node:fs";
|
|
5075
|
+
import { dirname as dirname9, join as join35, resolve as resolve3 } from "node:path";
|
|
4739
5076
|
function suppressedRanges(src) {
|
|
4740
5077
|
const ranges = [];
|
|
4741
5078
|
const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
|
|
@@ -4766,13 +5103,13 @@ function topRelativeSpecifiers(src) {
|
|
|
4766
5103
|
return specifiers;
|
|
4767
5104
|
}
|
|
4768
5105
|
function specifierIsMissing(specifier, baseDir) {
|
|
4769
|
-
const base =
|
|
4770
|
-
if (
|
|
5106
|
+
const base = resolve3(baseDir, specifier);
|
|
5107
|
+
if (existsSync26(base)) return false;
|
|
4771
5108
|
for (const ext of [".js", ".cjs", ".mjs"]) {
|
|
4772
|
-
if (
|
|
5109
|
+
if (existsSync26(base + ext)) return false;
|
|
4773
5110
|
}
|
|
4774
5111
|
for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
|
|
4775
|
-
if (
|
|
5112
|
+
if (existsSync26(join35(base, idx))) return false;
|
|
4776
5113
|
}
|
|
4777
5114
|
return true;
|
|
4778
5115
|
}
|
|
@@ -4798,7 +5135,7 @@ function relativeRequireTargetsBroken(scriptPath) {
|
|
|
4798
5135
|
}
|
|
4799
5136
|
const specifiers = topRelativeSpecifiers(raw);
|
|
4800
5137
|
if (specifiers.length === 0) return false;
|
|
4801
|
-
const baseDir =
|
|
5138
|
+
const baseDir = dirname9(realPath);
|
|
4802
5139
|
for (const spec of specifiers) {
|
|
4803
5140
|
if (specifierIsMissing(spec, baseDir)) return true;
|
|
4804
5141
|
}
|
|
@@ -4827,8 +5164,8 @@ function commandTokens(command) {
|
|
|
4827
5164
|
return tokens;
|
|
4828
5165
|
}
|
|
4829
5166
|
function readPathMapSafe() {
|
|
4830
|
-
const mapPath =
|
|
4831
|
-
if (!
|
|
5167
|
+
const mapPath = join36(repoHome(), "path-map.json");
|
|
5168
|
+
if (!existsSync27(mapPath)) return { projects: {} };
|
|
4832
5169
|
try {
|
|
4833
5170
|
return JSON.parse(readFileSync9(mapPath, "utf8"));
|
|
4834
5171
|
} catch {
|
|
@@ -4902,8 +5239,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
4902
5239
|
return anyWarn;
|
|
4903
5240
|
}
|
|
4904
5241
|
function reportPreserveSymlinksCheck(section2) {
|
|
4905
|
-
const settingsPath =
|
|
4906
|
-
if (!
|
|
5242
|
+
const settingsPath = join36(claudeHome(), "settings.json");
|
|
5243
|
+
if (!existsSync27(settingsPath)) {
|
|
4907
5244
|
addItem(
|
|
4908
5245
|
section2,
|
|
4909
5246
|
`${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
|
|
@@ -4936,8 +5273,8 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
4936
5273
|
|
|
4937
5274
|
// src/commands.doctor.checks.settings-drift.ts
|
|
4938
5275
|
init_color();
|
|
4939
|
-
import { existsSync as
|
|
4940
|
-
import { join as
|
|
5276
|
+
import { existsSync as existsSync28, readFileSync as readFileSync10 } from "node:fs";
|
|
5277
|
+
import { join as join37 } from "node:path";
|
|
4941
5278
|
init_config();
|
|
4942
5279
|
init_utils_json();
|
|
4943
5280
|
function diffMergedSettings(merged, settings) {
|
|
@@ -4955,7 +5292,7 @@ function tryReadJson(filePath) {
|
|
|
4955
5292
|
}
|
|
4956
5293
|
}
|
|
4957
5294
|
function reportHooksBaseSelfCleanNote(section2) {
|
|
4958
|
-
const basePath =
|
|
5295
|
+
const basePath = join37(repoHome(), "shared", "settings.base.json");
|
|
4959
5296
|
const base = tryReadJson(basePath);
|
|
4960
5297
|
if (base === null) return;
|
|
4961
5298
|
if (!baseHasGsdHookEntries(base)) return;
|
|
@@ -4968,14 +5305,14 @@ function reportSettingsDriftCheck(section2) {
|
|
|
4968
5305
|
const claude = claudeHome();
|
|
4969
5306
|
const repo = repoHome();
|
|
4970
5307
|
const host = HOST;
|
|
4971
|
-
const settingsPath =
|
|
4972
|
-
const basePath =
|
|
4973
|
-
const hostPath =
|
|
4974
|
-
if (!
|
|
5308
|
+
const settingsPath = join37(claude, "settings.json");
|
|
5309
|
+
const basePath = join37(repo, "shared", "settings.base.json");
|
|
5310
|
+
const hostPath = join37(repo, "hosts", `${host}.json`);
|
|
5311
|
+
if (!existsSync28(settingsPath)) {
|
|
4975
5312
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
4976
5313
|
return;
|
|
4977
5314
|
}
|
|
4978
|
-
if (!
|
|
5315
|
+
if (!existsSync28(basePath)) {
|
|
4979
5316
|
addItem(
|
|
4980
5317
|
section2,
|
|
4981
5318
|
`${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
|
|
@@ -4994,7 +5331,7 @@ function reportSettingsDriftCheck(section2) {
|
|
|
4994
5331
|
if (settings === null) {
|
|
4995
5332
|
return;
|
|
4996
5333
|
}
|
|
4997
|
-
const hostExists =
|
|
5334
|
+
const hostExists = existsSync28(hostPath);
|
|
4998
5335
|
const hostObj = hostExists ? tryReadJson(hostPath) : null;
|
|
4999
5336
|
if (hostExists && hostObj === null) {
|
|
5000
5337
|
addItem(
|
|
@@ -5211,15 +5548,15 @@ function reportSyncModality(section2) {
|
|
|
5211
5548
|
// src/commands.doctor.checks.crlf.ts
|
|
5212
5549
|
init_color();
|
|
5213
5550
|
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
5214
|
-
import { existsSync as
|
|
5215
|
-
import { join as
|
|
5551
|
+
import { existsSync as existsSync29, readFileSync as readFileSync13 } from "node:fs";
|
|
5552
|
+
import { join as join38 } from "node:path";
|
|
5216
5553
|
init_config();
|
|
5217
5554
|
var PROBE_TIMEOUT_MS2 = 3e3;
|
|
5218
5555
|
var GUARD_LINE = /^\*\s+-text\b/;
|
|
5219
5556
|
function hasGitattributesGuard(repo) {
|
|
5220
5557
|
try {
|
|
5221
|
-
const path =
|
|
5222
|
-
if (!
|
|
5558
|
+
const path = join38(repo, ".gitattributes");
|
|
5559
|
+
if (!existsSync29(path)) return false;
|
|
5223
5560
|
const content = readFileSync13(path, "utf8");
|
|
5224
5561
|
return content.split("\n").map((line) => line.trim()).some((line) => line !== "" && !line.startsWith("#") && GUARD_LINE.test(line));
|
|
5225
5562
|
} catch {
|
|
@@ -5260,34 +5597,34 @@ function reportCrlfGuardCheck(section2, run = execFileSync13) {
|
|
|
5260
5597
|
|
|
5261
5598
|
// src/spinner.ts
|
|
5262
5599
|
init_color();
|
|
5263
|
-
import { existsSync as
|
|
5600
|
+
import { existsSync as existsSync34 } from "node:fs";
|
|
5264
5601
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
5265
5602
|
import { Worker } from "node:worker_threads";
|
|
5266
5603
|
|
|
5267
5604
|
// src/commands.push.recovery.ts
|
|
5268
5605
|
init_config();
|
|
5269
5606
|
import { readFileSync as readFileSync18, rmSync as rmSync15, writeFileSync as writeFileSync9 } from "node:fs";
|
|
5270
|
-
import { join as
|
|
5607
|
+
import { join as join46 } from "node:path";
|
|
5271
5608
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
5272
5609
|
|
|
5273
5610
|
// src/commands.push.recovery.actions.ts
|
|
5274
5611
|
init_config();
|
|
5275
5612
|
import { readFileSync as readFileSync17 } from "node:fs";
|
|
5276
|
-
import { isAbsolute as isAbsolute2, resolve as
|
|
5613
|
+
import { isAbsolute as isAbsolute2, resolve as resolve4, sep as sep9 } from "node:path";
|
|
5277
5614
|
|
|
5278
5615
|
// src/commands.push.recovery.memory.ts
|
|
5279
5616
|
init_config();
|
|
5280
5617
|
init_config_sharedDirs_guard();
|
|
5281
5618
|
import {
|
|
5282
|
-
cpSync as
|
|
5283
|
-
lstatSync as
|
|
5619
|
+
cpSync as cpSync6,
|
|
5620
|
+
lstatSync as lstatSync14,
|
|
5284
5621
|
mkdirSync as mkdirSync9,
|
|
5285
5622
|
readFileSync as readFileSync14,
|
|
5286
5623
|
realpathSync as realpathSync5,
|
|
5287
5624
|
statSync as statSync6,
|
|
5288
5625
|
writeFileSync as writeFileSync6
|
|
5289
5626
|
} from "node:fs";
|
|
5290
|
-
import { dirname as
|
|
5627
|
+
import { dirname as dirname10, join as join39, sep as sep6 } from "node:path";
|
|
5291
5628
|
init_push_gitleaks_scan();
|
|
5292
5629
|
|
|
5293
5630
|
// src/rel-path-guard.ts
|
|
@@ -5316,11 +5653,11 @@ function resolveMemoryLocalPath(logical, relPath, map) {
|
|
|
5316
5653
|
if (!isSafeRelPath(relPath)) return null;
|
|
5317
5654
|
const abs = map.projects[logical]?.[HOST];
|
|
5318
5655
|
if (abs === void 0) return null;
|
|
5319
|
-
const memoryRoot =
|
|
5320
|
-
const localPath =
|
|
5656
|
+
const memoryRoot = join39(claudeHome(), "projects", encodePath(abs), "memory");
|
|
5657
|
+
const localPath = join39(memoryRoot, ...relPath.split("/"));
|
|
5321
5658
|
if (localPath !== memoryRoot && !localPath.startsWith(memoryRoot + sep6)) return null;
|
|
5322
5659
|
try {
|
|
5323
|
-
if (
|
|
5660
|
+
if (lstatSync14(localPath).isSymbolicLink()) return null;
|
|
5324
5661
|
const realLocal = realpathSync5(localPath);
|
|
5325
5662
|
const realRoot = realpathSync5(memoryRoot);
|
|
5326
5663
|
if (!realLocal.startsWith(realRoot + sep6)) return null;
|
|
@@ -5371,29 +5708,29 @@ function applyMemoryRedact(f, ts, map, scan = scanFile) {
|
|
|
5371
5708
|
);
|
|
5372
5709
|
}
|
|
5373
5710
|
writeFileSync6(localPath, after, "utf8");
|
|
5374
|
-
const dest =
|
|
5375
|
-
mkdirSync9(
|
|
5376
|
-
|
|
5711
|
+
const dest = join39(repoHome(), "shared", "projects", logical, "memory", ...relPath.split("/"));
|
|
5712
|
+
mkdirSync9(dirname10(dest), { recursive: true });
|
|
5713
|
+
cpSync6(localPath, dest, { force: true });
|
|
5377
5714
|
return true;
|
|
5378
5715
|
}
|
|
5379
5716
|
|
|
5380
5717
|
// src/commands.push.recovery.skills.ts
|
|
5381
5718
|
init_config();
|
|
5382
5719
|
import {
|
|
5383
|
-
cpSync as
|
|
5384
|
-
lstatSync as
|
|
5720
|
+
cpSync as cpSync7,
|
|
5721
|
+
lstatSync as lstatSync16,
|
|
5385
5722
|
mkdirSync as mkdirSync11,
|
|
5386
5723
|
readFileSync as readFileSync15,
|
|
5387
5724
|
realpathSync as realpathSync6,
|
|
5388
5725
|
statSync as statSync7,
|
|
5389
5726
|
writeFileSync as writeFileSync7
|
|
5390
5727
|
} from "node:fs";
|
|
5391
|
-
import { dirname as
|
|
5728
|
+
import { dirname as dirname11, join as join41, sep as sep7 } from "node:path";
|
|
5392
5729
|
|
|
5393
5730
|
// src/skills-sync.ts
|
|
5394
5731
|
init_config();
|
|
5395
|
-
import { existsSync as
|
|
5396
|
-
import { join as
|
|
5732
|
+
import { existsSync as existsSync30, lstatSync as lstatSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync14, rmSync as rmSync13 } from "node:fs";
|
|
5733
|
+
import { join as join40 } from "node:path";
|
|
5397
5734
|
|
|
5398
5735
|
// src/skills-sync.tracked.ts
|
|
5399
5736
|
init_utils();
|
|
@@ -5417,7 +5754,7 @@ function isSkillExcluded(name) {
|
|
|
5417
5754
|
return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
|
|
5418
5755
|
}
|
|
5419
5756
|
function copySkillsPush(src, dst) {
|
|
5420
|
-
const srcNames =
|
|
5757
|
+
const srcNames = readdirSync14(src, { encoding: "utf8" });
|
|
5421
5758
|
const blockSet = /* @__PURE__ */ new Set([
|
|
5422
5759
|
...srcNames.filter((n) => isGsdOwned(n)),
|
|
5423
5760
|
...ALWAYS_NEVER_SYNC
|
|
@@ -5428,11 +5765,11 @@ function copySkillsPull(src, dst, isRootPreserved) {
|
|
|
5428
5765
|
copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded, isRootPreserved);
|
|
5429
5766
|
}
|
|
5430
5767
|
function syncSkillsPull(ts, prePostHeads) {
|
|
5431
|
-
const sharedSkills =
|
|
5432
|
-
if (!
|
|
5433
|
-
const localSkills =
|
|
5768
|
+
const sharedSkills = join40(repoHome(), "shared", "skills");
|
|
5769
|
+
if (!existsSync30(sharedSkills)) return;
|
|
5770
|
+
const localSkills = join40(claudeHome(), "skills");
|
|
5434
5771
|
backupBeforeWrite(localSkills, ts);
|
|
5435
|
-
const dstStat =
|
|
5772
|
+
const dstStat = lstatSync15(localSkills, { throwIfNoEntry: false });
|
|
5436
5773
|
if (dstStat?.isSymbolicLink() === true) {
|
|
5437
5774
|
rmSync13(localSkills, { recursive: true, force: true });
|
|
5438
5775
|
mkdirSync10(localSkills, { recursive: true });
|
|
@@ -5441,11 +5778,11 @@ function syncSkillsPull(ts, prePostHeads) {
|
|
|
5441
5778
|
copySkillsPull(sharedSkills, localSkills, (name) => !tracked.has(name));
|
|
5442
5779
|
}
|
|
5443
5780
|
function syncSkillsPush() {
|
|
5444
|
-
const localSkills =
|
|
5445
|
-
const stat =
|
|
5781
|
+
const localSkills = join40(claudeHome(), "skills");
|
|
5782
|
+
const stat = lstatSync15(localSkills, { throwIfNoEntry: false });
|
|
5446
5783
|
if (stat === void 0) return;
|
|
5447
5784
|
if (stat.isSymbolicLink()) return;
|
|
5448
|
-
const sharedSkills =
|
|
5785
|
+
const sharedSkills = join40(repoHome(), "shared", "skills");
|
|
5449
5786
|
copySkillsPush(localSkills, sharedSkills);
|
|
5450
5787
|
}
|
|
5451
5788
|
|
|
@@ -5468,12 +5805,12 @@ function resolveSkillLocalPath(name, relPath) {
|
|
|
5468
5805
|
if (!SAFE_SKILL_NAME.test(name) || name === "." || name === "..") return null;
|
|
5469
5806
|
if (isGsdOwned(name)) return null;
|
|
5470
5807
|
if (!isSafeRelPath(relPath)) return null;
|
|
5471
|
-
const skillsRoot =
|
|
5472
|
-
const skillRoot =
|
|
5473
|
-
const localPath =
|
|
5808
|
+
const skillsRoot = join41(claudeHome(), "skills");
|
|
5809
|
+
const skillRoot = join41(skillsRoot, name);
|
|
5810
|
+
const localPath = join41(skillRoot, ...relPath.split("/"));
|
|
5474
5811
|
if (localPath !== skillRoot && !localPath.startsWith(skillRoot + sep7)) return null;
|
|
5475
5812
|
try {
|
|
5476
|
-
if (
|
|
5813
|
+
if (lstatSync16(localPath).isSymbolicLink()) return null;
|
|
5477
5814
|
const realLocal = realpathSync6(localPath);
|
|
5478
5815
|
const realRoot = realpathSync6(skillsRoot);
|
|
5479
5816
|
if (!realLocal.startsWith(realRoot + sep7)) return null;
|
|
@@ -5522,9 +5859,9 @@ function applySkillRedact(f, ts, scan = scanFile) {
|
|
|
5522
5859
|
);
|
|
5523
5860
|
}
|
|
5524
5861
|
writeFileSync7(localPath, after, "utf8");
|
|
5525
|
-
const dest =
|
|
5526
|
-
mkdirSync11(
|
|
5527
|
-
|
|
5862
|
+
const dest = join41(repoHome(), "shared", "skills", name, ...relPath.split("/"));
|
|
5863
|
+
mkdirSync11(dirname11(dest), { recursive: true });
|
|
5864
|
+
cpSync7(localPath, dest, { force: true });
|
|
5528
5865
|
return true;
|
|
5529
5866
|
}
|
|
5530
5867
|
|
|
@@ -5600,26 +5937,26 @@ function applyDeferredAllows(findings, state, repo) {
|
|
|
5600
5937
|
// src/commands.push.recovery.redact.ts
|
|
5601
5938
|
init_config();
|
|
5602
5939
|
init_config_sharedDirs_guard();
|
|
5603
|
-
import { cpSync as
|
|
5604
|
-
import { dirname as
|
|
5940
|
+
import { cpSync as cpSync8, existsSync as existsSync33, mkdirSync as mkdirSync12 } from "node:fs";
|
|
5941
|
+
import { dirname as dirname13, join as join44, sep as sep8 } from "node:path";
|
|
5605
5942
|
|
|
5606
5943
|
// src/commands.redact.ts
|
|
5607
5944
|
init_config();
|
|
5608
|
-
import { existsSync as
|
|
5609
|
-
import { dirname as
|
|
5945
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
5946
|
+
import { dirname as dirname12, join as join43 } from "node:path";
|
|
5610
5947
|
|
|
5611
5948
|
// src/commands.redact.subtree.ts
|
|
5612
|
-
import { existsSync as
|
|
5613
|
-
import { join as
|
|
5949
|
+
import { existsSync as existsSync31, lstatSync as lstatSync17, readFileSync as readFileSync16, readdirSync as readdirSync15, statSync as statSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
5950
|
+
import { join as join42 } from "node:path";
|
|
5614
5951
|
init_utils_fs();
|
|
5615
5952
|
init_utils();
|
|
5616
5953
|
function collectFiles(dir, out) {
|
|
5617
|
-
if (!
|
|
5618
|
-
const st =
|
|
5954
|
+
if (!existsSync31(dir)) return;
|
|
5955
|
+
const st = lstatSync17(dir);
|
|
5619
5956
|
if (!st.isDirectory()) return;
|
|
5620
|
-
for (const entry of
|
|
5621
|
-
const abs =
|
|
5622
|
-
const lst =
|
|
5957
|
+
for (const entry of readdirSync15(dir)) {
|
|
5958
|
+
const abs = join42(dir, entry);
|
|
5959
|
+
const lst = lstatSync17(abs);
|
|
5623
5960
|
if (lst.isSymbolicLink()) continue;
|
|
5624
5961
|
if (lst.isDirectory()) {
|
|
5625
5962
|
collectFiles(abs, out);
|
|
@@ -5728,15 +6065,15 @@ init_utils_json();
|
|
|
5728
6065
|
init_utils();
|
|
5729
6066
|
function resolveLiveTranscript(id) {
|
|
5730
6067
|
try {
|
|
5731
|
-
const mapPath =
|
|
5732
|
-
if (!
|
|
6068
|
+
const mapPath = join43(repoHome(), "path-map.json");
|
|
6069
|
+
if (!existsSync32(mapPath)) return null;
|
|
5733
6070
|
const projects = readJson(mapPath).projects;
|
|
5734
6071
|
const claude = claudeHome();
|
|
5735
6072
|
for (const hostMap of Object.values(projects)) {
|
|
5736
6073
|
const abs = hostMap[HOST];
|
|
5737
6074
|
if (abs === void 0) continue;
|
|
5738
|
-
const live =
|
|
5739
|
-
if (
|
|
6075
|
+
const live = join43(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
6076
|
+
if (existsSync32(live)) return live;
|
|
5740
6077
|
}
|
|
5741
6078
|
return null;
|
|
5742
6079
|
} catch {
|
|
@@ -5756,17 +6093,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
5756
6093
|
}
|
|
5757
6094
|
const repo = repoHome();
|
|
5758
6095
|
const backup = backupBase();
|
|
5759
|
-
if (!
|
|
6096
|
+
if (!existsSync32(repo)) die(`repo not cloned at ${repo}`);
|
|
5760
6097
|
const handle = acquireLock("redact");
|
|
5761
6098
|
if (handle === null) process.exit(0);
|
|
5762
6099
|
try {
|
|
5763
6100
|
const localPath = resolveLiveTranscript(id);
|
|
5764
|
-
if (localPath === null || !
|
|
6101
|
+
if (localPath === null || !existsSync32(localPath)) {
|
|
5765
6102
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
5766
6103
|
process.exitCode = 1;
|
|
5767
6104
|
return;
|
|
5768
6105
|
}
|
|
5769
|
-
const sessionDir =
|
|
6106
|
+
const sessionDir = join43(dirname12(localPath), id);
|
|
5770
6107
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
5771
6108
|
if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
|
|
5772
6109
|
log(
|
|
@@ -5827,8 +6164,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
5827
6164
|
assertSafeLogical(logical);
|
|
5828
6165
|
const abs = hostMap[HOST];
|
|
5829
6166
|
if (abs === void 0) continue;
|
|
5830
|
-
if (localPath.startsWith(
|
|
5831
|
-
return
|
|
6167
|
+
if (localPath.startsWith(join44(claude, "projects", encodePath(abs)) + sep8)) {
|
|
6168
|
+
return join44(repo, "shared", "projects", logical);
|
|
5832
6169
|
}
|
|
5833
6170
|
}
|
|
5834
6171
|
return null;
|
|
@@ -5838,7 +6175,7 @@ function preflightRedactable(f, map, nowMs) {
|
|
|
5838
6175
|
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
5839
6176
|
const localPath = resolveLiveTranscript(sid);
|
|
5840
6177
|
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
5841
|
-
const sessionDir =
|
|
6178
|
+
const sessionDir = join44(dirname13(localPath), sid);
|
|
5842
6179
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
5843
6180
|
if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
|
|
5844
6181
|
return `session ${sid}: looks active (modified within the last 5 minutes)`;
|
|
@@ -5867,7 +6204,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
5867
6204
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
5868
6205
|
);
|
|
5869
6206
|
}
|
|
5870
|
-
const sessionDir =
|
|
6207
|
+
const sessionDir = join44(dirname13(localPath), sid);
|
|
5871
6208
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
5872
6209
|
if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
|
|
5873
6210
|
return refuse(
|
|
@@ -5900,9 +6237,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
5900
6237
|
);
|
|
5901
6238
|
}
|
|
5902
6239
|
mkdirSync12(stagedProjectDir, { recursive: true });
|
|
5903
|
-
|
|
5904
|
-
if (
|
|
5905
|
-
|
|
6240
|
+
cpSync8(localPath, join44(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
6241
|
+
if (existsSync33(sessionDir)) {
|
|
6242
|
+
cpSync8(sessionDir, join44(stagedProjectDir, sid), { force: true, recursive: true });
|
|
5906
6243
|
}
|
|
5907
6244
|
return true;
|
|
5908
6245
|
}
|
|
@@ -5910,14 +6247,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
5910
6247
|
// src/commands.push.recovery.drop.ts
|
|
5911
6248
|
init_config();
|
|
5912
6249
|
import { rmSync as rmSync14 } from "node:fs";
|
|
5913
|
-
import { join as
|
|
6250
|
+
import { join as join45 } from "node:path";
|
|
5914
6251
|
function dropSessionFromStaged(sid, map) {
|
|
5915
6252
|
const logicals = Object.keys(map.projects);
|
|
5916
6253
|
if (logicals.length === 0) return false;
|
|
5917
6254
|
const repo = repoHome();
|
|
5918
6255
|
for (const logical of logicals) {
|
|
5919
|
-
const jsonl =
|
|
5920
|
-
const dir =
|
|
6256
|
+
const jsonl = join45(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
6257
|
+
const dir = join45(repo, "shared", "projects", logical, sid);
|
|
5921
6258
|
rmSync14(jsonl, { force: true });
|
|
5922
6259
|
rmSync14(dir, { recursive: true, force: true });
|
|
5923
6260
|
}
|
|
@@ -6189,8 +6526,8 @@ function makeDefaultReadLine(repo) {
|
|
|
6189
6526
|
const cache = /* @__PURE__ */ new Map();
|
|
6190
6527
|
return (file, line) => {
|
|
6191
6528
|
try {
|
|
6192
|
-
const repoRoot =
|
|
6193
|
-
const target =
|
|
6529
|
+
const repoRoot = resolve4(repo);
|
|
6530
|
+
const target = resolve4(repoRoot, file);
|
|
6194
6531
|
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep9)) {
|
|
6195
6532
|
return null;
|
|
6196
6533
|
}
|
|
@@ -6402,7 +6739,7 @@ function applyThenRescan(scanVerdict, repoHome2) {
|
|
|
6402
6739
|
return next;
|
|
6403
6740
|
}
|
|
6404
6741
|
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
6405
|
-
const ignPath =
|
|
6742
|
+
const ignPath = join46(repoHome2, ".gitleaksignore");
|
|
6406
6743
|
let before;
|
|
6407
6744
|
try {
|
|
6408
6745
|
before = readFileSync18(ignPath, "utf8");
|
|
@@ -6508,7 +6845,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
6508
6845
|
`);
|
|
6509
6846
|
}
|
|
6510
6847
|
function resolveWorkerPath(deps = {}) {
|
|
6511
|
-
const check = deps.existsSyncFn ??
|
|
6848
|
+
const check = deps.existsSyncFn ?? existsSync34;
|
|
6512
6849
|
const base = deps.baseUrl ?? import.meta.url;
|
|
6513
6850
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
6514
6851
|
if (check(mjs)) return mjs;
|
|
@@ -6575,8 +6912,8 @@ function withSpinner(label, fn, deps) {
|
|
|
6575
6912
|
// src/commands.doctor.gitleaks-version.ts
|
|
6576
6913
|
init_color();
|
|
6577
6914
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
6578
|
-
import { existsSync as
|
|
6579
|
-
import { join as
|
|
6915
|
+
import { existsSync as existsSync35 } from "node:fs";
|
|
6916
|
+
import { join as join47 } from "node:path";
|
|
6580
6917
|
init_config();
|
|
6581
6918
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
6582
6919
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -6585,7 +6922,7 @@ function majorMinorOf(value) {
|
|
|
6585
6922
|
return m === null ? null : [m[1], m[2]];
|
|
6586
6923
|
}
|
|
6587
6924
|
function readGitleaksVersion(run, tomlExists) {
|
|
6588
|
-
const tomlPath =
|
|
6925
|
+
const tomlPath = join47(repoHome(), ".gitleaks.toml");
|
|
6589
6926
|
const args = ["version"];
|
|
6590
6927
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
6591
6928
|
try {
|
|
@@ -6597,7 +6934,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
6597
6934
|
return null;
|
|
6598
6935
|
}
|
|
6599
6936
|
}
|
|
6600
|
-
function reportGitleaksVersionCheck(section2, run = execFileSync15, tomlExists =
|
|
6937
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync15, tomlExists = existsSync35) {
|
|
6601
6938
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
6602
6939
|
if (raw === null) return;
|
|
6603
6940
|
const local = majorMinorOf(raw);
|
|
@@ -6854,8 +7191,8 @@ function gatherDoctorSections(opts) {
|
|
|
6854
7191
|
reportLongPathsCheck(host);
|
|
6855
7192
|
reportCrlfGuardCheck(host);
|
|
6856
7193
|
const links = section("Shared links");
|
|
6857
|
-
const mapPath =
|
|
6858
|
-
const rawMap =
|
|
7194
|
+
const mapPath = join48(repoHome(), "path-map.json");
|
|
7195
|
+
const rawMap = existsSync36(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
6859
7196
|
const map = rawMap ?? { projects: {} };
|
|
6860
7197
|
reportSharedLinks(links, map);
|
|
6861
7198
|
reportDroppedNamesMigration(links);
|
|
@@ -6953,8 +7290,8 @@ function parseDoctorArgs(args) {
|
|
|
6953
7290
|
// src/commands.drop-session.ts
|
|
6954
7291
|
init_config();
|
|
6955
7292
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
6956
|
-
import { existsSync as
|
|
6957
|
-
import { join as
|
|
7293
|
+
import { existsSync as existsSync38, readdirSync as readdirSync16, statSync as statSync9 } from "node:fs";
|
|
7294
|
+
import { join as join50, relative as relative7 } from "node:path";
|
|
6958
7295
|
|
|
6959
7296
|
// src/commands.drop-session.git.ts
|
|
6960
7297
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
@@ -6997,8 +7334,8 @@ function isInIndex(rel, repo) {
|
|
|
6997
7334
|
init_config();
|
|
6998
7335
|
init_utils();
|
|
6999
7336
|
init_utils_json();
|
|
7000
|
-
import { existsSync as
|
|
7001
|
-
import { join as
|
|
7337
|
+
import { existsSync as existsSync37 } from "node:fs";
|
|
7338
|
+
import { join as join49 } from "node:path";
|
|
7002
7339
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
7003
7340
|
function reportScrubHint(id, matches) {
|
|
7004
7341
|
const live = resolveLiveTranscript2(id, matches);
|
|
@@ -7014,8 +7351,8 @@ function reportScrubHint(id, matches) {
|
|
|
7014
7351
|
}
|
|
7015
7352
|
function resolveLiveTranscript2(id, matches) {
|
|
7016
7353
|
try {
|
|
7017
|
-
const mapPath =
|
|
7018
|
-
if (!
|
|
7354
|
+
const mapPath = join49(repoHome(), "path-map.json");
|
|
7355
|
+
if (!existsSync37(mapPath)) return null;
|
|
7019
7356
|
const projects = readJson(mapPath).projects;
|
|
7020
7357
|
const claude = claudeHome();
|
|
7021
7358
|
for (const rel of matches) {
|
|
@@ -7023,8 +7360,8 @@ function resolveLiveTranscript2(id, matches) {
|
|
|
7023
7360
|
if (logical === void 0) continue;
|
|
7024
7361
|
const abs = projects[logical]?.[HOST];
|
|
7025
7362
|
if (abs === void 0) continue;
|
|
7026
|
-
const live =
|
|
7027
|
-
if (
|
|
7363
|
+
const live = join49(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
7364
|
+
if (existsSync37(live)) return live;
|
|
7028
7365
|
}
|
|
7029
7366
|
return null;
|
|
7030
7367
|
} catch {
|
|
@@ -7040,12 +7377,12 @@ function cmdDropSession(id) {
|
|
|
7040
7377
|
process.exit(1);
|
|
7041
7378
|
}
|
|
7042
7379
|
const repo = repoHome();
|
|
7043
|
-
if (!
|
|
7380
|
+
if (!existsSync38(repo)) die(`repo not cloned at ${repo}`);
|
|
7044
7381
|
const handle = acquireLock("drop-session");
|
|
7045
7382
|
if (handle === null) process.exit(0);
|
|
7046
7383
|
try {
|
|
7047
|
-
const repoProjects =
|
|
7048
|
-
if (!
|
|
7384
|
+
const repoProjects = join50(repo, "shared", "projects");
|
|
7385
|
+
if (!existsSync38(repoProjects)) {
|
|
7049
7386
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
7050
7387
|
}
|
|
7051
7388
|
const matches = collectMatches(repoProjects, id, repo);
|
|
@@ -7067,14 +7404,14 @@ function cmdDropSession(id) {
|
|
|
7067
7404
|
}
|
|
7068
7405
|
function collectMatches(repoProjects, id, repo) {
|
|
7069
7406
|
const matches = [];
|
|
7070
|
-
for (const logical of
|
|
7071
|
-
const candidate =
|
|
7072
|
-
if (
|
|
7073
|
-
matches.push(
|
|
7074
|
-
}
|
|
7075
|
-
const dir =
|
|
7076
|
-
if (
|
|
7077
|
-
const dirRel =
|
|
7407
|
+
for (const logical of readdirSync16(repoProjects)) {
|
|
7408
|
+
const candidate = join50(repoProjects, logical, `${id}.jsonl`);
|
|
7409
|
+
if (existsSync38(candidate)) {
|
|
7410
|
+
matches.push(relative7(repo, candidate));
|
|
7411
|
+
}
|
|
7412
|
+
const dir = join50(repoProjects, logical, id);
|
|
7413
|
+
if (existsSync38(dir) && statSync9(dir).isDirectory()) {
|
|
7414
|
+
const dirRel = relative7(repo, dir);
|
|
7078
7415
|
const staged = expandStagedDir(dirRel, repo);
|
|
7079
7416
|
if (staged.length > 0) matches.push(...staged);
|
|
7080
7417
|
else matches.push(dirRel);
|
|
@@ -7109,8 +7446,8 @@ function unstageOne(rel, repo) {
|
|
|
7109
7446
|
|
|
7110
7447
|
// src/commands.pull.ts
|
|
7111
7448
|
init_autostash_guard();
|
|
7112
|
-
import { existsSync as
|
|
7113
|
-
import { join as
|
|
7449
|
+
import { existsSync as existsSync47, mkdirSync as mkdirSync14 } from "node:fs";
|
|
7450
|
+
import { join as join61 } from "node:path";
|
|
7114
7451
|
|
|
7115
7452
|
// src/commands.push.sections.ts
|
|
7116
7453
|
init_color();
|
|
@@ -7218,18 +7555,18 @@ init_config();
|
|
|
7218
7555
|
|
|
7219
7556
|
// src/extras-sync.ts
|
|
7220
7557
|
init_config();
|
|
7221
|
-
import { existsSync as
|
|
7222
|
-
import { join as
|
|
7558
|
+
import { existsSync as existsSync40, statSync as statSync10 } from "node:fs";
|
|
7559
|
+
import { join as join53 } from "node:path";
|
|
7223
7560
|
init_utils();
|
|
7224
7561
|
init_utils_json();
|
|
7225
7562
|
|
|
7226
7563
|
// src/extras-sync.remap.ts
|
|
7227
7564
|
init_config();
|
|
7228
|
-
import { existsSync as
|
|
7229
|
-
import { dirname as
|
|
7565
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync13, readdirSync as readdirSync17, readFileSync as readFileSync19, realpathSync as realpathSync7, rmSync as rmSync16 } from "node:fs";
|
|
7566
|
+
import { dirname as dirname14, join as join52, sep as sep11 } from "node:path";
|
|
7230
7567
|
|
|
7231
7568
|
// src/extras-sync.planning-diff.ts
|
|
7232
|
-
import { join as
|
|
7569
|
+
import { join as join51, normalize as normalize2, sep as sep10 } from "node:path";
|
|
7233
7570
|
init_utils();
|
|
7234
7571
|
function processRecord(fields, i, changed, deleted) {
|
|
7235
7572
|
const status = fields[i];
|
|
@@ -7281,7 +7618,7 @@ function planningDeleteTargets(opts) {
|
|
|
7281
7618
|
const { deleted } = parsePlanningDiff(raw);
|
|
7282
7619
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
7283
7620
|
const prefix = logicalPrefix + ".planning/";
|
|
7284
|
-
const planningRoot =
|
|
7621
|
+
const planningRoot = join51(localRoot, ".planning");
|
|
7285
7622
|
const planningRootBoundary = planningRoot + sep10;
|
|
7286
7623
|
const targets = [];
|
|
7287
7624
|
for (const repoPath of deleted) {
|
|
@@ -7289,7 +7626,7 @@ function planningDeleteTargets(opts) {
|
|
|
7289
7626
|
continue;
|
|
7290
7627
|
}
|
|
7291
7628
|
const remainder = repoPath.slice(logicalPrefix.length);
|
|
7292
|
-
const candidate =
|
|
7629
|
+
const candidate = join51(localRoot, remainder);
|
|
7293
7630
|
const resolved = normalize2(candidate);
|
|
7294
7631
|
if (!resolved.startsWith(planningRootBoundary)) {
|
|
7295
7632
|
throw new NomadFatal(
|
|
@@ -7310,7 +7647,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
7310
7647
|
const would = [];
|
|
7311
7648
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
7312
7649
|
const { src, dst } = paths(t);
|
|
7313
|
-
if (!
|
|
7650
|
+
if (!existsSync39(src)) continue;
|
|
7314
7651
|
const item2 = `${t.logical}/${t.dirname}`;
|
|
7315
7652
|
if (dryRun) {
|
|
7316
7653
|
would.push(item2);
|
|
@@ -7323,15 +7660,15 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
7323
7660
|
return { ...counts, done, would };
|
|
7324
7661
|
}
|
|
7325
7662
|
function pruneEmptyAncestors(target, planningRoot) {
|
|
7326
|
-
let dir =
|
|
7663
|
+
let dir = dirname14(target);
|
|
7327
7664
|
while (dir !== planningRoot && dir.startsWith(planningRoot + sep11)) {
|
|
7328
7665
|
try {
|
|
7329
|
-
if (
|
|
7666
|
+
if (readdirSync17(dir).length > 0) break;
|
|
7330
7667
|
rmSync16(dir, { recursive: true, force: true });
|
|
7331
7668
|
} catch {
|
|
7332
7669
|
break;
|
|
7333
7670
|
}
|
|
7334
|
-
dir =
|
|
7671
|
+
dir = dirname14(dir);
|
|
7335
7672
|
}
|
|
7336
7673
|
}
|
|
7337
7674
|
function tryRealpath(dir) {
|
|
@@ -7345,8 +7682,8 @@ function isInsidePlanningRoot(parentReal, rootReal) {
|
|
|
7345
7682
|
return parentReal === rootReal || parentReal.startsWith(rootReal + sep11);
|
|
7346
7683
|
}
|
|
7347
7684
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
7348
|
-
if (
|
|
7349
|
-
const parentReal = tryRealpath(
|
|
7685
|
+
if (existsSync39(repoCounterpart)) return;
|
|
7686
|
+
const parentReal = tryRealpath(dirname14(target));
|
|
7350
7687
|
if (parentReal === void 0) return;
|
|
7351
7688
|
const rootReal = tryRealpath(planningRoot);
|
|
7352
7689
|
if (rootReal === void 0) return;
|
|
@@ -7355,7 +7692,7 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
|
7355
7692
|
pruneEmptyAncestors(target, planningRoot);
|
|
7356
7693
|
}
|
|
7357
7694
|
function localDivergesFromPreDelete(target, pre, repoRel, repo) {
|
|
7358
|
-
if (!
|
|
7695
|
+
if (!existsSync39(target)) return false;
|
|
7359
7696
|
try {
|
|
7360
7697
|
const preBlob = gitCaptureBuffer(["show", `${pre}:${repoRel}`], repo);
|
|
7361
7698
|
return !readFileSync19(target).equals(preBlob);
|
|
@@ -7400,7 +7737,7 @@ function keptDeletePreview(v, prePostHeads, repo) {
|
|
|
7400
7737
|
return kept;
|
|
7401
7738
|
}
|
|
7402
7739
|
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
7403
|
-
const repoExtras =
|
|
7740
|
+
const repoExtras = join52(repo, "shared", "extras");
|
|
7404
7741
|
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
7405
7742
|
if (t.dirname !== ".planning") continue;
|
|
7406
7743
|
let raw;
|
|
@@ -7415,14 +7752,14 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
7415
7752
|
}
|
|
7416
7753
|
const pairs = deletePairsFor(t, raw);
|
|
7417
7754
|
if (pairs.length === 0) continue;
|
|
7418
|
-
backupExtrasWrite(
|
|
7419
|
-
const planningRoot =
|
|
7755
|
+
backupExtrasWrite(join52(t.localRoot, t.dirname), ts, t.localRoot);
|
|
7756
|
+
const planningRoot = join52(t.localRoot, ".planning");
|
|
7420
7757
|
for (const { target, relToLocal, repoRel } of pairs) {
|
|
7421
7758
|
if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
|
|
7422
7759
|
warn(keptDeleteWarnLine(t.logical, relToLocal));
|
|
7423
7760
|
continue;
|
|
7424
7761
|
}
|
|
7425
|
-
deletePlanningTarget(target, planningRoot,
|
|
7762
|
+
deletePlanningTarget(target, planningRoot, join52(repoExtras, t.logical, relToLocal));
|
|
7426
7763
|
}
|
|
7427
7764
|
}
|
|
7428
7765
|
}
|
|
@@ -7431,14 +7768,14 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
7431
7768
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
7432
7769
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
7433
7770
|
const repo = repoHome();
|
|
7434
|
-
const repoExtras =
|
|
7771
|
+
const repoExtras = join52(repo, "shared", "extras");
|
|
7435
7772
|
if (!dryRun) mkdirSync13(repoExtras, { recursive: true });
|
|
7436
7773
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
7437
7774
|
v,
|
|
7438
7775
|
dryRun,
|
|
7439
|
-
({ localRoot, logical, dirname:
|
|
7440
|
-
src:
|
|
7441
|
-
dst:
|
|
7776
|
+
({ localRoot, logical, dirname: dirname15 }) => ({
|
|
7777
|
+
src: join52(localRoot, dirname15),
|
|
7778
|
+
dst: join52(repoExtras, logical, dirname15)
|
|
7442
7779
|
}),
|
|
7443
7780
|
(dst) => backupRepoWrite(dst, ts, repo),
|
|
7444
7781
|
// Push copy routing per extra type:
|
|
@@ -7451,7 +7788,7 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
7451
7788
|
// config.never-sync.ts)
|
|
7452
7789
|
// remains the hard security boundary.
|
|
7453
7790
|
// All others: copyExtrasFiltered with per-extra denylist.
|
|
7454
|
-
(src, dst,
|
|
7791
|
+
(src, dst, dirname15) => dirname15 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname15)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname15))
|
|
7455
7792
|
);
|
|
7456
7793
|
return { unmapped, skipped, pushed: done, wouldPush: would };
|
|
7457
7794
|
}
|
|
@@ -7467,9 +7804,9 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
7467
7804
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
7468
7805
|
v,
|
|
7469
7806
|
dryRun,
|
|
7470
|
-
({ localRoot, logical, dirname:
|
|
7471
|
-
src:
|
|
7472
|
-
dst:
|
|
7807
|
+
({ localRoot, logical, dirname: dirname15 }) => ({
|
|
7808
|
+
src: join52(repo, "shared", "extras", logical, dirname15),
|
|
7809
|
+
dst: join52(localRoot, dirname15)
|
|
7473
7810
|
}),
|
|
7474
7811
|
// Snapshot the host-side dst BEFORE the copy step touches it. Anchor on
|
|
7475
7812
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -7487,12 +7824,12 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
7487
7824
|
// copyExtrasFileSkipDiverged keeps a locally-edited file rather than
|
|
7488
7825
|
// clobbering it, so the divergence WARN's keep-local promise holds for
|
|
7489
7826
|
// every extra, not just `.planning`.
|
|
7490
|
-
(src, dst,
|
|
7491
|
-
if (
|
|
7492
|
-
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(
|
|
7493
|
-
if (
|
|
7827
|
+
(src, dst, dirname15) => {
|
|
7828
|
+
if (dirname15 === ".claude")
|
|
7829
|
+
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname15));
|
|
7830
|
+
if (dirname15 === ".planning") {
|
|
7494
7831
|
const divergedSet = new Set(listDivergingModified(dst, src));
|
|
7495
|
-
return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(
|
|
7832
|
+
return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname15), divergedSet);
|
|
7496
7833
|
}
|
|
7497
7834
|
return copyExtrasFileSkipDiverged(src, dst);
|
|
7498
7835
|
}
|
|
@@ -7516,20 +7853,20 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
7516
7853
|
const v = loadValidatedExtras({});
|
|
7517
7854
|
if (v === null) return 0;
|
|
7518
7855
|
const counts = { unmapped: 0, skipped: 0 };
|
|
7519
|
-
const backupRoot =
|
|
7856
|
+
const backupRoot = join53(backupBase(), ts, "extras");
|
|
7520
7857
|
const repo = repoHome();
|
|
7521
7858
|
let divergedCount = 0;
|
|
7522
|
-
for (const { logical, localRoot, dirname:
|
|
7523
|
-
const local =
|
|
7524
|
-
const repoEntry =
|
|
7525
|
-
if (!
|
|
7859
|
+
for (const { logical, localRoot, dirname: dirname15 } of eachExtrasTarget(v, counts)) {
|
|
7860
|
+
const local = join53(localRoot, dirname15);
|
|
7861
|
+
const repoEntry = join53(repo, "shared", "extras", logical, dirname15);
|
|
7862
|
+
if (!existsSync40(local) || !existsSync40(repoEntry)) continue;
|
|
7526
7863
|
const modified = listDivergingModified(local, repoEntry);
|
|
7527
7864
|
if (modified.length === 0) continue;
|
|
7528
7865
|
divergedCount += modified.length;
|
|
7529
|
-
const projectBackupRoot =
|
|
7866
|
+
const projectBackupRoot = join53(backupRoot, encodePath(localRoot));
|
|
7530
7867
|
warn(
|
|
7531
7868
|
divergenceWarnLine({
|
|
7532
|
-
dirname:
|
|
7869
|
+
dirname: dirname15,
|
|
7533
7870
|
logical,
|
|
7534
7871
|
isDir: statSync10(local).isDirectory(),
|
|
7535
7872
|
count: modified.length,
|
|
@@ -7548,8 +7885,8 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
7548
7885
|
|
|
7549
7886
|
// src/links.baseline.ts
|
|
7550
7887
|
init_config();
|
|
7551
|
-
import { lstatSync as
|
|
7552
|
-
import { join as
|
|
7888
|
+
import { lstatSync as lstatSync18, readdirSync as readdirSync18 } from "node:fs";
|
|
7889
|
+
import { join as join54, relative as relative8, sep as sep12 } from "node:path";
|
|
7553
7890
|
init_utils();
|
|
7554
7891
|
var SHARED_BASELINE_KIND = "shared-links-baseline/1";
|
|
7555
7892
|
var SHARED_BASELINE_CONFIG_HASH = "not-applicable";
|
|
@@ -7560,13 +7897,13 @@ function readSharedBaseline() {
|
|
|
7560
7897
|
return parsed;
|
|
7561
7898
|
}
|
|
7562
7899
|
function baselineKey(claude, abs) {
|
|
7563
|
-
return
|
|
7900
|
+
return relative8(claude, abs).split(sep12).join("/");
|
|
7564
7901
|
}
|
|
7565
7902
|
function addLocalPath(abs, claude, scan) {
|
|
7566
7903
|
const key = baselineKey(claude, abs);
|
|
7567
7904
|
let st;
|
|
7568
7905
|
try {
|
|
7569
|
-
st =
|
|
7906
|
+
st = lstatSync18(abs, { throwIfNoEntry: false });
|
|
7570
7907
|
} catch {
|
|
7571
7908
|
scan.declined.push(key);
|
|
7572
7909
|
return;
|
|
@@ -7585,13 +7922,13 @@ function addLocalPath(abs, claude, scan) {
|
|
|
7585
7922
|
function collectSharedFiles(dir, claude, scan) {
|
|
7586
7923
|
let entries;
|
|
7587
7924
|
try {
|
|
7588
|
-
entries =
|
|
7925
|
+
entries = readdirSync18(dir);
|
|
7589
7926
|
} catch {
|
|
7590
7927
|
scan.declined.push(baselineKey(claude, dir));
|
|
7591
7928
|
return;
|
|
7592
7929
|
}
|
|
7593
7930
|
for (const entry of entries) {
|
|
7594
|
-
const abs =
|
|
7931
|
+
const abs = join54(dir, entry);
|
|
7595
7932
|
if (isDeniedName(ALWAYS_NEVER_SYNC, entry)) {
|
|
7596
7933
|
scan.declined.push(baselineKey(claude, abs));
|
|
7597
7934
|
continue;
|
|
@@ -7607,7 +7944,7 @@ function enumerateLocalSharedScan(map, opts = {}) {
|
|
|
7607
7944
|
scan.declined.push(name);
|
|
7608
7945
|
continue;
|
|
7609
7946
|
}
|
|
7610
|
-
addLocalPath(
|
|
7947
|
+
addLocalPath(join54(claude, name), claude, scan);
|
|
7611
7948
|
}
|
|
7612
7949
|
return scan;
|
|
7613
7950
|
}
|
|
@@ -7619,7 +7956,7 @@ function buildSharedBaseline(map, opts = {}) {
|
|
|
7619
7956
|
const files = {};
|
|
7620
7957
|
for (const [key, st] of Object.entries(enumerateLocalSharedFiles(map, opts))) {
|
|
7621
7958
|
try {
|
|
7622
|
-
files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(
|
|
7959
|
+
files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(join54(claude, key)) };
|
|
7623
7960
|
} catch {
|
|
7624
7961
|
}
|
|
7625
7962
|
}
|
|
@@ -7636,8 +7973,8 @@ function writeSharedBaseline(map, opts = {}) {
|
|
|
7636
7973
|
|
|
7637
7974
|
// src/commands.pull.win32.ts
|
|
7638
7975
|
init_color();
|
|
7639
|
-
import { existsSync as
|
|
7640
|
-
import { join as
|
|
7976
|
+
import { existsSync as existsSync45 } from "node:fs";
|
|
7977
|
+
import { join as join59 } from "node:path";
|
|
7641
7978
|
|
|
7642
7979
|
// src/commands.pull.recovery.git.ts
|
|
7643
7980
|
init_utils();
|
|
@@ -7700,8 +8037,8 @@ function gitProbe(args, repo) {
|
|
|
7700
8037
|
|
|
7701
8038
|
// src/links.deletions.ts
|
|
7702
8039
|
init_config();
|
|
7703
|
-
import { existsSync as
|
|
7704
|
-
import { join as
|
|
8040
|
+
import { existsSync as existsSync41, lstatSync as lstatSync19, rmSync as rmSync17 } from "node:fs";
|
|
8041
|
+
import { join as join55, relative as relative9, resolve as resolve5, sep as sep13 } from "node:path";
|
|
7705
8042
|
init_utils();
|
|
7706
8043
|
init_utils_fs();
|
|
7707
8044
|
function isUnknown(declined, key) {
|
|
@@ -7711,15 +8048,15 @@ function deletionFor(key, names, claude, sharedRoot) {
|
|
|
7711
8048
|
const segments = key.split("/");
|
|
7712
8049
|
const name = segments[0];
|
|
7713
8050
|
if (!names.has(name)) return null;
|
|
7714
|
-
if (!
|
|
8051
|
+
if (!existsSync41(join55(claude, name))) return null;
|
|
7715
8052
|
if (segments.some((segment) => isDeniedName(ALWAYS_NEVER_SYNC, segment))) return null;
|
|
7716
|
-
const repoPath =
|
|
7717
|
-
const rel =
|
|
8053
|
+
const repoPath = resolve5(sharedRoot, key);
|
|
8054
|
+
const rel = relative9(join55(sharedRoot, name), repoPath);
|
|
7718
8055
|
if (rel === "" || rel === ".." || rel.startsWith(`..${sep13}`)) return null;
|
|
7719
8056
|
if (segments.some((segment) => segment === "" || segment === "." || segment === ".."))
|
|
7720
8057
|
return null;
|
|
7721
|
-
if (!
|
|
7722
|
-
return { kind: "deletion", name, localPath:
|
|
8058
|
+
if (!existsSync41(repoPath)) return null;
|
|
8059
|
+
return { kind: "deletion", name, localPath: join55(claude, key), repoPath };
|
|
7723
8060
|
}
|
|
7724
8061
|
function planSharedLinkDeletions(map, opts = {}) {
|
|
7725
8062
|
if (process.platform !== "win32") return [];
|
|
@@ -7727,7 +8064,7 @@ function planSharedLinkDeletions(map, opts = {}) {
|
|
|
7727
8064
|
const baseline = readSharedBaseline();
|
|
7728
8065
|
if (baseline === null) return [];
|
|
7729
8066
|
const claude = claudeHome();
|
|
7730
|
-
const sharedRoot =
|
|
8067
|
+
const sharedRoot = join55(repoHome(), "shared");
|
|
7731
8068
|
const linkNames = opts.linkNames ?? allSharedLinks(map);
|
|
7732
8069
|
const scan = enumerateLocalSharedScan(map, { linkNames });
|
|
7733
8070
|
const present = new Set(Object.keys(scan.files).map((key) => key.toLowerCase()));
|
|
@@ -7748,7 +8085,7 @@ function applySharedLinkDeletions(map, ts, opts = {}) {
|
|
|
7748
8085
|
const removed = [];
|
|
7749
8086
|
for (const entry of planSharedLinkDeletions(map, opts)) {
|
|
7750
8087
|
try {
|
|
7751
|
-
if (!
|
|
8088
|
+
if (!lstatSync19(entry.repoPath).isFile()) continue;
|
|
7752
8089
|
backupRepoWrite(entry.repoPath, ts, repo);
|
|
7753
8090
|
rmSync17(entry.repoPath, { force: true });
|
|
7754
8091
|
removed.push(entry);
|
|
@@ -7761,8 +8098,8 @@ function applySharedLinkDeletions(map, ts, opts = {}) {
|
|
|
7761
8098
|
|
|
7762
8099
|
// src/links.mirror.ts
|
|
7763
8100
|
init_config();
|
|
7764
|
-
import { existsSync as
|
|
7765
|
-
import { join as
|
|
8101
|
+
import { existsSync as existsSync42, lstatSync as lstatSync20, rmSync as rmSync18 } from "node:fs";
|
|
8102
|
+
import { join as join56 } from "node:path";
|
|
7766
8103
|
init_utils();
|
|
7767
8104
|
init_utils_fs();
|
|
7768
8105
|
function emitMirror(onPreview, name, localPath, repoPath) {
|
|
@@ -7776,16 +8113,22 @@ function emitMirrorWet(onPreview, name, localPath, repoPath) {
|
|
|
7776
8113
|
if (onPreview) onPreview({ kind: "mirror", name, localPath, repoPath });
|
|
7777
8114
|
}
|
|
7778
8115
|
function notShared(policy, target) {
|
|
7779
|
-
return !policy.adoptNew && !
|
|
8116
|
+
return !policy.adoptNew && !existsSync42(target);
|
|
7780
8117
|
}
|
|
7781
8118
|
function mirrorOneSharedName(name, claude, repo, policy, opts) {
|
|
7782
|
-
const localPath =
|
|
7783
|
-
const target =
|
|
8119
|
+
const localPath = join56(claude, name);
|
|
8120
|
+
const target = join56(repo, "shared", name);
|
|
7784
8121
|
let stat;
|
|
7785
8122
|
try {
|
|
7786
|
-
stat =
|
|
8123
|
+
stat = lstatSync20(localPath, { throwIfNoEntry: false });
|
|
7787
8124
|
} catch (err) {
|
|
7788
8125
|
if (notShared(policy, target)) return;
|
|
8126
|
+
if (opts.dryRun === true) {
|
|
8127
|
+
warn(
|
|
8128
|
+
`${name} could not be read (${err.message}), 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`
|
|
8129
|
+
);
|
|
8130
|
+
return;
|
|
8131
|
+
}
|
|
7789
8132
|
warn(
|
|
7790
8133
|
`${name} could not be read (${err.message}), so it was left out of shared/ this run. Check its permissions, or whether another program has it open`
|
|
7791
8134
|
);
|
|
@@ -7800,9 +8143,9 @@ function mirrorOneSharedName(name, claude, repo, policy, opts) {
|
|
|
7800
8143
|
}
|
|
7801
8144
|
if (policy.backupTs !== void 0) backupRepoWrite(target, policy.backupTs, repo);
|
|
7802
8145
|
if (policy.overlay && stat.isDirectory()) {
|
|
7803
|
-
copyExtrasOverlayFiltered(localPath, target,
|
|
8146
|
+
copyExtrasOverlayFiltered(localPath, target, ALWAYS_NEVER_SYNC);
|
|
7804
8147
|
} else {
|
|
7805
|
-
copyExtrasFiltered(localPath, target,
|
|
8148
|
+
copyExtrasFiltered(localPath, target, ALWAYS_NEVER_SYNC);
|
|
7806
8149
|
}
|
|
7807
8150
|
emitMirrorWet(opts.onPreview, name, localPath, target);
|
|
7808
8151
|
}
|
|
@@ -7817,20 +8160,20 @@ function mirrorSharedNames(map, policy, opts = {}) {
|
|
|
7817
8160
|
}
|
|
7818
8161
|
}
|
|
7819
8162
|
function syncSharedLinksPush(map, opts = {}) {
|
|
7820
|
-
mirrorSharedNames(map, { adoptNew:
|
|
8163
|
+
mirrorSharedNames(map, { adoptNew: false, overlay: false }, opts);
|
|
7821
8164
|
}
|
|
7822
8165
|
function stageLocalSharedEdits(map, ts, opts = {}) {
|
|
7823
8166
|
mirrorSharedNames(map, { adoptNew: false, overlay: true, backupTs: ts }, opts);
|
|
7824
8167
|
}
|
|
7825
8168
|
function presentAt(abs) {
|
|
7826
8169
|
try {
|
|
7827
|
-
return
|
|
8170
|
+
return lstatSync20(abs, { throwIfNoEntry: false }) !== void 0;
|
|
7828
8171
|
} catch {
|
|
7829
8172
|
return true;
|
|
7830
8173
|
}
|
|
7831
8174
|
}
|
|
7832
8175
|
function removeUntrackedDenied(repo, path, segment, ts) {
|
|
7833
|
-
const abs =
|
|
8176
|
+
const abs = join56(repo, path);
|
|
7834
8177
|
const denied = `the path segment "${segment}" is on the never-sync list`;
|
|
7835
8178
|
if (!presentAt(abs)) {
|
|
7836
8179
|
warn(
|
|
@@ -7838,7 +8181,7 @@ function removeUntrackedDenied(repo, path, segment, ts) {
|
|
|
7838
8181
|
);
|
|
7839
8182
|
return;
|
|
7840
8183
|
}
|
|
7841
|
-
const snapshotted =
|
|
8184
|
+
const snapshotted = existsSync42(abs);
|
|
7842
8185
|
try {
|
|
7843
8186
|
backupRepoWrite(abs, ts, repo);
|
|
7844
8187
|
} catch (err) {
|
|
@@ -7874,7 +8217,7 @@ function reportTrackedDenied(repo, path, segment) {
|
|
|
7874
8217
|
);
|
|
7875
8218
|
return;
|
|
7876
8219
|
}
|
|
7877
|
-
if (!
|
|
8220
|
+
if (!existsSync42(join56(repo, path))) return;
|
|
7878
8221
|
warn(
|
|
7879
8222
|
`${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`
|
|
7880
8223
|
);
|
|
@@ -7892,8 +8235,8 @@ function revertDeniedMirrorPaths(repo, status, ts) {
|
|
|
7892
8235
|
|
|
7893
8236
|
// src/preview.ts
|
|
7894
8237
|
init_config();
|
|
7895
|
-
import { existsSync as
|
|
7896
|
-
import { join as
|
|
8238
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
8239
|
+
import { join as join58 } from "node:path";
|
|
7897
8240
|
|
|
7898
8241
|
// node_modules/diff/libesm/diff/base.js
|
|
7899
8242
|
var Diff = class {
|
|
@@ -8168,16 +8511,16 @@ function diffLinesToUnified(oldStr, newStr) {
|
|
|
8168
8511
|
|
|
8169
8512
|
// src/preview.skills.ts
|
|
8170
8513
|
init_config();
|
|
8171
|
-
import { existsSync as
|
|
8172
|
-
import { join as
|
|
8514
|
+
import { existsSync as existsSync43, readdirSync as readdirSync19 } from "node:fs";
|
|
8515
|
+
import { join as join57 } from "node:path";
|
|
8173
8516
|
function buildSkillsPreviewSection() {
|
|
8174
8517
|
const s = section("Skills");
|
|
8175
|
-
const sharedSkills =
|
|
8176
|
-
if (!
|
|
8177
|
-
const sharedNames =
|
|
8518
|
+
const sharedSkills = join57(repoHome(), "shared", "skills");
|
|
8519
|
+
if (!existsSync43(sharedSkills)) return s;
|
|
8520
|
+
const sharedNames = readdirSync19(sharedSkills, { encoding: "utf8" }).filter((name) => !isSkillExcluded(name)).sort((a, b) => a.localeCompare(b, "en"));
|
|
8178
8521
|
for (const name of sharedNames) addItem(s, name);
|
|
8179
|
-
const localSkills =
|
|
8180
|
-
const localOnly =
|
|
8522
|
+
const localSkills = join57(claudeHome(), "skills");
|
|
8523
|
+
const localOnly = existsSync43(localSkills) ? readdirSync19(localSkills, { encoding: "utf8" }).filter(
|
|
8181
8524
|
(name) => !isSkillExcluded(name) && !sharedNames.includes(name)
|
|
8182
8525
|
).length : 0;
|
|
8183
8526
|
if (localOnly > 0) {
|
|
@@ -8199,7 +8542,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
8199
8542
|
return lines.join("\n");
|
|
8200
8543
|
}
|
|
8201
8544
|
function readJsonOrNull(path) {
|
|
8202
|
-
if (!
|
|
8545
|
+
if (!existsSync44(path)) return null;
|
|
8203
8546
|
try {
|
|
8204
8547
|
return readJson(path);
|
|
8205
8548
|
} catch {
|
|
@@ -8213,12 +8556,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
8213
8556
|
}
|
|
8214
8557
|
const notes = [];
|
|
8215
8558
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
8216
|
-
if (hostOverrides === null &&
|
|
8559
|
+
if (hostOverrides === null && existsSync44(hostPath)) {
|
|
8217
8560
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
8218
8561
|
}
|
|
8219
8562
|
const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
|
|
8220
8563
|
const current = readJsonOrNull(settingsPath);
|
|
8221
|
-
if (current === null &&
|
|
8564
|
+
if (current === null && existsSync44(settingsPath)) {
|
|
8222
8565
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
8223
8566
|
}
|
|
8224
8567
|
const strippedCurrent = stripGsdHookEntries(current ?? {});
|
|
@@ -8285,9 +8628,9 @@ function computePreview(ts, map, verb = "pull", plans) {
|
|
|
8285
8628
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
8286
8629
|
});
|
|
8287
8630
|
const settingsResult = previewSettings(
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8631
|
+
join58(repo, "shared", "settings.base.json"),
|
|
8632
|
+
join58(repo, "hosts", `${HOST}.json`),
|
|
8633
|
+
join58(claude, "settings.json")
|
|
8291
8634
|
);
|
|
8292
8635
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
8293
8636
|
const sessions = section("Sessions");
|
|
@@ -8303,7 +8646,7 @@ function computePreview(ts, map, verb = "pull", plans) {
|
|
|
8303
8646
|
const extras = section("Extras");
|
|
8304
8647
|
let extrasSkipped = 0;
|
|
8305
8648
|
let extrasUnmapped = 0;
|
|
8306
|
-
if (
|
|
8649
|
+
if (existsSync44(join58(repo, "path-map.json")) && existsSync44(join58(repo, "shared", "extras"))) {
|
|
8307
8650
|
const extrasResult = remapExtrasPull(ts, { dryRun: true });
|
|
8308
8651
|
for (const entry of extrasResult.wouldPull) {
|
|
8309
8652
|
addItem(extras, entry);
|
|
@@ -8324,7 +8667,7 @@ function computePreview(ts, map, verb = "pull", plans) {
|
|
|
8324
8667
|
init_utils();
|
|
8325
8668
|
init_utils_json();
|
|
8326
8669
|
function readMapForMirror(mapPath) {
|
|
8327
|
-
if (!
|
|
8670
|
+
if (!existsSync45(mapPath)) return { projects: {} };
|
|
8328
8671
|
try {
|
|
8329
8672
|
return readPathMap(mapPath);
|
|
8330
8673
|
} catch {
|
|
@@ -8371,7 +8714,7 @@ function reconcileSharedLinksBeforePull(repo, ts) {
|
|
|
8371
8714
|
let namesDerived = false;
|
|
8372
8715
|
let derivedSharedDirs;
|
|
8373
8716
|
try {
|
|
8374
|
-
const map = readMapForMirror(
|
|
8717
|
+
const map = readMapForMirror(join59(repo, "path-map.json"));
|
|
8375
8718
|
if (map !== null) {
|
|
8376
8719
|
linkNames = allSharedLinks(map);
|
|
8377
8720
|
namesDerived = true;
|
|
@@ -8410,7 +8753,7 @@ function buildMirrorSection(events, discard) {
|
|
|
8410
8753
|
function describeSkippedMirrorDiscard(repo, ts) {
|
|
8411
8754
|
if (process.platform !== "win32") return null;
|
|
8412
8755
|
try {
|
|
8413
|
-
const map = readMapForMirror(
|
|
8756
|
+
const map = readMapForMirror(join59(repo, "path-map.json"));
|
|
8414
8757
|
const linkNames = map !== null ? allSharedLinks(map, { quiet: true }) : void 0;
|
|
8415
8758
|
const captures = [];
|
|
8416
8759
|
stageLocalSharedEdits(map, ts, {
|
|
@@ -8419,13 +8762,13 @@ function describeSkippedMirrorDiscard(repo, ts) {
|
|
|
8419
8762
|
linkNames
|
|
8420
8763
|
});
|
|
8421
8764
|
if (captures.length === 0) return null;
|
|
8422
|
-
return { count: captures.length, backupPath:
|
|
8765
|
+
return { count: captures.length, backupPath: join59(backupBase(), ts) };
|
|
8423
8766
|
} catch {
|
|
8424
8767
|
return null;
|
|
8425
8768
|
}
|
|
8426
8769
|
}
|
|
8427
8770
|
function planSharedReconcileBeforePull(repo, ts) {
|
|
8428
|
-
const map = readMapForMirror(
|
|
8771
|
+
const map = readMapForMirror(join59(repo, "path-map.json"));
|
|
8429
8772
|
const captures = [];
|
|
8430
8773
|
const linkNames = process.platform === "win32" && map !== null ? allSharedLinks(map) : void 0;
|
|
8431
8774
|
stageLocalSharedEdits(map, ts, { dryRun: true, onPreview: (e) => captures.push(e), linkNames });
|
|
@@ -8440,8 +8783,8 @@ function planSharedReconcileBeforePull(repo, ts) {
|
|
|
8440
8783
|
// src/commands.pull.collision.ts
|
|
8441
8784
|
init_config();
|
|
8442
8785
|
init_exit_codes();
|
|
8443
|
-
import { existsSync as
|
|
8444
|
-
import { join as
|
|
8786
|
+
import { existsSync as existsSync46, lstatSync as lstatSync21, readFileSync as readFileSync20, rmSync as rmSync19 } from "node:fs";
|
|
8787
|
+
import { join as join60 } from "node:path";
|
|
8445
8788
|
init_utils();
|
|
8446
8789
|
var SHARED_PREFIX = /^shared\//;
|
|
8447
8790
|
var EXTENSION = /(\.[^.]+)?$/;
|
|
@@ -8497,24 +8840,24 @@ function isContainedMirrorPath(rel) {
|
|
|
8497
8840
|
}
|
|
8498
8841
|
function matchesLocalOriginal(repo, rel) {
|
|
8499
8842
|
try {
|
|
8500
|
-
const local =
|
|
8501
|
-
return readFileSync20(
|
|
8843
|
+
const local = join60(claudeHome(), rel.replace(SHARED_PREFIX, ""));
|
|
8844
|
+
return readFileSync20(join60(repo, rel)).equals(readFileSync20(local));
|
|
8502
8845
|
} catch {
|
|
8503
8846
|
return false;
|
|
8504
8847
|
}
|
|
8505
8848
|
}
|
|
8506
8849
|
function removeMirroredCopies(repo, repoRelPaths) {
|
|
8507
8850
|
for (const rel of repoRelPaths.filter(isContainedMirrorPath)) {
|
|
8508
|
-
const abs =
|
|
8851
|
+
const abs = join60(repo, rel);
|
|
8509
8852
|
try {
|
|
8510
|
-
if (
|
|
8853
|
+
if (lstatSync21(abs, { throwIfNoEntry: false })?.isFile() !== true) continue;
|
|
8511
8854
|
if (!matchesLocalOriginal(repo, rel)) continue;
|
|
8512
8855
|
rmSync19(abs, { force: true });
|
|
8513
8856
|
} catch (err) {
|
|
8514
8857
|
warn(`could not remove nomad's copy at ${abs}: ${err.message}`);
|
|
8515
8858
|
}
|
|
8516
8859
|
}
|
|
8517
|
-
return repoRelPaths.every((rel) => !
|
|
8860
|
+
return repoRelPaths.every((rel) => !existsSync46(join60(repo, rel)));
|
|
8518
8861
|
}
|
|
8519
8862
|
function pullWithCollisionRunbook(repo, mirrored) {
|
|
8520
8863
|
try {
|
|
@@ -8596,7 +8939,27 @@ function recoverForceRemote(mode, repo) {
|
|
|
8596
8939
|
const { synced } = classifyTouched(allTouched);
|
|
8597
8940
|
if (synced.length > 0) {
|
|
8598
8941
|
die(
|
|
8599
|
-
"force-remote refused:
|
|
8942
|
+
"force-remote refused: the sync repo's synced config differs from origin/main.\nDiffering paths:\n" + // Quoted for display only: `-z` hands back raw names, and a path
|
|
8943
|
+
// carrying a newline or a terminal control sequence would otherwise
|
|
8944
|
+
// split or rewrite the recovery steps printed below it. The
|
|
8945
|
+
// classification above uses the raw value.
|
|
8946
|
+
synced.map((p) => ` ${JSON.stringify(p)}`).join("\n") + `
|
|
8947
|
+
The comparison runs both ways, so a path can be listed because origin/main is ahead of
|
|
8948
|
+
you, with nothing of yours at risk on it.
|
|
8949
|
+
|
|
8950
|
+
Manual recovery. Step 1 is not optional: this refusal happens BEFORE recovery parks your
|
|
8951
|
+
local commits, so nothing has been saved for you yet and step 3 discards whatever is not
|
|
8952
|
+
on another ref.
|
|
8953
|
+
1. git branch nomad-rescue HEAD (keeps every local commit)
|
|
8954
|
+
2. copy any uncommitted work above OUT of the repo (it exists nowhere else)
|
|
8955
|
+
3. git reset --hard origin/main (this is what clears the refusal)
|
|
8956
|
+
4. nomad pull
|
|
8957
|
+
|
|
8958
|
+
Copying or moving the files alone does not clear it: git still reports a moved file as a
|
|
8959
|
+
deletion, and a committed change is still ahead of origin/main. The in-progress rebase or
|
|
8960
|
+
merge has ALREADY been aborted, so re-running 'nomad pull --force-remote' now behaves as
|
|
8961
|
+
an ordinary pull.
|
|
8962
|
+
(see FAQ: "Every pull fails with unmerged files")`
|
|
8600
8963
|
);
|
|
8601
8964
|
}
|
|
8602
8965
|
const branchName = freshStrandedBranch(repo);
|
|
@@ -8696,15 +9059,23 @@ function handleWedge(repo, forceRemote) {
|
|
|
8696
9059
|
return die(wedgeMarkerRunbookText(state), { code: EXIT.CONFLICT });
|
|
8697
9060
|
}
|
|
8698
9061
|
function runPullCore(opts = {}) {
|
|
9062
|
+
const backup = backupBase();
|
|
9063
|
+
const ts = freshBackupTs(backup);
|
|
9064
|
+
try {
|
|
9065
|
+
return runPullWithBackupTs(ts, opts);
|
|
9066
|
+
} finally {
|
|
9067
|
+
discardEmptyBackupDir(join61(backup, ts));
|
|
9068
|
+
}
|
|
9069
|
+
}
|
|
9070
|
+
function runPullWithBackupTs(ts, opts) {
|
|
8699
9071
|
const dryRun = opts.dryRun === true;
|
|
8700
9072
|
const forceRemote = opts.forceRemote === true;
|
|
8701
9073
|
const compose = opts.compose === true;
|
|
8702
9074
|
const repo = repoHome();
|
|
8703
9075
|
const backup = backupBase();
|
|
8704
|
-
const ts = freshBackupTs(backup);
|
|
8705
9076
|
const recovered = handleWedge(repo, forceRemote);
|
|
8706
9077
|
if (!dryRun) {
|
|
8707
|
-
const backupRoot =
|
|
9078
|
+
const backupRoot = join61(backup, ts);
|
|
8708
9079
|
try {
|
|
8709
9080
|
mkdirSync14(backupRoot, { recursive: true });
|
|
8710
9081
|
} catch (err) {
|
|
@@ -8727,8 +9098,8 @@ function runPullCore(opts = {}) {
|
|
|
8727
9098
|
pullWithCollisionRunbook(repo, mirrored);
|
|
8728
9099
|
});
|
|
8729
9100
|
assertNoAutostashConflict(repo, "nomad pull");
|
|
8730
|
-
const mapPath =
|
|
8731
|
-
const map =
|
|
9101
|
+
const mapPath = join61(repo, "path-map.json");
|
|
9102
|
+
const map = existsSync47(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
8732
9103
|
const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
|
|
8733
9104
|
if (dryRun) {
|
|
8734
9105
|
computePreview(ts, map, "pull", plansAgainst(sharedPlans, map));
|
|
@@ -8755,8 +9126,8 @@ function runPullCore(opts = {}) {
|
|
|
8755
9126
|
}
|
|
8756
9127
|
function cmdPull(opts = {}) {
|
|
8757
9128
|
const repo = repoHome();
|
|
8758
|
-
if (!
|
|
8759
|
-
if (!
|
|
9129
|
+
if (!existsSync47(repo)) die(`repo not cloned at ${repo}`);
|
|
9130
|
+
if (!existsSync47(join61(repo, "shared", "settings.base.json"))) {
|
|
8760
9131
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
8761
9132
|
}
|
|
8762
9133
|
const handle = acquireLock("pull");
|
|
@@ -8779,13 +9150,13 @@ function cmdPull(opts = {}) {
|
|
|
8779
9150
|
|
|
8780
9151
|
// src/commands.push.ts
|
|
8781
9152
|
init_config();
|
|
8782
|
-
import { existsSync as
|
|
8783
|
-
import { join as
|
|
9153
|
+
import { existsSync as existsSync51 } from "node:fs";
|
|
9154
|
+
import { join as join66 } from "node:path";
|
|
8784
9155
|
|
|
8785
9156
|
// src/commands.push.selection.ts
|
|
8786
9157
|
init_config();
|
|
8787
|
-
import { existsSync as
|
|
8788
|
-
import { join as
|
|
9158
|
+
import { existsSync as existsSync48, statSync as statSync11 } from "node:fs";
|
|
9159
|
+
import { join as join62 } from "node:path";
|
|
8789
9160
|
init_utils_json();
|
|
8790
9161
|
function buildCurrentMap(map) {
|
|
8791
9162
|
const current = {};
|
|
@@ -8794,8 +9165,8 @@ function buildCurrentMap(map) {
|
|
|
8794
9165
|
for (const [, hostMap] of Object.entries(map.projects)) {
|
|
8795
9166
|
const localPath = hostMap[HOST];
|
|
8796
9167
|
if (!localPath) continue;
|
|
8797
|
-
const localDir =
|
|
8798
|
-
if (!
|
|
9168
|
+
const localDir = join62(claude, "projects", encodePath(localPath));
|
|
9169
|
+
if (!existsSync48(localDir)) continue;
|
|
8799
9170
|
for (const f of enumerateSourceFiles(localDir)) {
|
|
8800
9171
|
const st = statSync11(f);
|
|
8801
9172
|
current[f] = { size: st.size, mtime: st.mtimeMs };
|
|
@@ -8826,7 +9197,7 @@ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
|
|
|
8826
9197
|
};
|
|
8827
9198
|
}
|
|
8828
9199
|
function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
|
|
8829
|
-
const map =
|
|
9200
|
+
const map = existsSync48(mapPath) ? readPathMap(mapPath) : null;
|
|
8830
9201
|
const { selection, newManifest } = computePushSelection(
|
|
8831
9202
|
map,
|
|
8832
9203
|
old,
|
|
@@ -8919,14 +9290,14 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
8919
9290
|
|
|
8920
9291
|
// src/commands.push.settings.ts
|
|
8921
9292
|
init_config();
|
|
8922
|
-
import { existsSync as
|
|
8923
|
-
import { join as
|
|
9293
|
+
import { existsSync as existsSync49 } from "node:fs";
|
|
9294
|
+
import { join as join63 } from "node:path";
|
|
8924
9295
|
init_utils();
|
|
8925
9296
|
init_utils_fs();
|
|
8926
9297
|
init_utils_json();
|
|
8927
9298
|
function stripGsdHooksFromBase(repo, backup) {
|
|
8928
|
-
const basePath =
|
|
8929
|
-
if (!
|
|
9299
|
+
const basePath = join63(repo, "shared", "settings.base.json");
|
|
9300
|
+
if (!existsSync49(basePath)) return;
|
|
8930
9301
|
let base;
|
|
8931
9302
|
try {
|
|
8932
9303
|
base = readJson(basePath);
|
|
@@ -8940,14 +9311,14 @@ function stripGsdHooksFromBase(repo, backup) {
|
|
|
8940
9311
|
writeJsonAtomic(basePath, stripped);
|
|
8941
9312
|
}
|
|
8942
9313
|
function reportSettingsAheadDrift(repo) {
|
|
8943
|
-
const basePath =
|
|
8944
|
-
if (!
|
|
8945
|
-
const settingsPath =
|
|
8946
|
-
if (!
|
|
9314
|
+
const basePath = join63(repo, "shared", "settings.base.json");
|
|
9315
|
+
if (!existsSync49(basePath)) return;
|
|
9316
|
+
const settingsPath = join63(claudeHome(), "settings.json");
|
|
9317
|
+
if (!existsSync49(settingsPath)) return;
|
|
8947
9318
|
try {
|
|
8948
9319
|
const base = readJson(basePath);
|
|
8949
|
-
const hostPath =
|
|
8950
|
-
const overrides =
|
|
9320
|
+
const hostPath = join63(repo, "hosts", `${HOST}.json`);
|
|
9321
|
+
const overrides = existsSync49(hostPath) ? readJson(hostPath) : {};
|
|
8951
9322
|
const merged = deepMerge(base, overrides);
|
|
8952
9323
|
const settings = readJson(settingsPath);
|
|
8953
9324
|
const { ahead } = classifySettingsDrift(merged, settings);
|
|
@@ -8964,12 +9335,12 @@ function reportSettingsAheadDrift(repo) {
|
|
|
8964
9335
|
// src/commands.push.guards.ts
|
|
8965
9336
|
init_push_checks();
|
|
8966
9337
|
init_utils();
|
|
8967
|
-
import { join as
|
|
9338
|
+
import { join as join64, relative as relative10 } from "node:path";
|
|
8968
9339
|
function guardGitlinks(repo) {
|
|
8969
|
-
const gitlinks = findGitlinks(
|
|
9340
|
+
const gitlinks = findGitlinks(join64(repo, "shared"));
|
|
8970
9341
|
if (gitlinks.length === 0) return;
|
|
8971
9342
|
for (const p of gitlinks) {
|
|
8972
|
-
const rel =
|
|
9343
|
+
const rel = relative10(repo, p).replaceAll("\\", "/");
|
|
8973
9344
|
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
8974
9345
|
}
|
|
8975
9346
|
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
@@ -9076,9 +9447,9 @@ init_color();
|
|
|
9076
9447
|
init_config();
|
|
9077
9448
|
init_config_sharedDirs_guard();
|
|
9078
9449
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
9079
|
-
import { copyFileSync, existsSync as
|
|
9450
|
+
import { copyFileSync, existsSync as existsSync50, lstatSync as lstatSync22, mkdirSync as mkdirSync15, readdirSync as readdirSync20, rmSync as rmSync20 } from "node:fs";
|
|
9080
9451
|
import { homedir as homedir7 } from "node:os";
|
|
9081
|
-
import { join as
|
|
9452
|
+
import { join as join65, relative as relative11, sep as sep14 } from "node:path";
|
|
9082
9453
|
init_push_leak_verdict();
|
|
9083
9454
|
init_push_gitleaks();
|
|
9084
9455
|
init_utils_fs();
|
|
@@ -9090,7 +9461,7 @@ function stageSessionDir(localDir, dstDir, changed) {
|
|
|
9090
9461
|
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
9091
9462
|
if (matching.length === 0) return false;
|
|
9092
9463
|
for (const src of matching) {
|
|
9093
|
-
copyFileAtomic(src,
|
|
9464
|
+
copyFileAtomic(src, join65(dstDir, relative11(localDir, src)));
|
|
9094
9465
|
}
|
|
9095
9466
|
return true;
|
|
9096
9467
|
}
|
|
@@ -9106,14 +9477,14 @@ function stageSessions(tmpRoot, map, changed) {
|
|
|
9106
9477
|
if (!p || p === "TBD") continue;
|
|
9107
9478
|
reverse.set(encodePath(p), logical);
|
|
9108
9479
|
}
|
|
9109
|
-
const localProjects =
|
|
9110
|
-
if (!
|
|
9480
|
+
const localProjects = join65(claudeHome(), "projects");
|
|
9481
|
+
if (!existsSync50(localProjects)) return 0;
|
|
9111
9482
|
let staged = 0;
|
|
9112
|
-
for (const dir of
|
|
9483
|
+
for (const dir of readdirSync20(localProjects)) {
|
|
9113
9484
|
const logical = reverse.get(dir);
|
|
9114
9485
|
if (!logical) continue;
|
|
9115
|
-
const localDir =
|
|
9116
|
-
const dstDir =
|
|
9486
|
+
const localDir = join65(localProjects, dir);
|
|
9487
|
+
const dstDir = join65(tmpRoot, "shared", "projects", logical);
|
|
9117
9488
|
if (stageSessionDir(localDir, dstDir, changed)) staged++;
|
|
9118
9489
|
}
|
|
9119
9490
|
return staged;
|
|
@@ -9127,11 +9498,11 @@ function stageExtras(tmpRoot, map) {
|
|
|
9127
9498
|
assertSafeLogical(logical);
|
|
9128
9499
|
const localRoot = map.projects[logical]?.[HOST];
|
|
9129
9500
|
if (!localRoot || localRoot === "TBD") continue;
|
|
9130
|
-
for (const
|
|
9131
|
-
if (!whitelist.includes(
|
|
9132
|
-
const src =
|
|
9133
|
-
if (!
|
|
9134
|
-
const dst =
|
|
9501
|
+
for (const dirname15 of dirnames) {
|
|
9502
|
+
if (!whitelist.includes(dirname15)) continue;
|
|
9503
|
+
const src = join65(localRoot, dirname15);
|
|
9504
|
+
if (!existsSync50(src)) continue;
|
|
9505
|
+
const dst = join65(tmpRoot, "shared", "extras", logical, dirname15);
|
|
9135
9506
|
copyExtras(src, dst);
|
|
9136
9507
|
staged++;
|
|
9137
9508
|
}
|
|
@@ -9139,19 +9510,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
9139
9510
|
return staged;
|
|
9140
9511
|
}
|
|
9141
9512
|
function stageSkills(tmpRoot) {
|
|
9142
|
-
const localSkills =
|
|
9143
|
-
const stat =
|
|
9513
|
+
const localSkills = join65(claudeHome(), "skills");
|
|
9514
|
+
const stat = lstatSync22(localSkills, { throwIfNoEntry: false });
|
|
9144
9515
|
if (stat === void 0 || stat.isSymbolicLink()) return 0;
|
|
9145
|
-
const names =
|
|
9516
|
+
const names = readdirSync20(localSkills, { encoding: "utf8" }).filter((n) => !isSkillExcluded(n));
|
|
9146
9517
|
if (names.length === 0) return 0;
|
|
9147
|
-
copySkillsPush(localSkills,
|
|
9518
|
+
copySkillsPush(localSkills, join65(tmpRoot, "shared", "skills"));
|
|
9148
9519
|
return names.length;
|
|
9149
9520
|
}
|
|
9150
9521
|
function previewPushLeaks(map, opts = {}) {
|
|
9151
|
-
const cacheDir =
|
|
9522
|
+
const cacheDir = join65(homedir7(), ".cache", "claude-nomad");
|
|
9152
9523
|
mkdirSync15(cacheDir, { recursive: true });
|
|
9153
9524
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes4(4).toString("hex")}`;
|
|
9154
|
-
const tmpRoot =
|
|
9525
|
+
const tmpRoot = join65(cacheDir, `push-preview-tree-${stamp}`);
|
|
9155
9526
|
try {
|
|
9156
9527
|
const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
|
|
9157
9528
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
@@ -9159,9 +9530,9 @@ function previewPushLeaks(map, opts = {}) {
|
|
|
9159
9530
|
if (sessionCount + extrasCount + skillCount === 0) {
|
|
9160
9531
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
9161
9532
|
}
|
|
9162
|
-
const ignoreFile =
|
|
9163
|
-
if (
|
|
9164
|
-
copyFileSync(ignoreFile,
|
|
9533
|
+
const ignoreFile = join65(repoHome(), ".gitleaksignore");
|
|
9534
|
+
if (existsSync50(ignoreFile)) {
|
|
9535
|
+
copyFileSync(ignoreFile, join65(tmpRoot, ".gitleaksignore"));
|
|
9165
9536
|
}
|
|
9166
9537
|
let findings;
|
|
9167
9538
|
try {
|
|
@@ -9289,7 +9660,7 @@ async function runPushCore(opts = {}) {
|
|
|
9289
9660
|
const scannerVersion = probeGitleaks();
|
|
9290
9661
|
const configHash = computeConfigHash();
|
|
9291
9662
|
const old = readManifest(manifestPath());
|
|
9292
|
-
const mapPath =
|
|
9663
|
+
const mapPath = join66(repo, "path-map.json");
|
|
9293
9664
|
const { map, selection, newManifest } = loadSelectionForPush(
|
|
9294
9665
|
mapPath,
|
|
9295
9666
|
old,
|
|
@@ -9342,7 +9713,7 @@ async function cmdPush(opts = {}) {
|
|
|
9342
9713
|
opts.allowRule
|
|
9343
9714
|
);
|
|
9344
9715
|
const repo = repoHome();
|
|
9345
|
-
if (!
|
|
9716
|
+
if (!existsSync51(repo)) die(`repo not cloned at ${repo}`);
|
|
9346
9717
|
const handle = acquireLock("push");
|
|
9347
9718
|
if (handle === null) process.exit(0);
|
|
9348
9719
|
try {
|
|
@@ -9360,8 +9731,8 @@ async function cmdPush(opts = {}) {
|
|
|
9360
9731
|
}
|
|
9361
9732
|
|
|
9362
9733
|
// src/commands.sync.ts
|
|
9363
|
-
import { existsSync as
|
|
9364
|
-
import { join as
|
|
9734
|
+
import { existsSync as existsSync52 } from "node:fs";
|
|
9735
|
+
import { join as join67 } from "node:path";
|
|
9365
9736
|
init_config();
|
|
9366
9737
|
init_color();
|
|
9367
9738
|
init_utils();
|
|
@@ -9502,8 +9873,8 @@ async function cmdSync(opts = {}) {
|
|
|
9502
9873
|
const dryRun = opts.dryRun === true;
|
|
9503
9874
|
const verbose = opts.verbose === true;
|
|
9504
9875
|
const repo = repoHome();
|
|
9505
|
-
if (!
|
|
9506
|
-
if (!
|
|
9876
|
+
if (!existsSync52(repo)) die(`repo not cloned at ${repo}`);
|
|
9877
|
+
if (!existsSync52(join67(repo, "shared", "settings.base.json"))) {
|
|
9507
9878
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
9508
9879
|
}
|
|
9509
9880
|
const handle = acquireLock("sync");
|
|
@@ -9579,8 +9950,8 @@ init_config();
|
|
|
9579
9950
|
// src/crash-report.write.ts
|
|
9580
9951
|
init_config();
|
|
9581
9952
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
9582
|
-
import { mkdirSync as mkdirSync16, readdirSync as
|
|
9583
|
-
import { join as
|
|
9953
|
+
import { mkdirSync as mkdirSync16, readdirSync as readdirSync21, statSync as statSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
|
|
9954
|
+
import { join as join69 } from "node:path";
|
|
9584
9955
|
|
|
9585
9956
|
// src/crash-report.ts
|
|
9586
9957
|
var CRASH_MAX_STACK_LINES = 50;
|
|
@@ -9665,15 +10036,15 @@ function buildCrashReport(input) {
|
|
|
9665
10036
|
// src/crash-report.redact.ts
|
|
9666
10037
|
import { mkdtempSync as mkdtempSync3, rmSync as rmSync21, writeFileSync as writeFileSync10 } from "node:fs";
|
|
9667
10038
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
9668
|
-
import { join as
|
|
10039
|
+
import { join as join68 } from "node:path";
|
|
9669
10040
|
init_push_gitleaks_scan();
|
|
9670
10041
|
var CRASH_SCAN_TIMEOUT_MS = 3e3;
|
|
9671
10042
|
var SCAN_UNAVAILABLE_ADVISORY = "\n\n[gitleaks value-based scan unavailable; only structural redaction applied. Review before sharing this file publicly.]\n";
|
|
9672
10043
|
function redactWithGitleaks(text, scan = scanFile) {
|
|
9673
10044
|
let dir;
|
|
9674
10045
|
try {
|
|
9675
|
-
dir = mkdtempSync3(
|
|
9676
|
-
const tmp =
|
|
10046
|
+
dir = mkdtempSync3(join68(tmpdir2(), "nomad-crash-scan-"));
|
|
10047
|
+
const tmp = join68(dir, "crash.txt");
|
|
9677
10048
|
writeFileSync10(tmp, text, { mode: 384 });
|
|
9678
10049
|
const findings = scan(tmp, false, CRASH_SCAN_TIMEOUT_MS);
|
|
9679
10050
|
if (findings === null) return text + SCAN_UNAVAILABLE_ADVISORY;
|
|
@@ -9696,9 +10067,9 @@ init_utils();
|
|
|
9696
10067
|
var CRASH_RETENTION_KEEP = 20;
|
|
9697
10068
|
function listCrashFiles(dir = crashDir()) {
|
|
9698
10069
|
try {
|
|
9699
|
-
return
|
|
10070
|
+
return readdirSync21(dir).flatMap((name) => {
|
|
9700
10071
|
try {
|
|
9701
|
-
return [{ name, mtimeMs: statSync12(
|
|
10072
|
+
return [{ name, mtimeMs: statSync12(join69(dir, name)).mtimeMs }];
|
|
9702
10073
|
} catch {
|
|
9703
10074
|
return [];
|
|
9704
10075
|
}
|
|
@@ -9712,7 +10083,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
9712
10083
|
const targets = prunableByCount(files, keep);
|
|
9713
10084
|
for (const name of targets) {
|
|
9714
10085
|
try {
|
|
9715
|
-
unlinkSync2(
|
|
10086
|
+
unlinkSync2(join69(dir, name));
|
|
9716
10087
|
} catch {
|
|
9717
10088
|
}
|
|
9718
10089
|
}
|
|
@@ -9720,7 +10091,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
9720
10091
|
function writeCrashReport(text, dir = crashDir()) {
|
|
9721
10092
|
mkdirSync16(dir, { recursive: true, mode: 448 });
|
|
9722
10093
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes5(4).toString("hex")}`;
|
|
9723
|
-
const path =
|
|
10094
|
+
const path = join69(dir, `crash-${stamp}.txt`);
|
|
9724
10095
|
writeFileSync11(path, text, { mode: 384 });
|
|
9725
10096
|
pruneCrashDir(dir);
|
|
9726
10097
|
return path;
|
|
@@ -9752,18 +10123,18 @@ function handleCrash(err, argv, opts) {
|
|
|
9752
10123
|
|
|
9753
10124
|
// src/diff.ts
|
|
9754
10125
|
init_config();
|
|
9755
|
-
import { existsSync as
|
|
9756
|
-
import { join as
|
|
10126
|
+
import { existsSync as existsSync53 } from "node:fs";
|
|
10127
|
+
import { join as join70 } from "node:path";
|
|
9757
10128
|
init_utils();
|
|
9758
10129
|
init_utils_fs();
|
|
9759
10130
|
init_utils_json();
|
|
9760
10131
|
function cmdDiff() {
|
|
9761
10132
|
try {
|
|
9762
10133
|
const repo = repoHome();
|
|
9763
|
-
if (!
|
|
10134
|
+
if (!existsSync53(repo)) die(`repo not cloned at ${repo}`);
|
|
9764
10135
|
const ts = freshBackupTs(backupBase());
|
|
9765
|
-
const mapPath =
|
|
9766
|
-
const map =
|
|
10136
|
+
const mapPath = join70(repo, "path-map.json");
|
|
10137
|
+
const map = existsSync53(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
9767
10138
|
divergenceCheckExtras(ts);
|
|
9768
10139
|
computePreview(ts, map, "diff");
|
|
9769
10140
|
} catch (err) {
|
|
@@ -9778,8 +10149,8 @@ function cmdDiff() {
|
|
|
9778
10149
|
|
|
9779
10150
|
// src/init.ts
|
|
9780
10151
|
init_config();
|
|
9781
|
-
import { existsSync as
|
|
9782
|
-
import { join as
|
|
10152
|
+
import { existsSync as existsSync55, mkdirSync as mkdirSync17, writeFileSync as writeFileSync12 } from "node:fs";
|
|
10153
|
+
import { join as join72 } from "node:path";
|
|
9783
10154
|
|
|
9784
10155
|
// src/init.gh-onboard.ts
|
|
9785
10156
|
init_config();
|
|
@@ -9861,33 +10232,33 @@ init_config();
|
|
|
9861
10232
|
init_utils();
|
|
9862
10233
|
init_utils_fs();
|
|
9863
10234
|
init_utils_json();
|
|
9864
|
-
import { copyFileSync as copyFileSync2, cpSync as
|
|
9865
|
-
import { join as
|
|
10235
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync9, existsSync as existsSync54, rmSync as rmSync22, statSync as statSync13 } from "node:fs";
|
|
10236
|
+
import { join as join71 } from "node:path";
|
|
9866
10237
|
function snapshotIntoShared(map) {
|
|
9867
10238
|
const repo = repoHome();
|
|
9868
10239
|
const claude = claudeHome();
|
|
9869
10240
|
for (const name of allSharedLinks(map)) {
|
|
9870
|
-
const src =
|
|
9871
|
-
if (!
|
|
9872
|
-
const dst =
|
|
10241
|
+
const src = join71(claude, name);
|
|
10242
|
+
if (!existsSync54(src)) continue;
|
|
10243
|
+
const dst = join71(repo, "shared", name);
|
|
9873
10244
|
if (statSync13(src).isDirectory()) {
|
|
9874
|
-
const gk =
|
|
9875
|
-
if (
|
|
9876
|
-
|
|
10245
|
+
const gk = join71(dst, ".gitkeep");
|
|
10246
|
+
if (existsSync54(gk)) rmSync22(gk);
|
|
10247
|
+
cpSync9(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
9877
10248
|
} else {
|
|
9878
10249
|
copyFileSync2(src, dst);
|
|
9879
10250
|
}
|
|
9880
10251
|
log(`snapshotted shared/${name} from ${src}`);
|
|
9881
10252
|
}
|
|
9882
|
-
const userSettings =
|
|
9883
|
-
if (
|
|
10253
|
+
const userSettings = join71(claude, "settings.json");
|
|
10254
|
+
if (existsSync54(userSettings)) {
|
|
9884
10255
|
let parsed;
|
|
9885
10256
|
try {
|
|
9886
10257
|
parsed = readJson(userSettings);
|
|
9887
10258
|
} catch (err) {
|
|
9888
10259
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
9889
10260
|
}
|
|
9890
|
-
const hostFile =
|
|
10261
|
+
const hostFile = join71(repo, "hosts", `${HOST}.json`);
|
|
9891
10262
|
writeJsonAtomic(hostFile, parsed);
|
|
9892
10263
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
9893
10264
|
}
|
|
@@ -9901,14 +10272,14 @@ var GITATTRIBUTES = "# nomad: sync content is byte-managed, disable all line-end
|
|
|
9901
10272
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
9902
10273
|
function preflightConflict(repoHome2) {
|
|
9903
10274
|
const candidates = [
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
10275
|
+
join72(repoHome2, "shared", "settings.base.json"),
|
|
10276
|
+
join72(repoHome2, "shared", "CLAUDE.md"),
|
|
10277
|
+
join72(repoHome2, "path-map.json"),
|
|
10278
|
+
join72(repoHome2, "hosts"),
|
|
10279
|
+
join72(repoHome2, "shared")
|
|
9909
10280
|
];
|
|
9910
10281
|
for (const c of candidates) {
|
|
9911
|
-
if (
|
|
10282
|
+
if (existsSync55(c)) return c;
|
|
9912
10283
|
}
|
|
9913
10284
|
return null;
|
|
9914
10285
|
}
|
|
@@ -9926,27 +10297,27 @@ function cmdInit(opts = {}) {
|
|
|
9926
10297
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
9927
10298
|
}
|
|
9928
10299
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
9929
|
-
mkdirSync17(
|
|
9930
|
-
mkdirSync17(
|
|
10300
|
+
mkdirSync17(join72(repo, "shared"), { recursive: true });
|
|
10301
|
+
mkdirSync17(join72(repo, "hosts"), { recursive: true });
|
|
9931
10302
|
for (const name of SHARED_KEEP_DIRS) {
|
|
9932
|
-
mkdirSync17(
|
|
10303
|
+
mkdirSync17(join72(repo, "shared", name), { recursive: true });
|
|
9933
10304
|
}
|
|
9934
|
-
const userClaudeMd =
|
|
9935
|
-
if (!snapshot || !
|
|
9936
|
-
writeFileSync12(
|
|
10305
|
+
const userClaudeMd = join72(claude, "CLAUDE.md");
|
|
10306
|
+
if (!snapshot || !existsSync55(userClaudeMd)) {
|
|
10307
|
+
writeFileSync12(join72(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
9937
10308
|
item("created shared/CLAUDE.md");
|
|
9938
10309
|
}
|
|
9939
10310
|
for (const name of SHARED_KEEP_DIRS) {
|
|
9940
|
-
writeFileSync12(
|
|
10311
|
+
writeFileSync12(join72(repo, "shared", name, ".gitkeep"), "");
|
|
9941
10312
|
item(`created shared/${name}/.gitkeep`);
|
|
9942
10313
|
}
|
|
9943
|
-
writeFileSync12(
|
|
10314
|
+
writeFileSync12(join72(repo, "hosts", ".gitkeep"), "");
|
|
9944
10315
|
item("created hosts/.gitkeep");
|
|
9945
|
-
writeJsonAtomic(
|
|
10316
|
+
writeJsonAtomic(join72(repo, "shared", "settings.base.json"), {});
|
|
9946
10317
|
item("created shared/settings.base.json");
|
|
9947
|
-
writeJsonAtomic(
|
|
10318
|
+
writeJsonAtomic(join72(repo, "path-map.json"), { projects: {} });
|
|
9948
10319
|
item("created path-map.json");
|
|
9949
|
-
writeFileSync12(
|
|
10320
|
+
writeFileSync12(join72(repo, ".gitattributes"), GITATTRIBUTES);
|
|
9950
10321
|
item("created .gitattributes");
|
|
9951
10322
|
if (snapshot) {
|
|
9952
10323
|
snapshotIntoShared({ projects: {} });
|
|
@@ -10012,21 +10383,21 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
10012
10383
|
// src/init.prompt.ts
|
|
10013
10384
|
init_config();
|
|
10014
10385
|
init_utils();
|
|
10015
|
-
import { existsSync as
|
|
10016
|
-
import { join as
|
|
10386
|
+
import { existsSync as existsSync56, readdirSync as readdirSync22, statSync as statSync14 } from "node:fs";
|
|
10387
|
+
import { join as join73 } from "node:path";
|
|
10017
10388
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
10018
10389
|
function nonEmptyExists(path) {
|
|
10019
|
-
if (!
|
|
10390
|
+
if (!existsSync56(path)) return false;
|
|
10020
10391
|
try {
|
|
10021
|
-
if (statSync14(path).isDirectory()) return
|
|
10392
|
+
if (statSync14(path).isDirectory()) return readdirSync22(path).length > 0;
|
|
10022
10393
|
return true;
|
|
10023
10394
|
} catch {
|
|
10024
10395
|
return false;
|
|
10025
10396
|
}
|
|
10026
10397
|
}
|
|
10027
10398
|
function hasExistingClaudeConfig(claudeHome2) {
|
|
10028
|
-
if (
|
|
10029
|
-
return SHARED_LINKS.some((name) => nonEmptyExists(
|
|
10399
|
+
if (existsSync56(join73(claudeHome2, "settings.json"))) return true;
|
|
10400
|
+
return SHARED_LINKS.some((name) => nonEmptyExists(join73(claudeHome2, name)));
|
|
10030
10401
|
}
|
|
10031
10402
|
async function confirmSnapshotDefault(claudeHome2) {
|
|
10032
10403
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
@@ -10333,7 +10704,7 @@ function parseSyncArgs(argv) {
|
|
|
10333
10704
|
// package.json
|
|
10334
10705
|
var package_default = {
|
|
10335
10706
|
name: "claude-nomad",
|
|
10336
|
-
version: "0.
|
|
10707
|
+
version: "0.67.0",
|
|
10337
10708
|
type: "module",
|
|
10338
10709
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
10339
10710
|
keywords: [
|
|
@@ -10562,7 +10933,9 @@ var DEFAULT_HELP = [
|
|
|
10562
10933
|
row(" --backups", "Required: confirm backup pruning is the intended target."),
|
|
10563
10934
|
row(" --dry-run", "List the snapshots that would be removed without deleting."),
|
|
10564
10935
|
row(" --older-than <dur>", "Delete snapshots older than a duration (e.g. 14d, 24h, 30m)."),
|
|
10565
|
-
cont("Default when no retention flag is given: 14d."),
|
|
10936
|
+
cont("Default when no retention flag is given: 14d. A snapshot holding"),
|
|
10937
|
+
cont("nothing is removed in every mode, whatever its age, and never"),
|
|
10938
|
+
cont("uses up one of the --keep slots."),
|
|
10566
10939
|
row(" --keep <N>", "Keep the N most-recent snapshots, delete the rest. Mutually"),
|
|
10567
10940
|
cont("exclusive with --older-than."),
|
|
10568
10941
|
"",
|
|
@@ -10578,15 +10951,15 @@ var DEFAULT_HELP = [
|
|
|
10578
10951
|
init_config();
|
|
10579
10952
|
init_utils();
|
|
10580
10953
|
init_utils_json();
|
|
10581
|
-
import { existsSync as
|
|
10582
|
-
import { join as
|
|
10954
|
+
import { existsSync as existsSync57, readFileSync as readFileSync21, readdirSync as readdirSync23 } from "node:fs";
|
|
10955
|
+
import { join as join74 } from "node:path";
|
|
10583
10956
|
function resumeCmd(sessionId) {
|
|
10584
10957
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
10585
10958
|
fail(`invalid session id: ${sessionId}`);
|
|
10586
10959
|
process.exit(1);
|
|
10587
10960
|
}
|
|
10588
|
-
const projectsRoot =
|
|
10589
|
-
if (!
|
|
10961
|
+
const projectsRoot = join74(claudeHome(), "projects");
|
|
10962
|
+
if (!existsSync57(projectsRoot)) {
|
|
10590
10963
|
fail(`${projectsRoot} does not exist`);
|
|
10591
10964
|
process.exit(1);
|
|
10592
10965
|
}
|
|
@@ -10600,8 +10973,8 @@ function resumeCmd(sessionId) {
|
|
|
10600
10973
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
10601
10974
|
process.exit(1);
|
|
10602
10975
|
}
|
|
10603
|
-
const mapPath =
|
|
10604
|
-
if (!
|
|
10976
|
+
const mapPath = join74(repoHome(), "path-map.json");
|
|
10977
|
+
if (!existsSync57(mapPath)) {
|
|
10605
10978
|
fail("path-map.json missing");
|
|
10606
10979
|
process.exit(1);
|
|
10607
10980
|
}
|
|
@@ -10623,9 +10996,9 @@ function resumeCmd(sessionId) {
|
|
|
10623
10996
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
10624
10997
|
}
|
|
10625
10998
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
10626
|
-
for (const dir of
|
|
10627
|
-
const candidate =
|
|
10628
|
-
if (
|
|
10999
|
+
for (const dir of readdirSync23(projectsRoot)) {
|
|
11000
|
+
const candidate = join74(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
11001
|
+
if (existsSync57(candidate)) return candidate;
|
|
10629
11002
|
}
|
|
10630
11003
|
return null;
|
|
10631
11004
|
}
|