claude-nomad 0.65.0 → 0.66.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 +77 -0
- package/README.md +3 -2
- package/dist/nomad.mjs +1177 -872
- 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,7 +803,7 @@ 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,
|
|
@@ -795,7 +811,7 @@ import {
|
|
|
795
811
|
symlinkSync,
|
|
796
812
|
writeFileSync
|
|
797
813
|
} from "node:fs";
|
|
798
|
-
import { dirname, join as
|
|
814
|
+
import { dirname, join as join5, relative as relative3, sep as sep2 } from "node:path";
|
|
799
815
|
function isRetryableRenameCode(code) {
|
|
800
816
|
return code === "EPERM" || code === "EBUSY";
|
|
801
817
|
}
|
|
@@ -846,14 +862,14 @@ function nowTimestamp() {
|
|
|
846
862
|
}
|
|
847
863
|
function freshBackupTs(backupRoot) {
|
|
848
864
|
const base = nowTimestamp();
|
|
849
|
-
if (!existsSync3(
|
|
865
|
+
if (!existsSync3(join5(backupRoot, base))) return base;
|
|
850
866
|
let n = 1;
|
|
851
|
-
while (existsSync3(
|
|
867
|
+
while (existsSync3(join5(backupRoot, `${base}-${n}`))) n++;
|
|
852
868
|
return `${base}-${n}`;
|
|
853
869
|
}
|
|
854
870
|
function ensureSymlink(linkPath, target) {
|
|
855
871
|
if (existsSync3(linkPath)) {
|
|
856
|
-
if (
|
|
872
|
+
if (lstatSync4(linkPath).isSymbolicLink()) return;
|
|
857
873
|
die(`${linkPath} exists and is not a symlink. Move it aside first.`);
|
|
858
874
|
}
|
|
859
875
|
mkdirSync(dirname(linkPath), { recursive: true });
|
|
@@ -861,21 +877,22 @@ function ensureSymlink(linkPath, target) {
|
|
|
861
877
|
log(`linked ${linkPath} -> ${target}`);
|
|
862
878
|
}
|
|
863
879
|
function backupUnder(absPath, anchor, destRoot) {
|
|
864
|
-
if (!existsSync3(absPath)) return;
|
|
865
|
-
const rel =
|
|
866
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${sep2}`)) return;
|
|
867
|
-
const dst =
|
|
880
|
+
if (!existsSync3(absPath)) return false;
|
|
881
|
+
const rel = relative3(anchor, absPath);
|
|
882
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep2}`)) return false;
|
|
883
|
+
const dst = join5(destRoot, rel);
|
|
868
884
|
mkdirSync(dirname(dst), { recursive: true });
|
|
869
885
|
cpSync3(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
886
|
+
return true;
|
|
870
887
|
}
|
|
871
888
|
function backupBeforeWrite(absPath, ts) {
|
|
872
|
-
backupUnder(absPath, claudeHome(),
|
|
889
|
+
return backupUnder(absPath, claudeHome(), join5(backupBase(), ts));
|
|
873
890
|
}
|
|
874
891
|
function backupRepoWrite(absPath, ts, repoHome2) {
|
|
875
|
-
backupUnder(absPath, repoHome2,
|
|
892
|
+
backupUnder(absPath, repoHome2, join5(backupBase(), ts, "repo"));
|
|
876
893
|
}
|
|
877
894
|
function backupExtrasWrite(absPath, ts, projectRoot) {
|
|
878
|
-
backupUnder(absPath, projectRoot,
|
|
895
|
+
backupUnder(absPath, projectRoot, join5(backupBase(), ts, "extras", encodePath(projectRoot)));
|
|
879
896
|
}
|
|
880
897
|
var RENAME_RETRY_MAX_ATTEMPTS, RENAME_RETRY_BACKOFF_MS;
|
|
881
898
|
var init_utils_fs = __esm({
|
|
@@ -891,12 +908,12 @@ var init_utils_fs = __esm({
|
|
|
891
908
|
|
|
892
909
|
// src/commands.pull.wedge.ts
|
|
893
910
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
894
|
-
import { existsSync as
|
|
895
|
-
import { join as
|
|
911
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
912
|
+
import { join as join20 } from "node:path";
|
|
896
913
|
function detectWedge(repo) {
|
|
897
|
-
const g =
|
|
898
|
-
if (
|
|
899
|
-
if (
|
|
914
|
+
const g = join20(repo, ".git");
|
|
915
|
+
if (existsSync16(join20(g, "rebase-merge")) || existsSync16(join20(g, "rebase-apply"))) return "rebase";
|
|
916
|
+
if (existsSync16(join20(g, "MERGE_HEAD"))) return "merge";
|
|
900
917
|
return null;
|
|
901
918
|
}
|
|
902
919
|
function probeUnmergedIndex(repo) {
|
|
@@ -966,15 +983,15 @@ var init_commands_pull_wedge = __esm({
|
|
|
966
983
|
});
|
|
967
984
|
|
|
968
985
|
// src/push-gitleaks.config.ts
|
|
969
|
-
import { existsSync as
|
|
986
|
+
import { existsSync as existsSync17, mkdtempSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
970
987
|
import { tmpdir } from "node:os";
|
|
971
|
-
import { join as
|
|
988
|
+
import { join as join21 } from "node:path";
|
|
972
989
|
import { fileURLToPath } from "node:url";
|
|
973
990
|
function resolveTomlPath(repo = repoHome()) {
|
|
974
|
-
const repoToml =
|
|
975
|
-
if (
|
|
991
|
+
const repoToml = join21(repo, ".gitleaks.toml");
|
|
992
|
+
if (existsSync17(repoToml)) return repoToml;
|
|
976
993
|
const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
|
|
977
|
-
return
|
|
994
|
+
return existsSync17(bundled) ? bundled : null;
|
|
978
995
|
}
|
|
979
996
|
function assertOverlayAllowlistsScoped(overlayBody) {
|
|
980
997
|
if (OVERLAY_INLINE_ALLOWLIST_RE.test(overlayBody)) {
|
|
@@ -1012,17 +1029,17 @@ function buildOverlayTempConfig(overlayBody, bundled) {
|
|
|
1012
1029
|
path = ${JSON.stringify(bundled)}
|
|
1013
1030
|
|
|
1014
1031
|
${overlayBody}`;
|
|
1015
|
-
const tempPath = mkdtempSync(
|
|
1016
|
-
const configPath =
|
|
1032
|
+
const tempPath = mkdtempSync(join21(tmpdir(), "nomad-gitleaks-cfg-"));
|
|
1033
|
+
const configPath = join21(tempPath, "config.toml");
|
|
1017
1034
|
writeFileSync3(configPath, tempBody, { mode: 384, flag: "wx" });
|
|
1018
1035
|
return { configPath, tempPath };
|
|
1019
1036
|
}
|
|
1020
1037
|
function resolveTomlConfig() {
|
|
1021
1038
|
const repo = repoHome();
|
|
1022
|
-
const overlayPath =
|
|
1023
|
-
const repoToml =
|
|
1039
|
+
const overlayPath = join21(repo, ".gitleaks.overlay.toml");
|
|
1040
|
+
const repoToml = join21(repo, ".gitleaks.toml");
|
|
1024
1041
|
const bundled = resolveTomlPath(repo);
|
|
1025
|
-
if (!
|
|
1042
|
+
if (!existsSync17(overlayPath)) {
|
|
1026
1043
|
return { path: bundled, tempPath: null };
|
|
1027
1044
|
}
|
|
1028
1045
|
if (bundled === repoToml) {
|
|
@@ -1108,9 +1125,9 @@ var init_autostash_guard = __esm({
|
|
|
1108
1125
|
|
|
1109
1126
|
// src/push-checks.ts
|
|
1110
1127
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
1111
|
-
import { readdirSync as
|
|
1128
|
+
import { readdirSync as readdirSync8, rmSync as rmSync7 } from "node:fs";
|
|
1112
1129
|
import { homedir as homedir2, platform } from "node:os";
|
|
1113
|
-
import { delimiter, join as
|
|
1130
|
+
import { delimiter, join as join22 } from "node:path";
|
|
1114
1131
|
function gitleaksInstallHint() {
|
|
1115
1132
|
const head = "gitleaks not on PATH (required for nomad push). Install:";
|
|
1116
1133
|
const plat = platform();
|
|
@@ -1150,23 +1167,23 @@ function gitleaksInstallHint() {
|
|
|
1150
1167
|
}
|
|
1151
1168
|
function findGitlinks(dir) {
|
|
1152
1169
|
const hits = [];
|
|
1153
|
-
function
|
|
1170
|
+
function walk2(current) {
|
|
1154
1171
|
let entries;
|
|
1155
1172
|
try {
|
|
1156
|
-
entries =
|
|
1173
|
+
entries = readdirSync8(current, { withFileTypes: true });
|
|
1157
1174
|
} catch {
|
|
1158
1175
|
return;
|
|
1159
1176
|
}
|
|
1160
1177
|
for (const e of entries) {
|
|
1161
|
-
const p =
|
|
1178
|
+
const p = join22(current, e.name);
|
|
1162
1179
|
if (e.name === ".git") {
|
|
1163
1180
|
hits.push(p);
|
|
1164
1181
|
continue;
|
|
1165
1182
|
}
|
|
1166
|
-
if (e.isDirectory())
|
|
1183
|
+
if (e.isDirectory()) walk2(p);
|
|
1167
1184
|
}
|
|
1168
1185
|
}
|
|
1169
|
-
|
|
1186
|
+
walk2(dir);
|
|
1170
1187
|
return hits;
|
|
1171
1188
|
}
|
|
1172
1189
|
function probeGitleaks() {
|
|
@@ -1222,7 +1239,7 @@ var init_push_checks = __esm({
|
|
|
1222
1239
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
1223
1240
|
import { mkdirSync as mkdirSync3, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, rmSync as rmSync8 } from "node:fs";
|
|
1224
1241
|
import { homedir as homedir3 } from "node:os";
|
|
1225
|
-
import { join as
|
|
1242
|
+
import { join as join26 } from "node:path";
|
|
1226
1243
|
function toFinding(entry) {
|
|
1227
1244
|
if (typeof entry !== "object" || entry === null) return null;
|
|
1228
1245
|
const e = entry;
|
|
@@ -1270,9 +1287,9 @@ function resolveScanFailure(reportPath, e, forwardStreams) {
|
|
|
1270
1287
|
return report;
|
|
1271
1288
|
}
|
|
1272
1289
|
function scanStagedTree(repoDir, forwardStreams = false) {
|
|
1273
|
-
const cacheDir =
|
|
1290
|
+
const cacheDir = join26(homedir3(), ".cache", "claude-nomad");
|
|
1274
1291
|
mkdirSync3(cacheDir, { recursive: true });
|
|
1275
|
-
const reportPath =
|
|
1292
|
+
const reportPath = join26(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
|
|
1276
1293
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
1277
1294
|
const args = [
|
|
1278
1295
|
"protect",
|
|
@@ -1300,14 +1317,14 @@ function scanStagedTree(repoDir, forwardStreams = false) {
|
|
|
1300
1317
|
}
|
|
1301
1318
|
}
|
|
1302
1319
|
function scanFile(filePath, forwardStreams = false, timeoutMs = GITLEAKS_SCAN_TIMEOUT_MS) {
|
|
1303
|
-
const cacheDir =
|
|
1320
|
+
const cacheDir = join26(homedir3(), ".cache", "claude-nomad");
|
|
1304
1321
|
mkdirSync3(cacheDir, { recursive: true });
|
|
1305
1322
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
1306
1323
|
let reportDir;
|
|
1307
1324
|
let reportPath;
|
|
1308
1325
|
try {
|
|
1309
|
-
reportDir = mkdtempSync2(
|
|
1310
|
-
reportPath =
|
|
1326
|
+
reportDir = mkdtempSync2(join26(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}-`));
|
|
1327
|
+
reportPath = join26(reportDir, "report.json");
|
|
1311
1328
|
const args = [
|
|
1312
1329
|
"detect",
|
|
1313
1330
|
"--no-git",
|
|
@@ -1531,16 +1548,314 @@ var init_push_leak_verdict = __esm({
|
|
|
1531
1548
|
});
|
|
1532
1549
|
|
|
1533
1550
|
// src/commands.adopt.ts
|
|
1551
|
+
import { existsSync as existsSync5, lstatSync as lstatSync7 } from "node:fs";
|
|
1552
|
+
import { join as join8 } from "node:path";
|
|
1553
|
+
|
|
1554
|
+
// src/commands.adopt.recover.ts
|
|
1555
|
+
import { lstatSync as lstatSync6, rmSync as rmSync4 } from "node:fs";
|
|
1556
|
+
import { dirname as dirname2, join as join7, resolve as resolve2 } from "node:path";
|
|
1557
|
+
|
|
1558
|
+
// src/commands.adopt.scan.ts
|
|
1534
1559
|
init_config();
|
|
1535
|
-
init_config_sharedDirs_guard();
|
|
1536
1560
|
init_exit_codes();
|
|
1537
|
-
|
|
1538
|
-
import {
|
|
1561
|
+
init_utils();
|
|
1562
|
+
import { lstatSync, readdirSync } from "node:fs";
|
|
1563
|
+
import { join as join2, relative } from "node:path";
|
|
1564
|
+
function toForwardSlash(p) {
|
|
1565
|
+
return p.replaceAll("\\", "/");
|
|
1566
|
+
}
|
|
1567
|
+
function walk(root, dir, out) {
|
|
1568
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1569
|
+
const match = matchDeniedName(ALWAYS_NEVER_SYNC, entry.name);
|
|
1570
|
+
if (match !== null) {
|
|
1571
|
+
out.push({
|
|
1572
|
+
path: toForwardSlash(relative(root, join2(dir, entry.name))),
|
|
1573
|
+
matched: match.axis === "name" ? match.entry : null
|
|
1574
|
+
});
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
if (entry.isDirectory()) walk(root, join2(dir, entry.name), out);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
function scanDeniedEntries(root) {
|
|
1581
|
+
const stat = lstatSync(root, { throwIfNoEntry: false });
|
|
1582
|
+
if (!stat?.isDirectory()) return [];
|
|
1583
|
+
const out = [];
|
|
1584
|
+
walk(root, root, out);
|
|
1585
|
+
out.sort((a, b) => a.path.localeCompare(b.path));
|
|
1586
|
+
return out;
|
|
1587
|
+
}
|
|
1588
|
+
function scanOrFatal(name, root, state) {
|
|
1589
|
+
try {
|
|
1590
|
+
return scanDeniedEntries(root);
|
|
1591
|
+
} catch (err) {
|
|
1592
|
+
const message = err?.message;
|
|
1593
|
+
const text = typeof message === "string" ? message : String(err);
|
|
1594
|
+
throw new NomadFatal(
|
|
1595
|
+
`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.`,
|
|
1596
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
1597
|
+
);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
function describeMatch(hit) {
|
|
1601
|
+
return hit.matched === null ? "matches a credential filename shape" : `matches the never-sync name "${hit.matched}"`;
|
|
1602
|
+
}
|
|
1603
|
+
function remedies(hits, root, name) {
|
|
1604
|
+
const subject = hits.length === 1 ? "that path" : "those paths";
|
|
1605
|
+
let out = ` Move ${subject} out of ${root} and run \`nomad adopt ${name}\` again.`;
|
|
1606
|
+
if (hits.some((hit) => hit.matched !== null)) {
|
|
1607
|
+
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.";
|
|
1608
|
+
}
|
|
1609
|
+
if (hits.some((hit) => hit.matched === null)) {
|
|
1610
|
+
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.";
|
|
1611
|
+
}
|
|
1612
|
+
return out;
|
|
1613
|
+
}
|
|
1614
|
+
function deniedEntriesRefusal(name, root, hits, clauses) {
|
|
1615
|
+
const lines = hits.map((hit) => ` ${hit.path} (${describeMatch(hit)})`).join("\n");
|
|
1616
|
+
return new NomadFatal(
|
|
1617
|
+
`cannot adopt ${name}: ${clauses.found}:
|
|
1618
|
+
${lines}
|
|
1619
|
+
${clauses.state}${remedies(hits, root, name)}`,
|
|
1620
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
function refuseDeniedEntries(name, root) {
|
|
1624
|
+
const nothingChanged = "Nothing was changed.";
|
|
1625
|
+
const hits = scanOrFatal(name, root, () => nothingChanged);
|
|
1626
|
+
if (hits.length === 0) return;
|
|
1627
|
+
throw deniedEntriesRefusal(name, root, hits, {
|
|
1628
|
+
found: `${root} contains never-sync content`,
|
|
1629
|
+
state: nothingChanged
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// src/commands.adopt.recover.ts
|
|
1634
|
+
init_config();
|
|
1635
|
+
init_exit_codes();
|
|
1636
|
+
|
|
1637
|
+
// src/extras-sync.core.ts
|
|
1638
|
+
init_config();
|
|
1639
|
+
import { cpSync as cpSync2, existsSync as existsSync2, lstatSync as lstatSync3, readdirSync as readdirSync3, readFileSync as readFileSync2, rmSync as rmSync2 } from "node:fs";
|
|
1640
|
+
import { basename as basename2, join as join4, relative as relative2 } from "node:path";
|
|
1641
|
+
|
|
1642
|
+
// src/extras-sync.collision.ts
|
|
1643
|
+
init_utils();
|
|
1644
|
+
import { cpSync, existsSync, lstatSync as lstatSync2, readdirSync as readdirSync2, rmSync } from "node:fs";
|
|
1645
|
+
import { basename, join as join3 } from "node:path";
|
|
1646
|
+
function quoted(p) {
|
|
1647
|
+
return `"${p}"`;
|
|
1648
|
+
}
|
|
1649
|
+
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
1650
|
+
if (!existsSync(dst)) return;
|
|
1651
|
+
for (const name of readdirSync2(src)) {
|
|
1652
|
+
if (isExcluded(name)) continue;
|
|
1653
|
+
const dstPath = join3(dst, name);
|
|
1654
|
+
const dstStat = lstatSync2(dstPath, { throwIfNoEntry: false });
|
|
1655
|
+
if (dstStat === void 0) continue;
|
|
1656
|
+
if (dstStat.isSymbolicLink()) {
|
|
1657
|
+
rmSync(dstPath, { recursive: true, force: true });
|
|
1658
|
+
} else if (dstStat.isDirectory() && lstatSync2(join3(src, name)).isDirectory()) {
|
|
1659
|
+
stripCollidingDstSymlinks(join3(src, name), dstPath, isExcluded);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
function cpSyncGuarded(src, dst, filter, label) {
|
|
1664
|
+
try {
|
|
1665
|
+
cpSync(src, dst, { recursive: true, force: true, verbatimSymlinks: true, filter });
|
|
1666
|
+
} catch (err) {
|
|
1667
|
+
const e = err;
|
|
1668
|
+
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) {
|
|
1669
|
+
throw new NomadFatal(
|
|
1670
|
+
`${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`
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
throw err;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
function prunePreservingBy(src, dst, isPreserved, isRootPreserved) {
|
|
1677
|
+
for (const name of readdirSync2(dst)) {
|
|
1678
|
+
if (isPreserved(name) || isRootPreserved?.(name) === true) continue;
|
|
1679
|
+
const dstPath = join3(dst, name);
|
|
1680
|
+
const srcStat = lstatSync2(join3(src, name), { throwIfNoEntry: false });
|
|
1681
|
+
if (srcStat === void 0) {
|
|
1682
|
+
rmSync(dstPath, { recursive: true, force: true });
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
const dstStat = lstatSync2(dstPath);
|
|
1686
|
+
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
1687
|
+
prunePreservingBy(join3(src, name), dstPath, isPreserved);
|
|
1688
|
+
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
1689
|
+
rmSync(dstPath, { recursive: true, force: true });
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
function copyExtrasFilteredPreservingBy(src, dst, isPreserved, isRootPreserved) {
|
|
1694
|
+
const dstStat = lstatSync2(dst, { throwIfNoEntry: false });
|
|
1695
|
+
if (dstStat !== void 0) {
|
|
1696
|
+
if (dstStat.isDirectory()) {
|
|
1697
|
+
const srcStat = lstatSync2(src, { throwIfNoEntry: false });
|
|
1698
|
+
if (srcStat !== void 0 && !srcStat.isDirectory()) {
|
|
1699
|
+
throw new NomadFatal(
|
|
1700
|
+
`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`
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
prunePreservingBy(src, dst, isPreserved, isRootPreserved);
|
|
1704
|
+
} else {
|
|
1705
|
+
rmSync(dst, { recursive: true, force: true });
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
stripCollidingDstSymlinks(src, dst, isPreserved);
|
|
1709
|
+
cpSync(src, dst, {
|
|
1710
|
+
recursive: true,
|
|
1711
|
+
force: true,
|
|
1712
|
+
verbatimSymlinks: true,
|
|
1713
|
+
filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// src/extras-sync.guards.ts
|
|
1718
|
+
init_utils();
|
|
1719
|
+
init_config_sharedDirs_guard();
|
|
1720
|
+
import { isAbsolute, normalize, sep } from "node:path";
|
|
1721
|
+
function assertSafeLocalRoot(localRoot, logical) {
|
|
1722
|
+
if (!isAbsolute(localRoot)) {
|
|
1723
|
+
throw new NomadFatal(
|
|
1724
|
+
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be absolute)`
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
const canonical = process.platform === "win32" ? localRoot.split("/").join(sep) : localRoot;
|
|
1728
|
+
if (canonical !== normalize(canonical)) {
|
|
1729
|
+
throw new NomadFatal(
|
|
1730
|
+
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be already-normalized; no '..' or redundant segments)`
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
// src/extras-sync.core.ts
|
|
1736
|
+
init_utils();
|
|
1737
|
+
init_utils_json();
|
|
1738
|
+
function loadValidatedExtras(opts) {
|
|
1739
|
+
const repo = repoHome();
|
|
1740
|
+
const mapPath = join4(repo, "path-map.json");
|
|
1741
|
+
const repoExtras = join4(repo, "shared", "extras");
|
|
1742
|
+
if (!existsSync2(mapPath) || opts.requireRepoExtras === true && !existsSync2(repoExtras)) {
|
|
1743
|
+
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
1744
|
+
return null;
|
|
1745
|
+
}
|
|
1746
|
+
const map = readPathMap(mapPath);
|
|
1747
|
+
const extrasMap = map.extras ?? {};
|
|
1748
|
+
if (Object.keys(extrasMap).length === 0) return null;
|
|
1749
|
+
for (const logical of Object.keys(extrasMap)) {
|
|
1750
|
+
assertSafeLogical(logical);
|
|
1751
|
+
const localRoot = map.projects[logical]?.[HOST];
|
|
1752
|
+
if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
|
|
1753
|
+
}
|
|
1754
|
+
return { map, extrasMap };
|
|
1755
|
+
}
|
|
1756
|
+
function* eachExtrasTarget(v, counts) {
|
|
1757
|
+
const whitelist = SUPPORTED_EXTRAS;
|
|
1758
|
+
for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
|
|
1759
|
+
const localRoot = v.map.projects[logical]?.[HOST];
|
|
1760
|
+
if (!localRoot || localRoot === "TBD") {
|
|
1761
|
+
counts.unmapped++;
|
|
1762
|
+
continue;
|
|
1763
|
+
}
|
|
1764
|
+
for (const dirname15 of dirnames) {
|
|
1765
|
+
if (!whitelist.includes(dirname15)) {
|
|
1766
|
+
counts.skipped++;
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
yield { logical, localRoot, dirname: dirname15 };
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
function copyExtrasOverlayFiltered(src, dst, blockSet) {
|
|
1774
|
+
const dstStat = lstatSync3(dst, { throwIfNoEntry: false });
|
|
1775
|
+
if (dstStat !== void 0 && !dstStat.isDirectory()) {
|
|
1776
|
+
rmSync2(dst, { recursive: true, force: true });
|
|
1777
|
+
}
|
|
1778
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
1779
|
+
cpSyncGuarded(
|
|
1780
|
+
src,
|
|
1781
|
+
dst,
|
|
1782
|
+
(srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry)),
|
|
1783
|
+
"copyExtrasOverlayFiltered"
|
|
1784
|
+
);
|
|
1785
|
+
}
|
|
1786
|
+
function copyExtrasOverlaySkipDiverged(src, dst, blockSet, divergedSet) {
|
|
1787
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
1788
|
+
cpSyncGuarded(
|
|
1789
|
+
src,
|
|
1790
|
+
dst,
|
|
1791
|
+
(srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry)) && !divergedSet.has(relative2(src, srcEntry)),
|
|
1792
|
+
"copyExtrasOverlaySkipDiverged"
|
|
1793
|
+
);
|
|
1794
|
+
}
|
|
1795
|
+
function copyExtras(src, dst) {
|
|
1796
|
+
rmSync2(dst, { recursive: true, force: true });
|
|
1797
|
+
cpSync2(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
1798
|
+
}
|
|
1799
|
+
function copyExtrasFileSkipDiverged(src, dst) {
|
|
1800
|
+
if (existsSync2(dst)) {
|
|
1801
|
+
let dstBuf;
|
|
1802
|
+
try {
|
|
1803
|
+
dstBuf = readFileSync2(dst);
|
|
1804
|
+
} catch {
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
if (!readFileSync2(src).equals(dstBuf)) return;
|
|
1808
|
+
}
|
|
1809
|
+
copyExtras(src, dst);
|
|
1810
|
+
}
|
|
1811
|
+
function extrasDenySet(dirname15) {
|
|
1812
|
+
return isClaudeExtraName(dirname15) ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
1813
|
+
}
|
|
1814
|
+
function copyExtrasFiltered(src, dst, blockSet) {
|
|
1815
|
+
rmSync2(dst, { recursive: true, force: true });
|
|
1816
|
+
cpSync2(src, dst, {
|
|
1817
|
+
recursive: true,
|
|
1818
|
+
force: true,
|
|
1819
|
+
verbatimSymlinks: true,
|
|
1820
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry))
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
function prunePreservingDenied(src, dst, blockSet) {
|
|
1824
|
+
for (const name of readdirSync3(dst)) {
|
|
1825
|
+
if (isDeniedName(blockSet, name)) continue;
|
|
1826
|
+
const dstPath = join4(dst, name);
|
|
1827
|
+
const srcStat = lstatSync3(join4(src, name), { throwIfNoEntry: false });
|
|
1828
|
+
if (srcStat === void 0) {
|
|
1829
|
+
rmSync2(dstPath, { recursive: true, force: true });
|
|
1830
|
+
continue;
|
|
1831
|
+
}
|
|
1832
|
+
const dstStat = lstatSync3(dstPath);
|
|
1833
|
+
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
1834
|
+
prunePreservingDenied(join4(src, name), dstPath, blockSet);
|
|
1835
|
+
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
1836
|
+
rmSync2(dstPath, { recursive: true, force: true });
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
1841
|
+
const dstStat = lstatSync3(dst, { throwIfNoEntry: false });
|
|
1842
|
+
if (dstStat !== void 0) {
|
|
1843
|
+
if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
|
|
1844
|
+
else rmSync2(dst, { recursive: true, force: true });
|
|
1845
|
+
}
|
|
1846
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
1847
|
+
cpSync2(src, dst, {
|
|
1848
|
+
recursive: true,
|
|
1849
|
+
force: true,
|
|
1850
|
+
verbatimSymlinks: true,
|
|
1851
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry))
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1539
1854
|
|
|
1540
1855
|
// src/links.ts
|
|
1541
1856
|
init_config();
|
|
1542
|
-
import { existsSync as existsSync4, lstatSync as
|
|
1543
|
-
import { join as
|
|
1857
|
+
import { existsSync as existsSync4, lstatSync as lstatSync5, rmSync as rmSync3 } from "node:fs";
|
|
1858
|
+
import { join as join6 } from "node:path";
|
|
1544
1859
|
|
|
1545
1860
|
// src/hooks-filter.ts
|
|
1546
1861
|
init_config();
|
|
@@ -1829,224 +2144,6 @@ function buildCaptureSubset(merged, settings, opts) {
|
|
|
1829
2144
|
return out;
|
|
1830
2145
|
}
|
|
1831
2146
|
|
|
1832
|
-
// src/extras-sync.core.ts
|
|
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
2147
|
// src/links.ts
|
|
2051
2148
|
init_utils();
|
|
2052
2149
|
init_utils_fs();
|
|
@@ -2073,43 +2170,78 @@ function emitCopy(onPreview, from, to) {
|
|
|
2073
2170
|
}
|
|
2074
2171
|
}
|
|
2075
2172
|
function isAlreadySymlink(linkPath) {
|
|
2076
|
-
return existsSync4(linkPath) &&
|
|
2173
|
+
return existsSync4(linkPath) && lstatSync5(linkPath).isSymbolicLink();
|
|
2077
2174
|
}
|
|
2078
2175
|
function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
|
|
2079
2176
|
for (const name of linkNames) {
|
|
2080
|
-
const linkPath =
|
|
2081
|
-
const target =
|
|
2177
|
+
const linkPath = join6(claude, name);
|
|
2178
|
+
const target = join6(repo, "shared", name);
|
|
2082
2179
|
if (!existsSync4(linkPath)) continue;
|
|
2083
|
-
if (
|
|
2180
|
+
if (lstatSync5(linkPath).isSymbolicLink()) continue;
|
|
2084
2181
|
if (!existsSync4(target)) continue;
|
|
2085
2182
|
if (dryRun) {
|
|
2086
2183
|
emitAutoMove(onPreview, linkPath, ts, name);
|
|
2087
2184
|
continue;
|
|
2088
2185
|
}
|
|
2089
|
-
backupBeforeWrite(linkPath, ts);
|
|
2090
|
-
rmSync3(linkPath, { recursive: true, force: true });
|
|
2186
|
+
backupBeforeWrite(linkPath, ts);
|
|
2187
|
+
rmSync3(linkPath, { recursive: true, force: true });
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
function copySharedLinkPull(src, dst) {
|
|
2191
|
+
copyExtrasFilteredPreservingBy(src, dst, (name) => isDeniedName(ALWAYS_NEVER_SYNC, name));
|
|
2192
|
+
}
|
|
2193
|
+
function stillOccupied(abs) {
|
|
2194
|
+
try {
|
|
2195
|
+
return lstatSync5(abs, { throwIfNoEntry: false }) !== void 0;
|
|
2196
|
+
} catch {
|
|
2197
|
+
return true;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
function snapshotBeforeWin32Copy(linkPath, ts) {
|
|
2201
|
+
try {
|
|
2202
|
+
return backupBeforeWrite(linkPath, ts) ? "snapshotted" : "nothing-to-snapshot";
|
|
2203
|
+
} catch (err) {
|
|
2204
|
+
warn(
|
|
2205
|
+
`could not snapshot ${linkPath} before updating it (${err.message}), so it was left as it is. The rest of the pull continues`
|
|
2206
|
+
);
|
|
2207
|
+
return "failed";
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
function warnWin32ApplyFailed(linkPath, ts, err, snapshotted) {
|
|
2211
|
+
const state = stillOccupied(linkPath) ? "it may be unchanged, or partly updated" : "nothing is at that path now";
|
|
2212
|
+
const recover = snapshotted ? ` A copy of what it held before this pull is under backup/${ts}/.` : "";
|
|
2213
|
+
warn(
|
|
2214
|
+
`${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`
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
function applyOneSharedLinkWin32(target, linkPath, ts) {
|
|
2218
|
+
let snapshotted = false;
|
|
2219
|
+
try {
|
|
2220
|
+
const stat = lstatSync5(linkPath, { throwIfNoEntry: false });
|
|
2221
|
+
if (stat !== void 0) {
|
|
2222
|
+
const snapshot = snapshotBeforeWin32Copy(linkPath, ts);
|
|
2223
|
+
if (snapshot === "failed") return;
|
|
2224
|
+
snapshotted = snapshot === "snapshotted";
|
|
2225
|
+
if (stat.isSymbolicLink()) {
|
|
2226
|
+
rmSync3(linkPath, { recursive: true, force: true });
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
copySharedLinkPull(target, linkPath);
|
|
2230
|
+
} catch (err) {
|
|
2231
|
+
if (err instanceof NomadFatal) throw err;
|
|
2232
|
+
warnWin32ApplyFailed(linkPath, ts, err, snapshotted);
|
|
2091
2233
|
}
|
|
2092
2234
|
}
|
|
2093
|
-
function copySharedLinkPull(src, dst) {
|
|
2094
|
-
copyExtrasFilteredPreservingBy(src, dst, (name) => isDeniedName(ALWAYS_NEVER_SYNC, name));
|
|
2095
|
-
}
|
|
2096
2235
|
function applySharedLinksWin32(linkNames, claude, repo, ts, dryRun, onPreview) {
|
|
2097
2236
|
for (const name of linkNames) {
|
|
2098
|
-
const target =
|
|
2237
|
+
const target = join6(repo, "shared", name);
|
|
2099
2238
|
if (!existsSync4(target)) continue;
|
|
2100
|
-
const linkPath =
|
|
2239
|
+
const linkPath = join6(claude, name);
|
|
2101
2240
|
if (dryRun) {
|
|
2102
2241
|
emitCopy(onPreview, linkPath, target);
|
|
2103
2242
|
continue;
|
|
2104
2243
|
}
|
|
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);
|
|
2244
|
+
applyOneSharedLinkWin32(target, linkPath, ts);
|
|
2113
2245
|
}
|
|
2114
2246
|
}
|
|
2115
2247
|
function applySharedLinks(ts, map, opts = {}) {
|
|
@@ -2123,9 +2255,9 @@ function applySharedLinks(ts, map, opts = {}) {
|
|
|
2123
2255
|
}
|
|
2124
2256
|
runAutoMovePasses(linkNames, claude, repo, ts, dryRun, opts.onPreview);
|
|
2125
2257
|
for (const name of linkNames) {
|
|
2126
|
-
const target =
|
|
2258
|
+
const target = join6(repo, "shared", name);
|
|
2127
2259
|
if (!existsSync4(target)) continue;
|
|
2128
|
-
const linkPath =
|
|
2260
|
+
const linkPath = join6(claude, name);
|
|
2129
2261
|
if (isAlreadySymlink(linkPath)) continue;
|
|
2130
2262
|
if (dryRun) {
|
|
2131
2263
|
emitCreate(opts.onPreview, linkPath, target);
|
|
@@ -2167,8 +2299,8 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
2167
2299
|
const suppressDriftWarn = opts.suppressDriftWarn === true;
|
|
2168
2300
|
const repo = repoHome();
|
|
2169
2301
|
const claude = claudeHome();
|
|
2170
|
-
const basePath =
|
|
2171
|
-
const hostPath =
|
|
2302
|
+
const basePath = join6(repo, "shared", "settings.base.json");
|
|
2303
|
+
const hostPath = join6(repo, "hosts", `${HOST}.json`);
|
|
2172
2304
|
if (!existsSync4(basePath)) {
|
|
2173
2305
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
2174
2306
|
}
|
|
@@ -2176,7 +2308,7 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
2176
2308
|
const hasOverrides = existsSync4(hostPath);
|
|
2177
2309
|
const overrides = hasOverrides ? readJson(hostPath) : {};
|
|
2178
2310
|
const merged = deepMerge(base, overrides);
|
|
2179
|
-
const settingsPath =
|
|
2311
|
+
const settingsPath = join6(claude, "settings.json");
|
|
2180
2312
|
const { existing, present, malformed } = readExistingSettings(settingsPath);
|
|
2181
2313
|
if (!suppressDriftWarn && present) {
|
|
2182
2314
|
if (malformed) {
|
|
@@ -2198,21 +2330,140 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
2198
2330
|
return { label: overrideLabel };
|
|
2199
2331
|
}
|
|
2200
2332
|
|
|
2201
|
-
// src/commands.adopt.ts
|
|
2333
|
+
// src/commands.adopt.recover.ts
|
|
2202
2334
|
init_utils();
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2335
|
+
function errorText(err) {
|
|
2336
|
+
const message = err?.message;
|
|
2337
|
+
return typeof message === "string" ? message : String(err);
|
|
2338
|
+
}
|
|
2206
2339
|
function lexists(p) {
|
|
2207
2340
|
try {
|
|
2208
|
-
|
|
2341
|
+
lstatSync6(p);
|
|
2209
2342
|
return true;
|
|
2210
2343
|
} catch {
|
|
2211
2344
|
return false;
|
|
2212
2345
|
}
|
|
2213
2346
|
}
|
|
2347
|
+
function isDirectChildOf(root, abs) {
|
|
2348
|
+
return dirname2(resolve2(abs)) === resolve2(root);
|
|
2349
|
+
}
|
|
2350
|
+
function clearPartialCopy(linkPath) {
|
|
2351
|
+
return clearIfDirectChild(claudeHome(), linkPath);
|
|
2352
|
+
}
|
|
2353
|
+
function clearPartialShared(sharedTarget, repo) {
|
|
2354
|
+
return clearIfDirectChild(join7(repo, "shared"), sharedTarget);
|
|
2355
|
+
}
|
|
2356
|
+
function clearIfDirectChild(root, target) {
|
|
2357
|
+
const resolved = resolve2(target);
|
|
2358
|
+
if (!isDirectChildOf(root, resolved)) return false;
|
|
2359
|
+
try {
|
|
2360
|
+
rmSync4(resolved, { recursive: true, force: true });
|
|
2361
|
+
} catch {
|
|
2362
|
+
return false;
|
|
2363
|
+
}
|
|
2364
|
+
return !lexists(resolved);
|
|
2365
|
+
}
|
|
2366
|
+
function stageOrReport(stage) {
|
|
2367
|
+
try {
|
|
2368
|
+
stage();
|
|
2369
|
+
return "";
|
|
2370
|
+
} catch (err) {
|
|
2371
|
+
return ` Staging it failed too (${errorText(err)}), so it is in the repo but not staged.`;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
function describeLeftoverAt(name, linkPath, sourceRemoved) {
|
|
2375
|
+
if (!sourceRemoved) {
|
|
2376
|
+
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.`;
|
|
2377
|
+
}
|
|
2378
|
+
if (clearPartialCopy(linkPath)) return "";
|
|
2379
|
+
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.`;
|
|
2380
|
+
}
|
|
2381
|
+
function restoreWin32LocalCopy(name, linkPath, sharedTarget, ts, stage, opts) {
|
|
2382
|
+
try {
|
|
2383
|
+
copySharedLinkPull(sharedTarget, linkPath);
|
|
2384
|
+
} catch (err) {
|
|
2385
|
+
if (err instanceof NomadFatal) {
|
|
2386
|
+
const fatalStageFailure = stageOrReport(stage);
|
|
2387
|
+
if (fatalStageFailure !== "") warn(`${name}:${fatalStageFailure}`);
|
|
2388
|
+
throw err;
|
|
2389
|
+
}
|
|
2390
|
+
const leftover = describeLeftoverAt(name, linkPath, opts.sourceRemoved);
|
|
2391
|
+
const stageFailure = stageOrReport(stage);
|
|
2392
|
+
const staged = stageFailure === "" ? " and staged." : `.${stageFailure}`;
|
|
2393
|
+
const recover = opts.snapshotted ? ` A copy of what it held before is under backup/${ts}/.` : "";
|
|
2394
|
+
throw new NomadFatal(
|
|
2395
|
+
`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}`,
|
|
2396
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
2397
|
+
);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
function describePartialShared(name, linkPath) {
|
|
2401
|
+
if (process.platform === "win32") {
|
|
2402
|
+
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.`;
|
|
2403
|
+
}
|
|
2404
|
+
return ` A partial shared/${name} may still be in the repo; remove it first, because adopt refuses to run while it is there.`;
|
|
2405
|
+
}
|
|
2406
|
+
function copyIntoSharedOrFatal(name, linkPath, sharedTarget, repo) {
|
|
2407
|
+
try {
|
|
2408
|
+
copyExtrasFiltered(linkPath, sharedTarget, ALWAYS_NEVER_SYNC);
|
|
2409
|
+
} catch (err) {
|
|
2410
|
+
const leftover = clearPartialShared(sharedTarget, repo) ? "" : describePartialShared(name, linkPath);
|
|
2411
|
+
throw new NomadFatal(
|
|
2412
|
+
`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}`,
|
|
2413
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
2414
|
+
);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
function refuseLateDeniedEntries(name, linkPath, sharedTarget, repo, opts) {
|
|
2418
|
+
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.` : "";
|
|
2419
|
+
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}`;
|
|
2420
|
+
const late = scanOrFatal(name, linkPath, stateNow);
|
|
2421
|
+
if (late.length === 0) return;
|
|
2422
|
+
throw deniedEntriesRefusal(name, linkPath, late, {
|
|
2423
|
+
found: `never-sync content appeared under ${linkPath} after the preflight scan`,
|
|
2424
|
+
state: stateNow()
|
|
2425
|
+
});
|
|
2426
|
+
}
|
|
2427
|
+
function removeAdoptSource(linkPath) {
|
|
2428
|
+
const resolved = resolve2(linkPath);
|
|
2429
|
+
if (!isDirectChildOf(claudeHome(), resolved)) {
|
|
2430
|
+
return { ok: false, message: "it is not a direct child of the configured Claude home" };
|
|
2431
|
+
}
|
|
2432
|
+
try {
|
|
2433
|
+
rmSync4(resolved, { recursive: true, force: true });
|
|
2434
|
+
} catch (err) {
|
|
2435
|
+
return { ok: false, message: errorText(err) };
|
|
2436
|
+
}
|
|
2437
|
+
if (lexists(resolved)) {
|
|
2438
|
+
return { ok: false, message: "the delete was accepted but the entry is still there" };
|
|
2439
|
+
}
|
|
2440
|
+
return { ok: true };
|
|
2441
|
+
}
|
|
2442
|
+
function reportSourceRemovalFailure(name, linkPath, message, stage) {
|
|
2443
|
+
if (process.platform === "win32") {
|
|
2444
|
+
warn(
|
|
2445
|
+
`${name}: could not remove ${linkPath} (${message}); refreshing it from shared/${name} instead, which leaves the same result`
|
|
2446
|
+
);
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2449
|
+
const stageFailure = stageOrReport(stage);
|
|
2450
|
+
const staged = stageFailure === "" ? " and staged." : `.${stageFailure}`;
|
|
2451
|
+
throw new NomadFatal(
|
|
2452
|
+
`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.`,
|
|
2453
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
2454
|
+
);
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// src/commands.adopt.ts
|
|
2458
|
+
init_config();
|
|
2459
|
+
init_config_sharedDirs_guard();
|
|
2460
|
+
init_exit_codes();
|
|
2461
|
+
init_utils();
|
|
2462
|
+
init_utils_fs();
|
|
2463
|
+
init_utils_json();
|
|
2464
|
+
var ADOPT_PUSH_HINT = "run `nomad push` to share with other hosts";
|
|
2214
2465
|
function readMapIfPresent(repoHome2) {
|
|
2215
|
-
const mapPath =
|
|
2466
|
+
const mapPath = join8(repoHome2, "path-map.json");
|
|
2216
2467
|
return existsSync5(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
2217
2468
|
}
|
|
2218
2469
|
function isConfiguredTarget(name, map) {
|
|
@@ -2222,18 +2473,46 @@ function isValidAdoptName(name) {
|
|
|
2222
2473
|
if (SHARED_LINKS.includes(name)) return true;
|
|
2223
2474
|
return isValidSharedDir(name);
|
|
2224
2475
|
}
|
|
2476
|
+
function reportWin32AlreadyAdopted(name, sharedTarget) {
|
|
2477
|
+
if (process.platform !== "win32" || !lexists(sharedTarget)) return false;
|
|
2478
|
+
log(`${name}: already adopted (win32 copy-sync); run \`nomad pull\` to refresh the local copy`);
|
|
2479
|
+
return true;
|
|
2480
|
+
}
|
|
2481
|
+
function adoptStopsEarly(name, linkPath, sharedTarget) {
|
|
2482
|
+
if (!existsSync5(linkPath)) {
|
|
2483
|
+
if (reportWin32AlreadyAdopted(name, sharedTarget)) return true;
|
|
2484
|
+
log(`${name}: nothing to adopt (not present in ~/.claude/)`);
|
|
2485
|
+
return true;
|
|
2486
|
+
}
|
|
2487
|
+
if (lstatSync7(linkPath).isSymbolicLink()) {
|
|
2488
|
+
log(`${name}: already adopted (already a symlink)`);
|
|
2489
|
+
return true;
|
|
2490
|
+
}
|
|
2491
|
+
if (reportWin32AlreadyAdopted(name, sharedTarget)) return true;
|
|
2492
|
+
if (lexists(sharedTarget)) {
|
|
2493
|
+
fail(`${name}: shared/${name} already exists; would clobber. Remove it first.`);
|
|
2494
|
+
process.exit(1);
|
|
2495
|
+
}
|
|
2496
|
+
return false;
|
|
2497
|
+
}
|
|
2225
2498
|
function performAdoptMove(name, linkPath, sharedTarget, repo, backup) {
|
|
2226
2499
|
const ts = freshBackupTs(backup);
|
|
2227
|
-
backupBeforeWrite(linkPath, ts);
|
|
2228
|
-
|
|
2229
|
-
|
|
2500
|
+
const snapshotted = backupBeforeWrite(linkPath, ts);
|
|
2501
|
+
const rel = join8("shared", name);
|
|
2502
|
+
const stage = () => gitOrFatal(["add", "--", rel], `git add shared/${name}`, repo);
|
|
2503
|
+
copyIntoSharedOrFatal(name, linkPath, sharedTarget, repo);
|
|
2504
|
+
refuseLateDeniedEntries(name, linkPath, sharedTarget, repo, { snapshotted, ts });
|
|
2505
|
+
const removal = removeAdoptSource(linkPath);
|
|
2506
|
+
if (!removal.ok) reportSourceRemovalFailure(name, linkPath, removal.message, stage);
|
|
2230
2507
|
if (process.platform === "win32") {
|
|
2231
|
-
|
|
2508
|
+
restoreWin32LocalCopy(name, linkPath, sharedTarget, ts, stage, {
|
|
2509
|
+
snapshotted,
|
|
2510
|
+
sourceRemoved: removal.ok
|
|
2511
|
+
});
|
|
2232
2512
|
} else {
|
|
2233
2513
|
ensureSymlink(linkPath, sharedTarget);
|
|
2234
2514
|
}
|
|
2235
|
-
|
|
2236
|
-
gitOrFatal(["add", "--", rel], `git add shared/${name}`, repo);
|
|
2515
|
+
stage();
|
|
2237
2516
|
log(`adopted ${name}; ${ADOPT_PUSH_HINT}`);
|
|
2238
2517
|
}
|
|
2239
2518
|
function cmdAdopt(name, opts = {}) {
|
|
@@ -2259,24 +2538,10 @@ function cmdAdopt(name, opts = {}) {
|
|
|
2259
2538
|
);
|
|
2260
2539
|
process.exit(1);
|
|
2261
2540
|
}
|
|
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`);
|
|
2274
|
-
return;
|
|
2275
|
-
}
|
|
2276
|
-
if (lexists(sharedTarget)) {
|
|
2277
|
-
fail(`${name}: shared/${name} already exists; would clobber. Remove it first.`);
|
|
2278
|
-
process.exit(1);
|
|
2279
|
-
}
|
|
2541
|
+
const linkPath = join8(claude, name);
|
|
2542
|
+
const sharedTarget = join8(repo, "shared", name);
|
|
2543
|
+
if (adoptStopsEarly(name, linkPath, sharedTarget)) return;
|
|
2544
|
+
refuseDeniedEntries(name, linkPath);
|
|
2280
2545
|
if (dryRun) {
|
|
2281
2546
|
const ts = freshBackupTs(backup);
|
|
2282
2547
|
log(`would backup: ${linkPath} -> backup/${ts}/${name}`);
|
|
@@ -2302,7 +2567,7 @@ import { existsSync as existsSync6 } from "node:fs";
|
|
|
2302
2567
|
|
|
2303
2568
|
// src/commands.redact.core.ts
|
|
2304
2569
|
import { appendFileSync, readFileSync as readFileSync3 } from "node:fs";
|
|
2305
|
-
import { join as
|
|
2570
|
+
import { join as join9 } from "node:path";
|
|
2306
2571
|
function collectMatchIntervals(content, findings) {
|
|
2307
2572
|
const intervals = [];
|
|
2308
2573
|
for (const f of findings) {
|
|
@@ -2360,7 +2625,7 @@ function isAlreadyPresent(line, lines) {
|
|
|
2360
2625
|
function appendGitleaksIgnore(fingerprint, repo) {
|
|
2361
2626
|
const sanitized = fingerprint.replace(/[\r\n]/g, "").trim();
|
|
2362
2627
|
if (sanitized.length === 0) return;
|
|
2363
|
-
const ignPath =
|
|
2628
|
+
const ignPath = join9(repo, ".gitleaksignore");
|
|
2364
2629
|
let raw;
|
|
2365
2630
|
try {
|
|
2366
2631
|
raw = readFileSync3(ignPath, "utf8");
|
|
@@ -2397,7 +2662,7 @@ function cmdAllow(fingerprints) {
|
|
|
2397
2662
|
// src/commands.capture-settings.ts
|
|
2398
2663
|
init_config();
|
|
2399
2664
|
import { existsSync as existsSync7 } from "node:fs";
|
|
2400
|
-
import { join as
|
|
2665
|
+
import { join as join11 } from "node:path";
|
|
2401
2666
|
import { createInterface } from "node:readline/promises";
|
|
2402
2667
|
init_utils_fs();
|
|
2403
2668
|
init_utils_json();
|
|
@@ -2406,13 +2671,13 @@ init_utils_json();
|
|
|
2406
2671
|
init_config();
|
|
2407
2672
|
init_utils();
|
|
2408
2673
|
import { closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2409
|
-
import { dirname as
|
|
2674
|
+
import { dirname as dirname3, join as join10 } from "node:path";
|
|
2410
2675
|
function lockFilePath() {
|
|
2411
|
-
return
|
|
2676
|
+
return join10(home(), ".cache", "claude-nomad", "nomad.lock");
|
|
2412
2677
|
}
|
|
2413
2678
|
function acquireLock(verb) {
|
|
2414
2679
|
const lp = lockFilePath();
|
|
2415
|
-
mkdirSync2(
|
|
2680
|
+
mkdirSync2(dirname3(lp), { recursive: true });
|
|
2416
2681
|
try {
|
|
2417
2682
|
const fd = openSync2(lp, "wx");
|
|
2418
2683
|
try {
|
|
@@ -2534,7 +2799,7 @@ async function confirmCapture(destLabel, keys) {
|
|
|
2534
2799
|
}
|
|
2535
2800
|
}
|
|
2536
2801
|
function resolveCaptureDestination(repo, useHost) {
|
|
2537
|
-
const destPath = useHost ?
|
|
2802
|
+
const destPath = useHost ? join11(repo, "hosts", `${HOST}.json`) : join11(repo, "shared", "settings.base.json");
|
|
2538
2803
|
const existing = existsSync7(destPath) ? readJson(destPath) : {};
|
|
2539
2804
|
return { destPath, existing };
|
|
2540
2805
|
}
|
|
@@ -2546,17 +2811,17 @@ async function cmdCaptureSettings(opts) {
|
|
|
2546
2811
|
if (handle === null) process.exit(0);
|
|
2547
2812
|
try {
|
|
2548
2813
|
const claude = claudeHome();
|
|
2549
|
-
const basePath =
|
|
2814
|
+
const basePath = join11(repo, "shared", "settings.base.json");
|
|
2550
2815
|
if (!existsSync7(basePath)) {
|
|
2551
2816
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
2552
2817
|
}
|
|
2553
|
-
const settingsPath =
|
|
2818
|
+
const settingsPath = join11(claude, "settings.json");
|
|
2554
2819
|
if (!existsSync7(settingsPath)) {
|
|
2555
2820
|
log("no ~/.claude/settings.json found; nothing to capture");
|
|
2556
2821
|
return;
|
|
2557
2822
|
}
|
|
2558
2823
|
const base = readJson(basePath);
|
|
2559
|
-
const hostPath =
|
|
2824
|
+
const hostPath = join11(repo, "hosts", `${HOST}.json`);
|
|
2560
2825
|
const overrides = existsSync7(hostPath) ? readJson(hostPath) : {};
|
|
2561
2826
|
const merged = deepMerge(base, overrides);
|
|
2562
2827
|
const settings = readJson(settingsPath);
|
|
@@ -2595,8 +2860,8 @@ async function cmdCaptureSettings(opts) {
|
|
|
2595
2860
|
init_config();
|
|
2596
2861
|
init_exit_codes();
|
|
2597
2862
|
init_utils();
|
|
2598
|
-
import { existsSync as existsSync8, lstatSync as
|
|
2599
|
-
import { join as
|
|
2863
|
+
import { existsSync as existsSync8, lstatSync as lstatSync8, readdirSync as readdirSync4, rmSync as rmSync5, statSync as statSync2 } from "node:fs";
|
|
2864
|
+
import { join as join12 } from "node:path";
|
|
2600
2865
|
var TS_SHAPE = /^\d{8}-\d{6}(-\d+)?$/;
|
|
2601
2866
|
var DURATION_RE = /^(\d+)([dhm])$/;
|
|
2602
2867
|
var UNIT_MS = { d: 864e5, h: 36e5, m: 6e4 };
|
|
@@ -2611,7 +2876,7 @@ function parseDuration(s) {
|
|
|
2611
2876
|
}
|
|
2612
2877
|
function listBackupDirs(backupBase2) {
|
|
2613
2878
|
if (!existsSync8(backupBase2)) return [];
|
|
2614
|
-
return
|
|
2879
|
+
return readdirSync4(backupBase2).filter(isTsDir).map((name) => ({ name, mtimeMs: statSync2(join12(backupBase2, name)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2615
2880
|
}
|
|
2616
2881
|
function prunableByAge(dirs, olderThanMs, nowMs) {
|
|
2617
2882
|
return dirs.filter((d) => nowMs - d.mtimeMs > olderThanMs).map((d) => d.name);
|
|
@@ -2621,8 +2886,8 @@ function prunableByCount(dirs, keep) {
|
|
|
2621
2886
|
}
|
|
2622
2887
|
function safeDelete(backupBase2, name) {
|
|
2623
2888
|
if (!isTsDir(name)) return;
|
|
2624
|
-
const full =
|
|
2625
|
-
const st =
|
|
2889
|
+
const full = join12(backupBase2, name);
|
|
2890
|
+
const st = lstatSync8(full, { throwIfNoEntry: false });
|
|
2626
2891
|
if (!st || st.isSymbolicLink() || !st.isDirectory()) return;
|
|
2627
2892
|
rmSync5(full, { recursive: true, force: true });
|
|
2628
2893
|
}
|
|
@@ -2662,8 +2927,8 @@ init_config_sharedDirs_guard();
|
|
|
2662
2927
|
init_utils();
|
|
2663
2928
|
init_utils_fs();
|
|
2664
2929
|
init_utils_json();
|
|
2665
|
-
import { cpSync as
|
|
2666
|
-
import { join as
|
|
2930
|
+
import { cpSync as cpSync4, existsSync as existsSync9, lstatSync as lstatSync9, realpathSync, rmSync as rmSync6 } from "node:fs";
|
|
2931
|
+
import { join as join13, sep as sep3 } from "node:path";
|
|
2667
2932
|
function ejectChecklist() {
|
|
2668
2933
|
return [
|
|
2669
2934
|
"Manual steps remaining to finish leaving claude-nomad on this host:",
|
|
@@ -2679,28 +2944,28 @@ function errMessage(err) {
|
|
|
2679
2944
|
}
|
|
2680
2945
|
function lexists2(p) {
|
|
2681
2946
|
try {
|
|
2682
|
-
|
|
2947
|
+
lstatSync9(p);
|
|
2683
2948
|
return true;
|
|
2684
2949
|
} catch {
|
|
2685
2950
|
return false;
|
|
2686
2951
|
}
|
|
2687
2952
|
}
|
|
2688
2953
|
function readMapIfPresent2(repoHome2) {
|
|
2689
|
-
const mapPath =
|
|
2954
|
+
const mapPath = join13(repoHome2, "path-map.json");
|
|
2690
2955
|
return existsSync9(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
2691
2956
|
}
|
|
2692
2957
|
function classifyName(linkPath) {
|
|
2693
2958
|
if (!lexists2(linkPath)) return "absent";
|
|
2694
|
-
if (!
|
|
2959
|
+
if (!lstatSync9(linkPath).isSymbolicLink()) return "skip-real";
|
|
2695
2960
|
if (!existsSync9(linkPath)) return "dangling";
|
|
2696
2961
|
return "materialize";
|
|
2697
2962
|
}
|
|
2698
2963
|
function resolveSharedRoot(repoHome2) {
|
|
2699
2964
|
try {
|
|
2700
|
-
return realpathSync(
|
|
2965
|
+
return realpathSync(join13(repoHome2, "shared"));
|
|
2701
2966
|
} catch {
|
|
2702
2967
|
return die(
|
|
2703
|
-
`cannot resolve ${
|
|
2968
|
+
`cannot resolve ${join13(repoHome2, "shared")} (repo checkout incomplete). run \`nomad pull\` first, then re-run \`nomad eject\``
|
|
2704
2969
|
);
|
|
2705
2970
|
}
|
|
2706
2971
|
}
|
|
@@ -2716,7 +2981,7 @@ function materializeOne(name, linkPath, sharedRoot) {
|
|
|
2716
2981
|
const tmp = `${linkPath}.eject.tmp.${process.pid}.${Date.now()}`;
|
|
2717
2982
|
try {
|
|
2718
2983
|
rmSync6(tmp, { recursive: true, force: true });
|
|
2719
|
-
|
|
2984
|
+
cpSync4(target, tmp, {
|
|
2720
2985
|
recursive: true,
|
|
2721
2986
|
force: true,
|
|
2722
2987
|
dereference: true,
|
|
@@ -2740,7 +3005,7 @@ function skipRealMessage(name) {
|
|
|
2740
3005
|
function previewDryRun(names, classifications, claudeHome2, sharedRoot) {
|
|
2741
3006
|
for (const name of names) {
|
|
2742
3007
|
const cls = classifications.get(name);
|
|
2743
|
-
const linkPath =
|
|
3008
|
+
const linkPath = join13(claudeHome2, name);
|
|
2744
3009
|
if (cls === "absent") {
|
|
2745
3010
|
item(`skipped (absent): ${name}`);
|
|
2746
3011
|
} else if (cls === "skip-real") {
|
|
@@ -2770,7 +3035,7 @@ function runLiveEject(names, classifications, claudeHome2, sharedRoot) {
|
|
|
2770
3035
|
let skipped = 0;
|
|
2771
3036
|
for (const name of names) {
|
|
2772
3037
|
const cls = classifications.get(name);
|
|
2773
|
-
const linkPath =
|
|
3038
|
+
const linkPath = join13(claudeHome2, name);
|
|
2774
3039
|
if (cls === "absent") {
|
|
2775
3040
|
item(`skipped (absent): ${name}`);
|
|
2776
3041
|
skipped++;
|
|
@@ -2819,7 +3084,7 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2819
3084
|
const names = ejectNames(map);
|
|
2820
3085
|
const classifications = /* @__PURE__ */ new Map();
|
|
2821
3086
|
for (const name of names) {
|
|
2822
|
-
classifications.set(name, classifyName(
|
|
3087
|
+
classifications.set(name, classifyName(join13(claudeHome2, name)));
|
|
2823
3088
|
}
|
|
2824
3089
|
const base = new Set(allSharedLinks(map, { quiet: true }));
|
|
2825
3090
|
for (const name of names) {
|
|
@@ -2857,14 +3122,14 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2857
3122
|
}
|
|
2858
3123
|
|
|
2859
3124
|
// src/commands.doctor.ts
|
|
2860
|
-
import { existsSync as
|
|
2861
|
-
import { join as
|
|
3125
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
3126
|
+
import { join as join48 } from "node:path";
|
|
2862
3127
|
|
|
2863
3128
|
// src/commands.doctor.checks.repo.ts
|
|
2864
3129
|
init_color();
|
|
2865
3130
|
init_config();
|
|
2866
|
-
import { existsSync as
|
|
2867
|
-
import { join as
|
|
3131
|
+
import { existsSync as existsSync12, lstatSync as lstatSync10, readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
|
|
3132
|
+
import { join as join16 } from "node:path";
|
|
2868
3133
|
|
|
2869
3134
|
// src/commands.doctor.format.ts
|
|
2870
3135
|
init_color();
|
|
@@ -2939,10 +3204,16 @@ function readJsonSafe(path, label, section2) {
|
|
|
2939
3204
|
}
|
|
2940
3205
|
}
|
|
2941
3206
|
|
|
3207
|
+
// src/commands.doctor.checks.repo.win32.ts
|
|
3208
|
+
init_color();
|
|
3209
|
+
init_config();
|
|
3210
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
3211
|
+
import { join as join14, win32 as win32Path } from "node:path";
|
|
3212
|
+
|
|
2942
3213
|
// src/extras-sync.diff.ts
|
|
2943
3214
|
init_utils();
|
|
2944
3215
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2945
|
-
import { relative as
|
|
3216
|
+
import { relative as relative4 } from "node:path";
|
|
2946
3217
|
var NAME_STATUS_DIFF_TIMEOUT_MS = 3e3;
|
|
2947
3218
|
function parseNameStatus(stdout) {
|
|
2948
3219
|
const fields = stdout.split("\0").filter((f) => f.length > 0);
|
|
@@ -2961,7 +3232,7 @@ function parseDiffOutput(stdout) {
|
|
|
2961
3232
|
return parseNameStatus(stdout).map(labelEntry);
|
|
2962
3233
|
}
|
|
2963
3234
|
function parseModifiedPaths(stdout, a) {
|
|
2964
|
-
return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) =>
|
|
3235
|
+
return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative4(a, entry.path));
|
|
2965
3236
|
}
|
|
2966
3237
|
function runNameStatusDiff(a, b, parse) {
|
|
2967
3238
|
try {
|
|
@@ -2994,18 +3265,56 @@ function listDivergingModified(a, b) {
|
|
|
2994
3265
|
return runNameStatusDiff(a, b, (stdout) => parseModifiedPaths(stdout, a));
|
|
2995
3266
|
}
|
|
2996
3267
|
|
|
3268
|
+
// src/commands.doctor.checks.repo.win32.ts
|
|
3269
|
+
function win32CopyOkRow(name, exempt = 0) {
|
|
3270
|
+
const note = exempt > 0 ? dim(` (${exempt} never-synced path(s) not compared)`) : "";
|
|
3271
|
+
return { line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)${note}`, fail: false };
|
|
3272
|
+
}
|
|
3273
|
+
function win32CopyUnpublishedRow(name) {
|
|
3274
|
+
return {
|
|
3275
|
+
line: `${dim(infoGlyph)} ${name}: real local copy, not published (run \`nomad adopt ${name}\` to share it)`,
|
|
3276
|
+
fail: false
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
function relativeUnder(root, path) {
|
|
3280
|
+
const rel = win32Path.relative(root, path);
|
|
3281
|
+
const segments = rel.split(win32Path.sep);
|
|
3282
|
+
if (rel === "" || segments[0] === ".." || win32Path.isAbsolute(rel)) return null;
|
|
3283
|
+
return segments.join("/");
|
|
3284
|
+
}
|
|
3285
|
+
function divergingRepoPath(line, name, local, shared) {
|
|
3286
|
+
const path = line.replace(/ \((?:local|repo) only\)$/, "");
|
|
3287
|
+
const rel = relativeUnder(local, path) ?? relativeUnder(shared, path);
|
|
3288
|
+
return rel === null ? `shared/${name}` : `shared/${name}/${rel}`;
|
|
3289
|
+
}
|
|
3290
|
+
function classifyWin32Copy(name, p) {
|
|
3291
|
+
const sharedPath = join14(repoHome(), "shared", name);
|
|
3292
|
+
if (!existsSync10(sharedPath)) return win32CopyUnpublishedRow(name);
|
|
3293
|
+
const compared = listDivergingFiles(p, sharedPath);
|
|
3294
|
+
const diverging = compared.filter(
|
|
3295
|
+
(line) => deniedSegmentFor(divergingRepoPath(line, name, p, sharedPath)) === null
|
|
3296
|
+
);
|
|
3297
|
+
const exempt = compared.length - diverging.length;
|
|
3298
|
+
if (diverging.length === 0) return win32CopyOkRow(name, exempt);
|
|
3299
|
+
return {
|
|
3300
|
+
line: `${yellow(warnGlyph)} ${name}: ${diverging.length} file(s) diverge from shared/${name}`,
|
|
3301
|
+
fail: false,
|
|
3302
|
+
children: diverging
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
|
|
2997
3306
|
// src/init.classify.ts
|
|
2998
3307
|
init_config();
|
|
2999
3308
|
init_utils_json();
|
|
3000
|
-
import { existsSync as
|
|
3001
|
-
import { join as
|
|
3309
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
3310
|
+
import { join as join15 } from "node:path";
|
|
3002
3311
|
function classifyRepoState(repoHome2, host) {
|
|
3003
|
-
const basePath =
|
|
3004
|
-
const mapPath =
|
|
3005
|
-
const hostPath =
|
|
3006
|
-
const hasBase =
|
|
3007
|
-
const hasMap =
|
|
3008
|
-
const hasHost =
|
|
3312
|
+
const basePath = join15(repoHome2, "shared", "settings.base.json");
|
|
3313
|
+
const mapPath = join15(repoHome2, "path-map.json");
|
|
3314
|
+
const hostPath = join15(repoHome2, "hosts", `${host}.json`);
|
|
3315
|
+
const hasBase = existsSync11(basePath);
|
|
3316
|
+
const hasMap = existsSync11(mapPath);
|
|
3317
|
+
const hasHost = existsSync11(hostPath);
|
|
3009
3318
|
let mapEntryCount = 0;
|
|
3010
3319
|
if (hasMap) {
|
|
3011
3320
|
try {
|
|
@@ -3020,11 +3329,11 @@ function classifyRepoState(repoHome2, host) {
|
|
|
3020
3329
|
return "partial";
|
|
3021
3330
|
}
|
|
3022
3331
|
function reasonForPartial(repoHome2, host) {
|
|
3023
|
-
const basePath =
|
|
3024
|
-
const mapPath =
|
|
3025
|
-
const hostPath =
|
|
3026
|
-
if (!
|
|
3027
|
-
if (!
|
|
3332
|
+
const basePath = join15(repoHome2, "shared", "settings.base.json");
|
|
3333
|
+
const mapPath = join15(repoHome2, "path-map.json");
|
|
3334
|
+
const hostPath = join15(repoHome2, "hosts", `${host}.json`);
|
|
3335
|
+
if (!existsSync11(basePath)) return "- shared/settings.base.json missing";
|
|
3336
|
+
if (!existsSync11(mapPath)) return "- path-map.json missing";
|
|
3028
3337
|
let mapEntryCount;
|
|
3029
3338
|
try {
|
|
3030
3339
|
const map = readJson(mapPath);
|
|
@@ -3033,7 +3342,7 @@ function reasonForPartial(repoHome2, host) {
|
|
|
3033
3342
|
mapEntryCount = 0;
|
|
3034
3343
|
}
|
|
3035
3344
|
if (mapEntryCount === 0) return "- path-map.json.projects has no entries";
|
|
3036
|
-
if (!
|
|
3345
|
+
if (!existsSync11(hostPath)) return `- hosts/${host}.json missing`;
|
|
3037
3346
|
return "- partial state (unknown gap)";
|
|
3038
3347
|
}
|
|
3039
3348
|
|
|
@@ -3050,15 +3359,15 @@ function reportHostAndPaths(section2) {
|
|
|
3050
3359
|
if (isOverrideActive()) {
|
|
3051
3360
|
addItem(section2, `${dim(infoGlyph)} NOMAD_REPO: ${blue(repo)}`);
|
|
3052
3361
|
}
|
|
3053
|
-
addItem(section2, `${
|
|
3362
|
+
addItem(section2, `${existsSync12(repo) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(repo)}`);
|
|
3054
3363
|
addItem(
|
|
3055
3364
|
section2,
|
|
3056
|
-
`${
|
|
3365
|
+
`${existsSync12(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
|
|
3057
3366
|
);
|
|
3058
3367
|
}
|
|
3059
3368
|
function pathMapHostKeys() {
|
|
3060
|
-
const mapPath =
|
|
3061
|
-
if (!
|
|
3369
|
+
const mapPath = join16(repoHome(), "path-map.json");
|
|
3370
|
+
if (!existsSync12(mapPath)) return /* @__PURE__ */ new Set();
|
|
3062
3371
|
let raw;
|
|
3063
3372
|
try {
|
|
3064
3373
|
raw = readJson(mapPath);
|
|
@@ -3075,7 +3384,7 @@ function pathMapHostKeys() {
|
|
|
3075
3384
|
function hostOverrideLabels() {
|
|
3076
3385
|
let entries;
|
|
3077
3386
|
try {
|
|
3078
|
-
entries =
|
|
3387
|
+
entries = readdirSync5(join16(repoHome(), "hosts"));
|
|
3079
3388
|
} catch {
|
|
3080
3389
|
return /* @__PURE__ */ new Set();
|
|
3081
3390
|
}
|
|
@@ -3116,42 +3425,12 @@ function reportRepoState(section2) {
|
|
|
3116
3425
|
}
|
|
3117
3426
|
}
|
|
3118
3427
|
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
|
-
};
|
|
3428
|
+
return existsSync12(join16(repoHome(), "shared", name));
|
|
3150
3429
|
}
|
|
3151
3430
|
function classifySharedLink(name, p) {
|
|
3152
3431
|
let stat;
|
|
3153
3432
|
try {
|
|
3154
|
-
stat =
|
|
3433
|
+
stat = lstatSync10(p);
|
|
3155
3434
|
} catch (err) {
|
|
3156
3435
|
const code = err.code;
|
|
3157
3436
|
if (code === "ENOENT") {
|
|
@@ -3197,7 +3476,7 @@ function classifySymlinkTarget(name, p) {
|
|
|
3197
3476
|
function reportSharedLinks(section2, map) {
|
|
3198
3477
|
const claude = claudeHome();
|
|
3199
3478
|
for (const name of allSharedLinks(map, { quiet: true })) {
|
|
3200
|
-
const p =
|
|
3479
|
+
const p = join16(claude, name);
|
|
3201
3480
|
const { line, fail: fail2, children } = classifySharedLink(name, p);
|
|
3202
3481
|
addItem(section2, line);
|
|
3203
3482
|
if (fail2) process.exitCode = 1;
|
|
@@ -3209,10 +3488,10 @@ function reportSharedLinks(section2, map) {
|
|
|
3209
3488
|
function reportDroppedNamesMigration(section2) {
|
|
3210
3489
|
const claude = claudeHome();
|
|
3211
3490
|
for (const name of GSD_DROPPED_NAMES) {
|
|
3212
|
-
const p =
|
|
3491
|
+
const p = join16(claude, name);
|
|
3213
3492
|
let stat;
|
|
3214
3493
|
try {
|
|
3215
|
-
stat =
|
|
3494
|
+
stat = lstatSync10(p);
|
|
3216
3495
|
} catch {
|
|
3217
3496
|
continue;
|
|
3218
3497
|
}
|
|
@@ -3227,11 +3506,11 @@ function reportDroppedNamesMigration(section2) {
|
|
|
3227
3506
|
// src/commands.doctor.checks.settings.ts
|
|
3228
3507
|
init_color();
|
|
3229
3508
|
init_config();
|
|
3230
|
-
import { existsSync as
|
|
3231
|
-
import { join as
|
|
3509
|
+
import { existsSync as existsSync13, readdirSync as readdirSync6 } from "node:fs";
|
|
3510
|
+
import { join as join17 } from "node:path";
|
|
3232
3511
|
function loadBaseSettings(section2) {
|
|
3233
|
-
const basePath =
|
|
3234
|
-
if (!
|
|
3512
|
+
const basePath = join17(repoHome(), "shared", "settings.base.json");
|
|
3513
|
+
if (!existsSync13(basePath)) {
|
|
3235
3514
|
addItem(section2, `${red(failGlyph)} shared/settings.base.json missing at ${blue(basePath)}`);
|
|
3236
3515
|
process.exitCode = 1;
|
|
3237
3516
|
return null;
|
|
@@ -3239,8 +3518,8 @@ function loadBaseSettings(section2) {
|
|
|
3239
3518
|
return readJsonSafe(basePath, basePath, section2);
|
|
3240
3519
|
}
|
|
3241
3520
|
function loadAndReportSettings(section2) {
|
|
3242
|
-
const settingsPath =
|
|
3243
|
-
if (!
|
|
3521
|
+
const settingsPath = join17(claudeHome(), "settings.json");
|
|
3522
|
+
if (!existsSync13(settingsPath)) return null;
|
|
3244
3523
|
const settings = readJsonSafe(settingsPath, settingsPath, section2);
|
|
3245
3524
|
if (settings === null) return null;
|
|
3246
3525
|
const unknownKeys = Object.keys(settings).filter((k) => !KNOWN_SETTINGS_KEYS.has(k));
|
|
@@ -3256,13 +3535,13 @@ function loadAndReportSettings(section2) {
|
|
|
3256
3535
|
}
|
|
3257
3536
|
function reportHostOverrides(section2, base, settings) {
|
|
3258
3537
|
const repo = repoHome();
|
|
3259
|
-
const hostFile =
|
|
3538
|
+
const hostFile = join17(repo, "hosts", `${HOST}.json`);
|
|
3260
3539
|
let drift = [];
|
|
3261
3540
|
if (base !== null && settings !== null) {
|
|
3262
3541
|
const baseKeys = new Set(Object.keys(base));
|
|
3263
3542
|
drift = Object.keys(settings).filter((k) => !baseKeys.has(k));
|
|
3264
3543
|
}
|
|
3265
|
-
if (
|
|
3544
|
+
if (existsSync13(hostFile)) {
|
|
3266
3545
|
if (readJsonSafe(hostFile, hostFile, section2) !== null) {
|
|
3267
3546
|
addItem(section2, `${green(okGlyph)} host overrides: ${blue(hostFile)}`);
|
|
3268
3547
|
}
|
|
@@ -3271,9 +3550,9 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
3271
3550
|
section2,
|
|
3272
3551
|
`${red(failGlyph)} no hosts/${HOST}.json AND settings.json has unbased keys ${JSON.stringify(drift)}`
|
|
3273
3552
|
);
|
|
3274
|
-
const hostsDir =
|
|
3275
|
-
if (
|
|
3276
|
-
const cands =
|
|
3553
|
+
const hostsDir = join17(repo, "hosts");
|
|
3554
|
+
if (existsSync13(hostsDir)) {
|
|
3555
|
+
const cands = readdirSync6(hostsDir).filter((f) => f.endsWith(".json"));
|
|
3277
3556
|
if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
|
|
3278
3557
|
}
|
|
3279
3558
|
process.exitCode = 1;
|
|
@@ -3289,8 +3568,8 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
3289
3568
|
init_color();
|
|
3290
3569
|
init_config();
|
|
3291
3570
|
init_config_sharedDirs_guard();
|
|
3292
|
-
import { existsSync as
|
|
3293
|
-
import { join as
|
|
3571
|
+
import { existsSync as existsSync14, lstatSync as lstatSync11, readdirSync as readdirSync7, realpathSync as realpathSync2 } from "node:fs";
|
|
3572
|
+
import { join as join18, sep as sep4 } from "node:path";
|
|
3294
3573
|
init_utils_json();
|
|
3295
3574
|
function reportMappedProjects(section2, map) {
|
|
3296
3575
|
const mapped = Object.entries(map.projects).filter(([, hosts]) => hosts[HOST]);
|
|
@@ -3300,11 +3579,11 @@ function reportMappedProjects(section2, map) {
|
|
|
3300
3579
|
}
|
|
3301
3580
|
}
|
|
3302
3581
|
function reportUnmappedProjects(section2, map) {
|
|
3303
|
-
const localProjects =
|
|
3304
|
-
if (!
|
|
3582
|
+
const localProjects = join18(claudeHome(), "projects");
|
|
3583
|
+
if (!existsSync14(localProjects)) return;
|
|
3305
3584
|
let localDirs;
|
|
3306
3585
|
try {
|
|
3307
|
-
localDirs =
|
|
3586
|
+
localDirs = readdirSync7(localProjects);
|
|
3308
3587
|
} catch {
|
|
3309
3588
|
return;
|
|
3310
3589
|
}
|
|
@@ -3322,7 +3601,7 @@ function reportCurrentHostPathsMissing(section2, map) {
|
|
|
3322
3601
|
for (const [name, hosts] of Object.entries(map.projects)) {
|
|
3323
3602
|
const abspath = hosts[HOST];
|
|
3324
3603
|
if (!abspath || abspath === "TBD") continue;
|
|
3325
|
-
if (!
|
|
3604
|
+
if (!existsSync14(abspath)) {
|
|
3326
3605
|
addItem(
|
|
3327
3606
|
section2,
|
|
3328
3607
|
`${yellow(warnGlyph)} path-map: ${name} local path missing on ${HOST}: ${blue(abspath)}`
|
|
@@ -3332,7 +3611,7 @@ function reportCurrentHostPathsMissing(section2, map) {
|
|
|
3332
3611
|
}
|
|
3333
3612
|
function hasSharedLeftover(entry) {
|
|
3334
3613
|
try {
|
|
3335
|
-
return
|
|
3614
|
+
return lstatSync11(join18(repoHome(), "shared", entry), { throwIfNoEntry: false }) !== void 0;
|
|
3336
3615
|
} catch {
|
|
3337
3616
|
return false;
|
|
3338
3617
|
}
|
|
@@ -3342,10 +3621,10 @@ var PROBEABLE_REASONS = /* @__PURE__ */ new Set([
|
|
|
3342
3621
|
"secret-shaped"
|
|
3343
3622
|
]);
|
|
3344
3623
|
function classifyLocalLink(entry) {
|
|
3345
|
-
const linkPath =
|
|
3624
|
+
const linkPath = join18(claudeHome(), entry);
|
|
3346
3625
|
let stat;
|
|
3347
3626
|
try {
|
|
3348
|
-
stat =
|
|
3627
|
+
stat = lstatSync11(linkPath, { throwIfNoEntry: false });
|
|
3349
3628
|
} catch {
|
|
3350
3629
|
return "absent";
|
|
3351
3630
|
}
|
|
@@ -3358,7 +3637,7 @@ function classifyLocalLink(entry) {
|
|
|
3358
3637
|
}
|
|
3359
3638
|
let root;
|
|
3360
3639
|
try {
|
|
3361
|
-
root = realpathSync2(
|
|
3640
|
+
root = realpathSync2(join18(repoHome(), "shared"));
|
|
3362
3641
|
} catch {
|
|
3363
3642
|
return "foreign";
|
|
3364
3643
|
}
|
|
@@ -3442,8 +3721,8 @@ function reportPathCollisions(section2, map) {
|
|
|
3442
3721
|
else addItem(section2, `${green(okGlyph)} path-encoding: no collisions`);
|
|
3443
3722
|
}
|
|
3444
3723
|
function reportPathMap(section2) {
|
|
3445
|
-
const mapPath =
|
|
3446
|
-
if (!
|
|
3724
|
+
const mapPath = join18(repoHome(), "path-map.json");
|
|
3725
|
+
if (!existsSync14(mapPath)) {
|
|
3447
3726
|
addItem(section2, `${red(failGlyph)} path-map.json missing at ${blue(mapPath)}`);
|
|
3448
3727
|
process.exitCode = 1;
|
|
3449
3728
|
return;
|
|
@@ -3473,8 +3752,8 @@ function reportNeverSync(section2) {
|
|
|
3473
3752
|
|
|
3474
3753
|
// src/commands.doctor.checks.skills.ts
|
|
3475
3754
|
init_color();
|
|
3476
|
-
import { existsSync as
|
|
3477
|
-
import { join as
|
|
3755
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
3756
|
+
import { join as join19 } from "node:path";
|
|
3478
3757
|
init_config();
|
|
3479
3758
|
function stripSideIndicator(line) {
|
|
3480
3759
|
if (line.endsWith(" (local only)")) return line.slice(0, -" (local only)".length);
|
|
@@ -3483,24 +3762,24 @@ function stripSideIndicator(line) {
|
|
|
3483
3762
|
}
|
|
3484
3763
|
function isGsdDiffLine(line, localBase, sharedBase) {
|
|
3485
3764
|
const bare = stripSideIndicator(line);
|
|
3486
|
-
let
|
|
3765
|
+
let relative12;
|
|
3487
3766
|
if (bare.startsWith(localBase + "/")) {
|
|
3488
|
-
|
|
3767
|
+
relative12 = bare.slice(localBase.length + 1);
|
|
3489
3768
|
} else if (bare.startsWith(sharedBase + "/")) {
|
|
3490
|
-
|
|
3769
|
+
relative12 = bare.slice(sharedBase.length + 1);
|
|
3491
3770
|
} else {
|
|
3492
|
-
|
|
3771
|
+
relative12 = bare;
|
|
3493
3772
|
}
|
|
3494
|
-
return
|
|
3773
|
+
return relative12.split("/")[0].startsWith(GSD_PREFIX);
|
|
3495
3774
|
}
|
|
3496
3775
|
function reportSkillsDivergence(section2) {
|
|
3497
|
-
const sharedSkills =
|
|
3498
|
-
const localSkills =
|
|
3499
|
-
if (!
|
|
3776
|
+
const sharedSkills = join19(repoHome(), "shared", "skills");
|
|
3777
|
+
const localSkills = join19(claudeHome(), "skills");
|
|
3778
|
+
if (!existsSync15(sharedSkills)) {
|
|
3500
3779
|
addItem(section2, `${dim(infoGlyph)} skills: no shared/skills/ to compare`);
|
|
3501
3780
|
return;
|
|
3502
3781
|
}
|
|
3503
|
-
if (!
|
|
3782
|
+
if (!existsSync15(localSkills)) {
|
|
3504
3783
|
addItem(section2, `${dim(infoGlyph)} skills: no local skills/ to compare`);
|
|
3505
3784
|
return;
|
|
3506
3785
|
}
|
|
@@ -3523,8 +3802,8 @@ function reportSkillsDivergence(section2) {
|
|
|
3523
3802
|
init_color();
|
|
3524
3803
|
init_config();
|
|
3525
3804
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
3526
|
-
import { existsSync as
|
|
3527
|
-
import { join as
|
|
3805
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
3806
|
+
import { join as join23, relative as relative5 } from "node:path";
|
|
3528
3807
|
init_commands_pull_wedge();
|
|
3529
3808
|
init_push_checks();
|
|
3530
3809
|
init_utils();
|
|
@@ -3547,11 +3826,11 @@ function reportGitleaksProbe(section2) {
|
|
|
3547
3826
|
}
|
|
3548
3827
|
function reportGitlinks(section2) {
|
|
3549
3828
|
const repo = repoHome();
|
|
3550
|
-
const sharedDir =
|
|
3551
|
-
if (
|
|
3829
|
+
const sharedDir = join23(repo, "shared");
|
|
3830
|
+
if (existsSync18(sharedDir)) {
|
|
3552
3831
|
const gitlinks = findGitlinks(sharedDir);
|
|
3553
3832
|
for (const p of gitlinks) {
|
|
3554
|
-
const rel =
|
|
3833
|
+
const rel = relative5(repo, p).replaceAll("\\", "/");
|
|
3555
3834
|
addItem(
|
|
3556
3835
|
section2,
|
|
3557
3836
|
`${red(failGlyph)} gitlink: ${blue(rel)} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`
|
|
@@ -3646,13 +3925,13 @@ function reportGitIdentity(section2) {
|
|
|
3646
3925
|
|
|
3647
3926
|
// src/commands.doctor.checks.backups.ts
|
|
3648
3927
|
init_color();
|
|
3649
|
-
import { existsSync as
|
|
3650
|
-
import { join as
|
|
3928
|
+
import { existsSync as existsSync19, lstatSync as lstatSync12, readdirSync as readdirSync9 } from "node:fs";
|
|
3929
|
+
import { join as join24 } from "node:path";
|
|
3651
3930
|
init_config();
|
|
3652
3931
|
var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
|
|
3653
3932
|
function safeReaddir(dir) {
|
|
3654
3933
|
try {
|
|
3655
|
-
return
|
|
3934
|
+
return readdirSync9(dir);
|
|
3656
3935
|
} catch {
|
|
3657
3936
|
return [];
|
|
3658
3937
|
}
|
|
@@ -3663,8 +3942,8 @@ var BYTES_PER_MB = 1024 * 1024;
|
|
|
3663
3942
|
function dirSizeBytes(dir) {
|
|
3664
3943
|
let bytes = 0;
|
|
3665
3944
|
for (const entry of safeReaddir(dir)) {
|
|
3666
|
-
const full =
|
|
3667
|
-
const st =
|
|
3945
|
+
const full = join24(dir, entry);
|
|
3946
|
+
const st = lstatSync12(full, { throwIfNoEntry: false });
|
|
3668
3947
|
if (!st) continue;
|
|
3669
3948
|
if (st.isSymbolicLink()) continue;
|
|
3670
3949
|
if (st.isDirectory()) bytes += dirSizeBytes(full);
|
|
@@ -3674,11 +3953,11 @@ function dirSizeBytes(dir) {
|
|
|
3674
3953
|
}
|
|
3675
3954
|
function totalSizeMb(backupBase2, dirs) {
|
|
3676
3955
|
let bytes = 0;
|
|
3677
|
-
for (const name of dirs) bytes += dirSizeBytes(
|
|
3956
|
+
for (const name of dirs) bytes += dirSizeBytes(join24(backupBase2, name));
|
|
3678
3957
|
return bytes / BYTES_PER_MB;
|
|
3679
3958
|
}
|
|
3680
3959
|
function reportBackupsCheck(section2, backupBase2 = backupBase()) {
|
|
3681
|
-
if (!
|
|
3960
|
+
if (!existsSync19(backupBase2)) return;
|
|
3682
3961
|
const dirs = safeReaddir(backupBase2).filter((n) => TS_SHAPE2.test(n));
|
|
3683
3962
|
const count = dirs.length;
|
|
3684
3963
|
const sizeMb = totalSizeMb(backupBase2, dirs);
|
|
@@ -3759,8 +4038,8 @@ function reportCheckRemote(section2) {
|
|
|
3759
4038
|
|
|
3760
4039
|
// src/commands.doctor.check-schema.ts
|
|
3761
4040
|
init_color();
|
|
3762
|
-
import { existsSync as
|
|
3763
|
-
import { join as
|
|
4041
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
4042
|
+
import { join as join25 } from "node:path";
|
|
3764
4043
|
init_config();
|
|
3765
4044
|
|
|
3766
4045
|
// src/http-fetch.ts
|
|
@@ -3797,8 +4076,8 @@ function fetchSchemaKeys() {
|
|
|
3797
4076
|
}
|
|
3798
4077
|
}
|
|
3799
4078
|
function reportCheckSchema(section2) {
|
|
3800
|
-
const settingsPath =
|
|
3801
|
-
if (!
|
|
4079
|
+
const settingsPath = join25(claudeHome(), "settings.json");
|
|
4080
|
+
if (!existsSync20(settingsPath)) {
|
|
3802
4081
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
|
|
3803
4082
|
return;
|
|
3804
4083
|
}
|
|
@@ -3828,9 +4107,9 @@ function reportCheckSchema(section2) {
|
|
|
3828
4107
|
init_color();
|
|
3829
4108
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
3830
4109
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
3831
|
-
import { existsSync as
|
|
4110
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync8, readdirSync as readdirSync12, rmSync as rmSync12 } from "node:fs";
|
|
3832
4111
|
import { homedir as homedir6 } from "node:os";
|
|
3833
|
-
import { join as
|
|
4112
|
+
import { join as join32 } from "node:path";
|
|
3834
4113
|
|
|
3835
4114
|
// src/commands.doctor.check-shared.memory.ts
|
|
3836
4115
|
init_color();
|
|
@@ -3838,7 +4117,7 @@ import { execFileSync as execFileSync9 } from "node:child_process";
|
|
|
3838
4117
|
import { randomBytes } from "node:crypto";
|
|
3839
4118
|
import { mkdirSync as mkdirSync4, rmSync as rmSync9, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3840
4119
|
import { homedir as homedir4 } from "node:os";
|
|
3841
|
-
import { dirname as
|
|
4120
|
+
import { dirname as dirname4, join as join27 } from "node:path";
|
|
3842
4121
|
init_config();
|
|
3843
4122
|
init_push_gitleaks();
|
|
3844
4123
|
init_utils_fs();
|
|
@@ -3871,8 +4150,8 @@ function buildMemoryScanTree(tmpRoot) {
|
|
|
3871
4150
|
incomplete = true;
|
|
3872
4151
|
continue;
|
|
3873
4152
|
}
|
|
3874
|
-
const dest =
|
|
3875
|
-
mkdirSync4(
|
|
4153
|
+
const dest = join27(tmpRoot, rel);
|
|
4154
|
+
mkdirSync4(dirname4(dest), { recursive: true });
|
|
3876
4155
|
writeFileSync4(dest, blob);
|
|
3877
4156
|
logicals.add(m[1]);
|
|
3878
4157
|
}
|
|
@@ -3890,10 +4169,10 @@ function reportMemoryFindings(section2, findings) {
|
|
|
3890
4169
|
function reportCommittedMemory(section2) {
|
|
3891
4170
|
let tmpRoot;
|
|
3892
4171
|
try {
|
|
3893
|
-
const cacheDir =
|
|
4172
|
+
const cacheDir = join27(homedir4(), ".cache", "claude-nomad");
|
|
3894
4173
|
mkdirSync4(cacheDir, { recursive: true });
|
|
3895
4174
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
3896
|
-
tmpRoot =
|
|
4175
|
+
tmpRoot = join27(cacheDir, `check-shared-memory-tree-${stamp}`);
|
|
3897
4176
|
mkdirSync4(tmpRoot, { recursive: true, mode: 448 });
|
|
3898
4177
|
const { staged, incomplete } = buildMemoryScanTree(tmpRoot);
|
|
3899
4178
|
if (incomplete) {
|
|
@@ -3940,12 +4219,12 @@ function reportCommittedMemory(section2) {
|
|
|
3940
4219
|
|
|
3941
4220
|
// src/commands.doctor.check-shared.scan.ts
|
|
3942
4221
|
init_color();
|
|
3943
|
-
import { join as
|
|
4222
|
+
import { join as join28 } from "node:path";
|
|
3944
4223
|
init_config();
|
|
3945
4224
|
init_push_gitleaks();
|
|
3946
4225
|
function scrubPath(logical, sid, logicalToEncoded) {
|
|
3947
4226
|
const encoded = logicalToEncoded.get(logical) ?? logical;
|
|
3948
|
-
return
|
|
4227
|
+
return join28(claudeHome(), "projects", encoded, `${sid}.jsonl`);
|
|
3949
4228
|
}
|
|
3950
4229
|
function reportSessionFindings(section2, bySession) {
|
|
3951
4230
|
for (const [sid, counts] of bySession) {
|
|
@@ -4034,7 +4313,7 @@ import { execFileSync as execFileSync10 } from "node:child_process";
|
|
|
4034
4313
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
4035
4314
|
import { mkdirSync as mkdirSync5, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4036
4315
|
import { homedir as homedir5 } from "node:os";
|
|
4037
|
-
import { dirname as
|
|
4316
|
+
import { dirname as dirname5, join as join29 } from "node:path";
|
|
4038
4317
|
init_config();
|
|
4039
4318
|
init_push_gitleaks();
|
|
4040
4319
|
init_utils_fs();
|
|
@@ -4067,8 +4346,8 @@ function buildSkillScanTree(tmpRoot) {
|
|
|
4067
4346
|
incomplete = true;
|
|
4068
4347
|
continue;
|
|
4069
4348
|
}
|
|
4070
|
-
const dest =
|
|
4071
|
-
mkdirSync5(
|
|
4349
|
+
const dest = join29(tmpRoot, rel);
|
|
4350
|
+
mkdirSync5(dirname5(dest), { recursive: true });
|
|
4072
4351
|
writeFileSync5(dest, blob);
|
|
4073
4352
|
names.add(m[1]);
|
|
4074
4353
|
}
|
|
@@ -4090,10 +4369,10 @@ function reportSkillFindings(section2, findings) {
|
|
|
4090
4369
|
function reportCommittedSkills(section2) {
|
|
4091
4370
|
let tmpRoot;
|
|
4092
4371
|
try {
|
|
4093
|
-
const cacheDir =
|
|
4372
|
+
const cacheDir = join29(homedir5(), ".cache", "claude-nomad");
|
|
4094
4373
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4095
4374
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
4096
|
-
tmpRoot =
|
|
4375
|
+
tmpRoot = join29(cacheDir, `check-shared-skills-tree-${stamp}`);
|
|
4097
4376
|
mkdirSync5(tmpRoot, { recursive: true, mode: 448 });
|
|
4098
4377
|
const { staged, incomplete } = buildSkillScanTree(tmpRoot);
|
|
4099
4378
|
if (incomplete) {
|
|
@@ -4143,8 +4422,8 @@ init_config();
|
|
|
4143
4422
|
|
|
4144
4423
|
// src/remap.ts
|
|
4145
4424
|
init_config_sharedDirs_guard();
|
|
4146
|
-
import { cpSync as
|
|
4147
|
-
import { dirname as
|
|
4425
|
+
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";
|
|
4426
|
+
import { dirname as dirname7, join as join31, relative as relative6, sep as sep5 } from "node:path";
|
|
4148
4427
|
init_config();
|
|
4149
4428
|
|
|
4150
4429
|
// src/push-manifest.ts
|
|
@@ -4152,8 +4431,8 @@ init_config();
|
|
|
4152
4431
|
init_push_gitleaks_config();
|
|
4153
4432
|
init_utils_fs();
|
|
4154
4433
|
import { createHash } from "node:crypto";
|
|
4155
|
-
import { existsSync as
|
|
4156
|
-
import { dirname as
|
|
4434
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync6, readdirSync as readdirSync10, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
|
|
4435
|
+
import { dirname as dirname6, join as join30 } from "node:path";
|
|
4157
4436
|
function isChanged(prev, cur, hash) {
|
|
4158
4437
|
if (prev === void 0) return true;
|
|
4159
4438
|
if (prev.size !== cur.size) return true;
|
|
@@ -4193,8 +4472,8 @@ function hashFile(absPath) {
|
|
|
4193
4472
|
return createHash("sha256").update(readFileSync7(absPath)).digest("hex");
|
|
4194
4473
|
}
|
|
4195
4474
|
function enumerateDir(dir, results) {
|
|
4196
|
-
for (const entry of
|
|
4197
|
-
const fullPath =
|
|
4475
|
+
for (const entry of readdirSync10(dir)) {
|
|
4476
|
+
const fullPath = join30(dir, entry);
|
|
4198
4477
|
const st = statSync4(fullPath);
|
|
4199
4478
|
if (st.isDirectory()) {
|
|
4200
4479
|
enumerateDir(fullPath, results);
|
|
@@ -4205,8 +4484,8 @@ function enumerateDir(dir, results) {
|
|
|
4205
4484
|
}
|
|
4206
4485
|
function enumerateSourceFiles(projectDir) {
|
|
4207
4486
|
const results = [];
|
|
4208
|
-
for (const entry of
|
|
4209
|
-
const fullPath =
|
|
4487
|
+
for (const entry of readdirSync10(projectDir)) {
|
|
4488
|
+
const fullPath = join30(projectDir, entry);
|
|
4210
4489
|
const st = statSync4(fullPath);
|
|
4211
4490
|
if (st.isDirectory()) {
|
|
4212
4491
|
enumerateDir(fullPath, results);
|
|
@@ -4218,15 +4497,15 @@ function enumerateSourceFiles(projectDir) {
|
|
|
4218
4497
|
}
|
|
4219
4498
|
function fileIdentity(p) {
|
|
4220
4499
|
if (p === null) return "none::absent";
|
|
4221
|
-
if (!
|
|
4500
|
+
if (!existsSync21(p)) return `${p}::absent`;
|
|
4222
4501
|
return `${p}::${createHash("sha256").update(readFileSync7(p)).digest("hex")}`;
|
|
4223
4502
|
}
|
|
4224
4503
|
function computeConfigHash() {
|
|
4225
4504
|
const repo = repoHome();
|
|
4226
4505
|
const parts = [
|
|
4227
4506
|
fileIdentity(resolveTomlPath(repo)),
|
|
4228
|
-
fileIdentity(
|
|
4229
|
-
fileIdentity(
|
|
4507
|
+
fileIdentity(join30(repo, ".gitleaks.overlay.toml")),
|
|
4508
|
+
fileIdentity(join30(repo, ".gitleaksignore"))
|
|
4230
4509
|
];
|
|
4231
4510
|
return createHash("sha256").update(parts.join("\n")).digest("hex");
|
|
4232
4511
|
}
|
|
@@ -4246,7 +4525,7 @@ function readManifest(path) {
|
|
|
4246
4525
|
}
|
|
4247
4526
|
}
|
|
4248
4527
|
function writeManifest(path, manifest) {
|
|
4249
|
-
mkdirSync6(
|
|
4528
|
+
mkdirSync6(dirname6(path), { recursive: true });
|
|
4250
4529
|
writeJsonAtomic(path, manifest);
|
|
4251
4530
|
}
|
|
4252
4531
|
function buildManifest(files, scannerVersion, configHash) {
|
|
@@ -4261,7 +4540,7 @@ var TMP_SUFFIX = ".nomad-tmp";
|
|
|
4261
4540
|
function atomicMirror(src, dst, options) {
|
|
4262
4541
|
const tmp = `${dst}${TMP_SUFFIX}`;
|
|
4263
4542
|
rmSync11(tmp, { recursive: true, force: true });
|
|
4264
|
-
|
|
4543
|
+
cpSync5(src, tmp, options);
|
|
4265
4544
|
rmSync11(dst, { recursive: true, force: true });
|
|
4266
4545
|
renameAtomicRetry(tmp, dst);
|
|
4267
4546
|
}
|
|
@@ -4270,9 +4549,9 @@ function overlaySessionDir(src, dst) {
|
|
|
4270
4549
|
cpSyncGuarded(src, dst, void 0, "overlaySessionDir");
|
|
4271
4550
|
}
|
|
4272
4551
|
function copyFileAtomic(src, dst) {
|
|
4273
|
-
mkdirSync7(
|
|
4552
|
+
mkdirSync7(dirname7(dst), { recursive: true });
|
|
4274
4553
|
const tmp = `${dst}.tmp.${process.pid}`;
|
|
4275
|
-
|
|
4554
|
+
cpSync5(src, tmp, { force: true, preserveTimestamps: true });
|
|
4276
4555
|
renameAtomicRetry(tmp, dst);
|
|
4277
4556
|
}
|
|
4278
4557
|
function hasDeltaForDir(sel, localDir) {
|
|
@@ -4288,12 +4567,12 @@ function applySelective(sel, localDir, repoDst) {
|
|
|
4288
4567
|
const prefix = `${localDir}${sep5}`;
|
|
4289
4568
|
for (const src of sel.changed) {
|
|
4290
4569
|
if (!src.startsWith(prefix)) continue;
|
|
4291
|
-
copyFileAtomic(src,
|
|
4570
|
+
copyFileAtomic(src, join31(repoDst, relative6(localDir, src)));
|
|
4292
4571
|
}
|
|
4293
4572
|
for (const src of sel.deleted) {
|
|
4294
4573
|
if (!src.startsWith(prefix)) continue;
|
|
4295
|
-
const dst =
|
|
4296
|
-
if (
|
|
4574
|
+
const dst = join31(repoDst, relative6(localDir, src));
|
|
4575
|
+
if (existsSync22(dst)) rmSync11(dst);
|
|
4297
4576
|
}
|
|
4298
4577
|
}
|
|
4299
4578
|
function copyDirJsonlOnly(src, dst) {
|
|
@@ -4301,7 +4580,7 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
4301
4580
|
recursive: true,
|
|
4302
4581
|
force: true,
|
|
4303
4582
|
filter: (srcPath) => {
|
|
4304
|
-
const rel =
|
|
4583
|
+
const rel = relative6(src, srcPath);
|
|
4305
4584
|
if (rel === "") return true;
|
|
4306
4585
|
if (rel.split(sep5).length > 1) return true;
|
|
4307
4586
|
if (statSync5(srcPath).isDirectory()) return true;
|
|
@@ -4322,15 +4601,15 @@ function remapPull(ts, opts = {}) {
|
|
|
4322
4601
|
const wouldPull = [];
|
|
4323
4602
|
const repo = repoHome();
|
|
4324
4603
|
const claude = claudeHome();
|
|
4325
|
-
const mapPath =
|
|
4326
|
-
const repoProjects =
|
|
4327
|
-
if (!
|
|
4604
|
+
const mapPath = join31(repo, "path-map.json");
|
|
4605
|
+
const repoProjects = join31(repo, "shared", "projects");
|
|
4606
|
+
if (!existsSync22(mapPath) || !existsSync22(repoProjects)) {
|
|
4328
4607
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
4329
4608
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
4330
4609
|
return { unmapped: 0, pulled, wouldPull };
|
|
4331
4610
|
}
|
|
4332
4611
|
const map = readPathMap(mapPath);
|
|
4333
|
-
const localProjects =
|
|
4612
|
+
const localProjects = join31(claude, "projects");
|
|
4334
4613
|
if (!dryRun) mkdirSync7(localProjects, { recursive: true });
|
|
4335
4614
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
4336
4615
|
assertSafeLogical(logical);
|
|
@@ -4340,9 +4619,9 @@ function remapPull(ts, opts = {}) {
|
|
|
4340
4619
|
continue;
|
|
4341
4620
|
}
|
|
4342
4621
|
assertSafeLocalRoot(localPath, logical);
|
|
4343
|
-
const src =
|
|
4344
|
-
if (!
|
|
4345
|
-
const dst =
|
|
4622
|
+
const src = join31(repoProjects, logical);
|
|
4623
|
+
if (!existsSync22(src)) continue;
|
|
4624
|
+
const dst = join31(localProjects, encodePath(localPath));
|
|
4346
4625
|
if (dryRun) {
|
|
4347
4626
|
wouldPull.push(logical);
|
|
4348
4627
|
emitPreview(
|
|
@@ -4360,12 +4639,12 @@ function remapPull(ts, opts = {}) {
|
|
|
4360
4639
|
}
|
|
4361
4640
|
function countLocalOnly(src, dst) {
|
|
4362
4641
|
let count = 0;
|
|
4363
|
-
for (const name of
|
|
4364
|
-
const dstPath =
|
|
4365
|
-
const srcPath =
|
|
4366
|
-
if (
|
|
4642
|
+
for (const name of readdirSync11(dst)) {
|
|
4643
|
+
const dstPath = join31(dst, name);
|
|
4644
|
+
const srcPath = join31(src, name);
|
|
4645
|
+
if (lstatSync13(dstPath).isDirectory()) {
|
|
4367
4646
|
count += countLocalOnly(srcPath, dstPath);
|
|
4368
|
-
} else if (
|
|
4647
|
+
} else if (lstatSync13(srcPath, { throwIfNoEntry: false }) === void 0) {
|
|
4369
4648
|
count++;
|
|
4370
4649
|
}
|
|
4371
4650
|
}
|
|
@@ -4374,20 +4653,20 @@ function countLocalOnly(src, dst) {
|
|
|
4374
4653
|
function scanLocalOnly() {
|
|
4375
4654
|
const repo = repoHome();
|
|
4376
4655
|
const claude = claudeHome();
|
|
4377
|
-
const mapPath =
|
|
4378
|
-
const repoProjects =
|
|
4379
|
-
if (!
|
|
4656
|
+
const mapPath = join31(repo, "path-map.json");
|
|
4657
|
+
const repoProjects = join31(repo, "shared", "projects");
|
|
4658
|
+
if (!existsSync22(mapPath) || !existsSync22(repoProjects)) return 0;
|
|
4380
4659
|
const map = readPathMap(mapPath);
|
|
4381
|
-
const localProjects =
|
|
4660
|
+
const localProjects = join31(claude, "projects");
|
|
4382
4661
|
let count = 0;
|
|
4383
4662
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
4384
4663
|
assertSafeLogical(logical);
|
|
4385
4664
|
const localPath = hosts[HOST];
|
|
4386
4665
|
if (!localPath || localPath === "TBD") continue;
|
|
4387
4666
|
assertSafeLocalRoot(localPath, logical);
|
|
4388
|
-
const dst =
|
|
4389
|
-
if (!
|
|
4390
|
-
count += countLocalOnly(
|
|
4667
|
+
const dst = join31(localProjects, encodePath(localPath));
|
|
4668
|
+
if (!existsSync22(dst)) continue;
|
|
4669
|
+
count += countLocalOnly(join31(repoProjects, logical), dst);
|
|
4391
4670
|
}
|
|
4392
4671
|
return count;
|
|
4393
4672
|
}
|
|
@@ -4422,26 +4701,26 @@ function remapPush(ts, opts = {}) {
|
|
|
4422
4701
|
const wouldPush = [];
|
|
4423
4702
|
const repo = repoHome();
|
|
4424
4703
|
const claude = claudeHome();
|
|
4425
|
-
const mapPath =
|
|
4426
|
-
if (!
|
|
4704
|
+
const mapPath = join31(repo, "path-map.json");
|
|
4705
|
+
if (!existsSync22(mapPath)) {
|
|
4427
4706
|
log("no path-map.json; skipping session export");
|
|
4428
4707
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
4429
4708
|
}
|
|
4430
4709
|
const map = readPathMap(mapPath);
|
|
4431
|
-
const localProjects =
|
|
4432
|
-
const repoProjects =
|
|
4710
|
+
const localProjects = join31(claude, "projects");
|
|
4711
|
+
const repoProjects = join31(repo, "shared", "projects");
|
|
4433
4712
|
const reverse = buildReverseMap(map);
|
|
4434
|
-
if (!
|
|
4713
|
+
if (!existsSync22(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
4435
4714
|
if (!dryRun) mkdirSync7(repoProjects, { recursive: true });
|
|
4436
|
-
for (const dir of
|
|
4715
|
+
for (const dir of readdirSync11(localProjects)) {
|
|
4437
4716
|
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
4438
4717
|
const logical = reverse.get(dir);
|
|
4439
4718
|
if (!logical) {
|
|
4440
4719
|
unmapped++;
|
|
4441
4720
|
continue;
|
|
4442
4721
|
}
|
|
4443
|
-
const localDir =
|
|
4444
|
-
const repoDst =
|
|
4722
|
+
const localDir = join31(localProjects, dir);
|
|
4723
|
+
const repoDst = join31(repoProjects, logical);
|
|
4445
4724
|
if (skipForSelection(opts.selection, localDir)) continue;
|
|
4446
4725
|
if (dryRun) {
|
|
4447
4726
|
wouldPush.push(logical);
|
|
@@ -4465,8 +4744,8 @@ function buildScanTree(tmpRoot) {
|
|
|
4465
4744
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
4466
4745
|
let staged = 0;
|
|
4467
4746
|
const repo = repoHome();
|
|
4468
|
-
const mapPath =
|
|
4469
|
-
if (!
|
|
4747
|
+
const mapPath = join32(repo, "path-map.json");
|
|
4748
|
+
if (!existsSync23(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
4470
4749
|
let map;
|
|
4471
4750
|
try {
|
|
4472
4751
|
map = readJson(mapPath);
|
|
@@ -4483,12 +4762,12 @@ function buildScanTree(tmpRoot) {
|
|
|
4483
4762
|
if (!p || p === "TBD") continue;
|
|
4484
4763
|
reverse.set(encodePath(p), logical);
|
|
4485
4764
|
}
|
|
4486
|
-
const localProjects =
|
|
4487
|
-
if (!
|
|
4488
|
-
for (const dir of
|
|
4765
|
+
const localProjects = join32(claudeHome(), "projects");
|
|
4766
|
+
if (!existsSync23(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
4767
|
+
for (const dir of readdirSync12(localProjects)) {
|
|
4489
4768
|
const logical = reverse.get(dir);
|
|
4490
4769
|
if (!logical) continue;
|
|
4491
|
-
copyDirJsonlOnly(
|
|
4770
|
+
copyDirJsonlOnly(join32(localProjects, dir), join32(tmpRoot, "shared", "projects", logical));
|
|
4492
4771
|
logicalToEncoded.set(logical, dir);
|
|
4493
4772
|
staged++;
|
|
4494
4773
|
}
|
|
@@ -4518,11 +4797,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
4518
4797
|
return true;
|
|
4519
4798
|
}
|
|
4520
4799
|
function runLocalPreviewScan(section2) {
|
|
4521
|
-
const cacheDir =
|
|
4800
|
+
const cacheDir = join32(homedir6(), ".cache", "claude-nomad");
|
|
4522
4801
|
mkdirSync8(cacheDir, { recursive: true });
|
|
4523
4802
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes3(4).toString("hex")}`;
|
|
4524
|
-
const reportPath =
|
|
4525
|
-
const tmpRoot =
|
|
4803
|
+
const reportPath = join32(cacheDir, `check-shared-${stamp}.json`);
|
|
4804
|
+
const tmpRoot = join32(cacheDir, `check-shared-tree-${stamp}`);
|
|
4526
4805
|
try {
|
|
4527
4806
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
4528
4807
|
if (malformed) {
|
|
@@ -4555,8 +4834,8 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
4555
4834
|
|
|
4556
4835
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
4557
4836
|
init_color();
|
|
4558
|
-
import { existsSync as
|
|
4559
|
-
import { dirname as
|
|
4837
|
+
import { existsSync as existsSync24, readFileSync as readFileSync8, readdirSync as readdirSync13, realpathSync as realpathSync3 } from "node:fs";
|
|
4838
|
+
import { dirname as dirname8, extname, join as join33 } from "node:path";
|
|
4560
4839
|
init_config();
|
|
4561
4840
|
function typeFromPackageJson(pkgPath) {
|
|
4562
4841
|
try {
|
|
@@ -4576,11 +4855,11 @@ function effectiveType(hookPath) {
|
|
|
4576
4855
|
} catch {
|
|
4577
4856
|
return null;
|
|
4578
4857
|
}
|
|
4579
|
-
let dir =
|
|
4858
|
+
let dir = dirname8(real);
|
|
4580
4859
|
for (; ; ) {
|
|
4581
|
-
const pkg =
|
|
4582
|
-
if (
|
|
4583
|
-
const parent =
|
|
4860
|
+
const pkg = join33(dir, "package.json");
|
|
4861
|
+
if (existsSync24(pkg)) return typeFromPackageJson(pkg);
|
|
4862
|
+
const parent = dirname8(dir);
|
|
4584
4863
|
if (parent === dir) return "cjs";
|
|
4585
4864
|
dir = parent;
|
|
4586
4865
|
}
|
|
@@ -4608,7 +4887,7 @@ function remedy(family) {
|
|
|
4608
4887
|
}
|
|
4609
4888
|
function safeReaddir2(dir) {
|
|
4610
4889
|
try {
|
|
4611
|
-
return
|
|
4890
|
+
return readdirSync13(dir);
|
|
4612
4891
|
} catch {
|
|
4613
4892
|
return [];
|
|
4614
4893
|
}
|
|
@@ -4621,15 +4900,15 @@ function safeRead(path) {
|
|
|
4621
4900
|
}
|
|
4622
4901
|
}
|
|
4623
4902
|
function reportHookScopeCheck(section2) {
|
|
4624
|
-
const hooksDir =
|
|
4625
|
-
if (!
|
|
4903
|
+
const hooksDir = join33(claudeHome(), "hooks");
|
|
4904
|
+
if (!existsSync24(hooksDir)) {
|
|
4626
4905
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
4627
4906
|
return;
|
|
4628
4907
|
}
|
|
4629
4908
|
let anyWarn = false;
|
|
4630
4909
|
for (const name of safeReaddir2(hooksDir)) {
|
|
4631
4910
|
if (extname(name) !== ".js") continue;
|
|
4632
|
-
const abs =
|
|
4911
|
+
const abs = join33(hooksDir, name);
|
|
4633
4912
|
const eff = effectiveType(abs);
|
|
4634
4913
|
if (eff === null) continue;
|
|
4635
4914
|
const src = safeRead(abs);
|
|
@@ -4649,8 +4928,8 @@ function reportHookScopeCheck(section2) {
|
|
|
4649
4928
|
|
|
4650
4929
|
// src/commands.doctor.checks.hooks.ts
|
|
4651
4930
|
init_color();
|
|
4652
|
-
import { existsSync as
|
|
4653
|
-
import { join as
|
|
4931
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
4932
|
+
import { join as join34 } from "node:path";
|
|
4654
4933
|
init_config();
|
|
4655
4934
|
function expandHome(token) {
|
|
4656
4935
|
const h2 = home();
|
|
@@ -4693,7 +4972,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
4693
4972
|
for (const group of groups) {
|
|
4694
4973
|
for (const cmd of commandsFromGroup(group)) {
|
|
4695
4974
|
for (const resolved of claudePathsIn(cmd)) {
|
|
4696
|
-
if (
|
|
4975
|
+
if (existsSync25(resolved)) continue;
|
|
4697
4976
|
addItem(
|
|
4698
4977
|
section2,
|
|
4699
4978
|
`${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
|
|
@@ -4706,8 +4985,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
4706
4985
|
return anyFail;
|
|
4707
4986
|
}
|
|
4708
4987
|
function reportHooksTargetCheck(section2) {
|
|
4709
|
-
const settingsPath =
|
|
4710
|
-
if (!
|
|
4988
|
+
const settingsPath = join34(claudeHome(), "settings.json");
|
|
4989
|
+
if (!existsSync25(settingsPath)) {
|
|
4711
4990
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
4712
4991
|
return;
|
|
4713
4992
|
}
|
|
@@ -4730,12 +5009,12 @@ function reportHooksTargetCheck(section2) {
|
|
|
4730
5009
|
|
|
4731
5010
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
4732
5011
|
init_color();
|
|
4733
|
-
import { existsSync as
|
|
4734
|
-
import { join as
|
|
5012
|
+
import { existsSync as existsSync27, readFileSync as readFileSync9 } from "node:fs";
|
|
5013
|
+
import { join as join36 } from "node:path";
|
|
4735
5014
|
|
|
4736
5015
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
4737
|
-
import { closeSync as closeSync3, existsSync as
|
|
4738
|
-
import { dirname as
|
|
5016
|
+
import { closeSync as closeSync3, existsSync as existsSync26, openSync as openSync3, readSync, realpathSync as realpathSync4 } from "node:fs";
|
|
5017
|
+
import { dirname as dirname9, join as join35, resolve as resolve3 } from "node:path";
|
|
4739
5018
|
function suppressedRanges(src) {
|
|
4740
5019
|
const ranges = [];
|
|
4741
5020
|
const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
|
|
@@ -4766,13 +5045,13 @@ function topRelativeSpecifiers(src) {
|
|
|
4766
5045
|
return specifiers;
|
|
4767
5046
|
}
|
|
4768
5047
|
function specifierIsMissing(specifier, baseDir) {
|
|
4769
|
-
const base =
|
|
4770
|
-
if (
|
|
5048
|
+
const base = resolve3(baseDir, specifier);
|
|
5049
|
+
if (existsSync26(base)) return false;
|
|
4771
5050
|
for (const ext of [".js", ".cjs", ".mjs"]) {
|
|
4772
|
-
if (
|
|
5051
|
+
if (existsSync26(base + ext)) return false;
|
|
4773
5052
|
}
|
|
4774
5053
|
for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
|
|
4775
|
-
if (
|
|
5054
|
+
if (existsSync26(join35(base, idx))) return false;
|
|
4776
5055
|
}
|
|
4777
5056
|
return true;
|
|
4778
5057
|
}
|
|
@@ -4798,7 +5077,7 @@ function relativeRequireTargetsBroken(scriptPath) {
|
|
|
4798
5077
|
}
|
|
4799
5078
|
const specifiers = topRelativeSpecifiers(raw);
|
|
4800
5079
|
if (specifiers.length === 0) return false;
|
|
4801
|
-
const baseDir =
|
|
5080
|
+
const baseDir = dirname9(realPath);
|
|
4802
5081
|
for (const spec of specifiers) {
|
|
4803
5082
|
if (specifierIsMissing(spec, baseDir)) return true;
|
|
4804
5083
|
}
|
|
@@ -4827,8 +5106,8 @@ function commandTokens(command) {
|
|
|
4827
5106
|
return tokens;
|
|
4828
5107
|
}
|
|
4829
5108
|
function readPathMapSafe() {
|
|
4830
|
-
const mapPath =
|
|
4831
|
-
if (!
|
|
5109
|
+
const mapPath = join36(repoHome(), "path-map.json");
|
|
5110
|
+
if (!existsSync27(mapPath)) return { projects: {} };
|
|
4832
5111
|
try {
|
|
4833
5112
|
return JSON.parse(readFileSync9(mapPath, "utf8"));
|
|
4834
5113
|
} catch {
|
|
@@ -4902,8 +5181,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
4902
5181
|
return anyWarn;
|
|
4903
5182
|
}
|
|
4904
5183
|
function reportPreserveSymlinksCheck(section2) {
|
|
4905
|
-
const settingsPath =
|
|
4906
|
-
if (!
|
|
5184
|
+
const settingsPath = join36(claudeHome(), "settings.json");
|
|
5185
|
+
if (!existsSync27(settingsPath)) {
|
|
4907
5186
|
addItem(
|
|
4908
5187
|
section2,
|
|
4909
5188
|
`${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
|
|
@@ -4936,8 +5215,8 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
4936
5215
|
|
|
4937
5216
|
// src/commands.doctor.checks.settings-drift.ts
|
|
4938
5217
|
init_color();
|
|
4939
|
-
import { existsSync as
|
|
4940
|
-
import { join as
|
|
5218
|
+
import { existsSync as existsSync28, readFileSync as readFileSync10 } from "node:fs";
|
|
5219
|
+
import { join as join37 } from "node:path";
|
|
4941
5220
|
init_config();
|
|
4942
5221
|
init_utils_json();
|
|
4943
5222
|
function diffMergedSettings(merged, settings) {
|
|
@@ -4955,7 +5234,7 @@ function tryReadJson(filePath) {
|
|
|
4955
5234
|
}
|
|
4956
5235
|
}
|
|
4957
5236
|
function reportHooksBaseSelfCleanNote(section2) {
|
|
4958
|
-
const basePath =
|
|
5237
|
+
const basePath = join37(repoHome(), "shared", "settings.base.json");
|
|
4959
5238
|
const base = tryReadJson(basePath);
|
|
4960
5239
|
if (base === null) return;
|
|
4961
5240
|
if (!baseHasGsdHookEntries(base)) return;
|
|
@@ -4968,14 +5247,14 @@ function reportSettingsDriftCheck(section2) {
|
|
|
4968
5247
|
const claude = claudeHome();
|
|
4969
5248
|
const repo = repoHome();
|
|
4970
5249
|
const host = HOST;
|
|
4971
|
-
const settingsPath =
|
|
4972
|
-
const basePath =
|
|
4973
|
-
const hostPath =
|
|
4974
|
-
if (!
|
|
5250
|
+
const settingsPath = join37(claude, "settings.json");
|
|
5251
|
+
const basePath = join37(repo, "shared", "settings.base.json");
|
|
5252
|
+
const hostPath = join37(repo, "hosts", `${host}.json`);
|
|
5253
|
+
if (!existsSync28(settingsPath)) {
|
|
4975
5254
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
4976
5255
|
return;
|
|
4977
5256
|
}
|
|
4978
|
-
if (!
|
|
5257
|
+
if (!existsSync28(basePath)) {
|
|
4979
5258
|
addItem(
|
|
4980
5259
|
section2,
|
|
4981
5260
|
`${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
|
|
@@ -4994,7 +5273,7 @@ function reportSettingsDriftCheck(section2) {
|
|
|
4994
5273
|
if (settings === null) {
|
|
4995
5274
|
return;
|
|
4996
5275
|
}
|
|
4997
|
-
const hostExists =
|
|
5276
|
+
const hostExists = existsSync28(hostPath);
|
|
4998
5277
|
const hostObj = hostExists ? tryReadJson(hostPath) : null;
|
|
4999
5278
|
if (hostExists && hostObj === null) {
|
|
5000
5279
|
addItem(
|
|
@@ -5211,15 +5490,15 @@ function reportSyncModality(section2) {
|
|
|
5211
5490
|
// src/commands.doctor.checks.crlf.ts
|
|
5212
5491
|
init_color();
|
|
5213
5492
|
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
5214
|
-
import { existsSync as
|
|
5215
|
-
import { join as
|
|
5493
|
+
import { existsSync as existsSync29, readFileSync as readFileSync13 } from "node:fs";
|
|
5494
|
+
import { join as join38 } from "node:path";
|
|
5216
5495
|
init_config();
|
|
5217
5496
|
var PROBE_TIMEOUT_MS2 = 3e3;
|
|
5218
5497
|
var GUARD_LINE = /^\*\s+-text\b/;
|
|
5219
5498
|
function hasGitattributesGuard(repo) {
|
|
5220
5499
|
try {
|
|
5221
|
-
const path =
|
|
5222
|
-
if (!
|
|
5500
|
+
const path = join38(repo, ".gitattributes");
|
|
5501
|
+
if (!existsSync29(path)) return false;
|
|
5223
5502
|
const content = readFileSync13(path, "utf8");
|
|
5224
5503
|
return content.split("\n").map((line) => line.trim()).some((line) => line !== "" && !line.startsWith("#") && GUARD_LINE.test(line));
|
|
5225
5504
|
} catch {
|
|
@@ -5260,34 +5539,34 @@ function reportCrlfGuardCheck(section2, run = execFileSync13) {
|
|
|
5260
5539
|
|
|
5261
5540
|
// src/spinner.ts
|
|
5262
5541
|
init_color();
|
|
5263
|
-
import { existsSync as
|
|
5542
|
+
import { existsSync as existsSync34 } from "node:fs";
|
|
5264
5543
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
5265
5544
|
import { Worker } from "node:worker_threads";
|
|
5266
5545
|
|
|
5267
5546
|
// src/commands.push.recovery.ts
|
|
5268
5547
|
init_config();
|
|
5269
5548
|
import { readFileSync as readFileSync18, rmSync as rmSync15, writeFileSync as writeFileSync9 } from "node:fs";
|
|
5270
|
-
import { join as
|
|
5549
|
+
import { join as join46 } from "node:path";
|
|
5271
5550
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
5272
5551
|
|
|
5273
5552
|
// src/commands.push.recovery.actions.ts
|
|
5274
5553
|
init_config();
|
|
5275
5554
|
import { readFileSync as readFileSync17 } from "node:fs";
|
|
5276
|
-
import { isAbsolute as isAbsolute2, resolve as
|
|
5555
|
+
import { isAbsolute as isAbsolute2, resolve as resolve4, sep as sep9 } from "node:path";
|
|
5277
5556
|
|
|
5278
5557
|
// src/commands.push.recovery.memory.ts
|
|
5279
5558
|
init_config();
|
|
5280
5559
|
init_config_sharedDirs_guard();
|
|
5281
5560
|
import {
|
|
5282
|
-
cpSync as
|
|
5283
|
-
lstatSync as
|
|
5561
|
+
cpSync as cpSync6,
|
|
5562
|
+
lstatSync as lstatSync14,
|
|
5284
5563
|
mkdirSync as mkdirSync9,
|
|
5285
5564
|
readFileSync as readFileSync14,
|
|
5286
5565
|
realpathSync as realpathSync5,
|
|
5287
5566
|
statSync as statSync6,
|
|
5288
5567
|
writeFileSync as writeFileSync6
|
|
5289
5568
|
} from "node:fs";
|
|
5290
|
-
import { dirname as
|
|
5569
|
+
import { dirname as dirname10, join as join39, sep as sep6 } from "node:path";
|
|
5291
5570
|
init_push_gitleaks_scan();
|
|
5292
5571
|
|
|
5293
5572
|
// src/rel-path-guard.ts
|
|
@@ -5316,11 +5595,11 @@ function resolveMemoryLocalPath(logical, relPath, map) {
|
|
|
5316
5595
|
if (!isSafeRelPath(relPath)) return null;
|
|
5317
5596
|
const abs = map.projects[logical]?.[HOST];
|
|
5318
5597
|
if (abs === void 0) return null;
|
|
5319
|
-
const memoryRoot =
|
|
5320
|
-
const localPath =
|
|
5598
|
+
const memoryRoot = join39(claudeHome(), "projects", encodePath(abs), "memory");
|
|
5599
|
+
const localPath = join39(memoryRoot, ...relPath.split("/"));
|
|
5321
5600
|
if (localPath !== memoryRoot && !localPath.startsWith(memoryRoot + sep6)) return null;
|
|
5322
5601
|
try {
|
|
5323
|
-
if (
|
|
5602
|
+
if (lstatSync14(localPath).isSymbolicLink()) return null;
|
|
5324
5603
|
const realLocal = realpathSync5(localPath);
|
|
5325
5604
|
const realRoot = realpathSync5(memoryRoot);
|
|
5326
5605
|
if (!realLocal.startsWith(realRoot + sep6)) return null;
|
|
@@ -5371,29 +5650,29 @@ function applyMemoryRedact(f, ts, map, scan = scanFile) {
|
|
|
5371
5650
|
);
|
|
5372
5651
|
}
|
|
5373
5652
|
writeFileSync6(localPath, after, "utf8");
|
|
5374
|
-
const dest =
|
|
5375
|
-
mkdirSync9(
|
|
5376
|
-
|
|
5653
|
+
const dest = join39(repoHome(), "shared", "projects", logical, "memory", ...relPath.split("/"));
|
|
5654
|
+
mkdirSync9(dirname10(dest), { recursive: true });
|
|
5655
|
+
cpSync6(localPath, dest, { force: true });
|
|
5377
5656
|
return true;
|
|
5378
5657
|
}
|
|
5379
5658
|
|
|
5380
5659
|
// src/commands.push.recovery.skills.ts
|
|
5381
5660
|
init_config();
|
|
5382
5661
|
import {
|
|
5383
|
-
cpSync as
|
|
5384
|
-
lstatSync as
|
|
5662
|
+
cpSync as cpSync7,
|
|
5663
|
+
lstatSync as lstatSync16,
|
|
5385
5664
|
mkdirSync as mkdirSync11,
|
|
5386
5665
|
readFileSync as readFileSync15,
|
|
5387
5666
|
realpathSync as realpathSync6,
|
|
5388
5667
|
statSync as statSync7,
|
|
5389
5668
|
writeFileSync as writeFileSync7
|
|
5390
5669
|
} from "node:fs";
|
|
5391
|
-
import { dirname as
|
|
5670
|
+
import { dirname as dirname11, join as join41, sep as sep7 } from "node:path";
|
|
5392
5671
|
|
|
5393
5672
|
// src/skills-sync.ts
|
|
5394
5673
|
init_config();
|
|
5395
|
-
import { existsSync as
|
|
5396
|
-
import { join as
|
|
5674
|
+
import { existsSync as existsSync30, lstatSync as lstatSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync14, rmSync as rmSync13 } from "node:fs";
|
|
5675
|
+
import { join as join40 } from "node:path";
|
|
5397
5676
|
|
|
5398
5677
|
// src/skills-sync.tracked.ts
|
|
5399
5678
|
init_utils();
|
|
@@ -5417,7 +5696,7 @@ function isSkillExcluded(name) {
|
|
|
5417
5696
|
return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
|
|
5418
5697
|
}
|
|
5419
5698
|
function copySkillsPush(src, dst) {
|
|
5420
|
-
const srcNames =
|
|
5699
|
+
const srcNames = readdirSync14(src, { encoding: "utf8" });
|
|
5421
5700
|
const blockSet = /* @__PURE__ */ new Set([
|
|
5422
5701
|
...srcNames.filter((n) => isGsdOwned(n)),
|
|
5423
5702
|
...ALWAYS_NEVER_SYNC
|
|
@@ -5428,11 +5707,11 @@ function copySkillsPull(src, dst, isRootPreserved) {
|
|
|
5428
5707
|
copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded, isRootPreserved);
|
|
5429
5708
|
}
|
|
5430
5709
|
function syncSkillsPull(ts, prePostHeads) {
|
|
5431
|
-
const sharedSkills =
|
|
5432
|
-
if (!
|
|
5433
|
-
const localSkills =
|
|
5710
|
+
const sharedSkills = join40(repoHome(), "shared", "skills");
|
|
5711
|
+
if (!existsSync30(sharedSkills)) return;
|
|
5712
|
+
const localSkills = join40(claudeHome(), "skills");
|
|
5434
5713
|
backupBeforeWrite(localSkills, ts);
|
|
5435
|
-
const dstStat =
|
|
5714
|
+
const dstStat = lstatSync15(localSkills, { throwIfNoEntry: false });
|
|
5436
5715
|
if (dstStat?.isSymbolicLink() === true) {
|
|
5437
5716
|
rmSync13(localSkills, { recursive: true, force: true });
|
|
5438
5717
|
mkdirSync10(localSkills, { recursive: true });
|
|
@@ -5441,11 +5720,11 @@ function syncSkillsPull(ts, prePostHeads) {
|
|
|
5441
5720
|
copySkillsPull(sharedSkills, localSkills, (name) => !tracked.has(name));
|
|
5442
5721
|
}
|
|
5443
5722
|
function syncSkillsPush() {
|
|
5444
|
-
const localSkills =
|
|
5445
|
-
const stat =
|
|
5723
|
+
const localSkills = join40(claudeHome(), "skills");
|
|
5724
|
+
const stat = lstatSync15(localSkills, { throwIfNoEntry: false });
|
|
5446
5725
|
if (stat === void 0) return;
|
|
5447
5726
|
if (stat.isSymbolicLink()) return;
|
|
5448
|
-
const sharedSkills =
|
|
5727
|
+
const sharedSkills = join40(repoHome(), "shared", "skills");
|
|
5449
5728
|
copySkillsPush(localSkills, sharedSkills);
|
|
5450
5729
|
}
|
|
5451
5730
|
|
|
@@ -5468,12 +5747,12 @@ function resolveSkillLocalPath(name, relPath) {
|
|
|
5468
5747
|
if (!SAFE_SKILL_NAME.test(name) || name === "." || name === "..") return null;
|
|
5469
5748
|
if (isGsdOwned(name)) return null;
|
|
5470
5749
|
if (!isSafeRelPath(relPath)) return null;
|
|
5471
|
-
const skillsRoot =
|
|
5472
|
-
const skillRoot =
|
|
5473
|
-
const localPath =
|
|
5750
|
+
const skillsRoot = join41(claudeHome(), "skills");
|
|
5751
|
+
const skillRoot = join41(skillsRoot, name);
|
|
5752
|
+
const localPath = join41(skillRoot, ...relPath.split("/"));
|
|
5474
5753
|
if (localPath !== skillRoot && !localPath.startsWith(skillRoot + sep7)) return null;
|
|
5475
5754
|
try {
|
|
5476
|
-
if (
|
|
5755
|
+
if (lstatSync16(localPath).isSymbolicLink()) return null;
|
|
5477
5756
|
const realLocal = realpathSync6(localPath);
|
|
5478
5757
|
const realRoot = realpathSync6(skillsRoot);
|
|
5479
5758
|
if (!realLocal.startsWith(realRoot + sep7)) return null;
|
|
@@ -5522,9 +5801,9 @@ function applySkillRedact(f, ts, scan = scanFile) {
|
|
|
5522
5801
|
);
|
|
5523
5802
|
}
|
|
5524
5803
|
writeFileSync7(localPath, after, "utf8");
|
|
5525
|
-
const dest =
|
|
5526
|
-
mkdirSync11(
|
|
5527
|
-
|
|
5804
|
+
const dest = join41(repoHome(), "shared", "skills", name, ...relPath.split("/"));
|
|
5805
|
+
mkdirSync11(dirname11(dest), { recursive: true });
|
|
5806
|
+
cpSync7(localPath, dest, { force: true });
|
|
5528
5807
|
return true;
|
|
5529
5808
|
}
|
|
5530
5809
|
|
|
@@ -5600,26 +5879,26 @@ function applyDeferredAllows(findings, state, repo) {
|
|
|
5600
5879
|
// src/commands.push.recovery.redact.ts
|
|
5601
5880
|
init_config();
|
|
5602
5881
|
init_config_sharedDirs_guard();
|
|
5603
|
-
import { cpSync as
|
|
5604
|
-
import { dirname as
|
|
5882
|
+
import { cpSync as cpSync8, existsSync as existsSync33, mkdirSync as mkdirSync12 } from "node:fs";
|
|
5883
|
+
import { dirname as dirname13, join as join44, sep as sep8 } from "node:path";
|
|
5605
5884
|
|
|
5606
5885
|
// src/commands.redact.ts
|
|
5607
5886
|
init_config();
|
|
5608
|
-
import { existsSync as
|
|
5609
|
-
import { dirname as
|
|
5887
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
5888
|
+
import { dirname as dirname12, join as join43 } from "node:path";
|
|
5610
5889
|
|
|
5611
5890
|
// src/commands.redact.subtree.ts
|
|
5612
|
-
import { existsSync as
|
|
5613
|
-
import { join as
|
|
5891
|
+
import { existsSync as existsSync31, lstatSync as lstatSync17, readFileSync as readFileSync16, readdirSync as readdirSync15, statSync as statSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
5892
|
+
import { join as join42 } from "node:path";
|
|
5614
5893
|
init_utils_fs();
|
|
5615
5894
|
init_utils();
|
|
5616
5895
|
function collectFiles(dir, out) {
|
|
5617
|
-
if (!
|
|
5618
|
-
const st =
|
|
5896
|
+
if (!existsSync31(dir)) return;
|
|
5897
|
+
const st = lstatSync17(dir);
|
|
5619
5898
|
if (!st.isDirectory()) return;
|
|
5620
|
-
for (const entry of
|
|
5621
|
-
const abs =
|
|
5622
|
-
const lst =
|
|
5899
|
+
for (const entry of readdirSync15(dir)) {
|
|
5900
|
+
const abs = join42(dir, entry);
|
|
5901
|
+
const lst = lstatSync17(abs);
|
|
5623
5902
|
if (lst.isSymbolicLink()) continue;
|
|
5624
5903
|
if (lst.isDirectory()) {
|
|
5625
5904
|
collectFiles(abs, out);
|
|
@@ -5728,15 +6007,15 @@ init_utils_json();
|
|
|
5728
6007
|
init_utils();
|
|
5729
6008
|
function resolveLiveTranscript(id) {
|
|
5730
6009
|
try {
|
|
5731
|
-
const mapPath =
|
|
5732
|
-
if (!
|
|
6010
|
+
const mapPath = join43(repoHome(), "path-map.json");
|
|
6011
|
+
if (!existsSync32(mapPath)) return null;
|
|
5733
6012
|
const projects = readJson(mapPath).projects;
|
|
5734
6013
|
const claude = claudeHome();
|
|
5735
6014
|
for (const hostMap of Object.values(projects)) {
|
|
5736
6015
|
const abs = hostMap[HOST];
|
|
5737
6016
|
if (abs === void 0) continue;
|
|
5738
|
-
const live =
|
|
5739
|
-
if (
|
|
6017
|
+
const live = join43(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
6018
|
+
if (existsSync32(live)) return live;
|
|
5740
6019
|
}
|
|
5741
6020
|
return null;
|
|
5742
6021
|
} catch {
|
|
@@ -5756,17 +6035,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
5756
6035
|
}
|
|
5757
6036
|
const repo = repoHome();
|
|
5758
6037
|
const backup = backupBase();
|
|
5759
|
-
if (!
|
|
6038
|
+
if (!existsSync32(repo)) die(`repo not cloned at ${repo}`);
|
|
5760
6039
|
const handle = acquireLock("redact");
|
|
5761
6040
|
if (handle === null) process.exit(0);
|
|
5762
6041
|
try {
|
|
5763
6042
|
const localPath = resolveLiveTranscript(id);
|
|
5764
|
-
if (localPath === null || !
|
|
6043
|
+
if (localPath === null || !existsSync32(localPath)) {
|
|
5765
6044
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
5766
6045
|
process.exitCode = 1;
|
|
5767
6046
|
return;
|
|
5768
6047
|
}
|
|
5769
|
-
const sessionDir =
|
|
6048
|
+
const sessionDir = join43(dirname12(localPath), id);
|
|
5770
6049
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
5771
6050
|
if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
|
|
5772
6051
|
log(
|
|
@@ -5827,8 +6106,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
5827
6106
|
assertSafeLogical(logical);
|
|
5828
6107
|
const abs = hostMap[HOST];
|
|
5829
6108
|
if (abs === void 0) continue;
|
|
5830
|
-
if (localPath.startsWith(
|
|
5831
|
-
return
|
|
6109
|
+
if (localPath.startsWith(join44(claude, "projects", encodePath(abs)) + sep8)) {
|
|
6110
|
+
return join44(repo, "shared", "projects", logical);
|
|
5832
6111
|
}
|
|
5833
6112
|
}
|
|
5834
6113
|
return null;
|
|
@@ -5838,7 +6117,7 @@ function preflightRedactable(f, map, nowMs) {
|
|
|
5838
6117
|
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
5839
6118
|
const localPath = resolveLiveTranscript(sid);
|
|
5840
6119
|
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
5841
|
-
const sessionDir =
|
|
6120
|
+
const sessionDir = join44(dirname13(localPath), sid);
|
|
5842
6121
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
5843
6122
|
if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
|
|
5844
6123
|
return `session ${sid}: looks active (modified within the last 5 minutes)`;
|
|
@@ -5867,7 +6146,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
5867
6146
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
5868
6147
|
);
|
|
5869
6148
|
}
|
|
5870
|
-
const sessionDir =
|
|
6149
|
+
const sessionDir = join44(dirname13(localPath), sid);
|
|
5871
6150
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
5872
6151
|
if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
|
|
5873
6152
|
return refuse(
|
|
@@ -5900,9 +6179,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
5900
6179
|
);
|
|
5901
6180
|
}
|
|
5902
6181
|
mkdirSync12(stagedProjectDir, { recursive: true });
|
|
5903
|
-
|
|
5904
|
-
if (
|
|
5905
|
-
|
|
6182
|
+
cpSync8(localPath, join44(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
6183
|
+
if (existsSync33(sessionDir)) {
|
|
6184
|
+
cpSync8(sessionDir, join44(stagedProjectDir, sid), { force: true, recursive: true });
|
|
5906
6185
|
}
|
|
5907
6186
|
return true;
|
|
5908
6187
|
}
|
|
@@ -5910,14 +6189,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
5910
6189
|
// src/commands.push.recovery.drop.ts
|
|
5911
6190
|
init_config();
|
|
5912
6191
|
import { rmSync as rmSync14 } from "node:fs";
|
|
5913
|
-
import { join as
|
|
6192
|
+
import { join as join45 } from "node:path";
|
|
5914
6193
|
function dropSessionFromStaged(sid, map) {
|
|
5915
6194
|
const logicals = Object.keys(map.projects);
|
|
5916
6195
|
if (logicals.length === 0) return false;
|
|
5917
6196
|
const repo = repoHome();
|
|
5918
6197
|
for (const logical of logicals) {
|
|
5919
|
-
const jsonl =
|
|
5920
|
-
const dir =
|
|
6198
|
+
const jsonl = join45(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
6199
|
+
const dir = join45(repo, "shared", "projects", logical, sid);
|
|
5921
6200
|
rmSync14(jsonl, { force: true });
|
|
5922
6201
|
rmSync14(dir, { recursive: true, force: true });
|
|
5923
6202
|
}
|
|
@@ -6189,8 +6468,8 @@ function makeDefaultReadLine(repo) {
|
|
|
6189
6468
|
const cache = /* @__PURE__ */ new Map();
|
|
6190
6469
|
return (file, line) => {
|
|
6191
6470
|
try {
|
|
6192
|
-
const repoRoot =
|
|
6193
|
-
const target =
|
|
6471
|
+
const repoRoot = resolve4(repo);
|
|
6472
|
+
const target = resolve4(repoRoot, file);
|
|
6194
6473
|
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep9)) {
|
|
6195
6474
|
return null;
|
|
6196
6475
|
}
|
|
@@ -6402,7 +6681,7 @@ function applyThenRescan(scanVerdict, repoHome2) {
|
|
|
6402
6681
|
return next;
|
|
6403
6682
|
}
|
|
6404
6683
|
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
6405
|
-
const ignPath =
|
|
6684
|
+
const ignPath = join46(repoHome2, ".gitleaksignore");
|
|
6406
6685
|
let before;
|
|
6407
6686
|
try {
|
|
6408
6687
|
before = readFileSync18(ignPath, "utf8");
|
|
@@ -6508,7 +6787,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
6508
6787
|
`);
|
|
6509
6788
|
}
|
|
6510
6789
|
function resolveWorkerPath(deps = {}) {
|
|
6511
|
-
const check = deps.existsSyncFn ??
|
|
6790
|
+
const check = deps.existsSyncFn ?? existsSync34;
|
|
6512
6791
|
const base = deps.baseUrl ?? import.meta.url;
|
|
6513
6792
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
6514
6793
|
if (check(mjs)) return mjs;
|
|
@@ -6575,8 +6854,8 @@ function withSpinner(label, fn, deps) {
|
|
|
6575
6854
|
// src/commands.doctor.gitleaks-version.ts
|
|
6576
6855
|
init_color();
|
|
6577
6856
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
6578
|
-
import { existsSync as
|
|
6579
|
-
import { join as
|
|
6857
|
+
import { existsSync as existsSync35 } from "node:fs";
|
|
6858
|
+
import { join as join47 } from "node:path";
|
|
6580
6859
|
init_config();
|
|
6581
6860
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
6582
6861
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -6585,7 +6864,7 @@ function majorMinorOf(value) {
|
|
|
6585
6864
|
return m === null ? null : [m[1], m[2]];
|
|
6586
6865
|
}
|
|
6587
6866
|
function readGitleaksVersion(run, tomlExists) {
|
|
6588
|
-
const tomlPath =
|
|
6867
|
+
const tomlPath = join47(repoHome(), ".gitleaks.toml");
|
|
6589
6868
|
const args = ["version"];
|
|
6590
6869
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
6591
6870
|
try {
|
|
@@ -6597,7 +6876,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
6597
6876
|
return null;
|
|
6598
6877
|
}
|
|
6599
6878
|
}
|
|
6600
|
-
function reportGitleaksVersionCheck(section2, run = execFileSync15, tomlExists =
|
|
6879
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync15, tomlExists = existsSync35) {
|
|
6601
6880
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
6602
6881
|
if (raw === null) return;
|
|
6603
6882
|
const local = majorMinorOf(raw);
|
|
@@ -6854,8 +7133,8 @@ function gatherDoctorSections(opts) {
|
|
|
6854
7133
|
reportLongPathsCheck(host);
|
|
6855
7134
|
reportCrlfGuardCheck(host);
|
|
6856
7135
|
const links = section("Shared links");
|
|
6857
|
-
const mapPath =
|
|
6858
|
-
const rawMap =
|
|
7136
|
+
const mapPath = join48(repoHome(), "path-map.json");
|
|
7137
|
+
const rawMap = existsSync36(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
6859
7138
|
const map = rawMap ?? { projects: {} };
|
|
6860
7139
|
reportSharedLinks(links, map);
|
|
6861
7140
|
reportDroppedNamesMigration(links);
|
|
@@ -6953,8 +7232,8 @@ function parseDoctorArgs(args) {
|
|
|
6953
7232
|
// src/commands.drop-session.ts
|
|
6954
7233
|
init_config();
|
|
6955
7234
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
6956
|
-
import { existsSync as
|
|
6957
|
-
import { join as
|
|
7235
|
+
import { existsSync as existsSync38, readdirSync as readdirSync16, statSync as statSync9 } from "node:fs";
|
|
7236
|
+
import { join as join50, relative as relative7 } from "node:path";
|
|
6958
7237
|
|
|
6959
7238
|
// src/commands.drop-session.git.ts
|
|
6960
7239
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
@@ -6997,8 +7276,8 @@ function isInIndex(rel, repo) {
|
|
|
6997
7276
|
init_config();
|
|
6998
7277
|
init_utils();
|
|
6999
7278
|
init_utils_json();
|
|
7000
|
-
import { existsSync as
|
|
7001
|
-
import { join as
|
|
7279
|
+
import { existsSync as existsSync37 } from "node:fs";
|
|
7280
|
+
import { join as join49 } from "node:path";
|
|
7002
7281
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
7003
7282
|
function reportScrubHint(id, matches) {
|
|
7004
7283
|
const live = resolveLiveTranscript2(id, matches);
|
|
@@ -7014,8 +7293,8 @@ function reportScrubHint(id, matches) {
|
|
|
7014
7293
|
}
|
|
7015
7294
|
function resolveLiveTranscript2(id, matches) {
|
|
7016
7295
|
try {
|
|
7017
|
-
const mapPath =
|
|
7018
|
-
if (!
|
|
7296
|
+
const mapPath = join49(repoHome(), "path-map.json");
|
|
7297
|
+
if (!existsSync37(mapPath)) return null;
|
|
7019
7298
|
const projects = readJson(mapPath).projects;
|
|
7020
7299
|
const claude = claudeHome();
|
|
7021
7300
|
for (const rel of matches) {
|
|
@@ -7023,8 +7302,8 @@ function resolveLiveTranscript2(id, matches) {
|
|
|
7023
7302
|
if (logical === void 0) continue;
|
|
7024
7303
|
const abs = projects[logical]?.[HOST];
|
|
7025
7304
|
if (abs === void 0) continue;
|
|
7026
|
-
const live =
|
|
7027
|
-
if (
|
|
7305
|
+
const live = join49(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
7306
|
+
if (existsSync37(live)) return live;
|
|
7028
7307
|
}
|
|
7029
7308
|
return null;
|
|
7030
7309
|
} catch {
|
|
@@ -7040,12 +7319,12 @@ function cmdDropSession(id) {
|
|
|
7040
7319
|
process.exit(1);
|
|
7041
7320
|
}
|
|
7042
7321
|
const repo = repoHome();
|
|
7043
|
-
if (!
|
|
7322
|
+
if (!existsSync38(repo)) die(`repo not cloned at ${repo}`);
|
|
7044
7323
|
const handle = acquireLock("drop-session");
|
|
7045
7324
|
if (handle === null) process.exit(0);
|
|
7046
7325
|
try {
|
|
7047
|
-
const repoProjects =
|
|
7048
|
-
if (!
|
|
7326
|
+
const repoProjects = join50(repo, "shared", "projects");
|
|
7327
|
+
if (!existsSync38(repoProjects)) {
|
|
7049
7328
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
7050
7329
|
}
|
|
7051
7330
|
const matches = collectMatches(repoProjects, id, repo);
|
|
@@ -7067,14 +7346,14 @@ function cmdDropSession(id) {
|
|
|
7067
7346
|
}
|
|
7068
7347
|
function collectMatches(repoProjects, id, repo) {
|
|
7069
7348
|
const matches = [];
|
|
7070
|
-
for (const logical of
|
|
7071
|
-
const candidate =
|
|
7072
|
-
if (
|
|
7073
|
-
matches.push(
|
|
7074
|
-
}
|
|
7075
|
-
const dir =
|
|
7076
|
-
if (
|
|
7077
|
-
const dirRel =
|
|
7349
|
+
for (const logical of readdirSync16(repoProjects)) {
|
|
7350
|
+
const candidate = join50(repoProjects, logical, `${id}.jsonl`);
|
|
7351
|
+
if (existsSync38(candidate)) {
|
|
7352
|
+
matches.push(relative7(repo, candidate));
|
|
7353
|
+
}
|
|
7354
|
+
const dir = join50(repoProjects, logical, id);
|
|
7355
|
+
if (existsSync38(dir) && statSync9(dir).isDirectory()) {
|
|
7356
|
+
const dirRel = relative7(repo, dir);
|
|
7078
7357
|
const staged = expandStagedDir(dirRel, repo);
|
|
7079
7358
|
if (staged.length > 0) matches.push(...staged);
|
|
7080
7359
|
else matches.push(dirRel);
|
|
@@ -7109,8 +7388,8 @@ function unstageOne(rel, repo) {
|
|
|
7109
7388
|
|
|
7110
7389
|
// src/commands.pull.ts
|
|
7111
7390
|
init_autostash_guard();
|
|
7112
|
-
import { existsSync as
|
|
7113
|
-
import { join as
|
|
7391
|
+
import { existsSync as existsSync47, mkdirSync as mkdirSync14 } from "node:fs";
|
|
7392
|
+
import { join as join61 } from "node:path";
|
|
7114
7393
|
|
|
7115
7394
|
// src/commands.push.sections.ts
|
|
7116
7395
|
init_color();
|
|
@@ -7218,18 +7497,18 @@ init_config();
|
|
|
7218
7497
|
|
|
7219
7498
|
// src/extras-sync.ts
|
|
7220
7499
|
init_config();
|
|
7221
|
-
import { existsSync as
|
|
7222
|
-
import { join as
|
|
7500
|
+
import { existsSync as existsSync40, statSync as statSync10 } from "node:fs";
|
|
7501
|
+
import { join as join53 } from "node:path";
|
|
7223
7502
|
init_utils();
|
|
7224
7503
|
init_utils_json();
|
|
7225
7504
|
|
|
7226
7505
|
// src/extras-sync.remap.ts
|
|
7227
7506
|
init_config();
|
|
7228
|
-
import { existsSync as
|
|
7229
|
-
import { dirname as
|
|
7507
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync13, readdirSync as readdirSync17, readFileSync as readFileSync19, realpathSync as realpathSync7, rmSync as rmSync16 } from "node:fs";
|
|
7508
|
+
import { dirname as dirname14, join as join52, sep as sep11 } from "node:path";
|
|
7230
7509
|
|
|
7231
7510
|
// src/extras-sync.planning-diff.ts
|
|
7232
|
-
import { join as
|
|
7511
|
+
import { join as join51, normalize as normalize2, sep as sep10 } from "node:path";
|
|
7233
7512
|
init_utils();
|
|
7234
7513
|
function processRecord(fields, i, changed, deleted) {
|
|
7235
7514
|
const status = fields[i];
|
|
@@ -7281,7 +7560,7 @@ function planningDeleteTargets(opts) {
|
|
|
7281
7560
|
const { deleted } = parsePlanningDiff(raw);
|
|
7282
7561
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
7283
7562
|
const prefix = logicalPrefix + ".planning/";
|
|
7284
|
-
const planningRoot =
|
|
7563
|
+
const planningRoot = join51(localRoot, ".planning");
|
|
7285
7564
|
const planningRootBoundary = planningRoot + sep10;
|
|
7286
7565
|
const targets = [];
|
|
7287
7566
|
for (const repoPath of deleted) {
|
|
@@ -7289,7 +7568,7 @@ function planningDeleteTargets(opts) {
|
|
|
7289
7568
|
continue;
|
|
7290
7569
|
}
|
|
7291
7570
|
const remainder = repoPath.slice(logicalPrefix.length);
|
|
7292
|
-
const candidate =
|
|
7571
|
+
const candidate = join51(localRoot, remainder);
|
|
7293
7572
|
const resolved = normalize2(candidate);
|
|
7294
7573
|
if (!resolved.startsWith(planningRootBoundary)) {
|
|
7295
7574
|
throw new NomadFatal(
|
|
@@ -7310,7 +7589,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
7310
7589
|
const would = [];
|
|
7311
7590
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
7312
7591
|
const { src, dst } = paths(t);
|
|
7313
|
-
if (!
|
|
7592
|
+
if (!existsSync39(src)) continue;
|
|
7314
7593
|
const item2 = `${t.logical}/${t.dirname}`;
|
|
7315
7594
|
if (dryRun) {
|
|
7316
7595
|
would.push(item2);
|
|
@@ -7323,15 +7602,15 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
7323
7602
|
return { ...counts, done, would };
|
|
7324
7603
|
}
|
|
7325
7604
|
function pruneEmptyAncestors(target, planningRoot) {
|
|
7326
|
-
let dir =
|
|
7605
|
+
let dir = dirname14(target);
|
|
7327
7606
|
while (dir !== planningRoot && dir.startsWith(planningRoot + sep11)) {
|
|
7328
7607
|
try {
|
|
7329
|
-
if (
|
|
7608
|
+
if (readdirSync17(dir).length > 0) break;
|
|
7330
7609
|
rmSync16(dir, { recursive: true, force: true });
|
|
7331
7610
|
} catch {
|
|
7332
7611
|
break;
|
|
7333
7612
|
}
|
|
7334
|
-
dir =
|
|
7613
|
+
dir = dirname14(dir);
|
|
7335
7614
|
}
|
|
7336
7615
|
}
|
|
7337
7616
|
function tryRealpath(dir) {
|
|
@@ -7345,8 +7624,8 @@ function isInsidePlanningRoot(parentReal, rootReal) {
|
|
|
7345
7624
|
return parentReal === rootReal || parentReal.startsWith(rootReal + sep11);
|
|
7346
7625
|
}
|
|
7347
7626
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
7348
|
-
if (
|
|
7349
|
-
const parentReal = tryRealpath(
|
|
7627
|
+
if (existsSync39(repoCounterpart)) return;
|
|
7628
|
+
const parentReal = tryRealpath(dirname14(target));
|
|
7350
7629
|
if (parentReal === void 0) return;
|
|
7351
7630
|
const rootReal = tryRealpath(planningRoot);
|
|
7352
7631
|
if (rootReal === void 0) return;
|
|
@@ -7355,7 +7634,7 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
|
7355
7634
|
pruneEmptyAncestors(target, planningRoot);
|
|
7356
7635
|
}
|
|
7357
7636
|
function localDivergesFromPreDelete(target, pre, repoRel, repo) {
|
|
7358
|
-
if (!
|
|
7637
|
+
if (!existsSync39(target)) return false;
|
|
7359
7638
|
try {
|
|
7360
7639
|
const preBlob = gitCaptureBuffer(["show", `${pre}:${repoRel}`], repo);
|
|
7361
7640
|
return !readFileSync19(target).equals(preBlob);
|
|
@@ -7400,7 +7679,7 @@ function keptDeletePreview(v, prePostHeads, repo) {
|
|
|
7400
7679
|
return kept;
|
|
7401
7680
|
}
|
|
7402
7681
|
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
7403
|
-
const repoExtras =
|
|
7682
|
+
const repoExtras = join52(repo, "shared", "extras");
|
|
7404
7683
|
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
7405
7684
|
if (t.dirname !== ".planning") continue;
|
|
7406
7685
|
let raw;
|
|
@@ -7415,14 +7694,14 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
7415
7694
|
}
|
|
7416
7695
|
const pairs = deletePairsFor(t, raw);
|
|
7417
7696
|
if (pairs.length === 0) continue;
|
|
7418
|
-
backupExtrasWrite(
|
|
7419
|
-
const planningRoot =
|
|
7697
|
+
backupExtrasWrite(join52(t.localRoot, t.dirname), ts, t.localRoot);
|
|
7698
|
+
const planningRoot = join52(t.localRoot, ".planning");
|
|
7420
7699
|
for (const { target, relToLocal, repoRel } of pairs) {
|
|
7421
7700
|
if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
|
|
7422
7701
|
warn(keptDeleteWarnLine(t.logical, relToLocal));
|
|
7423
7702
|
continue;
|
|
7424
7703
|
}
|
|
7425
|
-
deletePlanningTarget(target, planningRoot,
|
|
7704
|
+
deletePlanningTarget(target, planningRoot, join52(repoExtras, t.logical, relToLocal));
|
|
7426
7705
|
}
|
|
7427
7706
|
}
|
|
7428
7707
|
}
|
|
@@ -7431,14 +7710,14 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
7431
7710
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
7432
7711
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
7433
7712
|
const repo = repoHome();
|
|
7434
|
-
const repoExtras =
|
|
7713
|
+
const repoExtras = join52(repo, "shared", "extras");
|
|
7435
7714
|
if (!dryRun) mkdirSync13(repoExtras, { recursive: true });
|
|
7436
7715
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
7437
7716
|
v,
|
|
7438
7717
|
dryRun,
|
|
7439
|
-
({ localRoot, logical, dirname:
|
|
7440
|
-
src:
|
|
7441
|
-
dst:
|
|
7718
|
+
({ localRoot, logical, dirname: dirname15 }) => ({
|
|
7719
|
+
src: join52(localRoot, dirname15),
|
|
7720
|
+
dst: join52(repoExtras, logical, dirname15)
|
|
7442
7721
|
}),
|
|
7443
7722
|
(dst) => backupRepoWrite(dst, ts, repo),
|
|
7444
7723
|
// Push copy routing per extra type:
|
|
@@ -7451,7 +7730,7 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
7451
7730
|
// config.never-sync.ts)
|
|
7452
7731
|
// remains the hard security boundary.
|
|
7453
7732
|
// All others: copyExtrasFiltered with per-extra denylist.
|
|
7454
|
-
(src, dst,
|
|
7733
|
+
(src, dst, dirname15) => dirname15 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname15)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname15))
|
|
7455
7734
|
);
|
|
7456
7735
|
return { unmapped, skipped, pushed: done, wouldPush: would };
|
|
7457
7736
|
}
|
|
@@ -7467,9 +7746,9 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
7467
7746
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
7468
7747
|
v,
|
|
7469
7748
|
dryRun,
|
|
7470
|
-
({ localRoot, logical, dirname:
|
|
7471
|
-
src:
|
|
7472
|
-
dst:
|
|
7749
|
+
({ localRoot, logical, dirname: dirname15 }) => ({
|
|
7750
|
+
src: join52(repo, "shared", "extras", logical, dirname15),
|
|
7751
|
+
dst: join52(localRoot, dirname15)
|
|
7473
7752
|
}),
|
|
7474
7753
|
// Snapshot the host-side dst BEFORE the copy step touches it. Anchor on
|
|
7475
7754
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -7487,12 +7766,12 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
7487
7766
|
// copyExtrasFileSkipDiverged keeps a locally-edited file rather than
|
|
7488
7767
|
// clobbering it, so the divergence WARN's keep-local promise holds for
|
|
7489
7768
|
// every extra, not just `.planning`.
|
|
7490
|
-
(src, dst,
|
|
7491
|
-
if (
|
|
7492
|
-
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(
|
|
7493
|
-
if (
|
|
7769
|
+
(src, dst, dirname15) => {
|
|
7770
|
+
if (dirname15 === ".claude")
|
|
7771
|
+
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname15));
|
|
7772
|
+
if (dirname15 === ".planning") {
|
|
7494
7773
|
const divergedSet = new Set(listDivergingModified(dst, src));
|
|
7495
|
-
return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(
|
|
7774
|
+
return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname15), divergedSet);
|
|
7496
7775
|
}
|
|
7497
7776
|
return copyExtrasFileSkipDiverged(src, dst);
|
|
7498
7777
|
}
|
|
@@ -7516,20 +7795,20 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
7516
7795
|
const v = loadValidatedExtras({});
|
|
7517
7796
|
if (v === null) return 0;
|
|
7518
7797
|
const counts = { unmapped: 0, skipped: 0 };
|
|
7519
|
-
const backupRoot =
|
|
7798
|
+
const backupRoot = join53(backupBase(), ts, "extras");
|
|
7520
7799
|
const repo = repoHome();
|
|
7521
7800
|
let divergedCount = 0;
|
|
7522
|
-
for (const { logical, localRoot, dirname:
|
|
7523
|
-
const local =
|
|
7524
|
-
const repoEntry =
|
|
7525
|
-
if (!
|
|
7801
|
+
for (const { logical, localRoot, dirname: dirname15 } of eachExtrasTarget(v, counts)) {
|
|
7802
|
+
const local = join53(localRoot, dirname15);
|
|
7803
|
+
const repoEntry = join53(repo, "shared", "extras", logical, dirname15);
|
|
7804
|
+
if (!existsSync40(local) || !existsSync40(repoEntry)) continue;
|
|
7526
7805
|
const modified = listDivergingModified(local, repoEntry);
|
|
7527
7806
|
if (modified.length === 0) continue;
|
|
7528
7807
|
divergedCount += modified.length;
|
|
7529
|
-
const projectBackupRoot =
|
|
7808
|
+
const projectBackupRoot = join53(backupRoot, encodePath(localRoot));
|
|
7530
7809
|
warn(
|
|
7531
7810
|
divergenceWarnLine({
|
|
7532
|
-
dirname:
|
|
7811
|
+
dirname: dirname15,
|
|
7533
7812
|
logical,
|
|
7534
7813
|
isDir: statSync10(local).isDirectory(),
|
|
7535
7814
|
count: modified.length,
|
|
@@ -7548,8 +7827,8 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
7548
7827
|
|
|
7549
7828
|
// src/links.baseline.ts
|
|
7550
7829
|
init_config();
|
|
7551
|
-
import { lstatSync as
|
|
7552
|
-
import { join as
|
|
7830
|
+
import { lstatSync as lstatSync18, readdirSync as readdirSync18 } from "node:fs";
|
|
7831
|
+
import { join as join54, relative as relative8, sep as sep12 } from "node:path";
|
|
7553
7832
|
init_utils();
|
|
7554
7833
|
var SHARED_BASELINE_KIND = "shared-links-baseline/1";
|
|
7555
7834
|
var SHARED_BASELINE_CONFIG_HASH = "not-applicable";
|
|
@@ -7560,13 +7839,13 @@ function readSharedBaseline() {
|
|
|
7560
7839
|
return parsed;
|
|
7561
7840
|
}
|
|
7562
7841
|
function baselineKey(claude, abs) {
|
|
7563
|
-
return
|
|
7842
|
+
return relative8(claude, abs).split(sep12).join("/");
|
|
7564
7843
|
}
|
|
7565
7844
|
function addLocalPath(abs, claude, scan) {
|
|
7566
7845
|
const key = baselineKey(claude, abs);
|
|
7567
7846
|
let st;
|
|
7568
7847
|
try {
|
|
7569
|
-
st =
|
|
7848
|
+
st = lstatSync18(abs, { throwIfNoEntry: false });
|
|
7570
7849
|
} catch {
|
|
7571
7850
|
scan.declined.push(key);
|
|
7572
7851
|
return;
|
|
@@ -7585,13 +7864,13 @@ function addLocalPath(abs, claude, scan) {
|
|
|
7585
7864
|
function collectSharedFiles(dir, claude, scan) {
|
|
7586
7865
|
let entries;
|
|
7587
7866
|
try {
|
|
7588
|
-
entries =
|
|
7867
|
+
entries = readdirSync18(dir);
|
|
7589
7868
|
} catch {
|
|
7590
7869
|
scan.declined.push(baselineKey(claude, dir));
|
|
7591
7870
|
return;
|
|
7592
7871
|
}
|
|
7593
7872
|
for (const entry of entries) {
|
|
7594
|
-
const abs =
|
|
7873
|
+
const abs = join54(dir, entry);
|
|
7595
7874
|
if (isDeniedName(ALWAYS_NEVER_SYNC, entry)) {
|
|
7596
7875
|
scan.declined.push(baselineKey(claude, abs));
|
|
7597
7876
|
continue;
|
|
@@ -7607,7 +7886,7 @@ function enumerateLocalSharedScan(map, opts = {}) {
|
|
|
7607
7886
|
scan.declined.push(name);
|
|
7608
7887
|
continue;
|
|
7609
7888
|
}
|
|
7610
|
-
addLocalPath(
|
|
7889
|
+
addLocalPath(join54(claude, name), claude, scan);
|
|
7611
7890
|
}
|
|
7612
7891
|
return scan;
|
|
7613
7892
|
}
|
|
@@ -7619,7 +7898,7 @@ function buildSharedBaseline(map, opts = {}) {
|
|
|
7619
7898
|
const files = {};
|
|
7620
7899
|
for (const [key, st] of Object.entries(enumerateLocalSharedFiles(map, opts))) {
|
|
7621
7900
|
try {
|
|
7622
|
-
files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(
|
|
7901
|
+
files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(join54(claude, key)) };
|
|
7623
7902
|
} catch {
|
|
7624
7903
|
}
|
|
7625
7904
|
}
|
|
@@ -7636,8 +7915,8 @@ function writeSharedBaseline(map, opts = {}) {
|
|
|
7636
7915
|
|
|
7637
7916
|
// src/commands.pull.win32.ts
|
|
7638
7917
|
init_color();
|
|
7639
|
-
import { existsSync as
|
|
7640
|
-
import { join as
|
|
7918
|
+
import { existsSync as existsSync45 } from "node:fs";
|
|
7919
|
+
import { join as join59 } from "node:path";
|
|
7641
7920
|
|
|
7642
7921
|
// src/commands.pull.recovery.git.ts
|
|
7643
7922
|
init_utils();
|
|
@@ -7700,8 +7979,8 @@ function gitProbe(args, repo) {
|
|
|
7700
7979
|
|
|
7701
7980
|
// src/links.deletions.ts
|
|
7702
7981
|
init_config();
|
|
7703
|
-
import { existsSync as
|
|
7704
|
-
import { join as
|
|
7982
|
+
import { existsSync as existsSync41, lstatSync as lstatSync19, rmSync as rmSync17 } from "node:fs";
|
|
7983
|
+
import { join as join55, relative as relative9, resolve as resolve5, sep as sep13 } from "node:path";
|
|
7705
7984
|
init_utils();
|
|
7706
7985
|
init_utils_fs();
|
|
7707
7986
|
function isUnknown(declined, key) {
|
|
@@ -7711,15 +7990,15 @@ function deletionFor(key, names, claude, sharedRoot) {
|
|
|
7711
7990
|
const segments = key.split("/");
|
|
7712
7991
|
const name = segments[0];
|
|
7713
7992
|
if (!names.has(name)) return null;
|
|
7714
|
-
if (!
|
|
7993
|
+
if (!existsSync41(join55(claude, name))) return null;
|
|
7715
7994
|
if (segments.some((segment) => isDeniedName(ALWAYS_NEVER_SYNC, segment))) return null;
|
|
7716
|
-
const repoPath =
|
|
7717
|
-
const rel =
|
|
7995
|
+
const repoPath = resolve5(sharedRoot, key);
|
|
7996
|
+
const rel = relative9(join55(sharedRoot, name), repoPath);
|
|
7718
7997
|
if (rel === "" || rel === ".." || rel.startsWith(`..${sep13}`)) return null;
|
|
7719
7998
|
if (segments.some((segment) => segment === "" || segment === "." || segment === ".."))
|
|
7720
7999
|
return null;
|
|
7721
|
-
if (!
|
|
7722
|
-
return { kind: "deletion", name, localPath:
|
|
8000
|
+
if (!existsSync41(repoPath)) return null;
|
|
8001
|
+
return { kind: "deletion", name, localPath: join55(claude, key), repoPath };
|
|
7723
8002
|
}
|
|
7724
8003
|
function planSharedLinkDeletions(map, opts = {}) {
|
|
7725
8004
|
if (process.platform !== "win32") return [];
|
|
@@ -7727,7 +8006,7 @@ function planSharedLinkDeletions(map, opts = {}) {
|
|
|
7727
8006
|
const baseline = readSharedBaseline();
|
|
7728
8007
|
if (baseline === null) return [];
|
|
7729
8008
|
const claude = claudeHome();
|
|
7730
|
-
const sharedRoot =
|
|
8009
|
+
const sharedRoot = join55(repoHome(), "shared");
|
|
7731
8010
|
const linkNames = opts.linkNames ?? allSharedLinks(map);
|
|
7732
8011
|
const scan = enumerateLocalSharedScan(map, { linkNames });
|
|
7733
8012
|
const present = new Set(Object.keys(scan.files).map((key) => key.toLowerCase()));
|
|
@@ -7748,7 +8027,7 @@ function applySharedLinkDeletions(map, ts, opts = {}) {
|
|
|
7748
8027
|
const removed = [];
|
|
7749
8028
|
for (const entry of planSharedLinkDeletions(map, opts)) {
|
|
7750
8029
|
try {
|
|
7751
|
-
if (!
|
|
8030
|
+
if (!lstatSync19(entry.repoPath).isFile()) continue;
|
|
7752
8031
|
backupRepoWrite(entry.repoPath, ts, repo);
|
|
7753
8032
|
rmSync17(entry.repoPath, { force: true });
|
|
7754
8033
|
removed.push(entry);
|
|
@@ -7761,8 +8040,8 @@ function applySharedLinkDeletions(map, ts, opts = {}) {
|
|
|
7761
8040
|
|
|
7762
8041
|
// src/links.mirror.ts
|
|
7763
8042
|
init_config();
|
|
7764
|
-
import { existsSync as
|
|
7765
|
-
import { join as
|
|
8043
|
+
import { existsSync as existsSync42, lstatSync as lstatSync20, rmSync as rmSync18 } from "node:fs";
|
|
8044
|
+
import { join as join56 } from "node:path";
|
|
7766
8045
|
init_utils();
|
|
7767
8046
|
init_utils_fs();
|
|
7768
8047
|
function emitMirror(onPreview, name, localPath, repoPath) {
|
|
@@ -7776,16 +8055,22 @@ function emitMirrorWet(onPreview, name, localPath, repoPath) {
|
|
|
7776
8055
|
if (onPreview) onPreview({ kind: "mirror", name, localPath, repoPath });
|
|
7777
8056
|
}
|
|
7778
8057
|
function notShared(policy, target) {
|
|
7779
|
-
return !policy.adoptNew && !
|
|
8058
|
+
return !policy.adoptNew && !existsSync42(target);
|
|
7780
8059
|
}
|
|
7781
8060
|
function mirrorOneSharedName(name, claude, repo, policy, opts) {
|
|
7782
|
-
const localPath =
|
|
7783
|
-
const target =
|
|
8061
|
+
const localPath = join56(claude, name);
|
|
8062
|
+
const target = join56(repo, "shared", name);
|
|
7784
8063
|
let stat;
|
|
7785
8064
|
try {
|
|
7786
|
-
stat =
|
|
8065
|
+
stat = lstatSync20(localPath, { throwIfNoEntry: false });
|
|
7787
8066
|
} catch (err) {
|
|
7788
8067
|
if (notShared(policy, target)) return;
|
|
8068
|
+
if (opts.dryRun === true) {
|
|
8069
|
+
warn(
|
|
8070
|
+
`${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`
|
|
8071
|
+
);
|
|
8072
|
+
return;
|
|
8073
|
+
}
|
|
7789
8074
|
warn(
|
|
7790
8075
|
`${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
8076
|
);
|
|
@@ -7800,9 +8085,9 @@ function mirrorOneSharedName(name, claude, repo, policy, opts) {
|
|
|
7800
8085
|
}
|
|
7801
8086
|
if (policy.backupTs !== void 0) backupRepoWrite(target, policy.backupTs, repo);
|
|
7802
8087
|
if (policy.overlay && stat.isDirectory()) {
|
|
7803
|
-
copyExtrasOverlayFiltered(localPath, target,
|
|
8088
|
+
copyExtrasOverlayFiltered(localPath, target, ALWAYS_NEVER_SYNC);
|
|
7804
8089
|
} else {
|
|
7805
|
-
copyExtrasFiltered(localPath, target,
|
|
8090
|
+
copyExtrasFiltered(localPath, target, ALWAYS_NEVER_SYNC);
|
|
7806
8091
|
}
|
|
7807
8092
|
emitMirrorWet(opts.onPreview, name, localPath, target);
|
|
7808
8093
|
}
|
|
@@ -7817,20 +8102,20 @@ function mirrorSharedNames(map, policy, opts = {}) {
|
|
|
7817
8102
|
}
|
|
7818
8103
|
}
|
|
7819
8104
|
function syncSharedLinksPush(map, opts = {}) {
|
|
7820
|
-
mirrorSharedNames(map, { adoptNew:
|
|
8105
|
+
mirrorSharedNames(map, { adoptNew: false, overlay: false }, opts);
|
|
7821
8106
|
}
|
|
7822
8107
|
function stageLocalSharedEdits(map, ts, opts = {}) {
|
|
7823
8108
|
mirrorSharedNames(map, { adoptNew: false, overlay: true, backupTs: ts }, opts);
|
|
7824
8109
|
}
|
|
7825
8110
|
function presentAt(abs) {
|
|
7826
8111
|
try {
|
|
7827
|
-
return
|
|
8112
|
+
return lstatSync20(abs, { throwIfNoEntry: false }) !== void 0;
|
|
7828
8113
|
} catch {
|
|
7829
8114
|
return true;
|
|
7830
8115
|
}
|
|
7831
8116
|
}
|
|
7832
8117
|
function removeUntrackedDenied(repo, path, segment, ts) {
|
|
7833
|
-
const abs =
|
|
8118
|
+
const abs = join56(repo, path);
|
|
7834
8119
|
const denied = `the path segment "${segment}" is on the never-sync list`;
|
|
7835
8120
|
if (!presentAt(abs)) {
|
|
7836
8121
|
warn(
|
|
@@ -7838,7 +8123,7 @@ function removeUntrackedDenied(repo, path, segment, ts) {
|
|
|
7838
8123
|
);
|
|
7839
8124
|
return;
|
|
7840
8125
|
}
|
|
7841
|
-
const snapshotted =
|
|
8126
|
+
const snapshotted = existsSync42(abs);
|
|
7842
8127
|
try {
|
|
7843
8128
|
backupRepoWrite(abs, ts, repo);
|
|
7844
8129
|
} catch (err) {
|
|
@@ -7874,7 +8159,7 @@ function reportTrackedDenied(repo, path, segment) {
|
|
|
7874
8159
|
);
|
|
7875
8160
|
return;
|
|
7876
8161
|
}
|
|
7877
|
-
if (!
|
|
8162
|
+
if (!existsSync42(join56(repo, path))) return;
|
|
7878
8163
|
warn(
|
|
7879
8164
|
`${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
8165
|
);
|
|
@@ -7892,8 +8177,8 @@ function revertDeniedMirrorPaths(repo, status, ts) {
|
|
|
7892
8177
|
|
|
7893
8178
|
// src/preview.ts
|
|
7894
8179
|
init_config();
|
|
7895
|
-
import { existsSync as
|
|
7896
|
-
import { join as
|
|
8180
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
8181
|
+
import { join as join58 } from "node:path";
|
|
7897
8182
|
|
|
7898
8183
|
// node_modules/diff/libesm/diff/base.js
|
|
7899
8184
|
var Diff = class {
|
|
@@ -8168,16 +8453,16 @@ function diffLinesToUnified(oldStr, newStr) {
|
|
|
8168
8453
|
|
|
8169
8454
|
// src/preview.skills.ts
|
|
8170
8455
|
init_config();
|
|
8171
|
-
import { existsSync as
|
|
8172
|
-
import { join as
|
|
8456
|
+
import { existsSync as existsSync43, readdirSync as readdirSync19 } from "node:fs";
|
|
8457
|
+
import { join as join57 } from "node:path";
|
|
8173
8458
|
function buildSkillsPreviewSection() {
|
|
8174
8459
|
const s = section("Skills");
|
|
8175
|
-
const sharedSkills =
|
|
8176
|
-
if (!
|
|
8177
|
-
const sharedNames =
|
|
8460
|
+
const sharedSkills = join57(repoHome(), "shared", "skills");
|
|
8461
|
+
if (!existsSync43(sharedSkills)) return s;
|
|
8462
|
+
const sharedNames = readdirSync19(sharedSkills, { encoding: "utf8" }).filter((name) => !isSkillExcluded(name)).sort((a, b) => a.localeCompare(b, "en"));
|
|
8178
8463
|
for (const name of sharedNames) addItem(s, name);
|
|
8179
|
-
const localSkills =
|
|
8180
|
-
const localOnly =
|
|
8464
|
+
const localSkills = join57(claudeHome(), "skills");
|
|
8465
|
+
const localOnly = existsSync43(localSkills) ? readdirSync19(localSkills, { encoding: "utf8" }).filter(
|
|
8181
8466
|
(name) => !isSkillExcluded(name) && !sharedNames.includes(name)
|
|
8182
8467
|
).length : 0;
|
|
8183
8468
|
if (localOnly > 0) {
|
|
@@ -8199,7 +8484,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
8199
8484
|
return lines.join("\n");
|
|
8200
8485
|
}
|
|
8201
8486
|
function readJsonOrNull(path) {
|
|
8202
|
-
if (!
|
|
8487
|
+
if (!existsSync44(path)) return null;
|
|
8203
8488
|
try {
|
|
8204
8489
|
return readJson(path);
|
|
8205
8490
|
} catch {
|
|
@@ -8213,12 +8498,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
8213
8498
|
}
|
|
8214
8499
|
const notes = [];
|
|
8215
8500
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
8216
|
-
if (hostOverrides === null &&
|
|
8501
|
+
if (hostOverrides === null && existsSync44(hostPath)) {
|
|
8217
8502
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
8218
8503
|
}
|
|
8219
8504
|
const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
|
|
8220
8505
|
const current = readJsonOrNull(settingsPath);
|
|
8221
|
-
if (current === null &&
|
|
8506
|
+
if (current === null && existsSync44(settingsPath)) {
|
|
8222
8507
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
8223
8508
|
}
|
|
8224
8509
|
const strippedCurrent = stripGsdHookEntries(current ?? {});
|
|
@@ -8285,9 +8570,9 @@ function computePreview(ts, map, verb = "pull", plans) {
|
|
|
8285
8570
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
8286
8571
|
});
|
|
8287
8572
|
const settingsResult = previewSettings(
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8573
|
+
join58(repo, "shared", "settings.base.json"),
|
|
8574
|
+
join58(repo, "hosts", `${HOST}.json`),
|
|
8575
|
+
join58(claude, "settings.json")
|
|
8291
8576
|
);
|
|
8292
8577
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
8293
8578
|
const sessions = section("Sessions");
|
|
@@ -8303,7 +8588,7 @@ function computePreview(ts, map, verb = "pull", plans) {
|
|
|
8303
8588
|
const extras = section("Extras");
|
|
8304
8589
|
let extrasSkipped = 0;
|
|
8305
8590
|
let extrasUnmapped = 0;
|
|
8306
|
-
if (
|
|
8591
|
+
if (existsSync44(join58(repo, "path-map.json")) && existsSync44(join58(repo, "shared", "extras"))) {
|
|
8307
8592
|
const extrasResult = remapExtrasPull(ts, { dryRun: true });
|
|
8308
8593
|
for (const entry of extrasResult.wouldPull) {
|
|
8309
8594
|
addItem(extras, entry);
|
|
@@ -8324,7 +8609,7 @@ function computePreview(ts, map, verb = "pull", plans) {
|
|
|
8324
8609
|
init_utils();
|
|
8325
8610
|
init_utils_json();
|
|
8326
8611
|
function readMapForMirror(mapPath) {
|
|
8327
|
-
if (!
|
|
8612
|
+
if (!existsSync45(mapPath)) return { projects: {} };
|
|
8328
8613
|
try {
|
|
8329
8614
|
return readPathMap(mapPath);
|
|
8330
8615
|
} catch {
|
|
@@ -8371,7 +8656,7 @@ function reconcileSharedLinksBeforePull(repo, ts) {
|
|
|
8371
8656
|
let namesDerived = false;
|
|
8372
8657
|
let derivedSharedDirs;
|
|
8373
8658
|
try {
|
|
8374
|
-
const map = readMapForMirror(
|
|
8659
|
+
const map = readMapForMirror(join59(repo, "path-map.json"));
|
|
8375
8660
|
if (map !== null) {
|
|
8376
8661
|
linkNames = allSharedLinks(map);
|
|
8377
8662
|
namesDerived = true;
|
|
@@ -8410,7 +8695,7 @@ function buildMirrorSection(events, discard) {
|
|
|
8410
8695
|
function describeSkippedMirrorDiscard(repo, ts) {
|
|
8411
8696
|
if (process.platform !== "win32") return null;
|
|
8412
8697
|
try {
|
|
8413
|
-
const map = readMapForMirror(
|
|
8698
|
+
const map = readMapForMirror(join59(repo, "path-map.json"));
|
|
8414
8699
|
const linkNames = map !== null ? allSharedLinks(map, { quiet: true }) : void 0;
|
|
8415
8700
|
const captures = [];
|
|
8416
8701
|
stageLocalSharedEdits(map, ts, {
|
|
@@ -8419,13 +8704,13 @@ function describeSkippedMirrorDiscard(repo, ts) {
|
|
|
8419
8704
|
linkNames
|
|
8420
8705
|
});
|
|
8421
8706
|
if (captures.length === 0) return null;
|
|
8422
|
-
return { count: captures.length, backupPath:
|
|
8707
|
+
return { count: captures.length, backupPath: join59(backupBase(), ts) };
|
|
8423
8708
|
} catch {
|
|
8424
8709
|
return null;
|
|
8425
8710
|
}
|
|
8426
8711
|
}
|
|
8427
8712
|
function planSharedReconcileBeforePull(repo, ts) {
|
|
8428
|
-
const map = readMapForMirror(
|
|
8713
|
+
const map = readMapForMirror(join59(repo, "path-map.json"));
|
|
8429
8714
|
const captures = [];
|
|
8430
8715
|
const linkNames = process.platform === "win32" && map !== null ? allSharedLinks(map) : void 0;
|
|
8431
8716
|
stageLocalSharedEdits(map, ts, { dryRun: true, onPreview: (e) => captures.push(e), linkNames });
|
|
@@ -8440,8 +8725,8 @@ function planSharedReconcileBeforePull(repo, ts) {
|
|
|
8440
8725
|
// src/commands.pull.collision.ts
|
|
8441
8726
|
init_config();
|
|
8442
8727
|
init_exit_codes();
|
|
8443
|
-
import { existsSync as
|
|
8444
|
-
import { join as
|
|
8728
|
+
import { existsSync as existsSync46, lstatSync as lstatSync21, readFileSync as readFileSync20, rmSync as rmSync19 } from "node:fs";
|
|
8729
|
+
import { join as join60 } from "node:path";
|
|
8445
8730
|
init_utils();
|
|
8446
8731
|
var SHARED_PREFIX = /^shared\//;
|
|
8447
8732
|
var EXTENSION = /(\.[^.]+)?$/;
|
|
@@ -8497,24 +8782,24 @@ function isContainedMirrorPath(rel) {
|
|
|
8497
8782
|
}
|
|
8498
8783
|
function matchesLocalOriginal(repo, rel) {
|
|
8499
8784
|
try {
|
|
8500
|
-
const local =
|
|
8501
|
-
return readFileSync20(
|
|
8785
|
+
const local = join60(claudeHome(), rel.replace(SHARED_PREFIX, ""));
|
|
8786
|
+
return readFileSync20(join60(repo, rel)).equals(readFileSync20(local));
|
|
8502
8787
|
} catch {
|
|
8503
8788
|
return false;
|
|
8504
8789
|
}
|
|
8505
8790
|
}
|
|
8506
8791
|
function removeMirroredCopies(repo, repoRelPaths) {
|
|
8507
8792
|
for (const rel of repoRelPaths.filter(isContainedMirrorPath)) {
|
|
8508
|
-
const abs =
|
|
8793
|
+
const abs = join60(repo, rel);
|
|
8509
8794
|
try {
|
|
8510
|
-
if (
|
|
8795
|
+
if (lstatSync21(abs, { throwIfNoEntry: false })?.isFile() !== true) continue;
|
|
8511
8796
|
if (!matchesLocalOriginal(repo, rel)) continue;
|
|
8512
8797
|
rmSync19(abs, { force: true });
|
|
8513
8798
|
} catch (err) {
|
|
8514
8799
|
warn(`could not remove nomad's copy at ${abs}: ${err.message}`);
|
|
8515
8800
|
}
|
|
8516
8801
|
}
|
|
8517
|
-
return repoRelPaths.every((rel) => !
|
|
8802
|
+
return repoRelPaths.every((rel) => !existsSync46(join60(repo, rel)));
|
|
8518
8803
|
}
|
|
8519
8804
|
function pullWithCollisionRunbook(repo, mirrored) {
|
|
8520
8805
|
try {
|
|
@@ -8596,7 +8881,27 @@ function recoverForceRemote(mode, repo) {
|
|
|
8596
8881
|
const { synced } = classifyTouched(allTouched);
|
|
8597
8882
|
if (synced.length > 0) {
|
|
8598
8883
|
die(
|
|
8599
|
-
"force-remote refused:
|
|
8884
|
+
"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
|
|
8885
|
+
// carrying a newline or a terminal control sequence would otherwise
|
|
8886
|
+
// split or rewrite the recovery steps printed below it. The
|
|
8887
|
+
// classification above uses the raw value.
|
|
8888
|
+
synced.map((p) => ` ${JSON.stringify(p)}`).join("\n") + `
|
|
8889
|
+
The comparison runs both ways, so a path can be listed because origin/main is ahead of
|
|
8890
|
+
you, with nothing of yours at risk on it.
|
|
8891
|
+
|
|
8892
|
+
Manual recovery. Step 1 is not optional: this refusal happens BEFORE recovery parks your
|
|
8893
|
+
local commits, so nothing has been saved for you yet and step 3 discards whatever is not
|
|
8894
|
+
on another ref.
|
|
8895
|
+
1. git branch nomad-rescue HEAD (keeps every local commit)
|
|
8896
|
+
2. copy any uncommitted work above OUT of the repo (it exists nowhere else)
|
|
8897
|
+
3. git reset --hard origin/main (this is what clears the refusal)
|
|
8898
|
+
4. nomad pull
|
|
8899
|
+
|
|
8900
|
+
Copying or moving the files alone does not clear it: git still reports a moved file as a
|
|
8901
|
+
deletion, and a committed change is still ahead of origin/main. The in-progress rebase or
|
|
8902
|
+
merge has ALREADY been aborted, so re-running 'nomad pull --force-remote' now behaves as
|
|
8903
|
+
an ordinary pull.
|
|
8904
|
+
(see FAQ: "Every pull fails with unmerged files")`
|
|
8600
8905
|
);
|
|
8601
8906
|
}
|
|
8602
8907
|
const branchName = freshStrandedBranch(repo);
|
|
@@ -8704,7 +9009,7 @@ function runPullCore(opts = {}) {
|
|
|
8704
9009
|
const ts = freshBackupTs(backup);
|
|
8705
9010
|
const recovered = handleWedge(repo, forceRemote);
|
|
8706
9011
|
if (!dryRun) {
|
|
8707
|
-
const backupRoot =
|
|
9012
|
+
const backupRoot = join61(backup, ts);
|
|
8708
9013
|
try {
|
|
8709
9014
|
mkdirSync14(backupRoot, { recursive: true });
|
|
8710
9015
|
} catch (err) {
|
|
@@ -8727,8 +9032,8 @@ function runPullCore(opts = {}) {
|
|
|
8727
9032
|
pullWithCollisionRunbook(repo, mirrored);
|
|
8728
9033
|
});
|
|
8729
9034
|
assertNoAutostashConflict(repo, "nomad pull");
|
|
8730
|
-
const mapPath =
|
|
8731
|
-
const map =
|
|
9035
|
+
const mapPath = join61(repo, "path-map.json");
|
|
9036
|
+
const map = existsSync47(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
8732
9037
|
const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
|
|
8733
9038
|
if (dryRun) {
|
|
8734
9039
|
computePreview(ts, map, "pull", plansAgainst(sharedPlans, map));
|
|
@@ -8755,8 +9060,8 @@ function runPullCore(opts = {}) {
|
|
|
8755
9060
|
}
|
|
8756
9061
|
function cmdPull(opts = {}) {
|
|
8757
9062
|
const repo = repoHome();
|
|
8758
|
-
if (!
|
|
8759
|
-
if (!
|
|
9063
|
+
if (!existsSync47(repo)) die(`repo not cloned at ${repo}`);
|
|
9064
|
+
if (!existsSync47(join61(repo, "shared", "settings.base.json"))) {
|
|
8760
9065
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
8761
9066
|
}
|
|
8762
9067
|
const handle = acquireLock("pull");
|
|
@@ -8779,13 +9084,13 @@ function cmdPull(opts = {}) {
|
|
|
8779
9084
|
|
|
8780
9085
|
// src/commands.push.ts
|
|
8781
9086
|
init_config();
|
|
8782
|
-
import { existsSync as
|
|
8783
|
-
import { join as
|
|
9087
|
+
import { existsSync as existsSync51 } from "node:fs";
|
|
9088
|
+
import { join as join66 } from "node:path";
|
|
8784
9089
|
|
|
8785
9090
|
// src/commands.push.selection.ts
|
|
8786
9091
|
init_config();
|
|
8787
|
-
import { existsSync as
|
|
8788
|
-
import { join as
|
|
9092
|
+
import { existsSync as existsSync48, statSync as statSync11 } from "node:fs";
|
|
9093
|
+
import { join as join62 } from "node:path";
|
|
8789
9094
|
init_utils_json();
|
|
8790
9095
|
function buildCurrentMap(map) {
|
|
8791
9096
|
const current = {};
|
|
@@ -8794,8 +9099,8 @@ function buildCurrentMap(map) {
|
|
|
8794
9099
|
for (const [, hostMap] of Object.entries(map.projects)) {
|
|
8795
9100
|
const localPath = hostMap[HOST];
|
|
8796
9101
|
if (!localPath) continue;
|
|
8797
|
-
const localDir =
|
|
8798
|
-
if (!
|
|
9102
|
+
const localDir = join62(claude, "projects", encodePath(localPath));
|
|
9103
|
+
if (!existsSync48(localDir)) continue;
|
|
8799
9104
|
for (const f of enumerateSourceFiles(localDir)) {
|
|
8800
9105
|
const st = statSync11(f);
|
|
8801
9106
|
current[f] = { size: st.size, mtime: st.mtimeMs };
|
|
@@ -8826,7 +9131,7 @@ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
|
|
|
8826
9131
|
};
|
|
8827
9132
|
}
|
|
8828
9133
|
function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
|
|
8829
|
-
const map =
|
|
9134
|
+
const map = existsSync48(mapPath) ? readPathMap(mapPath) : null;
|
|
8830
9135
|
const { selection, newManifest } = computePushSelection(
|
|
8831
9136
|
map,
|
|
8832
9137
|
old,
|
|
@@ -8919,14 +9224,14 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
8919
9224
|
|
|
8920
9225
|
// src/commands.push.settings.ts
|
|
8921
9226
|
init_config();
|
|
8922
|
-
import { existsSync as
|
|
8923
|
-
import { join as
|
|
9227
|
+
import { existsSync as existsSync49 } from "node:fs";
|
|
9228
|
+
import { join as join63 } from "node:path";
|
|
8924
9229
|
init_utils();
|
|
8925
9230
|
init_utils_fs();
|
|
8926
9231
|
init_utils_json();
|
|
8927
9232
|
function stripGsdHooksFromBase(repo, backup) {
|
|
8928
|
-
const basePath =
|
|
8929
|
-
if (!
|
|
9233
|
+
const basePath = join63(repo, "shared", "settings.base.json");
|
|
9234
|
+
if (!existsSync49(basePath)) return;
|
|
8930
9235
|
let base;
|
|
8931
9236
|
try {
|
|
8932
9237
|
base = readJson(basePath);
|
|
@@ -8940,14 +9245,14 @@ function stripGsdHooksFromBase(repo, backup) {
|
|
|
8940
9245
|
writeJsonAtomic(basePath, stripped);
|
|
8941
9246
|
}
|
|
8942
9247
|
function reportSettingsAheadDrift(repo) {
|
|
8943
|
-
const basePath =
|
|
8944
|
-
if (!
|
|
8945
|
-
const settingsPath =
|
|
8946
|
-
if (!
|
|
9248
|
+
const basePath = join63(repo, "shared", "settings.base.json");
|
|
9249
|
+
if (!existsSync49(basePath)) return;
|
|
9250
|
+
const settingsPath = join63(claudeHome(), "settings.json");
|
|
9251
|
+
if (!existsSync49(settingsPath)) return;
|
|
8947
9252
|
try {
|
|
8948
9253
|
const base = readJson(basePath);
|
|
8949
|
-
const hostPath =
|
|
8950
|
-
const overrides =
|
|
9254
|
+
const hostPath = join63(repo, "hosts", `${HOST}.json`);
|
|
9255
|
+
const overrides = existsSync49(hostPath) ? readJson(hostPath) : {};
|
|
8951
9256
|
const merged = deepMerge(base, overrides);
|
|
8952
9257
|
const settings = readJson(settingsPath);
|
|
8953
9258
|
const { ahead } = classifySettingsDrift(merged, settings);
|
|
@@ -8964,12 +9269,12 @@ function reportSettingsAheadDrift(repo) {
|
|
|
8964
9269
|
// src/commands.push.guards.ts
|
|
8965
9270
|
init_push_checks();
|
|
8966
9271
|
init_utils();
|
|
8967
|
-
import { join as
|
|
9272
|
+
import { join as join64, relative as relative10 } from "node:path";
|
|
8968
9273
|
function guardGitlinks(repo) {
|
|
8969
|
-
const gitlinks = findGitlinks(
|
|
9274
|
+
const gitlinks = findGitlinks(join64(repo, "shared"));
|
|
8970
9275
|
if (gitlinks.length === 0) return;
|
|
8971
9276
|
for (const p of gitlinks) {
|
|
8972
|
-
const rel =
|
|
9277
|
+
const rel = relative10(repo, p).replaceAll("\\", "/");
|
|
8973
9278
|
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
8974
9279
|
}
|
|
8975
9280
|
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
@@ -9076,9 +9381,9 @@ init_color();
|
|
|
9076
9381
|
init_config();
|
|
9077
9382
|
init_config_sharedDirs_guard();
|
|
9078
9383
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
9079
|
-
import { copyFileSync, existsSync as
|
|
9384
|
+
import { copyFileSync, existsSync as existsSync50, lstatSync as lstatSync22, mkdirSync as mkdirSync15, readdirSync as readdirSync20, rmSync as rmSync20 } from "node:fs";
|
|
9080
9385
|
import { homedir as homedir7 } from "node:os";
|
|
9081
|
-
import { join as
|
|
9386
|
+
import { join as join65, relative as relative11, sep as sep14 } from "node:path";
|
|
9082
9387
|
init_push_leak_verdict();
|
|
9083
9388
|
init_push_gitleaks();
|
|
9084
9389
|
init_utils_fs();
|
|
@@ -9090,7 +9395,7 @@ function stageSessionDir(localDir, dstDir, changed) {
|
|
|
9090
9395
|
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
9091
9396
|
if (matching.length === 0) return false;
|
|
9092
9397
|
for (const src of matching) {
|
|
9093
|
-
copyFileAtomic(src,
|
|
9398
|
+
copyFileAtomic(src, join65(dstDir, relative11(localDir, src)));
|
|
9094
9399
|
}
|
|
9095
9400
|
return true;
|
|
9096
9401
|
}
|
|
@@ -9106,14 +9411,14 @@ function stageSessions(tmpRoot, map, changed) {
|
|
|
9106
9411
|
if (!p || p === "TBD") continue;
|
|
9107
9412
|
reverse.set(encodePath(p), logical);
|
|
9108
9413
|
}
|
|
9109
|
-
const localProjects =
|
|
9110
|
-
if (!
|
|
9414
|
+
const localProjects = join65(claudeHome(), "projects");
|
|
9415
|
+
if (!existsSync50(localProjects)) return 0;
|
|
9111
9416
|
let staged = 0;
|
|
9112
|
-
for (const dir of
|
|
9417
|
+
for (const dir of readdirSync20(localProjects)) {
|
|
9113
9418
|
const logical = reverse.get(dir);
|
|
9114
9419
|
if (!logical) continue;
|
|
9115
|
-
const localDir =
|
|
9116
|
-
const dstDir =
|
|
9420
|
+
const localDir = join65(localProjects, dir);
|
|
9421
|
+
const dstDir = join65(tmpRoot, "shared", "projects", logical);
|
|
9117
9422
|
if (stageSessionDir(localDir, dstDir, changed)) staged++;
|
|
9118
9423
|
}
|
|
9119
9424
|
return staged;
|
|
@@ -9127,11 +9432,11 @@ function stageExtras(tmpRoot, map) {
|
|
|
9127
9432
|
assertSafeLogical(logical);
|
|
9128
9433
|
const localRoot = map.projects[logical]?.[HOST];
|
|
9129
9434
|
if (!localRoot || localRoot === "TBD") continue;
|
|
9130
|
-
for (const
|
|
9131
|
-
if (!whitelist.includes(
|
|
9132
|
-
const src =
|
|
9133
|
-
if (!
|
|
9134
|
-
const dst =
|
|
9435
|
+
for (const dirname15 of dirnames) {
|
|
9436
|
+
if (!whitelist.includes(dirname15)) continue;
|
|
9437
|
+
const src = join65(localRoot, dirname15);
|
|
9438
|
+
if (!existsSync50(src)) continue;
|
|
9439
|
+
const dst = join65(tmpRoot, "shared", "extras", logical, dirname15);
|
|
9135
9440
|
copyExtras(src, dst);
|
|
9136
9441
|
staged++;
|
|
9137
9442
|
}
|
|
@@ -9139,19 +9444,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
9139
9444
|
return staged;
|
|
9140
9445
|
}
|
|
9141
9446
|
function stageSkills(tmpRoot) {
|
|
9142
|
-
const localSkills =
|
|
9143
|
-
const stat =
|
|
9447
|
+
const localSkills = join65(claudeHome(), "skills");
|
|
9448
|
+
const stat = lstatSync22(localSkills, { throwIfNoEntry: false });
|
|
9144
9449
|
if (stat === void 0 || stat.isSymbolicLink()) return 0;
|
|
9145
|
-
const names =
|
|
9450
|
+
const names = readdirSync20(localSkills, { encoding: "utf8" }).filter((n) => !isSkillExcluded(n));
|
|
9146
9451
|
if (names.length === 0) return 0;
|
|
9147
|
-
copySkillsPush(localSkills,
|
|
9452
|
+
copySkillsPush(localSkills, join65(tmpRoot, "shared", "skills"));
|
|
9148
9453
|
return names.length;
|
|
9149
9454
|
}
|
|
9150
9455
|
function previewPushLeaks(map, opts = {}) {
|
|
9151
|
-
const cacheDir =
|
|
9456
|
+
const cacheDir = join65(homedir7(), ".cache", "claude-nomad");
|
|
9152
9457
|
mkdirSync15(cacheDir, { recursive: true });
|
|
9153
9458
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes4(4).toString("hex")}`;
|
|
9154
|
-
const tmpRoot =
|
|
9459
|
+
const tmpRoot = join65(cacheDir, `push-preview-tree-${stamp}`);
|
|
9155
9460
|
try {
|
|
9156
9461
|
const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
|
|
9157
9462
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
@@ -9159,9 +9464,9 @@ function previewPushLeaks(map, opts = {}) {
|
|
|
9159
9464
|
if (sessionCount + extrasCount + skillCount === 0) {
|
|
9160
9465
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
9161
9466
|
}
|
|
9162
|
-
const ignoreFile =
|
|
9163
|
-
if (
|
|
9164
|
-
copyFileSync(ignoreFile,
|
|
9467
|
+
const ignoreFile = join65(repoHome(), ".gitleaksignore");
|
|
9468
|
+
if (existsSync50(ignoreFile)) {
|
|
9469
|
+
copyFileSync(ignoreFile, join65(tmpRoot, ".gitleaksignore"));
|
|
9165
9470
|
}
|
|
9166
9471
|
let findings;
|
|
9167
9472
|
try {
|
|
@@ -9289,7 +9594,7 @@ async function runPushCore(opts = {}) {
|
|
|
9289
9594
|
const scannerVersion = probeGitleaks();
|
|
9290
9595
|
const configHash = computeConfigHash();
|
|
9291
9596
|
const old = readManifest(manifestPath());
|
|
9292
|
-
const mapPath =
|
|
9597
|
+
const mapPath = join66(repo, "path-map.json");
|
|
9293
9598
|
const { map, selection, newManifest } = loadSelectionForPush(
|
|
9294
9599
|
mapPath,
|
|
9295
9600
|
old,
|
|
@@ -9342,7 +9647,7 @@ async function cmdPush(opts = {}) {
|
|
|
9342
9647
|
opts.allowRule
|
|
9343
9648
|
);
|
|
9344
9649
|
const repo = repoHome();
|
|
9345
|
-
if (!
|
|
9650
|
+
if (!existsSync51(repo)) die(`repo not cloned at ${repo}`);
|
|
9346
9651
|
const handle = acquireLock("push");
|
|
9347
9652
|
if (handle === null) process.exit(0);
|
|
9348
9653
|
try {
|
|
@@ -9360,8 +9665,8 @@ async function cmdPush(opts = {}) {
|
|
|
9360
9665
|
}
|
|
9361
9666
|
|
|
9362
9667
|
// src/commands.sync.ts
|
|
9363
|
-
import { existsSync as
|
|
9364
|
-
import { join as
|
|
9668
|
+
import { existsSync as existsSync52 } from "node:fs";
|
|
9669
|
+
import { join as join67 } from "node:path";
|
|
9365
9670
|
init_config();
|
|
9366
9671
|
init_color();
|
|
9367
9672
|
init_utils();
|
|
@@ -9502,8 +9807,8 @@ async function cmdSync(opts = {}) {
|
|
|
9502
9807
|
const dryRun = opts.dryRun === true;
|
|
9503
9808
|
const verbose = opts.verbose === true;
|
|
9504
9809
|
const repo = repoHome();
|
|
9505
|
-
if (!
|
|
9506
|
-
if (!
|
|
9810
|
+
if (!existsSync52(repo)) die(`repo not cloned at ${repo}`);
|
|
9811
|
+
if (!existsSync52(join67(repo, "shared", "settings.base.json"))) {
|
|
9507
9812
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
9508
9813
|
}
|
|
9509
9814
|
const handle = acquireLock("sync");
|
|
@@ -9579,8 +9884,8 @@ init_config();
|
|
|
9579
9884
|
// src/crash-report.write.ts
|
|
9580
9885
|
init_config();
|
|
9581
9886
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
9582
|
-
import { mkdirSync as mkdirSync16, readdirSync as
|
|
9583
|
-
import { join as
|
|
9887
|
+
import { mkdirSync as mkdirSync16, readdirSync as readdirSync21, statSync as statSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
|
|
9888
|
+
import { join as join69 } from "node:path";
|
|
9584
9889
|
|
|
9585
9890
|
// src/crash-report.ts
|
|
9586
9891
|
var CRASH_MAX_STACK_LINES = 50;
|
|
@@ -9665,15 +9970,15 @@ function buildCrashReport(input) {
|
|
|
9665
9970
|
// src/crash-report.redact.ts
|
|
9666
9971
|
import { mkdtempSync as mkdtempSync3, rmSync as rmSync21, writeFileSync as writeFileSync10 } from "node:fs";
|
|
9667
9972
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
9668
|
-
import { join as
|
|
9973
|
+
import { join as join68 } from "node:path";
|
|
9669
9974
|
init_push_gitleaks_scan();
|
|
9670
9975
|
var CRASH_SCAN_TIMEOUT_MS = 3e3;
|
|
9671
9976
|
var SCAN_UNAVAILABLE_ADVISORY = "\n\n[gitleaks value-based scan unavailable; only structural redaction applied. Review before sharing this file publicly.]\n";
|
|
9672
9977
|
function redactWithGitleaks(text, scan = scanFile) {
|
|
9673
9978
|
let dir;
|
|
9674
9979
|
try {
|
|
9675
|
-
dir = mkdtempSync3(
|
|
9676
|
-
const tmp =
|
|
9980
|
+
dir = mkdtempSync3(join68(tmpdir2(), "nomad-crash-scan-"));
|
|
9981
|
+
const tmp = join68(dir, "crash.txt");
|
|
9677
9982
|
writeFileSync10(tmp, text, { mode: 384 });
|
|
9678
9983
|
const findings = scan(tmp, false, CRASH_SCAN_TIMEOUT_MS);
|
|
9679
9984
|
if (findings === null) return text + SCAN_UNAVAILABLE_ADVISORY;
|
|
@@ -9696,9 +10001,9 @@ init_utils();
|
|
|
9696
10001
|
var CRASH_RETENTION_KEEP = 20;
|
|
9697
10002
|
function listCrashFiles(dir = crashDir()) {
|
|
9698
10003
|
try {
|
|
9699
|
-
return
|
|
10004
|
+
return readdirSync21(dir).flatMap((name) => {
|
|
9700
10005
|
try {
|
|
9701
|
-
return [{ name, mtimeMs: statSync12(
|
|
10006
|
+
return [{ name, mtimeMs: statSync12(join69(dir, name)).mtimeMs }];
|
|
9702
10007
|
} catch {
|
|
9703
10008
|
return [];
|
|
9704
10009
|
}
|
|
@@ -9712,7 +10017,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
9712
10017
|
const targets = prunableByCount(files, keep);
|
|
9713
10018
|
for (const name of targets) {
|
|
9714
10019
|
try {
|
|
9715
|
-
unlinkSync2(
|
|
10020
|
+
unlinkSync2(join69(dir, name));
|
|
9716
10021
|
} catch {
|
|
9717
10022
|
}
|
|
9718
10023
|
}
|
|
@@ -9720,7 +10025,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
9720
10025
|
function writeCrashReport(text, dir = crashDir()) {
|
|
9721
10026
|
mkdirSync16(dir, { recursive: true, mode: 448 });
|
|
9722
10027
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes5(4).toString("hex")}`;
|
|
9723
|
-
const path =
|
|
10028
|
+
const path = join69(dir, `crash-${stamp}.txt`);
|
|
9724
10029
|
writeFileSync11(path, text, { mode: 384 });
|
|
9725
10030
|
pruneCrashDir(dir);
|
|
9726
10031
|
return path;
|
|
@@ -9752,18 +10057,18 @@ function handleCrash(err, argv, opts) {
|
|
|
9752
10057
|
|
|
9753
10058
|
// src/diff.ts
|
|
9754
10059
|
init_config();
|
|
9755
|
-
import { existsSync as
|
|
9756
|
-
import { join as
|
|
10060
|
+
import { existsSync as existsSync53 } from "node:fs";
|
|
10061
|
+
import { join as join70 } from "node:path";
|
|
9757
10062
|
init_utils();
|
|
9758
10063
|
init_utils_fs();
|
|
9759
10064
|
init_utils_json();
|
|
9760
10065
|
function cmdDiff() {
|
|
9761
10066
|
try {
|
|
9762
10067
|
const repo = repoHome();
|
|
9763
|
-
if (!
|
|
10068
|
+
if (!existsSync53(repo)) die(`repo not cloned at ${repo}`);
|
|
9764
10069
|
const ts = freshBackupTs(backupBase());
|
|
9765
|
-
const mapPath =
|
|
9766
|
-
const map =
|
|
10070
|
+
const mapPath = join70(repo, "path-map.json");
|
|
10071
|
+
const map = existsSync53(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
9767
10072
|
divergenceCheckExtras(ts);
|
|
9768
10073
|
computePreview(ts, map, "diff");
|
|
9769
10074
|
} catch (err) {
|
|
@@ -9778,8 +10083,8 @@ function cmdDiff() {
|
|
|
9778
10083
|
|
|
9779
10084
|
// src/init.ts
|
|
9780
10085
|
init_config();
|
|
9781
|
-
import { existsSync as
|
|
9782
|
-
import { join as
|
|
10086
|
+
import { existsSync as existsSync55, mkdirSync as mkdirSync17, writeFileSync as writeFileSync12 } from "node:fs";
|
|
10087
|
+
import { join as join72 } from "node:path";
|
|
9783
10088
|
|
|
9784
10089
|
// src/init.gh-onboard.ts
|
|
9785
10090
|
init_config();
|
|
@@ -9861,33 +10166,33 @@ init_config();
|
|
|
9861
10166
|
init_utils();
|
|
9862
10167
|
init_utils_fs();
|
|
9863
10168
|
init_utils_json();
|
|
9864
|
-
import { copyFileSync as copyFileSync2, cpSync as
|
|
9865
|
-
import { join as
|
|
10169
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync9, existsSync as existsSync54, rmSync as rmSync22, statSync as statSync13 } from "node:fs";
|
|
10170
|
+
import { join as join71 } from "node:path";
|
|
9866
10171
|
function snapshotIntoShared(map) {
|
|
9867
10172
|
const repo = repoHome();
|
|
9868
10173
|
const claude = claudeHome();
|
|
9869
10174
|
for (const name of allSharedLinks(map)) {
|
|
9870
|
-
const src =
|
|
9871
|
-
if (!
|
|
9872
|
-
const dst =
|
|
10175
|
+
const src = join71(claude, name);
|
|
10176
|
+
if (!existsSync54(src)) continue;
|
|
10177
|
+
const dst = join71(repo, "shared", name);
|
|
9873
10178
|
if (statSync13(src).isDirectory()) {
|
|
9874
|
-
const gk =
|
|
9875
|
-
if (
|
|
9876
|
-
|
|
10179
|
+
const gk = join71(dst, ".gitkeep");
|
|
10180
|
+
if (existsSync54(gk)) rmSync22(gk);
|
|
10181
|
+
cpSync9(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
9877
10182
|
} else {
|
|
9878
10183
|
copyFileSync2(src, dst);
|
|
9879
10184
|
}
|
|
9880
10185
|
log(`snapshotted shared/${name} from ${src}`);
|
|
9881
10186
|
}
|
|
9882
|
-
const userSettings =
|
|
9883
|
-
if (
|
|
10187
|
+
const userSettings = join71(claude, "settings.json");
|
|
10188
|
+
if (existsSync54(userSettings)) {
|
|
9884
10189
|
let parsed;
|
|
9885
10190
|
try {
|
|
9886
10191
|
parsed = readJson(userSettings);
|
|
9887
10192
|
} catch (err) {
|
|
9888
10193
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
9889
10194
|
}
|
|
9890
|
-
const hostFile =
|
|
10195
|
+
const hostFile = join71(repo, "hosts", `${HOST}.json`);
|
|
9891
10196
|
writeJsonAtomic(hostFile, parsed);
|
|
9892
10197
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
9893
10198
|
}
|
|
@@ -9901,14 +10206,14 @@ var GITATTRIBUTES = "# nomad: sync content is byte-managed, disable all line-end
|
|
|
9901
10206
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
9902
10207
|
function preflightConflict(repoHome2) {
|
|
9903
10208
|
const candidates = [
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
10209
|
+
join72(repoHome2, "shared", "settings.base.json"),
|
|
10210
|
+
join72(repoHome2, "shared", "CLAUDE.md"),
|
|
10211
|
+
join72(repoHome2, "path-map.json"),
|
|
10212
|
+
join72(repoHome2, "hosts"),
|
|
10213
|
+
join72(repoHome2, "shared")
|
|
9909
10214
|
];
|
|
9910
10215
|
for (const c of candidates) {
|
|
9911
|
-
if (
|
|
10216
|
+
if (existsSync55(c)) return c;
|
|
9912
10217
|
}
|
|
9913
10218
|
return null;
|
|
9914
10219
|
}
|
|
@@ -9926,27 +10231,27 @@ function cmdInit(opts = {}) {
|
|
|
9926
10231
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
9927
10232
|
}
|
|
9928
10233
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
9929
|
-
mkdirSync17(
|
|
9930
|
-
mkdirSync17(
|
|
10234
|
+
mkdirSync17(join72(repo, "shared"), { recursive: true });
|
|
10235
|
+
mkdirSync17(join72(repo, "hosts"), { recursive: true });
|
|
9931
10236
|
for (const name of SHARED_KEEP_DIRS) {
|
|
9932
|
-
mkdirSync17(
|
|
10237
|
+
mkdirSync17(join72(repo, "shared", name), { recursive: true });
|
|
9933
10238
|
}
|
|
9934
|
-
const userClaudeMd =
|
|
9935
|
-
if (!snapshot || !
|
|
9936
|
-
writeFileSync12(
|
|
10239
|
+
const userClaudeMd = join72(claude, "CLAUDE.md");
|
|
10240
|
+
if (!snapshot || !existsSync55(userClaudeMd)) {
|
|
10241
|
+
writeFileSync12(join72(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
9937
10242
|
item("created shared/CLAUDE.md");
|
|
9938
10243
|
}
|
|
9939
10244
|
for (const name of SHARED_KEEP_DIRS) {
|
|
9940
|
-
writeFileSync12(
|
|
10245
|
+
writeFileSync12(join72(repo, "shared", name, ".gitkeep"), "");
|
|
9941
10246
|
item(`created shared/${name}/.gitkeep`);
|
|
9942
10247
|
}
|
|
9943
|
-
writeFileSync12(
|
|
10248
|
+
writeFileSync12(join72(repo, "hosts", ".gitkeep"), "");
|
|
9944
10249
|
item("created hosts/.gitkeep");
|
|
9945
|
-
writeJsonAtomic(
|
|
10250
|
+
writeJsonAtomic(join72(repo, "shared", "settings.base.json"), {});
|
|
9946
10251
|
item("created shared/settings.base.json");
|
|
9947
|
-
writeJsonAtomic(
|
|
10252
|
+
writeJsonAtomic(join72(repo, "path-map.json"), { projects: {} });
|
|
9948
10253
|
item("created path-map.json");
|
|
9949
|
-
writeFileSync12(
|
|
10254
|
+
writeFileSync12(join72(repo, ".gitattributes"), GITATTRIBUTES);
|
|
9950
10255
|
item("created .gitattributes");
|
|
9951
10256
|
if (snapshot) {
|
|
9952
10257
|
snapshotIntoShared({ projects: {} });
|
|
@@ -10012,21 +10317,21 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
10012
10317
|
// src/init.prompt.ts
|
|
10013
10318
|
init_config();
|
|
10014
10319
|
init_utils();
|
|
10015
|
-
import { existsSync as
|
|
10016
|
-
import { join as
|
|
10320
|
+
import { existsSync as existsSync56, readdirSync as readdirSync22, statSync as statSync14 } from "node:fs";
|
|
10321
|
+
import { join as join73 } from "node:path";
|
|
10017
10322
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
10018
10323
|
function nonEmptyExists(path) {
|
|
10019
|
-
if (!
|
|
10324
|
+
if (!existsSync56(path)) return false;
|
|
10020
10325
|
try {
|
|
10021
|
-
if (statSync14(path).isDirectory()) return
|
|
10326
|
+
if (statSync14(path).isDirectory()) return readdirSync22(path).length > 0;
|
|
10022
10327
|
return true;
|
|
10023
10328
|
} catch {
|
|
10024
10329
|
return false;
|
|
10025
10330
|
}
|
|
10026
10331
|
}
|
|
10027
10332
|
function hasExistingClaudeConfig(claudeHome2) {
|
|
10028
|
-
if (
|
|
10029
|
-
return SHARED_LINKS.some((name) => nonEmptyExists(
|
|
10333
|
+
if (existsSync56(join73(claudeHome2, "settings.json"))) return true;
|
|
10334
|
+
return SHARED_LINKS.some((name) => nonEmptyExists(join73(claudeHome2, name)));
|
|
10030
10335
|
}
|
|
10031
10336
|
async function confirmSnapshotDefault(claudeHome2) {
|
|
10032
10337
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
@@ -10333,7 +10638,7 @@ function parseSyncArgs(argv) {
|
|
|
10333
10638
|
// package.json
|
|
10334
10639
|
var package_default = {
|
|
10335
10640
|
name: "claude-nomad",
|
|
10336
|
-
version: "0.
|
|
10641
|
+
version: "0.66.0",
|
|
10337
10642
|
type: "module",
|
|
10338
10643
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
10339
10644
|
keywords: [
|
|
@@ -10578,15 +10883,15 @@ var DEFAULT_HELP = [
|
|
|
10578
10883
|
init_config();
|
|
10579
10884
|
init_utils();
|
|
10580
10885
|
init_utils_json();
|
|
10581
|
-
import { existsSync as
|
|
10582
|
-
import { join as
|
|
10886
|
+
import { existsSync as existsSync57, readFileSync as readFileSync21, readdirSync as readdirSync23 } from "node:fs";
|
|
10887
|
+
import { join as join74 } from "node:path";
|
|
10583
10888
|
function resumeCmd(sessionId) {
|
|
10584
10889
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
10585
10890
|
fail(`invalid session id: ${sessionId}`);
|
|
10586
10891
|
process.exit(1);
|
|
10587
10892
|
}
|
|
10588
|
-
const projectsRoot =
|
|
10589
|
-
if (!
|
|
10893
|
+
const projectsRoot = join74(claudeHome(), "projects");
|
|
10894
|
+
if (!existsSync57(projectsRoot)) {
|
|
10590
10895
|
fail(`${projectsRoot} does not exist`);
|
|
10591
10896
|
process.exit(1);
|
|
10592
10897
|
}
|
|
@@ -10600,8 +10905,8 @@ function resumeCmd(sessionId) {
|
|
|
10600
10905
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
10601
10906
|
process.exit(1);
|
|
10602
10907
|
}
|
|
10603
|
-
const mapPath =
|
|
10604
|
-
if (!
|
|
10908
|
+
const mapPath = join74(repoHome(), "path-map.json");
|
|
10909
|
+
if (!existsSync57(mapPath)) {
|
|
10605
10910
|
fail("path-map.json missing");
|
|
10606
10911
|
process.exit(1);
|
|
10607
10912
|
}
|
|
@@ -10623,9 +10928,9 @@ function resumeCmd(sessionId) {
|
|
|
10623
10928
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
10624
10929
|
}
|
|
10625
10930
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
10626
|
-
for (const dir of
|
|
10627
|
-
const candidate =
|
|
10628
|
-
if (
|
|
10931
|
+
for (const dir of readdirSync23(projectsRoot)) {
|
|
10932
|
+
const candidate = join74(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
10933
|
+
if (existsSync57(candidate)) return candidate;
|
|
10629
10934
|
}
|
|
10630
10935
|
return null;
|
|
10631
10936
|
}
|