claude-nomad 0.53.3 → 0.55.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 +30 -0
- package/README.md +72 -11
- package/dist/nomad.mjs +694 -397
- package/package.json +1 -1
package/dist/nomad.mjs
CHANGED
|
@@ -614,19 +614,19 @@ var init_utils_fs = __esm({
|
|
|
614
614
|
});
|
|
615
615
|
|
|
616
616
|
// src/commands.pull.wedge.ts
|
|
617
|
-
import { execFileSync as
|
|
618
|
-
import { existsSync as
|
|
619
|
-
import { join as
|
|
617
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
618
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
619
|
+
import { join as join15 } from "node:path";
|
|
620
620
|
function detectWedge(repo) {
|
|
621
|
-
const g =
|
|
622
|
-
if (
|
|
623
|
-
if (
|
|
621
|
+
const g = join15(repo, ".git");
|
|
622
|
+
if (existsSync13(join15(g, "rebase-merge")) || existsSync13(join15(g, "rebase-apply"))) return "rebase";
|
|
623
|
+
if (existsSync13(join15(g, "MERGE_HEAD"))) return "merge";
|
|
624
624
|
return null;
|
|
625
625
|
}
|
|
626
626
|
function unmergedIndexPresent(repo) {
|
|
627
627
|
let raw;
|
|
628
628
|
try {
|
|
629
|
-
raw =
|
|
629
|
+
raw = execFileSync3("git", ["diff", "--diff-filter=U", "--name-only", "-z"], {
|
|
630
630
|
cwd: repo,
|
|
631
631
|
stdio: ["ignore", "pipe", "pipe"],
|
|
632
632
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -660,7 +660,7 @@ function wedgeMarkerRunbookText(state) {
|
|
|
660
660
|
function orphanedAutostashPresent(repo) {
|
|
661
661
|
let raw;
|
|
662
662
|
try {
|
|
663
|
-
raw =
|
|
663
|
+
raw = execFileSync3("git", ["stash", "list"], {
|
|
664
664
|
cwd: repo,
|
|
665
665
|
stdio: ["ignore", "pipe", "pipe"],
|
|
666
666
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -677,15 +677,15 @@ var init_commands_pull_wedge = __esm({
|
|
|
677
677
|
});
|
|
678
678
|
|
|
679
679
|
// src/push-gitleaks.config.ts
|
|
680
|
-
import { existsSync as
|
|
680
|
+
import { existsSync as existsSync14, mkdtempSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
681
681
|
import { tmpdir } from "node:os";
|
|
682
|
-
import { join as
|
|
682
|
+
import { join as join16 } from "node:path";
|
|
683
683
|
import { fileURLToPath } from "node:url";
|
|
684
684
|
function resolveTomlPath(repo = repoHome()) {
|
|
685
|
-
const repoToml =
|
|
686
|
-
if (
|
|
685
|
+
const repoToml = join16(repo, ".gitleaks.toml");
|
|
686
|
+
if (existsSync14(repoToml)) return repoToml;
|
|
687
687
|
const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
|
|
688
|
-
return
|
|
688
|
+
return existsSync14(bundled) ? bundled : null;
|
|
689
689
|
}
|
|
690
690
|
function assertOverlayAllowlistsScoped(overlayBody) {
|
|
691
691
|
if (OVERLAY_INLINE_ALLOWLIST_RE.test(overlayBody)) {
|
|
@@ -723,17 +723,17 @@ function buildOverlayTempConfig(overlayBody, bundled) {
|
|
|
723
723
|
path = ${JSON.stringify(bundled)}
|
|
724
724
|
|
|
725
725
|
${overlayBody}`;
|
|
726
|
-
const tempPath = mkdtempSync(
|
|
727
|
-
const configPath =
|
|
726
|
+
const tempPath = mkdtempSync(join16(tmpdir(), "nomad-gitleaks-cfg-"));
|
|
727
|
+
const configPath = join16(tempPath, "config.toml");
|
|
728
728
|
writeFileSync3(configPath, tempBody, { mode: 384, flag: "wx" });
|
|
729
729
|
return { configPath, tempPath };
|
|
730
730
|
}
|
|
731
731
|
function resolveTomlConfig() {
|
|
732
732
|
const repo = repoHome();
|
|
733
|
-
const overlayPath =
|
|
734
|
-
const repoToml =
|
|
733
|
+
const overlayPath = join16(repo, ".gitleaks.overlay.toml");
|
|
734
|
+
const repoToml = join16(repo, ".gitleaks.toml");
|
|
735
735
|
const bundled = resolveTomlPath(repo);
|
|
736
|
-
if (!
|
|
736
|
+
if (!existsSync14(overlayPath)) {
|
|
737
737
|
return { path: bundled, tempPath: null };
|
|
738
738
|
}
|
|
739
739
|
if (bundled === repoToml) {
|
|
@@ -779,10 +779,10 @@ var init_push_gitleaks_config = __esm({
|
|
|
779
779
|
});
|
|
780
780
|
|
|
781
781
|
// src/push-checks.ts
|
|
782
|
-
import { execFileSync as
|
|
783
|
-
import { readdirSync as
|
|
782
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
783
|
+
import { readdirSync as readdirSync5, rmSync as rmSync5 } from "node:fs";
|
|
784
784
|
import { homedir as homedir2, platform } from "node:os";
|
|
785
|
-
import { join as
|
|
785
|
+
import { join as join17 } from "node:path";
|
|
786
786
|
function gitleaksInstallHint() {
|
|
787
787
|
const head = "gitleaks not on PATH (required for nomad push). Install:";
|
|
788
788
|
const plat = platform();
|
|
@@ -820,12 +820,12 @@ function findGitlinks(dir) {
|
|
|
820
820
|
function walk(current) {
|
|
821
821
|
let entries;
|
|
822
822
|
try {
|
|
823
|
-
entries =
|
|
823
|
+
entries = readdirSync5(current, { withFileTypes: true });
|
|
824
824
|
} catch {
|
|
825
825
|
return;
|
|
826
826
|
}
|
|
827
827
|
for (const e of entries) {
|
|
828
|
-
const p =
|
|
828
|
+
const p = join17(current, e.name);
|
|
829
829
|
if (e.name === ".git") {
|
|
830
830
|
hits.push(p);
|
|
831
831
|
continue;
|
|
@@ -841,7 +841,7 @@ function probeGitleaks() {
|
|
|
841
841
|
const args = ["version"];
|
|
842
842
|
if (toml !== null) args.push("--config", toml);
|
|
843
843
|
try {
|
|
844
|
-
return
|
|
844
|
+
return execFileSync4("gitleaks", args, { stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
845
845
|
} catch (err) {
|
|
846
846
|
const e = err;
|
|
847
847
|
if (e.code === "ENOENT") throw new NomadFatal(gitleaksInstallHint());
|
|
@@ -861,7 +861,7 @@ function rebaseBeforePush(repo) {
|
|
|
861
861
|
const wedge = classifyWedge(repo);
|
|
862
862
|
if (wedge !== null) throw new NomadFatal(wedgePreflight(wedge));
|
|
863
863
|
try {
|
|
864
|
-
|
|
864
|
+
execFileSync4("git", ["pull", "--rebase", "--autostash"], {
|
|
865
865
|
cwd: repo,
|
|
866
866
|
stdio: ["ignore", "pipe", "pipe"]
|
|
867
867
|
});
|
|
@@ -883,10 +883,10 @@ var init_push_checks = __esm({
|
|
|
883
883
|
});
|
|
884
884
|
|
|
885
885
|
// src/push-gitleaks.scan.ts
|
|
886
|
-
import { execFileSync as
|
|
886
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
887
887
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync6 } from "node:fs";
|
|
888
888
|
import { homedir as homedir3 } from "node:os";
|
|
889
|
-
import { join as
|
|
889
|
+
import { join as join21 } from "node:path";
|
|
890
890
|
function readGitleaksReport(reportPath) {
|
|
891
891
|
try {
|
|
892
892
|
const raw = readFileSync5(reportPath, "utf8");
|
|
@@ -898,9 +898,9 @@ function readGitleaksReport(reportPath) {
|
|
|
898
898
|
}
|
|
899
899
|
}
|
|
900
900
|
function scanStagedTree(repoDir, forwardStreams = false) {
|
|
901
|
-
const cacheDir =
|
|
901
|
+
const cacheDir = join21(homedir3(), ".cache", "claude-nomad");
|
|
902
902
|
mkdirSync3(cacheDir, { recursive: true });
|
|
903
|
-
const reportPath =
|
|
903
|
+
const reportPath = join21(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
|
|
904
904
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
905
905
|
const args = [
|
|
906
906
|
"protect",
|
|
@@ -913,9 +913,9 @@ function scanStagedTree(repoDir, forwardStreams = false) {
|
|
|
913
913
|
if (toml !== null) args.push("--config", toml);
|
|
914
914
|
const opts = { cwd: repoDir, stdio: ["ignore", "pipe", "pipe"] };
|
|
915
915
|
try {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
916
|
+
execFileSync8("git", ["init", "-q"], opts);
|
|
917
|
+
execFileSync8("git", ["add", "-A"], opts);
|
|
918
|
+
execFileSync8("gitleaks", args, opts);
|
|
919
919
|
return [];
|
|
920
920
|
} catch (err) {
|
|
921
921
|
const e = err;
|
|
@@ -932,9 +932,9 @@ function scanStagedTree(repoDir, forwardStreams = false) {
|
|
|
932
932
|
}
|
|
933
933
|
}
|
|
934
934
|
function scanFile(filePath, forwardStreams = false) {
|
|
935
|
-
const cacheDir =
|
|
935
|
+
const cacheDir = join21(homedir3(), ".cache", "claude-nomad");
|
|
936
936
|
mkdirSync3(cacheDir, { recursive: true });
|
|
937
|
-
const reportPath =
|
|
937
|
+
const reportPath = join21(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}.json`);
|
|
938
938
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
939
939
|
const args = [
|
|
940
940
|
"detect",
|
|
@@ -947,7 +947,7 @@ function scanFile(filePath, forwardStreams = false) {
|
|
|
947
947
|
if (toml !== null) args.push("--config", toml);
|
|
948
948
|
const opts = { stdio: ["ignore", "pipe", "pipe"] };
|
|
949
949
|
try {
|
|
950
|
-
|
|
950
|
+
execFileSync8("gitleaks", args, opts);
|
|
951
951
|
return [];
|
|
952
952
|
} catch (err) {
|
|
953
953
|
const e = err;
|
|
@@ -1505,6 +1505,14 @@ function partitionByCaptureExclusion(keys) {
|
|
|
1505
1505
|
}
|
|
1506
1506
|
return { promotable, excluded };
|
|
1507
1507
|
}
|
|
1508
|
+
function describeSettings(keys) {
|
|
1509
|
+
const one = keys.length === 1;
|
|
1510
|
+
return {
|
|
1511
|
+
phrase: `${keys.length} ${one ? "setting" : "settings"} (${keys.join(", ")})`,
|
|
1512
|
+
pronoun: one ? "it" : "them",
|
|
1513
|
+
verb: one ? "is" : "are"
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1508
1516
|
var BIN_NODE_RE = /^"?(?:[A-Za-z]:)?[\\/](?:.*[\\/])?bin[\\/]node"?$/;
|
|
1509
1517
|
var LEADING_BIN_NODE_RE = /^"?(?:[A-Za-z]:)?[\\/](?:[^"\s]*[\\/])?bin[\\/]node"?(?=\s)/;
|
|
1510
1518
|
function normalizeNodePathsDeep(value) {
|
|
@@ -1614,14 +1622,16 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
1614
1622
|
const existing = readJson(settingsPath);
|
|
1615
1623
|
const drift = classifySettingsDrift(merged, existing);
|
|
1616
1624
|
if (drift.behind.length > 0) {
|
|
1625
|
+
const { phrase, pronoun } = describeSettings(drift.behind);
|
|
1617
1626
|
warn(
|
|
1618
|
-
`
|
|
1627
|
+
`your settings.json is missing ${phrase} that the synced copy has; run 'nomad pull' to restore ${pronoun}.`
|
|
1619
1628
|
);
|
|
1620
1629
|
}
|
|
1621
1630
|
const { promotable } = partitionByCaptureExclusion(drift.ahead);
|
|
1622
1631
|
if (promotable.length > 0) {
|
|
1632
|
+
const { phrase, pronoun, verb } = describeSettings(promotable);
|
|
1623
1633
|
warn(
|
|
1624
|
-
`
|
|
1634
|
+
`your settings.json has ${phrase} that ${verb} not yet synced; run 'nomad capture-settings' to save ${pronoun} to the repo before the next pull overwrites ${pronoun}.`
|
|
1625
1635
|
);
|
|
1626
1636
|
}
|
|
1627
1637
|
} catch {
|
|
@@ -2063,13 +2073,13 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2063
2073
|
}
|
|
2064
2074
|
|
|
2065
2075
|
// src/commands.doctor.ts
|
|
2066
|
-
import { existsSync as
|
|
2067
|
-
import { join as
|
|
2076
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
2077
|
+
import { join as join36 } from "node:path";
|
|
2068
2078
|
|
|
2069
2079
|
// src/commands.doctor.checks.repo.ts
|
|
2070
2080
|
init_color();
|
|
2071
2081
|
init_config();
|
|
2072
|
-
import { existsSync as existsSync9, lstatSync as lstatSync6, statSync as statSync3 } from "node:fs";
|
|
2082
|
+
import { existsSync as existsSync9, lstatSync as lstatSync6, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
|
|
2073
2083
|
import { join as join11 } from "node:path";
|
|
2074
2084
|
|
|
2075
2085
|
// src/commands.doctor.format.ts
|
|
@@ -2189,6 +2199,7 @@ function reasonForPartial(repoHome2, host) {
|
|
|
2189
2199
|
}
|
|
2190
2200
|
|
|
2191
2201
|
// src/commands.doctor.checks.repo.ts
|
|
2202
|
+
init_utils_json();
|
|
2192
2203
|
function isOverrideActive() {
|
|
2193
2204
|
return Boolean(process.env.NOMAD_REPO);
|
|
2194
2205
|
}
|
|
@@ -2206,6 +2217,46 @@ function reportHostAndPaths(section2) {
|
|
|
2206
2217
|
`${existsSync9(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
|
|
2207
2218
|
);
|
|
2208
2219
|
}
|
|
2220
|
+
function pathMapHostKeys() {
|
|
2221
|
+
const mapPath = join11(repoHome(), "path-map.json");
|
|
2222
|
+
if (!existsSync9(mapPath)) return /* @__PURE__ */ new Set();
|
|
2223
|
+
let raw;
|
|
2224
|
+
try {
|
|
2225
|
+
raw = readJson(mapPath);
|
|
2226
|
+
} catch {
|
|
2227
|
+
return /* @__PURE__ */ new Set();
|
|
2228
|
+
}
|
|
2229
|
+
if (validatePathMapShape(raw) !== null) return /* @__PURE__ */ new Set();
|
|
2230
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2231
|
+
for (const hosts of Object.values(raw.projects)) {
|
|
2232
|
+
for (const key of Object.keys(hosts)) keys.add(key);
|
|
2233
|
+
}
|
|
2234
|
+
return keys;
|
|
2235
|
+
}
|
|
2236
|
+
function hostOverrideLabels() {
|
|
2237
|
+
let entries;
|
|
2238
|
+
try {
|
|
2239
|
+
entries = readdirSync2(join11(repoHome(), "hosts"));
|
|
2240
|
+
} catch {
|
|
2241
|
+
return /* @__PURE__ */ new Set();
|
|
2242
|
+
}
|
|
2243
|
+
const labels = /* @__PURE__ */ new Set();
|
|
2244
|
+
for (const entry of entries) {
|
|
2245
|
+
if (entry.endsWith(".json")) labels.add(entry.slice(0, -".json".length));
|
|
2246
|
+
}
|
|
2247
|
+
return labels;
|
|
2248
|
+
}
|
|
2249
|
+
function reportHostKeyAlignment(section2) {
|
|
2250
|
+
if (process.env.NOMAD_HOST) return;
|
|
2251
|
+
const overrideLabels = hostOverrideLabels();
|
|
2252
|
+
const mapKeys = pathMapHostKeys();
|
|
2253
|
+
if (overrideLabels.has(HOST) || mapKeys.has(HOST)) return;
|
|
2254
|
+
if (overrideLabels.size === 0 && mapKeys.size === 0) return;
|
|
2255
|
+
addItem(
|
|
2256
|
+
section2,
|
|
2257
|
+
`${yellow(warnGlyph)} NOMAD_HOST unset: this repo configures other hosts, but the hostname key ${cyan(HOST)} matches no hosts/${HOST}.json or path-map entry; set NOMAD_HOST to the label this host should use so per-host settings and session sync line up`
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2209
2260
|
function reportRepoState(section2) {
|
|
2210
2261
|
const repo = repoHome();
|
|
2211
2262
|
const state = classifyRepoState(repo, HOST);
|
|
@@ -2301,7 +2352,7 @@ function reportDroppedNamesMigration(section2) {
|
|
|
2301
2352
|
// src/commands.doctor.checks.settings.ts
|
|
2302
2353
|
init_color();
|
|
2303
2354
|
init_config();
|
|
2304
|
-
import { existsSync as existsSync10, readdirSync as
|
|
2355
|
+
import { existsSync as existsSync10, readdirSync as readdirSync3 } from "node:fs";
|
|
2305
2356
|
import { join as join12 } from "node:path";
|
|
2306
2357
|
function loadBaseSettings(section2) {
|
|
2307
2358
|
const basePath = join12(repoHome(), "shared", "settings.base.json");
|
|
@@ -2347,7 +2398,7 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
2347
2398
|
);
|
|
2348
2399
|
const hostsDir = join12(repo, "hosts");
|
|
2349
2400
|
if (existsSync10(hostsDir)) {
|
|
2350
|
-
const cands =
|
|
2401
|
+
const cands = readdirSync3(hostsDir).filter((f) => f.endsWith(".json"));
|
|
2351
2402
|
if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
|
|
2352
2403
|
}
|
|
2353
2404
|
process.exitCode = 1;
|
|
@@ -2362,7 +2413,7 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
2362
2413
|
// src/commands.doctor.checks.pathmap.ts
|
|
2363
2414
|
init_color();
|
|
2364
2415
|
init_config();
|
|
2365
|
-
import { existsSync as existsSync11, readdirSync as
|
|
2416
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4 } from "node:fs";
|
|
2366
2417
|
import { join as join13 } from "node:path";
|
|
2367
2418
|
init_utils_json();
|
|
2368
2419
|
function reportMappedProjects(section2, map) {
|
|
@@ -2377,7 +2428,7 @@ function reportUnmappedProjects(section2, map) {
|
|
|
2377
2428
|
if (!existsSync11(localProjects)) return;
|
|
2378
2429
|
let localDirs;
|
|
2379
2430
|
try {
|
|
2380
|
-
localDirs =
|
|
2431
|
+
localDirs = readdirSync4(localProjects);
|
|
2381
2432
|
} catch {
|
|
2382
2433
|
return;
|
|
2383
2434
|
}
|
|
@@ -2391,6 +2442,18 @@ function reportUnmappedProjects(section2, map) {
|
|
|
2391
2442
|
addChildItem(section2, dim(dir));
|
|
2392
2443
|
}
|
|
2393
2444
|
}
|
|
2445
|
+
function reportCurrentHostPathsMissing(section2, map) {
|
|
2446
|
+
for (const [name, hosts] of Object.entries(map.projects)) {
|
|
2447
|
+
const abspath = hosts[HOST];
|
|
2448
|
+
if (!abspath || abspath === "TBD") continue;
|
|
2449
|
+
if (!existsSync11(abspath)) {
|
|
2450
|
+
addItem(
|
|
2451
|
+
section2,
|
|
2452
|
+
`${yellow(warnGlyph)} path-map: ${name} local path missing on ${HOST}: ${blue(abspath)}`
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2394
2457
|
function reportPathCollisions(section2, map) {
|
|
2395
2458
|
const seen = /* @__PURE__ */ new Map();
|
|
2396
2459
|
let collisionCount = 0;
|
|
@@ -2429,6 +2492,7 @@ function reportPathMap(section2) {
|
|
|
2429
2492
|
return;
|
|
2430
2493
|
}
|
|
2431
2494
|
reportMappedProjects(section2, map);
|
|
2495
|
+
reportCurrentHostPathsMissing(section2, map);
|
|
2432
2496
|
reportUnmappedProjects(section2, map);
|
|
2433
2497
|
reportPathCollisions(section2, map);
|
|
2434
2498
|
}
|
|
@@ -2441,18 +2505,103 @@ function reportNeverSync(section2) {
|
|
|
2441
2505
|
);
|
|
2442
2506
|
}
|
|
2443
2507
|
|
|
2508
|
+
// src/commands.doctor.checks.skills.ts
|
|
2509
|
+
init_color();
|
|
2510
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
2511
|
+
import { join as join14 } from "node:path";
|
|
2512
|
+
init_config();
|
|
2513
|
+
|
|
2514
|
+
// src/extras-sync.diff.ts
|
|
2515
|
+
init_utils();
|
|
2516
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2517
|
+
function labelDiffLine(line) {
|
|
2518
|
+
const tab = line.indexOf(" ");
|
|
2519
|
+
if (tab === -1) return line;
|
|
2520
|
+
const status = line.slice(0, tab);
|
|
2521
|
+
const path = line.slice(tab + 1);
|
|
2522
|
+
if (status === "D") return `${path} (local only)`;
|
|
2523
|
+
if (status === "A") return `${path} (repo only)`;
|
|
2524
|
+
return path;
|
|
2525
|
+
}
|
|
2526
|
+
function parseDiffOutput(stdout) {
|
|
2527
|
+
return stdout.split("\n").filter((line) => line.length > 0).map(labelDiffLine);
|
|
2528
|
+
}
|
|
2529
|
+
function listDivergingFiles(a, b) {
|
|
2530
|
+
try {
|
|
2531
|
+
const stdout = execFileSync2("git", ["diff", "--no-index", "--name-status", a, b], {
|
|
2532
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2533
|
+
}).toString();
|
|
2534
|
+
return parseDiffOutput(stdout);
|
|
2535
|
+
} catch (err) {
|
|
2536
|
+
const e = err;
|
|
2537
|
+
if (e.status === 1 && e.stdout !== void 0) {
|
|
2538
|
+
return parseDiffOutput(e.stdout.toString());
|
|
2539
|
+
}
|
|
2540
|
+
if (e.code === "ENOENT") {
|
|
2541
|
+
warn(`git not on PATH; divergence check skipped for ${a}`);
|
|
2542
|
+
return [];
|
|
2543
|
+
}
|
|
2544
|
+
warn(`divergence check failed for ${a}: ${e.message ?? String(err)}`);
|
|
2545
|
+
return [];
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
// src/commands.doctor.checks.skills.ts
|
|
2550
|
+
function stripSideIndicator(line) {
|
|
2551
|
+
if (line.endsWith(" (local only)")) return line.slice(0, -" (local only)".length);
|
|
2552
|
+
if (line.endsWith(" (repo only)")) return line.slice(0, -" (repo only)".length);
|
|
2553
|
+
return line;
|
|
2554
|
+
}
|
|
2555
|
+
function isGsdDiffLine(line, localBase, sharedBase) {
|
|
2556
|
+
const bare = stripSideIndicator(line);
|
|
2557
|
+
let relative6;
|
|
2558
|
+
if (bare.startsWith(localBase + "/")) {
|
|
2559
|
+
relative6 = bare.slice(localBase.length + 1);
|
|
2560
|
+
} else if (bare.startsWith(sharedBase + "/")) {
|
|
2561
|
+
relative6 = bare.slice(sharedBase.length + 1);
|
|
2562
|
+
} else {
|
|
2563
|
+
relative6 = bare;
|
|
2564
|
+
}
|
|
2565
|
+
return relative6.split("/")[0].startsWith(GSD_PREFIX);
|
|
2566
|
+
}
|
|
2567
|
+
function reportSkillsDivergence(section2) {
|
|
2568
|
+
const sharedSkills = join14(repoHome(), "shared", "skills");
|
|
2569
|
+
const localSkills = join14(claudeHome(), "skills");
|
|
2570
|
+
if (!existsSync12(sharedSkills)) {
|
|
2571
|
+
addItem(section2, `${dim(infoGlyph)} skills: no shared/skills/ to compare`);
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
if (!existsSync12(localSkills)) {
|
|
2575
|
+
addItem(section2, `${dim(infoGlyph)} skills: no local skills/ to compare`);
|
|
2576
|
+
return;
|
|
2577
|
+
}
|
|
2578
|
+
const diff = listDivergingFiles(localSkills, sharedSkills);
|
|
2579
|
+
const relevant = diff.filter((line) => !isGsdDiffLine(line, localSkills, sharedSkills));
|
|
2580
|
+
if (relevant.length === 0) {
|
|
2581
|
+
addItem(section2, `${green(okGlyph)} skills: in sync with shared/skills/`);
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
addItem(
|
|
2585
|
+
section2,
|
|
2586
|
+
`${yellow(warnGlyph)} skills: ${relevant.length} file(s) diverge from shared/skills/`
|
|
2587
|
+
);
|
|
2588
|
+
for (const f of relevant) {
|
|
2589
|
+
addChildItem(section2, f);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2444
2593
|
// src/commands.doctor.checks.git-state.ts
|
|
2445
2594
|
init_color();
|
|
2446
2595
|
init_config();
|
|
2447
|
-
import { execFileSync as
|
|
2448
|
-
import { existsSync as
|
|
2449
|
-
import { join as
|
|
2596
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
2597
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
2598
|
+
import { join as join18, relative as relative2 } from "node:path";
|
|
2450
2599
|
init_commands_pull_wedge();
|
|
2451
2600
|
init_push_checks();
|
|
2452
2601
|
init_utils();
|
|
2453
2602
|
function reportGitleaksProbe(section2) {
|
|
2454
2603
|
try {
|
|
2455
|
-
|
|
2604
|
+
execFileSync5("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
2456
2605
|
return true;
|
|
2457
2606
|
} catch (err) {
|
|
2458
2607
|
if (err.code === "ENOENT") {
|
|
@@ -2469,8 +2618,8 @@ function reportGitleaksProbe(section2) {
|
|
|
2469
2618
|
}
|
|
2470
2619
|
function reportGitlinks(section2) {
|
|
2471
2620
|
const repo = repoHome();
|
|
2472
|
-
const sharedDir =
|
|
2473
|
-
if (
|
|
2621
|
+
const sharedDir = join18(repo, "shared");
|
|
2622
|
+
if (existsSync15(sharedDir)) {
|
|
2474
2623
|
const gitlinks = findGitlinks(sharedDir);
|
|
2475
2624
|
for (const p of gitlinks) {
|
|
2476
2625
|
const rel = relative2(repo, p);
|
|
@@ -2488,7 +2637,7 @@ function reportGitlinks(section2) {
|
|
|
2488
2637
|
}
|
|
2489
2638
|
function reportRemote(section2) {
|
|
2490
2639
|
try {
|
|
2491
|
-
const url =
|
|
2640
|
+
const url = execFileSync5("git", ["remote", "get-url", "origin"], {
|
|
2492
2641
|
cwd: repoHome(),
|
|
2493
2642
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2494
2643
|
}).toString().trim();
|
|
@@ -2540,16 +2689,41 @@ function reportOrphanedAutostash(sec) {
|
|
|
2540
2689
|
} catch {
|
|
2541
2690
|
}
|
|
2542
2691
|
}
|
|
2692
|
+
function reportGitIdentity(section2) {
|
|
2693
|
+
const repo = repoHome();
|
|
2694
|
+
const missing = [];
|
|
2695
|
+
for (const field of ["user.name", "user.email"]) {
|
|
2696
|
+
try {
|
|
2697
|
+
const val = execFileSync5("git", ["config", field], {
|
|
2698
|
+
cwd: repo,
|
|
2699
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2700
|
+
}).toString().trim();
|
|
2701
|
+
if (!val) missing.push(field);
|
|
2702
|
+
} catch (err) {
|
|
2703
|
+
if (err.status === 1) missing.push(field);
|
|
2704
|
+
else return;
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
if (missing.length > 0) {
|
|
2708
|
+
const hint = missing.map((f) => `git config ${f} "..."`).join(" / ");
|
|
2709
|
+
addItem(
|
|
2710
|
+
section2,
|
|
2711
|
+
`${yellow(warnGlyph)} git identity: ${missing.join(", ")} not set (run: ${hint})`
|
|
2712
|
+
);
|
|
2713
|
+
} else {
|
|
2714
|
+
addItem(section2, `${green(okGlyph)} git identity: user.name and user.email configured`);
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2543
2717
|
|
|
2544
2718
|
// src/commands.doctor.checks.backups.ts
|
|
2545
2719
|
init_color();
|
|
2546
|
-
import { existsSync as
|
|
2547
|
-
import { join as
|
|
2720
|
+
import { existsSync as existsSync16, lstatSync as lstatSync7, readdirSync as readdirSync6 } from "node:fs";
|
|
2721
|
+
import { join as join19 } from "node:path";
|
|
2548
2722
|
init_config();
|
|
2549
2723
|
var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
|
|
2550
2724
|
function safeReaddir(dir) {
|
|
2551
2725
|
try {
|
|
2552
|
-
return
|
|
2726
|
+
return readdirSync6(dir);
|
|
2553
2727
|
} catch {
|
|
2554
2728
|
return [];
|
|
2555
2729
|
}
|
|
@@ -2560,7 +2734,7 @@ var BYTES_PER_MB = 1024 * 1024;
|
|
|
2560
2734
|
function dirSizeBytes(dir) {
|
|
2561
2735
|
let bytes = 0;
|
|
2562
2736
|
for (const entry of safeReaddir(dir)) {
|
|
2563
|
-
const full =
|
|
2737
|
+
const full = join19(dir, entry);
|
|
2564
2738
|
const st = lstatSync7(full, { throwIfNoEntry: false });
|
|
2565
2739
|
if (!st) continue;
|
|
2566
2740
|
if (st.isSymbolicLink()) continue;
|
|
@@ -2571,11 +2745,11 @@ function dirSizeBytes(dir) {
|
|
|
2571
2745
|
}
|
|
2572
2746
|
function totalSizeMb(backupBase2, dirs) {
|
|
2573
2747
|
let bytes = 0;
|
|
2574
|
-
for (const name of dirs) bytes += dirSizeBytes(
|
|
2748
|
+
for (const name of dirs) bytes += dirSizeBytes(join19(backupBase2, name));
|
|
2575
2749
|
return bytes / BYTES_PER_MB;
|
|
2576
2750
|
}
|
|
2577
2751
|
function reportBackupsCheck(section2, backupBase2 = backupBase()) {
|
|
2578
|
-
if (!
|
|
2752
|
+
if (!existsSync16(backupBase2)) return;
|
|
2579
2753
|
const dirs = safeReaddir(backupBase2).filter((n) => TS_SHAPE2.test(n));
|
|
2580
2754
|
const count = dirs.length;
|
|
2581
2755
|
const sizeMb = totalSizeMb(backupBase2, dirs);
|
|
@@ -2587,16 +2761,83 @@ function reportBackupsCheck(section2, backupBase2 = backupBase()) {
|
|
|
2587
2761
|
}
|
|
2588
2762
|
}
|
|
2589
2763
|
|
|
2764
|
+
// src/commands.doctor.check-remote.ts
|
|
2765
|
+
init_color();
|
|
2766
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
2767
|
+
init_config();
|
|
2768
|
+
init_utils_json();
|
|
2769
|
+
var GIT_REMOTE_TIMEOUT_MS = 3e3;
|
|
2770
|
+
function readRemotePathMap(sec, repo) {
|
|
2771
|
+
let rawJson;
|
|
2772
|
+
try {
|
|
2773
|
+
rawJson = execFileSync6("git", ["show", "origin/main:path-map.json"], {
|
|
2774
|
+
cwd: repo,
|
|
2775
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2776
|
+
timeout: GIT_REMOTE_TIMEOUT_MS
|
|
2777
|
+
}).toString();
|
|
2778
|
+
} catch {
|
|
2779
|
+
addItem(
|
|
2780
|
+
sec,
|
|
2781
|
+
`${yellow(warnGlyph)} remote check skipped (could not read path-map.json from origin/main)`
|
|
2782
|
+
);
|
|
2783
|
+
return false;
|
|
2784
|
+
}
|
|
2785
|
+
let map;
|
|
2786
|
+
try {
|
|
2787
|
+
map = JSON.parse(rawJson);
|
|
2788
|
+
} catch {
|
|
2789
|
+
addItem(sec, `${yellow(warnGlyph)} remote: path-map.json at origin/main is malformed JSON`);
|
|
2790
|
+
return false;
|
|
2791
|
+
}
|
|
2792
|
+
const shapeError = validatePathMapShape(map);
|
|
2793
|
+
if (shapeError !== null) {
|
|
2794
|
+
addItem(
|
|
2795
|
+
sec,
|
|
2796
|
+
`${yellow(warnGlyph)} remote: path-map.json at origin/main has invalid shape: ${shapeError}`
|
|
2797
|
+
);
|
|
2798
|
+
return false;
|
|
2799
|
+
}
|
|
2800
|
+
return true;
|
|
2801
|
+
}
|
|
2802
|
+
function reportCheckRemote(section2) {
|
|
2803
|
+
const repo = repoHome();
|
|
2804
|
+
let names;
|
|
2805
|
+
try {
|
|
2806
|
+
const out = execFileSync6("git", ["ls-tree", "--name-only", "origin/main"], {
|
|
2807
|
+
cwd: repo,
|
|
2808
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2809
|
+
timeout: GIT_REMOTE_TIMEOUT_MS
|
|
2810
|
+
}).toString().trim();
|
|
2811
|
+
names = out.split("\n").filter(Boolean);
|
|
2812
|
+
} catch {
|
|
2813
|
+
addItem(
|
|
2814
|
+
section2,
|
|
2815
|
+
`${yellow(warnGlyph)} remote check skipped (origin/main unavailable or git error)`
|
|
2816
|
+
);
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
if (!names.includes("shared")) {
|
|
2820
|
+
addItem(section2, `${yellow(warnGlyph)} remote: shared/ not found in origin/main`);
|
|
2821
|
+
return;
|
|
2822
|
+
}
|
|
2823
|
+
if (!names.includes("path-map.json")) {
|
|
2824
|
+
addItem(section2, `${yellow(warnGlyph)} remote: path-map.json not found in origin/main`);
|
|
2825
|
+
return;
|
|
2826
|
+
}
|
|
2827
|
+
if (!readRemotePathMap(section2, repo)) return;
|
|
2828
|
+
addItem(section2, `${green(okGlyph)} remote: origin/main has shared/ and a valid path-map.json`);
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2590
2831
|
// src/commands.doctor.check-schema.ts
|
|
2591
2832
|
init_color();
|
|
2592
|
-
import { existsSync as
|
|
2593
|
-
import { join as
|
|
2833
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
2834
|
+
import { join as join20 } from "node:path";
|
|
2594
2835
|
init_config();
|
|
2595
2836
|
|
|
2596
2837
|
// src/http-fetch.ts
|
|
2597
|
-
import { execFileSync as
|
|
2838
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
2598
2839
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
2599
|
-
function fetchUrl(url, run =
|
|
2840
|
+
function fetchUrl(url, run = execFileSync7) {
|
|
2600
2841
|
try {
|
|
2601
2842
|
return run("curl", ["-fsSL", "-m", "3", url], {
|
|
2602
2843
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -2627,8 +2868,8 @@ function fetchSchemaKeys() {
|
|
|
2627
2868
|
}
|
|
2628
2869
|
}
|
|
2629
2870
|
function reportCheckSchema(section2) {
|
|
2630
|
-
const settingsPath =
|
|
2631
|
-
if (!
|
|
2871
|
+
const settingsPath = join20(claudeHome(), "settings.json");
|
|
2872
|
+
if (!existsSync17(settingsPath)) {
|
|
2632
2873
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
|
|
2633
2874
|
return;
|
|
2634
2875
|
}
|
|
@@ -2657,19 +2898,19 @@ function reportCheckSchema(section2) {
|
|
|
2657
2898
|
// src/commands.doctor.check-shared.ts
|
|
2658
2899
|
init_color();
|
|
2659
2900
|
import { randomBytes } from "node:crypto";
|
|
2660
|
-
import { execFileSync as
|
|
2661
|
-
import { existsSync as
|
|
2901
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
2902
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync5, readdirSync as readdirSync8, rmSync as rmSync8 } from "node:fs";
|
|
2662
2903
|
import { homedir as homedir4 } from "node:os";
|
|
2663
|
-
import { join as
|
|
2904
|
+
import { join as join24 } from "node:path";
|
|
2664
2905
|
|
|
2665
2906
|
// src/commands.doctor.check-shared.scan.ts
|
|
2666
2907
|
init_color();
|
|
2667
|
-
import { join as
|
|
2908
|
+
import { join as join22 } from "node:path";
|
|
2668
2909
|
init_config();
|
|
2669
2910
|
init_push_gitleaks();
|
|
2670
2911
|
function scrubPath(logical, sid, logicalToEncoded) {
|
|
2671
2912
|
const encoded = logicalToEncoded.get(logical) ?? logical;
|
|
2672
|
-
return
|
|
2913
|
+
return join22(claudeHome(), "projects", encoded, `${sid}.jsonl`);
|
|
2673
2914
|
}
|
|
2674
2915
|
function reportSessionFindings(section2, bySession) {
|
|
2675
2916
|
for (const [sid, counts] of bySession) {
|
|
@@ -2757,8 +2998,8 @@ init_config();
|
|
|
2757
2998
|
|
|
2758
2999
|
// src/remap.ts
|
|
2759
3000
|
init_config_sharedDirs_guard();
|
|
2760
|
-
import { cpSync as cpSync4, existsSync as
|
|
2761
|
-
import { join as
|
|
3001
|
+
import { cpSync as cpSync4, existsSync as existsSync18, mkdirSync as mkdirSync4, readdirSync as readdirSync7, renameSync as renameSync3, rmSync as rmSync7, statSync as statSync4 } from "node:fs";
|
|
3002
|
+
import { join as join23, relative as relative3, sep as sep3 } from "node:path";
|
|
2762
3003
|
|
|
2763
3004
|
// src/extras-sync.guards.ts
|
|
2764
3005
|
init_utils();
|
|
@@ -2819,15 +3060,15 @@ function remapPull(ts, opts = {}) {
|
|
|
2819
3060
|
const wouldPull = [];
|
|
2820
3061
|
const repo = repoHome();
|
|
2821
3062
|
const claude = claudeHome();
|
|
2822
|
-
const mapPath =
|
|
2823
|
-
const repoProjects =
|
|
2824
|
-
if (!
|
|
3063
|
+
const mapPath = join23(repo, "path-map.json");
|
|
3064
|
+
const repoProjects = join23(repo, "shared", "projects");
|
|
3065
|
+
if (!existsSync18(mapPath) || !existsSync18(repoProjects)) {
|
|
2825
3066
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
2826
3067
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
2827
3068
|
return { unmapped: 0, pulled, wouldPull };
|
|
2828
3069
|
}
|
|
2829
3070
|
const map = readPathMap(mapPath);
|
|
2830
|
-
const localProjects =
|
|
3071
|
+
const localProjects = join23(claude, "projects");
|
|
2831
3072
|
if (!dryRun) mkdirSync4(localProjects, { recursive: true });
|
|
2832
3073
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
2833
3074
|
assertSafeLogical(logical);
|
|
@@ -2837,9 +3078,9 @@ function remapPull(ts, opts = {}) {
|
|
|
2837
3078
|
continue;
|
|
2838
3079
|
}
|
|
2839
3080
|
assertSafeLocalRoot(localPath, logical);
|
|
2840
|
-
const src =
|
|
2841
|
-
if (!
|
|
2842
|
-
const dst =
|
|
3081
|
+
const src = join23(repoProjects, logical);
|
|
3082
|
+
if (!existsSync18(src)) continue;
|
|
3083
|
+
const dst = join23(localProjects, encodePath(localPath));
|
|
2843
3084
|
if (dryRun) {
|
|
2844
3085
|
wouldPull.push(logical);
|
|
2845
3086
|
emitPreview(
|
|
@@ -2886,31 +3127,31 @@ function remapPush(ts, opts = {}) {
|
|
|
2886
3127
|
const wouldPush = [];
|
|
2887
3128
|
const repo = repoHome();
|
|
2888
3129
|
const claude = claudeHome();
|
|
2889
|
-
const mapPath =
|
|
2890
|
-
if (!
|
|
3130
|
+
const mapPath = join23(repo, "path-map.json");
|
|
3131
|
+
if (!existsSync18(mapPath)) {
|
|
2891
3132
|
log("no path-map.json; skipping session export");
|
|
2892
3133
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
2893
3134
|
}
|
|
2894
3135
|
const map = readPathMap(mapPath);
|
|
2895
|
-
const localProjects =
|
|
2896
|
-
const repoProjects =
|
|
3136
|
+
const localProjects = join23(claude, "projects");
|
|
3137
|
+
const repoProjects = join23(repo, "shared", "projects");
|
|
2897
3138
|
const reverse = buildReverseMap(map);
|
|
2898
|
-
if (!
|
|
3139
|
+
if (!existsSync18(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
2899
3140
|
if (!dryRun) mkdirSync4(repoProjects, { recursive: true });
|
|
2900
|
-
for (const dir of
|
|
3141
|
+
for (const dir of readdirSync7(localProjects)) {
|
|
2901
3142
|
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
2902
3143
|
const logical = reverse.get(dir);
|
|
2903
3144
|
if (!logical) {
|
|
2904
3145
|
unmapped++;
|
|
2905
3146
|
continue;
|
|
2906
3147
|
}
|
|
2907
|
-
const repoDst =
|
|
3148
|
+
const repoDst = join23(repoProjects, logical);
|
|
2908
3149
|
if (dryRun) {
|
|
2909
3150
|
wouldPush.push(logical);
|
|
2910
3151
|
continue;
|
|
2911
3152
|
}
|
|
2912
3153
|
backupRepoWrite(repoDst, ts, repo);
|
|
2913
|
-
copyDirJsonlOnly(
|
|
3154
|
+
copyDirJsonlOnly(join23(localProjects, dir), repoDst);
|
|
2914
3155
|
pushed.push(logical);
|
|
2915
3156
|
}
|
|
2916
3157
|
return { unmapped, collisions: 0, pushed, wouldPush };
|
|
@@ -2923,8 +3164,8 @@ function buildScanTree(tmpRoot) {
|
|
|
2923
3164
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
2924
3165
|
let staged = 0;
|
|
2925
3166
|
const repo = repoHome();
|
|
2926
|
-
const mapPath =
|
|
2927
|
-
if (!
|
|
3167
|
+
const mapPath = join24(repo, "path-map.json");
|
|
3168
|
+
if (!existsSync19(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
2928
3169
|
let map;
|
|
2929
3170
|
try {
|
|
2930
3171
|
map = readJson(mapPath);
|
|
@@ -2941,12 +3182,12 @@ function buildScanTree(tmpRoot) {
|
|
|
2941
3182
|
if (!p || p === "TBD") continue;
|
|
2942
3183
|
reverse.set(encodePath(p), logical);
|
|
2943
3184
|
}
|
|
2944
|
-
const localProjects =
|
|
2945
|
-
if (!
|
|
2946
|
-
for (const dir of
|
|
3185
|
+
const localProjects = join24(claudeHome(), "projects");
|
|
3186
|
+
if (!existsSync19(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
3187
|
+
for (const dir of readdirSync8(localProjects)) {
|
|
2947
3188
|
const logical = reverse.get(dir);
|
|
2948
3189
|
if (!logical) continue;
|
|
2949
|
-
copyDirJsonlOnly(
|
|
3190
|
+
copyDirJsonlOnly(join24(localProjects, dir), join24(tmpRoot, "shared", "projects", logical));
|
|
2950
3191
|
logicalToEncoded.set(logical, dir);
|
|
2951
3192
|
staged++;
|
|
2952
3193
|
}
|
|
@@ -2954,7 +3195,7 @@ function buildScanTree(tmpRoot) {
|
|
|
2954
3195
|
}
|
|
2955
3196
|
function probeGitleaksForScan() {
|
|
2956
3197
|
try {
|
|
2957
|
-
|
|
3198
|
+
execFileSync9("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
2958
3199
|
return "ok";
|
|
2959
3200
|
} catch (err) {
|
|
2960
3201
|
if (err.code === "ENOENT") return "missing";
|
|
@@ -2977,11 +3218,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
2977
3218
|
}
|
|
2978
3219
|
function reportCheckShared(section2, gitleaksReady) {
|
|
2979
3220
|
if (!ensureGitleaksReady(section2, gitleaksReady)) return;
|
|
2980
|
-
const cacheDir =
|
|
3221
|
+
const cacheDir = join24(homedir4(), ".cache", "claude-nomad");
|
|
2981
3222
|
mkdirSync5(cacheDir, { recursive: true });
|
|
2982
3223
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
2983
|
-
const reportPath =
|
|
2984
|
-
const tmpRoot =
|
|
3224
|
+
const reportPath = join24(cacheDir, `check-shared-${stamp}.json`);
|
|
3225
|
+
const tmpRoot = join24(cacheDir, `check-shared-tree-${stamp}`);
|
|
2985
3226
|
try {
|
|
2986
3227
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
2987
3228
|
if (malformed) {
|
|
@@ -3002,8 +3243,8 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
3002
3243
|
|
|
3003
3244
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
3004
3245
|
init_color();
|
|
3005
|
-
import { existsSync as
|
|
3006
|
-
import { dirname as dirname3, extname, join as
|
|
3246
|
+
import { existsSync as existsSync20, readFileSync as readFileSync6, readdirSync as readdirSync9, realpathSync as realpathSync2 } from "node:fs";
|
|
3247
|
+
import { dirname as dirname3, extname, join as join25 } from "node:path";
|
|
3007
3248
|
init_config();
|
|
3008
3249
|
function typeFromPackageJson(pkgPath) {
|
|
3009
3250
|
try {
|
|
@@ -3025,8 +3266,8 @@ function effectiveType(hookPath) {
|
|
|
3025
3266
|
}
|
|
3026
3267
|
let dir = dirname3(real);
|
|
3027
3268
|
for (; ; ) {
|
|
3028
|
-
const pkg =
|
|
3029
|
-
if (
|
|
3269
|
+
const pkg = join25(dir, "package.json");
|
|
3270
|
+
if (existsSync20(pkg)) return typeFromPackageJson(pkg);
|
|
3030
3271
|
const parent = dirname3(dir);
|
|
3031
3272
|
if (parent === dir) return "cjs";
|
|
3032
3273
|
dir = parent;
|
|
@@ -3055,7 +3296,7 @@ function remedy(family) {
|
|
|
3055
3296
|
}
|
|
3056
3297
|
function safeReaddir2(dir) {
|
|
3057
3298
|
try {
|
|
3058
|
-
return
|
|
3299
|
+
return readdirSync9(dir);
|
|
3059
3300
|
} catch {
|
|
3060
3301
|
return [];
|
|
3061
3302
|
}
|
|
@@ -3068,15 +3309,15 @@ function safeRead(path) {
|
|
|
3068
3309
|
}
|
|
3069
3310
|
}
|
|
3070
3311
|
function reportHookScopeCheck(section2) {
|
|
3071
|
-
const hooksDir =
|
|
3072
|
-
if (!
|
|
3312
|
+
const hooksDir = join25(claudeHome(), "hooks");
|
|
3313
|
+
if (!existsSync20(hooksDir)) {
|
|
3073
3314
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
3074
3315
|
return;
|
|
3075
3316
|
}
|
|
3076
3317
|
let anyWarn = false;
|
|
3077
3318
|
for (const name of safeReaddir2(hooksDir)) {
|
|
3078
3319
|
if (extname(name) !== ".js") continue;
|
|
3079
|
-
const abs =
|
|
3320
|
+
const abs = join25(hooksDir, name);
|
|
3080
3321
|
const eff = effectiveType(abs);
|
|
3081
3322
|
if (eff === null) continue;
|
|
3082
3323
|
const src = safeRead(abs);
|
|
@@ -3096,8 +3337,8 @@ function reportHookScopeCheck(section2) {
|
|
|
3096
3337
|
|
|
3097
3338
|
// src/commands.doctor.checks.hooks.ts
|
|
3098
3339
|
init_color();
|
|
3099
|
-
import { existsSync as
|
|
3100
|
-
import { join as
|
|
3340
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
3341
|
+
import { join as join26 } from "node:path";
|
|
3101
3342
|
init_config();
|
|
3102
3343
|
function expandHome(token) {
|
|
3103
3344
|
const h2 = home();
|
|
@@ -3136,7 +3377,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3136
3377
|
for (const group of groups) {
|
|
3137
3378
|
for (const cmd of commandsFromGroup(group)) {
|
|
3138
3379
|
for (const resolved of claudePathsIn(cmd)) {
|
|
3139
|
-
if (
|
|
3380
|
+
if (existsSync21(resolved)) continue;
|
|
3140
3381
|
addItem(
|
|
3141
3382
|
section2,
|
|
3142
3383
|
`${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
|
|
@@ -3149,8 +3390,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3149
3390
|
return anyFail;
|
|
3150
3391
|
}
|
|
3151
3392
|
function reportHooksTargetCheck(section2) {
|
|
3152
|
-
const settingsPath =
|
|
3153
|
-
if (!
|
|
3393
|
+
const settingsPath = join26(claudeHome(), "settings.json");
|
|
3394
|
+
if (!existsSync21(settingsPath)) {
|
|
3154
3395
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
3155
3396
|
return;
|
|
3156
3397
|
}
|
|
@@ -3173,12 +3414,12 @@ function reportHooksTargetCheck(section2) {
|
|
|
3173
3414
|
|
|
3174
3415
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
3175
3416
|
init_color();
|
|
3176
|
-
import { existsSync as
|
|
3177
|
-
import { join as
|
|
3417
|
+
import { existsSync as existsSync23, readFileSync as readFileSync7 } from "node:fs";
|
|
3418
|
+
import { join as join28 } from "node:path";
|
|
3178
3419
|
|
|
3179
3420
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
3180
|
-
import { closeSync as closeSync3, existsSync as
|
|
3181
|
-
import { dirname as dirname4, join as
|
|
3421
|
+
import { closeSync as closeSync3, existsSync as existsSync22, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
|
|
3422
|
+
import { dirname as dirname4, join as join27, resolve as resolve2 } from "node:path";
|
|
3182
3423
|
function suppressedRanges(src) {
|
|
3183
3424
|
const ranges = [];
|
|
3184
3425
|
const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
|
|
@@ -3210,12 +3451,12 @@ function topRelativeSpecifiers(src) {
|
|
|
3210
3451
|
}
|
|
3211
3452
|
function specifierIsMissing(specifier, baseDir) {
|
|
3212
3453
|
const base = resolve2(baseDir, specifier);
|
|
3213
|
-
if (
|
|
3454
|
+
if (existsSync22(base)) return false;
|
|
3214
3455
|
for (const ext of [".js", ".cjs", ".mjs"]) {
|
|
3215
|
-
if (
|
|
3456
|
+
if (existsSync22(base + ext)) return false;
|
|
3216
3457
|
}
|
|
3217
3458
|
for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
|
|
3218
|
-
if (
|
|
3459
|
+
if (existsSync22(join27(base, idx))) return false;
|
|
3219
3460
|
}
|
|
3220
3461
|
return true;
|
|
3221
3462
|
}
|
|
@@ -3270,8 +3511,8 @@ function commandTokens(command) {
|
|
|
3270
3511
|
return tokens;
|
|
3271
3512
|
}
|
|
3272
3513
|
function readPathMapSafe() {
|
|
3273
|
-
const mapPath =
|
|
3274
|
-
if (!
|
|
3514
|
+
const mapPath = join28(repoHome(), "path-map.json");
|
|
3515
|
+
if (!existsSync23(mapPath)) return { projects: {} };
|
|
3275
3516
|
try {
|
|
3276
3517
|
return JSON.parse(readFileSync7(mapPath, "utf8"));
|
|
3277
3518
|
} catch {
|
|
@@ -3344,8 +3585,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
3344
3585
|
return anyWarn;
|
|
3345
3586
|
}
|
|
3346
3587
|
function reportPreserveSymlinksCheck(section2) {
|
|
3347
|
-
const settingsPath =
|
|
3348
|
-
if (!
|
|
3588
|
+
const settingsPath = join28(claudeHome(), "settings.json");
|
|
3589
|
+
if (!existsSync23(settingsPath)) {
|
|
3349
3590
|
addItem(
|
|
3350
3591
|
section2,
|
|
3351
3592
|
`${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
|
|
@@ -3378,8 +3619,8 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
3378
3619
|
|
|
3379
3620
|
// src/commands.doctor.checks.settings-drift.ts
|
|
3380
3621
|
init_color();
|
|
3381
|
-
import { existsSync as
|
|
3382
|
-
import { join as
|
|
3622
|
+
import { existsSync as existsSync24, readFileSync as readFileSync8 } from "node:fs";
|
|
3623
|
+
import { join as join29 } from "node:path";
|
|
3383
3624
|
init_config();
|
|
3384
3625
|
init_utils_json();
|
|
3385
3626
|
function diffMergedSettings(merged, settings) {
|
|
@@ -3397,7 +3638,7 @@ function tryReadJson(filePath) {
|
|
|
3397
3638
|
}
|
|
3398
3639
|
}
|
|
3399
3640
|
function reportHooksBaseSelfCleanNote(section2) {
|
|
3400
|
-
const basePath =
|
|
3641
|
+
const basePath = join29(repoHome(), "shared", "settings.base.json");
|
|
3401
3642
|
const base = tryReadJson(basePath);
|
|
3402
3643
|
if (base === null) return;
|
|
3403
3644
|
if (!baseHasGsdHookEntries(base)) return;
|
|
@@ -3410,14 +3651,14 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3410
3651
|
const claude = claudeHome();
|
|
3411
3652
|
const repo = repoHome();
|
|
3412
3653
|
const host = HOST;
|
|
3413
|
-
const settingsPath =
|
|
3414
|
-
const basePath =
|
|
3415
|
-
const hostPath =
|
|
3416
|
-
if (!
|
|
3654
|
+
const settingsPath = join29(claude, "settings.json");
|
|
3655
|
+
const basePath = join29(repo, "shared", "settings.base.json");
|
|
3656
|
+
const hostPath = join29(repo, "hosts", `${host}.json`);
|
|
3657
|
+
if (!existsSync24(settingsPath)) {
|
|
3417
3658
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
3418
3659
|
return;
|
|
3419
3660
|
}
|
|
3420
|
-
if (!
|
|
3661
|
+
if (!existsSync24(basePath)) {
|
|
3421
3662
|
addItem(
|
|
3422
3663
|
section2,
|
|
3423
3664
|
`${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
|
|
@@ -3436,7 +3677,7 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3436
3677
|
if (settings === null) {
|
|
3437
3678
|
return;
|
|
3438
3679
|
}
|
|
3439
|
-
const hostExists =
|
|
3680
|
+
const hostExists = existsSync24(hostPath);
|
|
3440
3681
|
const hostObj = hostExists ? tryReadJson(hostPath) : null;
|
|
3441
3682
|
if (hostExists && hostObj === null) {
|
|
3442
3683
|
addItem(
|
|
@@ -3594,14 +3835,14 @@ function reportNodeEngineCheck(section2) {
|
|
|
3594
3835
|
|
|
3595
3836
|
// src/spinner.ts
|
|
3596
3837
|
init_color();
|
|
3597
|
-
import { existsSync as
|
|
3838
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
3598
3839
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3599
3840
|
import { Worker } from "node:worker_threads";
|
|
3600
3841
|
|
|
3601
3842
|
// src/commands.push.recovery.ts
|
|
3602
3843
|
init_config();
|
|
3603
3844
|
import { readFileSync as readFileSync13, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3604
|
-
import { join as
|
|
3845
|
+
import { join as join34 } from "node:path";
|
|
3605
3846
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
3606
3847
|
|
|
3607
3848
|
// src/commands.push.recovery.actions.ts
|
|
@@ -3612,25 +3853,25 @@ import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "nod
|
|
|
3612
3853
|
// src/commands.push.recovery.redact.ts
|
|
3613
3854
|
init_config();
|
|
3614
3855
|
init_config_sharedDirs_guard();
|
|
3615
|
-
import { cpSync as cpSync5, existsSync as
|
|
3616
|
-
import { dirname as dirname6, join as
|
|
3856
|
+
import { cpSync as cpSync5, existsSync as existsSync27, mkdirSync as mkdirSync6, statSync as statSync7 } from "node:fs";
|
|
3857
|
+
import { dirname as dirname6, join as join32, sep as sep4 } from "node:path";
|
|
3617
3858
|
|
|
3618
3859
|
// src/commands.redact.ts
|
|
3619
3860
|
init_config();
|
|
3620
|
-
import { existsSync as
|
|
3621
|
-
import { dirname as dirname5, join as
|
|
3861
|
+
import { existsSync as existsSync26, statSync as statSync6 } from "node:fs";
|
|
3862
|
+
import { dirname as dirname5, join as join31 } from "node:path";
|
|
3622
3863
|
|
|
3623
3864
|
// src/commands.redact.subtree.ts
|
|
3624
|
-
import { existsSync as
|
|
3625
|
-
import { join as
|
|
3865
|
+
import { existsSync as existsSync25, lstatSync as lstatSync8, readFileSync as readFileSync11, readdirSync as readdirSync10, statSync as statSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3866
|
+
import { join as join30 } from "node:path";
|
|
3626
3867
|
init_utils_fs();
|
|
3627
3868
|
init_utils();
|
|
3628
3869
|
function collectFiles(dir, out) {
|
|
3629
|
-
if (!
|
|
3870
|
+
if (!existsSync25(dir)) return;
|
|
3630
3871
|
const st = lstatSync8(dir);
|
|
3631
3872
|
if (!st.isDirectory()) return;
|
|
3632
|
-
for (const entry of
|
|
3633
|
-
const abs =
|
|
3873
|
+
for (const entry of readdirSync10(dir)) {
|
|
3874
|
+
const abs = join30(dir, entry);
|
|
3634
3875
|
const lst = lstatSync8(abs);
|
|
3635
3876
|
if (lst.isSymbolicLink()) continue;
|
|
3636
3877
|
if (lst.isDirectory()) {
|
|
@@ -3682,10 +3923,10 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
3682
3923
|
|
|
3683
3924
|
// src/commands.pushed-history.ts
|
|
3684
3925
|
init_utils();
|
|
3685
|
-
import { execFileSync as
|
|
3926
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
3686
3927
|
function pushedRef(repo) {
|
|
3687
3928
|
try {
|
|
3688
|
-
const ref =
|
|
3929
|
+
const ref = execFileSync10("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
|
|
3689
3930
|
cwd: repo,
|
|
3690
3931
|
stdio: ["ignore", "pipe", "ignore"]
|
|
3691
3932
|
}).toString().trim();
|
|
@@ -3698,7 +3939,7 @@ function sessionInPushedHistory(id, repo) {
|
|
|
3698
3939
|
const ref = pushedRef(repo);
|
|
3699
3940
|
if (ref === null) return false;
|
|
3700
3941
|
try {
|
|
3701
|
-
const out =
|
|
3942
|
+
const out = execFileSync10(
|
|
3702
3943
|
"git",
|
|
3703
3944
|
[
|
|
3704
3945
|
"log",
|
|
@@ -3735,15 +3976,15 @@ init_utils_json();
|
|
|
3735
3976
|
init_utils();
|
|
3736
3977
|
function resolveLiveTranscript(id) {
|
|
3737
3978
|
try {
|
|
3738
|
-
const mapPath =
|
|
3739
|
-
if (!
|
|
3979
|
+
const mapPath = join31(repoHome(), "path-map.json");
|
|
3980
|
+
if (!existsSync26(mapPath)) return null;
|
|
3740
3981
|
const projects = readJson(mapPath).projects;
|
|
3741
3982
|
const claude = claudeHome();
|
|
3742
3983
|
for (const hostMap of Object.values(projects)) {
|
|
3743
3984
|
const abs = hostMap[HOST];
|
|
3744
3985
|
if (abs === void 0) continue;
|
|
3745
|
-
const live =
|
|
3746
|
-
if (
|
|
3986
|
+
const live = join31(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
3987
|
+
if (existsSync26(live)) return live;
|
|
3747
3988
|
}
|
|
3748
3989
|
return null;
|
|
3749
3990
|
} catch {
|
|
@@ -3763,17 +4004,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
3763
4004
|
}
|
|
3764
4005
|
const repo = repoHome();
|
|
3765
4006
|
const backup = backupBase();
|
|
3766
|
-
if (!
|
|
4007
|
+
if (!existsSync26(repo)) die(`repo not cloned at ${repo}`);
|
|
3767
4008
|
const handle = acquireLock("redact");
|
|
3768
4009
|
if (handle === null) process.exit(0);
|
|
3769
4010
|
try {
|
|
3770
4011
|
const localPath = resolveLiveTranscript(id);
|
|
3771
|
-
if (localPath === null || !
|
|
4012
|
+
if (localPath === null || !existsSync26(localPath)) {
|
|
3772
4013
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
3773
4014
|
process.exitCode = 1;
|
|
3774
4015
|
return;
|
|
3775
4016
|
}
|
|
3776
|
-
const sessionDir =
|
|
4017
|
+
const sessionDir = join31(dirname5(localPath), id);
|
|
3777
4018
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3778
4019
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync6(p).mtimeMs);
|
|
3779
4020
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -3889,8 +4130,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
3889
4130
|
assertSafeLogical(logical);
|
|
3890
4131
|
const abs = hostMap[HOST];
|
|
3891
4132
|
if (abs === void 0) continue;
|
|
3892
|
-
if (localPath.startsWith(
|
|
3893
|
-
return
|
|
4133
|
+
if (localPath.startsWith(join32(claude, "projects", encodePath(abs)) + sep4)) {
|
|
4134
|
+
return join32(repo, "shared", "projects", logical);
|
|
3894
4135
|
}
|
|
3895
4136
|
}
|
|
3896
4137
|
return null;
|
|
@@ -3900,7 +4141,7 @@ function preflightRedactable(f, map, nowMs) {
|
|
|
3900
4141
|
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
3901
4142
|
const localPath = resolveLiveTranscript(sid);
|
|
3902
4143
|
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
3903
|
-
const sessionDir =
|
|
4144
|
+
const sessionDir = join32(dirname6(localPath), sid);
|
|
3904
4145
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3905
4146
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
3906
4147
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -3930,7 +4171,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3930
4171
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
3931
4172
|
);
|
|
3932
4173
|
}
|
|
3933
|
-
const sessionDir =
|
|
4174
|
+
const sessionDir = join32(dirname6(localPath), sid);
|
|
3934
4175
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3935
4176
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
3936
4177
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -3964,9 +4205,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3964
4205
|
);
|
|
3965
4206
|
}
|
|
3966
4207
|
mkdirSync6(stagedProjectDir, { recursive: true });
|
|
3967
|
-
cpSync5(localPath,
|
|
3968
|
-
if (
|
|
3969
|
-
cpSync5(sessionDir,
|
|
4208
|
+
cpSync5(localPath, join32(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
4209
|
+
if (existsSync27(sessionDir)) {
|
|
4210
|
+
cpSync5(sessionDir, join32(stagedProjectDir, sid), { force: true, recursive: true });
|
|
3970
4211
|
}
|
|
3971
4212
|
return true;
|
|
3972
4213
|
}
|
|
@@ -3974,14 +4215,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3974
4215
|
// src/commands.push.recovery.drop.ts
|
|
3975
4216
|
init_config();
|
|
3976
4217
|
import { rmSync as rmSync9 } from "node:fs";
|
|
3977
|
-
import { join as
|
|
4218
|
+
import { join as join33 } from "node:path";
|
|
3978
4219
|
function dropSessionFromStaged(sid, map) {
|
|
3979
4220
|
const logicals = Object.keys(map.projects);
|
|
3980
4221
|
if (logicals.length === 0) return false;
|
|
3981
4222
|
const repo = repoHome();
|
|
3982
4223
|
for (const logical of logicals) {
|
|
3983
|
-
const jsonl =
|
|
3984
|
-
const dir =
|
|
4224
|
+
const jsonl = join33(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
4225
|
+
const dir = join33(repo, "shared", "projects", logical, sid);
|
|
3985
4226
|
rmSync9(jsonl, { force: true });
|
|
3986
4227
|
rmSync9(dir, { recursive: true, force: true });
|
|
3987
4228
|
}
|
|
@@ -4138,7 +4379,7 @@ function applyThenRescan(scanVerdict, repoHome2) {
|
|
|
4138
4379
|
return next;
|
|
4139
4380
|
}
|
|
4140
4381
|
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
4141
|
-
const ignPath =
|
|
4382
|
+
const ignPath = join34(repoHome2, ".gitleaksignore");
|
|
4142
4383
|
let before;
|
|
4143
4384
|
try {
|
|
4144
4385
|
before = readFileSync13(ignPath, "utf8");
|
|
@@ -4237,7 +4478,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
4237
4478
|
`);
|
|
4238
4479
|
}
|
|
4239
4480
|
function resolveWorkerPath(deps = {}) {
|
|
4240
|
-
const check = deps.existsSyncFn ??
|
|
4481
|
+
const check = deps.existsSyncFn ?? existsSync28;
|
|
4241
4482
|
const base = deps.baseUrl ?? import.meta.url;
|
|
4242
4483
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
4243
4484
|
if (check(mjs)) return mjs;
|
|
@@ -4303,9 +4544,9 @@ function withSpinner(label, fn, deps) {
|
|
|
4303
4544
|
|
|
4304
4545
|
// src/commands.doctor.gitleaks-version.ts
|
|
4305
4546
|
init_color();
|
|
4306
|
-
import { execFileSync as
|
|
4307
|
-
import { existsSync as
|
|
4308
|
-
import { join as
|
|
4547
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
4548
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
4549
|
+
import { join as join35 } from "node:path";
|
|
4309
4550
|
init_config();
|
|
4310
4551
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
4311
4552
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -4314,7 +4555,7 @@ function majorMinorOf(value) {
|
|
|
4314
4555
|
return m === null ? null : [m[1], m[2]];
|
|
4315
4556
|
}
|
|
4316
4557
|
function readGitleaksVersion(run, tomlExists) {
|
|
4317
|
-
const tomlPath =
|
|
4558
|
+
const tomlPath = join35(repoHome(), ".gitleaks.toml");
|
|
4318
4559
|
const args = ["version"];
|
|
4319
4560
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
4320
4561
|
try {
|
|
@@ -4326,7 +4567,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
4326
4567
|
return null;
|
|
4327
4568
|
}
|
|
4328
4569
|
}
|
|
4329
|
-
function reportGitleaksVersionCheck(section2, run =
|
|
4570
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync29) {
|
|
4330
4571
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
4331
4572
|
if (raw === null) return;
|
|
4332
4573
|
const local = majorMinorOf(raw);
|
|
@@ -4346,7 +4587,7 @@ function reportGitleaksVersionCheck(section2, run = execFileSync9, tomlExists =
|
|
|
4346
4587
|
|
|
4347
4588
|
// src/commands.doctor.checks.deps.ts
|
|
4348
4589
|
init_color();
|
|
4349
|
-
import { execFileSync as
|
|
4590
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
4350
4591
|
var VERSION_TOKEN = /(\d{1,9}\.\d{1,9}\.\d{1,9})/;
|
|
4351
4592
|
var PROBE_TIMEOUT_MS = 3e3;
|
|
4352
4593
|
var FETCHER_BASE = "HTTP fetcher";
|
|
@@ -4383,14 +4624,14 @@ function reportFetcherRow(section2, run) {
|
|
|
4383
4624
|
);
|
|
4384
4625
|
}
|
|
4385
4626
|
}
|
|
4386
|
-
function reportOptionalDeps(section2, run =
|
|
4627
|
+
function reportOptionalDeps(section2, run = execFileSync12) {
|
|
4387
4628
|
const gh = probeOptionalDep("gh", run);
|
|
4388
4629
|
if (gh.status === "present") {
|
|
4389
4630
|
addItem(section2, `${green(okGlyph)} gh: ${gh.version ?? "present"}`);
|
|
4390
4631
|
} else {
|
|
4391
4632
|
addItem(
|
|
4392
4633
|
section2,
|
|
4393
|
-
`${yellow(warnGlyph)} gh: not installed (optional;
|
|
4634
|
+
`${yellow(warnGlyph)} gh: not installed (optional; nomad init needs it to create the sync repo and auto-disable Actions, and the Actions-drift check uses it)`
|
|
4394
4635
|
);
|
|
4395
4636
|
}
|
|
4396
4637
|
reportFetcherRow(section2, run);
|
|
@@ -4398,11 +4639,11 @@ function reportOptionalDeps(section2, run = execFileSync10) {
|
|
|
4398
4639
|
|
|
4399
4640
|
// src/commands.doctor.actions-drift.ts
|
|
4400
4641
|
init_color();
|
|
4401
|
-
import { execFileSync as
|
|
4642
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
4402
4643
|
init_config();
|
|
4403
4644
|
|
|
4404
4645
|
// src/gh-actions.ts
|
|
4405
|
-
import { execFileSync as
|
|
4646
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
4406
4647
|
var GH_TIMEOUT_MS = 5e3;
|
|
4407
4648
|
function parseGitHubRemote(remoteUrl) {
|
|
4408
4649
|
const normalized = remoteUrl.trim().replace(/\/$/, "");
|
|
@@ -4410,7 +4651,7 @@ function parseGitHubRemote(remoteUrl) {
|
|
|
4410
4651
|
if (m === null) return null;
|
|
4411
4652
|
return { owner: m[1], repo: m[2] };
|
|
4412
4653
|
}
|
|
4413
|
-
function ghAuthStatus(run =
|
|
4654
|
+
function ghAuthStatus(run = execFileSync13) {
|
|
4414
4655
|
try {
|
|
4415
4656
|
run("gh", ["auth", "status"], {
|
|
4416
4657
|
stdio: ["ignore", "ignore", "ignore"],
|
|
@@ -4424,7 +4665,7 @@ function ghAuthStatus(run = execFileSync11) {
|
|
|
4424
4665
|
return "gh-probe-error";
|
|
4425
4666
|
}
|
|
4426
4667
|
}
|
|
4427
|
-
function isRepoPrivate(ref, run =
|
|
4668
|
+
function isRepoPrivate(ref, run = execFileSync13) {
|
|
4428
4669
|
const out = run("gh", ["repo", "view", `${ref.owner}/${ref.repo}`, "--json", "isPrivate"], {
|
|
4429
4670
|
stdio: ["ignore", "pipe", "ignore"],
|
|
4430
4671
|
timeout: GH_TIMEOUT_MS
|
|
@@ -4432,7 +4673,7 @@ function isRepoPrivate(ref, run = execFileSync11) {
|
|
|
4432
4673
|
const parsed = JSON.parse(out);
|
|
4433
4674
|
return parsed.isPrivate === true;
|
|
4434
4675
|
}
|
|
4435
|
-
function isActionsEnabled(ref, run =
|
|
4676
|
+
function isActionsEnabled(ref, run = execFileSync13) {
|
|
4436
4677
|
const out = run(
|
|
4437
4678
|
"gh",
|
|
4438
4679
|
["api", `repos/${ref.owner}/${ref.repo}/actions/permissions`, "--jq", ".enabled"],
|
|
@@ -4440,7 +4681,7 @@ function isActionsEnabled(ref, run = execFileSync11) {
|
|
|
4440
4681
|
).toString().trim();
|
|
4441
4682
|
return out === "true";
|
|
4442
4683
|
}
|
|
4443
|
-
function disableActions(ref, run =
|
|
4684
|
+
function disableActions(ref, run = execFileSync13) {
|
|
4444
4685
|
run(
|
|
4445
4686
|
"gh",
|
|
4446
4687
|
[
|
|
@@ -4454,7 +4695,7 @@ function disableActions(ref, run = execFileSync11) {
|
|
|
4454
4695
|
{ stdio: ["ignore", "ignore", "pipe"], timeout: GH_TIMEOUT_MS }
|
|
4455
4696
|
);
|
|
4456
4697
|
}
|
|
4457
|
-
function readOriginRemote(cwd, run =
|
|
4698
|
+
function readOriginRemote(cwd, run = execFileSync13) {
|
|
4458
4699
|
return run("git", ["remote", "get-url", "origin"], {
|
|
4459
4700
|
cwd,
|
|
4460
4701
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -4462,7 +4703,7 @@ function readOriginRemote(cwd, run = execFileSync11) {
|
|
|
4462
4703
|
}
|
|
4463
4704
|
|
|
4464
4705
|
// src/commands.doctor.actions-drift.ts
|
|
4465
|
-
function reportActionsDrift(section2, run =
|
|
4706
|
+
function reportActionsDrift(section2, run = execFileSync14) {
|
|
4466
4707
|
let remote;
|
|
4467
4708
|
try {
|
|
4468
4709
|
remote = readOriginRemote(repoHome(), run);
|
|
@@ -4519,7 +4760,13 @@ function buildVerdictSection(sections) {
|
|
|
4519
4760
|
|
|
4520
4761
|
// src/commands.doctor.compact.ts
|
|
4521
4762
|
init_color();
|
|
4522
|
-
var ALWAYS_FULL = /* @__PURE__ */ new Set([
|
|
4763
|
+
var ALWAYS_FULL = /* @__PURE__ */ new Set([
|
|
4764
|
+
"Nomad Version",
|
|
4765
|
+
"Summary",
|
|
4766
|
+
"Shared scan",
|
|
4767
|
+
"Schema scan",
|
|
4768
|
+
"Remote check"
|
|
4769
|
+
]);
|
|
4523
4770
|
function isProblem(item2) {
|
|
4524
4771
|
return item2.includes(failGlyph) || item2.includes(warnGlyph);
|
|
4525
4772
|
}
|
|
@@ -4540,10 +4787,11 @@ function compactSections(sections) {
|
|
|
4540
4787
|
function gatherDoctorSections(opts) {
|
|
4541
4788
|
const host = section("Environment");
|
|
4542
4789
|
reportHostAndPaths(host);
|
|
4790
|
+
reportHostKeyAlignment(host);
|
|
4543
4791
|
reportRepoState(host);
|
|
4544
4792
|
const links = section("Shared links");
|
|
4545
|
-
const mapPath =
|
|
4546
|
-
const rawMap =
|
|
4793
|
+
const mapPath = join36(repoHome(), "path-map.json");
|
|
4794
|
+
const rawMap = existsSync30(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
4547
4795
|
const map = rawMap ?? { projects: {} };
|
|
4548
4796
|
reportSharedLinks(links, map);
|
|
4549
4797
|
reportDroppedNamesMigration(links);
|
|
@@ -4561,10 +4809,13 @@ function gatherDoctorSections(opts) {
|
|
|
4561
4809
|
reportPathMap(pathMap);
|
|
4562
4810
|
const neverSync = section("Never-sync");
|
|
4563
4811
|
reportNeverSync(neverSync);
|
|
4812
|
+
const skills = section("Skills");
|
|
4813
|
+
reportSkillsDivergence(skills);
|
|
4564
4814
|
const repository = section("Repository");
|
|
4565
4815
|
const gitleaksReady = reportGitleaksProbe(repository);
|
|
4566
4816
|
reportGitlinks(repository);
|
|
4567
4817
|
reportRemote(repository);
|
|
4818
|
+
reportGitIdentity(repository);
|
|
4568
4819
|
reportRebaseClean(repository);
|
|
4569
4820
|
reportRebaseState(repository);
|
|
4570
4821
|
reportOrphanedAutostash(repository);
|
|
@@ -4581,6 +4832,8 @@ function gatherDoctorSections(opts) {
|
|
|
4581
4832
|
if (opts.checkShared === true) reportCheckShared(sharedScan, gitleaksReady);
|
|
4582
4833
|
const schemaScan = section("Schema scan");
|
|
4583
4834
|
if (opts.checkSchema === true) reportCheckSchema(schemaScan);
|
|
4835
|
+
const remoteCheck = section("Remote check");
|
|
4836
|
+
if (opts.checkRemote === true) reportCheckRemote(remoteCheck);
|
|
4584
4837
|
const body = [
|
|
4585
4838
|
nomadVersion,
|
|
4586
4839
|
depVersions,
|
|
@@ -4590,10 +4843,12 @@ function gatherDoctorSections(opts) {
|
|
|
4590
4843
|
settings,
|
|
4591
4844
|
pathMap,
|
|
4592
4845
|
neverSync,
|
|
4846
|
+
skills,
|
|
4593
4847
|
repository,
|
|
4594
4848
|
housekeeping,
|
|
4595
4849
|
sharedScan,
|
|
4596
|
-
schemaScan
|
|
4850
|
+
schemaScan,
|
|
4851
|
+
remoteCheck
|
|
4597
4852
|
];
|
|
4598
4853
|
return [...body, buildVerdictSection(body)];
|
|
4599
4854
|
}
|
|
@@ -4618,27 +4873,29 @@ function parseDoctorArgs(args) {
|
|
|
4618
4873
|
}
|
|
4619
4874
|
let checkShared = false;
|
|
4620
4875
|
let checkSchema = false;
|
|
4876
|
+
let checkRemote = false;
|
|
4621
4877
|
let verbose = false;
|
|
4622
4878
|
for (const arg of args) {
|
|
4623
4879
|
if (arg === "--check-shared") checkShared = true;
|
|
4624
4880
|
else if (arg === "--check-schema") checkSchema = true;
|
|
4881
|
+
else if (arg === "--check-remote") checkRemote = true;
|
|
4625
4882
|
else if (arg === "--verbose" || arg === "--all" || arg === "-v") verbose = true;
|
|
4626
4883
|
else return { kind: "error" };
|
|
4627
4884
|
}
|
|
4628
|
-
return { kind: "run", checkShared, checkSchema, verbose };
|
|
4885
|
+
return { kind: "run", checkShared, checkSchema, checkRemote, verbose };
|
|
4629
4886
|
}
|
|
4630
4887
|
|
|
4631
4888
|
// src/commands.drop-session.ts
|
|
4632
4889
|
init_config();
|
|
4633
|
-
import { execFileSync as
|
|
4634
|
-
import { existsSync as
|
|
4635
|
-
import { join as
|
|
4890
|
+
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
4891
|
+
import { existsSync as existsSync32, readdirSync as readdirSync11, statSync as statSync8 } from "node:fs";
|
|
4892
|
+
import { join as join38, relative as relative4 } from "node:path";
|
|
4636
4893
|
|
|
4637
4894
|
// src/commands.drop-session.git.ts
|
|
4638
|
-
import { execFileSync as
|
|
4895
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
4639
4896
|
function expandStagedDir(dirRel, repo) {
|
|
4640
4897
|
try {
|
|
4641
|
-
const out =
|
|
4898
|
+
const out = execFileSync15("git", ["ls-files", "-z", "--", dirRel], {
|
|
4642
4899
|
cwd: repo,
|
|
4643
4900
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4644
4901
|
});
|
|
@@ -4649,7 +4906,7 @@ function expandStagedDir(dirRel, repo) {
|
|
|
4649
4906
|
}
|
|
4650
4907
|
function isTrackedInHead(rel, repo) {
|
|
4651
4908
|
try {
|
|
4652
|
-
|
|
4909
|
+
execFileSync15("git", ["cat-file", "-e", `HEAD:${rel}`], {
|
|
4653
4910
|
cwd: repo,
|
|
4654
4911
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4655
4912
|
});
|
|
@@ -4660,7 +4917,7 @@ function isTrackedInHead(rel, repo) {
|
|
|
4660
4917
|
}
|
|
4661
4918
|
function isInIndex(rel, repo) {
|
|
4662
4919
|
try {
|
|
4663
|
-
const out =
|
|
4920
|
+
const out = execFileSync15("git", ["ls-files", "--", rel], {
|
|
4664
4921
|
cwd: repo,
|
|
4665
4922
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4666
4923
|
});
|
|
@@ -4674,8 +4931,8 @@ function isInIndex(rel, repo) {
|
|
|
4674
4931
|
init_config();
|
|
4675
4932
|
init_utils();
|
|
4676
4933
|
init_utils_json();
|
|
4677
|
-
import { existsSync as
|
|
4678
|
-
import { join as
|
|
4934
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
4935
|
+
import { join as join37 } from "node:path";
|
|
4679
4936
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
4680
4937
|
function reportScrubHint(id, matches) {
|
|
4681
4938
|
const live = resolveLiveTranscript2(id, matches);
|
|
@@ -4691,8 +4948,8 @@ function reportScrubHint(id, matches) {
|
|
|
4691
4948
|
}
|
|
4692
4949
|
function resolveLiveTranscript2(id, matches) {
|
|
4693
4950
|
try {
|
|
4694
|
-
const mapPath =
|
|
4695
|
-
if (!
|
|
4951
|
+
const mapPath = join37(repoHome(), "path-map.json");
|
|
4952
|
+
if (!existsSync31(mapPath)) return null;
|
|
4696
4953
|
const projects = readJson(mapPath).projects;
|
|
4697
4954
|
const claude = claudeHome();
|
|
4698
4955
|
for (const rel of matches) {
|
|
@@ -4700,8 +4957,8 @@ function resolveLiveTranscript2(id, matches) {
|
|
|
4700
4957
|
if (logical === void 0) continue;
|
|
4701
4958
|
const abs = projects[logical]?.[HOST];
|
|
4702
4959
|
if (abs === void 0) continue;
|
|
4703
|
-
const live =
|
|
4704
|
-
if (
|
|
4960
|
+
const live = join37(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
4961
|
+
if (existsSync31(live)) return live;
|
|
4705
4962
|
}
|
|
4706
4963
|
return null;
|
|
4707
4964
|
} catch {
|
|
@@ -4717,12 +4974,12 @@ function cmdDropSession(id) {
|
|
|
4717
4974
|
process.exit(1);
|
|
4718
4975
|
}
|
|
4719
4976
|
const repo = repoHome();
|
|
4720
|
-
if (!
|
|
4977
|
+
if (!existsSync32(repo)) die(`repo not cloned at ${repo}`);
|
|
4721
4978
|
const handle = acquireLock("drop-session");
|
|
4722
4979
|
if (handle === null) process.exit(0);
|
|
4723
4980
|
try {
|
|
4724
|
-
const repoProjects =
|
|
4725
|
-
if (!
|
|
4981
|
+
const repoProjects = join38(repo, "shared", "projects");
|
|
4982
|
+
if (!existsSync32(repoProjects)) {
|
|
4726
4983
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
4727
4984
|
}
|
|
4728
4985
|
const matches = collectMatches(repoProjects, id, repo);
|
|
@@ -4744,13 +5001,13 @@ function cmdDropSession(id) {
|
|
|
4744
5001
|
}
|
|
4745
5002
|
function collectMatches(repoProjects, id, repo) {
|
|
4746
5003
|
const matches = [];
|
|
4747
|
-
for (const logical of
|
|
4748
|
-
const candidate =
|
|
4749
|
-
if (
|
|
5004
|
+
for (const logical of readdirSync11(repoProjects)) {
|
|
5005
|
+
const candidate = join38(repoProjects, logical, `${id}.jsonl`);
|
|
5006
|
+
if (existsSync32(candidate)) {
|
|
4750
5007
|
matches.push(relative4(repo, candidate));
|
|
4751
5008
|
}
|
|
4752
|
-
const dir =
|
|
4753
|
-
if (
|
|
5009
|
+
const dir = join38(repoProjects, logical, id);
|
|
5010
|
+
if (existsSync32(dir) && statSync8(dir).isDirectory()) {
|
|
4754
5011
|
const dirRel = relative4(repo, dir);
|
|
4755
5012
|
const staged = expandStagedDir(dirRel, repo);
|
|
4756
5013
|
if (staged.length > 0) matches.push(...staged);
|
|
@@ -4766,12 +5023,12 @@ function unstageOne(rel, repo) {
|
|
|
4766
5023
|
}
|
|
4767
5024
|
try {
|
|
4768
5025
|
if (isTrackedInHead(rel, repo)) {
|
|
4769
|
-
|
|
5026
|
+
execFileSync16("git", ["restore", "--staged", "--worktree", "--", rel], {
|
|
4770
5027
|
cwd: repo,
|
|
4771
5028
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4772
5029
|
});
|
|
4773
5030
|
} else {
|
|
4774
|
-
|
|
5031
|
+
execFileSync16("git", ["rm", "--cached", "-f", "--", rel], {
|
|
4775
5032
|
cwd: repo,
|
|
4776
5033
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4777
5034
|
});
|
|
@@ -4785,8 +5042,8 @@ function unstageOne(rel, repo) {
|
|
|
4785
5042
|
}
|
|
4786
5043
|
|
|
4787
5044
|
// src/commands.pull.ts
|
|
4788
|
-
import { existsSync as
|
|
4789
|
-
import { join as
|
|
5045
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync9 } from "node:fs";
|
|
5046
|
+
import { join as join45 } from "node:path";
|
|
4790
5047
|
|
|
4791
5048
|
// src/commands.push.sections.ts
|
|
4792
5049
|
init_color();
|
|
@@ -4881,55 +5138,20 @@ init_config();
|
|
|
4881
5138
|
|
|
4882
5139
|
// src/extras-sync.ts
|
|
4883
5140
|
init_config();
|
|
4884
|
-
import { existsSync as
|
|
4885
|
-
import { join as
|
|
4886
|
-
|
|
4887
|
-
// src/extras-sync.diff.ts
|
|
4888
|
-
init_utils();
|
|
4889
|
-
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
4890
|
-
function labelDiffLine(line) {
|
|
4891
|
-
const tab = line.indexOf(" ");
|
|
4892
|
-
if (tab === -1) return line;
|
|
4893
|
-
const status = line.slice(0, tab);
|
|
4894
|
-
const path = line.slice(tab + 1);
|
|
4895
|
-
if (status === "D") return `${path} (local only)`;
|
|
4896
|
-
if (status === "A") return `${path} (repo only)`;
|
|
4897
|
-
return path;
|
|
4898
|
-
}
|
|
4899
|
-
function parseDiffOutput(stdout) {
|
|
4900
|
-
return stdout.split("\n").filter((line) => line.length > 0).map(labelDiffLine);
|
|
4901
|
-
}
|
|
4902
|
-
function listDivergingFiles(a, b) {
|
|
4903
|
-
try {
|
|
4904
|
-
const stdout = execFileSync15("git", ["diff", "--no-index", "--name-status", a, b], {
|
|
4905
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4906
|
-
}).toString();
|
|
4907
|
-
return parseDiffOutput(stdout);
|
|
4908
|
-
} catch (err) {
|
|
4909
|
-
const e = err;
|
|
4910
|
-
if (e.status === 1 && e.stdout !== void 0) {
|
|
4911
|
-
return parseDiffOutput(e.stdout.toString());
|
|
4912
|
-
}
|
|
4913
|
-
if (e.code === "ENOENT") {
|
|
4914
|
-
warn(`git not on PATH; divergence check skipped for ${a}`);
|
|
4915
|
-
return [];
|
|
4916
|
-
}
|
|
4917
|
-
warn(`divergence check failed for ${a}: ${e.message ?? String(err)}`);
|
|
4918
|
-
return [];
|
|
4919
|
-
}
|
|
4920
|
-
}
|
|
5141
|
+
import { existsSync as existsSync35, statSync as statSync9 } from "node:fs";
|
|
5142
|
+
import { join as join42 } from "node:path";
|
|
4921
5143
|
|
|
4922
5144
|
// src/extras-sync.core.ts
|
|
4923
5145
|
init_config();
|
|
4924
|
-
import { cpSync as cpSync6, existsSync as
|
|
4925
|
-
import { basename, join as
|
|
5146
|
+
import { cpSync as cpSync6, existsSync as existsSync33, lstatSync as lstatSync9, readdirSync as readdirSync12, rmSync as rmSync11 } from "node:fs";
|
|
5147
|
+
import { basename, join as join39 } from "node:path";
|
|
4926
5148
|
init_utils();
|
|
4927
5149
|
init_utils_json();
|
|
4928
5150
|
function loadValidatedExtras(opts) {
|
|
4929
5151
|
const repo = repoHome();
|
|
4930
|
-
const mapPath =
|
|
4931
|
-
const repoExtras =
|
|
4932
|
-
if (!
|
|
5152
|
+
const mapPath = join39(repo, "path-map.json");
|
|
5153
|
+
const repoExtras = join39(repo, "shared", "extras");
|
|
5154
|
+
if (!existsSync33(mapPath) || opts.requireRepoExtras === true && !existsSync33(repoExtras)) {
|
|
4933
5155
|
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
4934
5156
|
return null;
|
|
4935
5157
|
}
|
|
@@ -4961,16 +5183,16 @@ function* eachExtrasTarget(v, counts) {
|
|
|
4961
5183
|
}
|
|
4962
5184
|
}
|
|
4963
5185
|
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
4964
|
-
if (!
|
|
4965
|
-
for (const name of
|
|
5186
|
+
if (!existsSync33(dst)) return;
|
|
5187
|
+
for (const name of readdirSync12(src)) {
|
|
4966
5188
|
if (isExcluded(name)) continue;
|
|
4967
|
-
const dstPath =
|
|
5189
|
+
const dstPath = join39(dst, name);
|
|
4968
5190
|
const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
|
|
4969
5191
|
if (dstStat === void 0) continue;
|
|
4970
5192
|
if (dstStat.isSymbolicLink()) {
|
|
4971
5193
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
4972
|
-
} else if (dstStat.isDirectory() && lstatSync9(
|
|
4973
|
-
stripCollidingDstSymlinks(
|
|
5194
|
+
} else if (dstStat.isDirectory() && lstatSync9(join39(src, name)).isDirectory()) {
|
|
5195
|
+
stripCollidingDstSymlinks(join39(src, name), dstPath, isExcluded);
|
|
4974
5196
|
}
|
|
4975
5197
|
}
|
|
4976
5198
|
}
|
|
@@ -5010,17 +5232,17 @@ function copyExtrasFiltered(src, dst, blockSet) {
|
|
|
5010
5232
|
});
|
|
5011
5233
|
}
|
|
5012
5234
|
function prunePreservingDenied(src, dst, blockSet) {
|
|
5013
|
-
for (const name of
|
|
5235
|
+
for (const name of readdirSync12(dst)) {
|
|
5014
5236
|
if (isDeniedName(blockSet, name)) continue;
|
|
5015
|
-
const dstPath =
|
|
5016
|
-
const srcStat = lstatSync9(
|
|
5237
|
+
const dstPath = join39(dst, name);
|
|
5238
|
+
const srcStat = lstatSync9(join39(src, name), { throwIfNoEntry: false });
|
|
5017
5239
|
if (srcStat === void 0) {
|
|
5018
5240
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5019
5241
|
continue;
|
|
5020
5242
|
}
|
|
5021
5243
|
const dstStat = lstatSync9(dstPath);
|
|
5022
5244
|
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
5023
|
-
prunePreservingDenied(
|
|
5245
|
+
prunePreservingDenied(join39(src, name), dstPath, blockSet);
|
|
5024
5246
|
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
5025
5247
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5026
5248
|
}
|
|
@@ -5041,17 +5263,17 @@ function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
|
5041
5263
|
});
|
|
5042
5264
|
}
|
|
5043
5265
|
function prunePreservingBy(src, dst, isPreserved) {
|
|
5044
|
-
for (const name of
|
|
5266
|
+
for (const name of readdirSync12(dst)) {
|
|
5045
5267
|
if (isPreserved(name)) continue;
|
|
5046
|
-
const dstPath =
|
|
5047
|
-
const srcStat = lstatSync9(
|
|
5268
|
+
const dstPath = join39(dst, name);
|
|
5269
|
+
const srcStat = lstatSync9(join39(src, name), { throwIfNoEntry: false });
|
|
5048
5270
|
if (srcStat === void 0) {
|
|
5049
5271
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5050
5272
|
continue;
|
|
5051
5273
|
}
|
|
5052
5274
|
const dstStat = lstatSync9(dstPath);
|
|
5053
5275
|
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
5054
|
-
prunePreservingBy(
|
|
5276
|
+
prunePreservingBy(join39(src, name), dstPath, isPreserved);
|
|
5055
5277
|
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
5056
5278
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5057
5279
|
}
|
|
@@ -5078,11 +5300,11 @@ init_utils_json();
|
|
|
5078
5300
|
|
|
5079
5301
|
// src/extras-sync.remap.ts
|
|
5080
5302
|
init_config();
|
|
5081
|
-
import { existsSync as
|
|
5082
|
-
import { dirname as dirname7, join as
|
|
5303
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync7, readdirSync as readdirSync13, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
|
|
5304
|
+
import { dirname as dirname7, join as join41, sep as sep7 } from "node:path";
|
|
5083
5305
|
|
|
5084
5306
|
// src/extras-sync.planning-diff.ts
|
|
5085
|
-
import { join as
|
|
5307
|
+
import { join as join40, normalize as normalize2, sep as sep6 } from "node:path";
|
|
5086
5308
|
init_utils();
|
|
5087
5309
|
function processRecord(fields, i, changed, deleted) {
|
|
5088
5310
|
const status = fields[i];
|
|
@@ -5134,7 +5356,7 @@ function planningDeleteTargets(opts) {
|
|
|
5134
5356
|
const { deleted } = parsePlanningDiff(raw);
|
|
5135
5357
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
5136
5358
|
const prefix = logicalPrefix + ".planning/";
|
|
5137
|
-
const planningRoot =
|
|
5359
|
+
const planningRoot = join40(localRoot, ".planning");
|
|
5138
5360
|
const planningRootBoundary = planningRoot + sep6;
|
|
5139
5361
|
const targets = [];
|
|
5140
5362
|
for (const repoPath of deleted) {
|
|
@@ -5142,7 +5364,7 @@ function planningDeleteTargets(opts) {
|
|
|
5142
5364
|
continue;
|
|
5143
5365
|
}
|
|
5144
5366
|
const remainder = repoPath.slice(logicalPrefix.length);
|
|
5145
|
-
const candidate =
|
|
5367
|
+
const candidate = join40(localRoot, remainder);
|
|
5146
5368
|
const resolved = normalize2(candidate);
|
|
5147
5369
|
if (!resolved.startsWith(planningRootBoundary)) {
|
|
5148
5370
|
throw new NomadFatal(
|
|
@@ -5163,7 +5385,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
5163
5385
|
const would = [];
|
|
5164
5386
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
5165
5387
|
const { src, dst } = paths(t);
|
|
5166
|
-
if (!
|
|
5388
|
+
if (!existsSync34(src)) continue;
|
|
5167
5389
|
const item2 = `${t.logical}/${t.dirname}`;
|
|
5168
5390
|
if (dryRun) {
|
|
5169
5391
|
would.push(item2);
|
|
@@ -5179,7 +5401,7 @@ function pruneEmptyAncestors(target, planningRoot) {
|
|
|
5179
5401
|
let dir = dirname7(target);
|
|
5180
5402
|
while (dir !== planningRoot && dir.startsWith(planningRoot + sep7)) {
|
|
5181
5403
|
try {
|
|
5182
|
-
if (
|
|
5404
|
+
if (readdirSync13(dir).length > 0) break;
|
|
5183
5405
|
rmSync12(dir, { recursive: true, force: true });
|
|
5184
5406
|
} catch {
|
|
5185
5407
|
break;
|
|
@@ -5198,7 +5420,7 @@ function isInsidePlanningRoot(parentReal, rootReal) {
|
|
|
5198
5420
|
return parentReal === rootReal || parentReal.startsWith(rootReal + sep7);
|
|
5199
5421
|
}
|
|
5200
5422
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
5201
|
-
if (
|
|
5423
|
+
if (existsSync34(repoCounterpart)) return;
|
|
5202
5424
|
const parentReal = tryRealpath(dirname7(target));
|
|
5203
5425
|
if (parentReal === void 0) return;
|
|
5204
5426
|
const rootReal = tryRealpath(planningRoot);
|
|
@@ -5208,7 +5430,7 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
|
5208
5430
|
pruneEmptyAncestors(target, planningRoot);
|
|
5209
5431
|
}
|
|
5210
5432
|
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
5211
|
-
const repoExtras =
|
|
5433
|
+
const repoExtras = join41(repo, "shared", "extras");
|
|
5212
5434
|
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
5213
5435
|
if (t.dirname !== ".planning") continue;
|
|
5214
5436
|
let raw;
|
|
@@ -5234,11 +5456,11 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
5234
5456
|
}
|
|
5235
5457
|
const targets = planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot });
|
|
5236
5458
|
if (targets.length === 0) continue;
|
|
5237
|
-
backupExtrasWrite(
|
|
5238
|
-
const planningRoot =
|
|
5459
|
+
backupExtrasWrite(join41(t.localRoot, t.dirname), ts, t.localRoot);
|
|
5460
|
+
const planningRoot = join41(t.localRoot, ".planning");
|
|
5239
5461
|
for (const target of targets) {
|
|
5240
5462
|
const relToLocal = target.slice(t.localRoot.length + sep7.length);
|
|
5241
|
-
deletePlanningTarget(target, planningRoot,
|
|
5463
|
+
deletePlanningTarget(target, planningRoot, join41(repoExtras, t.logical, relToLocal));
|
|
5242
5464
|
}
|
|
5243
5465
|
}
|
|
5244
5466
|
}
|
|
@@ -5247,14 +5469,14 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
5247
5469
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
5248
5470
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
5249
5471
|
const repo = repoHome();
|
|
5250
|
-
const repoExtras =
|
|
5472
|
+
const repoExtras = join41(repo, "shared", "extras");
|
|
5251
5473
|
if (!dryRun) mkdirSync7(repoExtras, { recursive: true });
|
|
5252
5474
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
5253
5475
|
v,
|
|
5254
5476
|
dryRun,
|
|
5255
5477
|
({ localRoot, logical, dirname: dirname8 }) => ({
|
|
5256
|
-
src:
|
|
5257
|
-
dst:
|
|
5478
|
+
src: join41(localRoot, dirname8),
|
|
5479
|
+
dst: join41(repoExtras, logical, dirname8)
|
|
5258
5480
|
}),
|
|
5259
5481
|
(dst) => backupRepoWrite(dst, ts, repo),
|
|
5260
5482
|
// Push copy routing per extra type:
|
|
@@ -5283,8 +5505,8 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
5283
5505
|
v,
|
|
5284
5506
|
dryRun,
|
|
5285
5507
|
({ localRoot, logical, dirname: dirname8 }) => ({
|
|
5286
|
-
src:
|
|
5287
|
-
dst:
|
|
5508
|
+
src: join41(repo, "shared", "extras", logical, dirname8),
|
|
5509
|
+
dst: join41(localRoot, dirname8)
|
|
5288
5510
|
}),
|
|
5289
5511
|
// Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
|
|
5290
5512
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -5312,21 +5534,36 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
5312
5534
|
}
|
|
5313
5535
|
|
|
5314
5536
|
// src/extras-sync.ts
|
|
5537
|
+
function divergenceWarnLine(o) {
|
|
5538
|
+
const kind = o.isDir ? "folder" : "file";
|
|
5539
|
+
const name = o.isDir ? `${o.dirname}/` : o.dirname;
|
|
5540
|
+
const one = o.count === 1;
|
|
5541
|
+
const fileCount = one ? "1 file" : `${o.count} files`;
|
|
5542
|
+
const them = one ? "it" : "them";
|
|
5543
|
+
const yours = one ? "your current file is" : "your current files are";
|
|
5544
|
+
return `local ${kind} ${name} in repo ${o.logical} differs from the synced copy in ${fileCount}; the next pull step will overwrite ${them} with the synced version (${yours} backed up to ${o.projectBackupRoot}/)`;
|
|
5545
|
+
}
|
|
5315
5546
|
function divergenceCheckExtras(ts) {
|
|
5316
5547
|
const v = loadValidatedExtras({});
|
|
5317
5548
|
if (v === null) return;
|
|
5318
5549
|
const counts = { unmapped: 0, skipped: 0 };
|
|
5319
|
-
const backupRoot =
|
|
5550
|
+
const backupRoot = join42(backupBase(), ts, "extras");
|
|
5320
5551
|
const repo = repoHome();
|
|
5321
5552
|
for (const { logical, localRoot, dirname: dirname8 } of eachExtrasTarget(v, counts)) {
|
|
5322
|
-
const local =
|
|
5323
|
-
const repoEntry =
|
|
5324
|
-
if (!
|
|
5553
|
+
const local = join42(localRoot, dirname8);
|
|
5554
|
+
const repoEntry = join42(repo, "shared", "extras", logical, dirname8);
|
|
5555
|
+
if (!existsSync35(local) || !existsSync35(repoEntry)) continue;
|
|
5325
5556
|
const diff = listDivergingFiles(local, repoEntry);
|
|
5326
5557
|
if (diff.length === 0) continue;
|
|
5327
|
-
const projectBackupRoot =
|
|
5558
|
+
const projectBackupRoot = join42(backupRoot, encodePath(localRoot));
|
|
5328
5559
|
warn(
|
|
5329
|
-
|
|
5560
|
+
divergenceWarnLine({
|
|
5561
|
+
dirname: dirname8,
|
|
5562
|
+
logical,
|
|
5563
|
+
isDir: statSync9(local).isDirectory(),
|
|
5564
|
+
count: diff.length,
|
|
5565
|
+
projectBackupRoot
|
|
5566
|
+
})
|
|
5330
5567
|
);
|
|
5331
5568
|
for (const f of diff) warn(` ${f}`);
|
|
5332
5569
|
}
|
|
@@ -5334,8 +5571,8 @@ function divergenceCheckExtras(ts) {
|
|
|
5334
5571
|
|
|
5335
5572
|
// src/skills-sync.ts
|
|
5336
5573
|
init_config();
|
|
5337
|
-
import { existsSync as
|
|
5338
|
-
import { join as
|
|
5574
|
+
import { existsSync as existsSync36, lstatSync as lstatSync10, mkdirSync as mkdirSync8, readdirSync as readdirSync14, rmSync as rmSync13 } from "node:fs";
|
|
5575
|
+
import { join as join43 } from "node:path";
|
|
5339
5576
|
init_utils_fs();
|
|
5340
5577
|
function isGsdOwned(name) {
|
|
5341
5578
|
return name.startsWith(GSD_PREFIX);
|
|
@@ -5344,7 +5581,7 @@ function isSkillExcluded(name) {
|
|
|
5344
5581
|
return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
|
|
5345
5582
|
}
|
|
5346
5583
|
function copySkillsPush(src, dst) {
|
|
5347
|
-
const srcNames =
|
|
5584
|
+
const srcNames = readdirSync14(src, { encoding: "utf8" });
|
|
5348
5585
|
const blockSet = /* @__PURE__ */ new Set([
|
|
5349
5586
|
...srcNames.filter((n) => isGsdOwned(n)),
|
|
5350
5587
|
...ALWAYS_NEVER_SYNC
|
|
@@ -5355,9 +5592,9 @@ function copySkillsPull(src, dst) {
|
|
|
5355
5592
|
copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
|
|
5356
5593
|
}
|
|
5357
5594
|
function syncSkillsPull(ts) {
|
|
5358
|
-
const sharedSkills =
|
|
5359
|
-
if (!
|
|
5360
|
-
const localSkills =
|
|
5595
|
+
const sharedSkills = join43(repoHome(), "shared", "skills");
|
|
5596
|
+
if (!existsSync36(sharedSkills)) return;
|
|
5597
|
+
const localSkills = join43(claudeHome(), "skills");
|
|
5361
5598
|
const dstStat = lstatSync10(localSkills, { throwIfNoEntry: false });
|
|
5362
5599
|
if (dstStat?.isSymbolicLink() === true) {
|
|
5363
5600
|
backupBeforeWrite(localSkills, ts);
|
|
@@ -5367,18 +5604,18 @@ function syncSkillsPull(ts) {
|
|
|
5367
5604
|
copySkillsPull(sharedSkills, localSkills);
|
|
5368
5605
|
}
|
|
5369
5606
|
function syncSkillsPush() {
|
|
5370
|
-
const localSkills =
|
|
5607
|
+
const localSkills = join43(claudeHome(), "skills");
|
|
5371
5608
|
const stat = lstatSync10(localSkills, { throwIfNoEntry: false });
|
|
5372
5609
|
if (stat === void 0) return;
|
|
5373
5610
|
if (stat.isSymbolicLink()) return;
|
|
5374
|
-
const sharedSkills =
|
|
5611
|
+
const sharedSkills = join43(repoHome(), "shared", "skills");
|
|
5375
5612
|
copySkillsPush(localSkills, sharedSkills);
|
|
5376
5613
|
}
|
|
5377
5614
|
|
|
5378
5615
|
// src/preview.ts
|
|
5379
5616
|
init_config();
|
|
5380
|
-
import { existsSync as
|
|
5381
|
-
import { join as
|
|
5617
|
+
import { existsSync as existsSync37 } from "node:fs";
|
|
5618
|
+
import { join as join44 } from "node:path";
|
|
5382
5619
|
|
|
5383
5620
|
// node_modules/diff/libesm/diff/base.js
|
|
5384
5621
|
var Diff = class {
|
|
@@ -5664,7 +5901,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
5664
5901
|
return lines.join("\n");
|
|
5665
5902
|
}
|
|
5666
5903
|
function readJsonOrNull(path) {
|
|
5667
|
-
if (!
|
|
5904
|
+
if (!existsSync37(path)) return null;
|
|
5668
5905
|
try {
|
|
5669
5906
|
return readJson(path);
|
|
5670
5907
|
} catch {
|
|
@@ -5678,12 +5915,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
5678
5915
|
}
|
|
5679
5916
|
const notes = [];
|
|
5680
5917
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
5681
|
-
if (hostOverrides === null &&
|
|
5918
|
+
if (hostOverrides === null && existsSync37(hostPath)) {
|
|
5682
5919
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
5683
5920
|
}
|
|
5684
5921
|
const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
|
|
5685
5922
|
const current = readJsonOrNull(settingsPath);
|
|
5686
|
-
if (current === null &&
|
|
5923
|
+
if (current === null && existsSync37(settingsPath)) {
|
|
5687
5924
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
5688
5925
|
}
|
|
5689
5926
|
const strippedCurrent = stripGsdHookEntries(current ?? {});
|
|
@@ -5724,9 +5961,9 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
5724
5961
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
5725
5962
|
});
|
|
5726
5963
|
const settingsResult = previewSettings(
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5964
|
+
join44(repo, "shared", "settings.base.json"),
|
|
5965
|
+
join44(repo, "hosts", `${HOST}.json`),
|
|
5966
|
+
join44(claude, "settings.json")
|
|
5730
5967
|
);
|
|
5731
5968
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
5732
5969
|
const sessions = section("Sessions");
|
|
@@ -5748,9 +5985,9 @@ init_config();
|
|
|
5748
5985
|
init_commands_pull_wedge();
|
|
5749
5986
|
init_utils();
|
|
5750
5987
|
init_utils_fs();
|
|
5751
|
-
import { execFileSync as
|
|
5988
|
+
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
5752
5989
|
function gitCapture(args, cwd) {
|
|
5753
|
-
return
|
|
5990
|
+
return execFileSync17("git", args, {
|
|
5754
5991
|
cwd,
|
|
5755
5992
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5756
5993
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -5927,8 +6164,8 @@ function cmdPull(opts = {}) {
|
|
|
5927
6164
|
const forceRemote = opts.forceRemote === true;
|
|
5928
6165
|
const repo = repoHome();
|
|
5929
6166
|
const backup = backupBase();
|
|
5930
|
-
if (!
|
|
5931
|
-
if (!
|
|
6167
|
+
if (!existsSync38(repo)) die(`repo not cloned at ${repo}`);
|
|
6168
|
+
if (!existsSync38(join45(repo, "shared", "settings.base.json"))) {
|
|
5932
6169
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
5933
6170
|
}
|
|
5934
6171
|
const handle = acquireLock("pull");
|
|
@@ -5937,7 +6174,7 @@ function cmdPull(opts = {}) {
|
|
|
5937
6174
|
const ts = freshBackupTs(backup);
|
|
5938
6175
|
handleWedge(repo, forceRemote);
|
|
5939
6176
|
if (!dryRun) {
|
|
5940
|
-
const backupRoot =
|
|
6177
|
+
const backupRoot = join45(backup, ts);
|
|
5941
6178
|
try {
|
|
5942
6179
|
mkdirSync9(backupRoot, { recursive: true });
|
|
5943
6180
|
} catch (err) {
|
|
@@ -5950,8 +6187,8 @@ function cmdPull(opts = {}) {
|
|
|
5950
6187
|
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
5951
6188
|
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
5952
6189
|
});
|
|
5953
|
-
const mapPath =
|
|
5954
|
-
const map =
|
|
6190
|
+
const mapPath = join45(repo, "path-map.json");
|
|
6191
|
+
const map = existsSync38(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
5955
6192
|
divergenceCheckExtras(ts);
|
|
5956
6193
|
if (dryRun) {
|
|
5957
6194
|
computePreview(ts, map, "pull");
|
|
@@ -5973,8 +6210,8 @@ function cmdPull(opts = {}) {
|
|
|
5973
6210
|
|
|
5974
6211
|
// src/commands.push.ts
|
|
5975
6212
|
init_config();
|
|
5976
|
-
import { existsSync as
|
|
5977
|
-
import { join as
|
|
6213
|
+
import { existsSync as existsSync40 } from "node:fs";
|
|
6214
|
+
import { join as join47, relative as relative5 } from "node:path";
|
|
5978
6215
|
|
|
5979
6216
|
// src/commands.push.allowlist.ts
|
|
5980
6217
|
init_config();
|
|
@@ -6067,7 +6304,7 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
6067
6304
|
|
|
6068
6305
|
// src/push-global-config.ts
|
|
6069
6306
|
init_config();
|
|
6070
|
-
import { execFileSync as
|
|
6307
|
+
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
6071
6308
|
var STATUS_LABELS = {
|
|
6072
6309
|
A: "add",
|
|
6073
6310
|
M: "modify",
|
|
@@ -6107,7 +6344,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
|
|
|
6107
6344
|
}
|
|
6108
6345
|
function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
|
|
6109
6346
|
const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
|
|
6110
|
-
const raw =
|
|
6347
|
+
const raw = execFileSync18("git", args, {
|
|
6111
6348
|
cwd: repoHome2,
|
|
6112
6349
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6113
6350
|
}).toString();
|
|
@@ -6146,9 +6383,9 @@ init_color();
|
|
|
6146
6383
|
init_config();
|
|
6147
6384
|
init_config_sharedDirs_guard();
|
|
6148
6385
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
6149
|
-
import { copyFileSync, existsSync as
|
|
6386
|
+
import { copyFileSync, existsSync as existsSync39, mkdirSync as mkdirSync10, readdirSync as readdirSync15, rmSync as rmSync14 } from "node:fs";
|
|
6150
6387
|
import { homedir as homedir5 } from "node:os";
|
|
6151
|
-
import { join as
|
|
6388
|
+
import { join as join46 } from "node:path";
|
|
6152
6389
|
init_push_leak_verdict();
|
|
6153
6390
|
init_push_gitleaks();
|
|
6154
6391
|
init_utils_fs();
|
|
@@ -6163,13 +6400,13 @@ function stageSessions(tmpRoot, map) {
|
|
|
6163
6400
|
if (!p || p === "TBD") continue;
|
|
6164
6401
|
reverse.set(encodePath(p), logical);
|
|
6165
6402
|
}
|
|
6166
|
-
const localProjects =
|
|
6167
|
-
if (!
|
|
6403
|
+
const localProjects = join46(claudeHome(), "projects");
|
|
6404
|
+
if (!existsSync39(localProjects)) return 0;
|
|
6168
6405
|
let staged = 0;
|
|
6169
|
-
for (const dir of
|
|
6406
|
+
for (const dir of readdirSync15(localProjects)) {
|
|
6170
6407
|
const logical = reverse.get(dir);
|
|
6171
6408
|
if (!logical) continue;
|
|
6172
|
-
copyDirJsonlOnly(
|
|
6409
|
+
copyDirJsonlOnly(join46(localProjects, dir), join46(tmpRoot, "shared", "projects", logical));
|
|
6173
6410
|
staged++;
|
|
6174
6411
|
}
|
|
6175
6412
|
return staged;
|
|
@@ -6185,9 +6422,9 @@ function stageExtras(tmpRoot, map) {
|
|
|
6185
6422
|
if (!localRoot || localRoot === "TBD") continue;
|
|
6186
6423
|
for (const dirname8 of dirnames) {
|
|
6187
6424
|
if (!whitelist.includes(dirname8)) continue;
|
|
6188
|
-
const src =
|
|
6189
|
-
if (!
|
|
6190
|
-
const dst =
|
|
6425
|
+
const src = join46(localRoot, dirname8);
|
|
6426
|
+
if (!existsSync39(src)) continue;
|
|
6427
|
+
const dst = join46(tmpRoot, "shared", "extras", logical, dirname8);
|
|
6191
6428
|
copyExtras(src, dst);
|
|
6192
6429
|
staged++;
|
|
6193
6430
|
}
|
|
@@ -6195,19 +6432,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
6195
6432
|
return staged;
|
|
6196
6433
|
}
|
|
6197
6434
|
function previewPushLeaks(map) {
|
|
6198
|
-
const cacheDir =
|
|
6435
|
+
const cacheDir = join46(homedir5(), ".cache", "claude-nomad");
|
|
6199
6436
|
mkdirSync10(cacheDir, { recursive: true });
|
|
6200
6437
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
6201
|
-
const tmpRoot =
|
|
6438
|
+
const tmpRoot = join46(cacheDir, `push-preview-tree-${stamp}`);
|
|
6202
6439
|
try {
|
|
6203
6440
|
const sessionCount = stageSessions(tmpRoot, map);
|
|
6204
6441
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
6205
6442
|
if (sessionCount + extrasCount === 0) {
|
|
6206
6443
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
6207
6444
|
}
|
|
6208
|
-
const ignoreFile =
|
|
6209
|
-
if (
|
|
6210
|
-
copyFileSync(ignoreFile,
|
|
6445
|
+
const ignoreFile = join46(repoHome(), ".gitleaksignore");
|
|
6446
|
+
if (existsSync39(ignoreFile)) {
|
|
6447
|
+
copyFileSync(ignoreFile, join46(tmpRoot, ".gitleaksignore"));
|
|
6211
6448
|
}
|
|
6212
6449
|
let findings;
|
|
6213
6450
|
try {
|
|
@@ -6229,8 +6466,8 @@ init_utils();
|
|
|
6229
6466
|
init_utils_fs();
|
|
6230
6467
|
init_utils_json();
|
|
6231
6468
|
function stripGsdHooksFromBase(repo, backup) {
|
|
6232
|
-
const basePath =
|
|
6233
|
-
if (!
|
|
6469
|
+
const basePath = join47(repo, "shared", "settings.base.json");
|
|
6470
|
+
if (!existsSync40(basePath)) return;
|
|
6234
6471
|
let base;
|
|
6235
6472
|
try {
|
|
6236
6473
|
base = readJson(basePath);
|
|
@@ -6244,28 +6481,28 @@ function stripGsdHooksFromBase(repo, backup) {
|
|
|
6244
6481
|
writeJsonAtomic(basePath, stripped);
|
|
6245
6482
|
}
|
|
6246
6483
|
function reportSettingsAheadDrift(repo) {
|
|
6247
|
-
const basePath =
|
|
6248
|
-
if (!
|
|
6249
|
-
const settingsPath =
|
|
6250
|
-
if (!
|
|
6484
|
+
const basePath = join47(repo, "shared", "settings.base.json");
|
|
6485
|
+
if (!existsSync40(basePath)) return;
|
|
6486
|
+
const settingsPath = join47(claudeHome(), "settings.json");
|
|
6487
|
+
if (!existsSync40(settingsPath)) return;
|
|
6251
6488
|
try {
|
|
6252
6489
|
const base = readJson(basePath);
|
|
6253
|
-
const hostPath =
|
|
6254
|
-
const overrides =
|
|
6490
|
+
const hostPath = join47(repo, "hosts", `${HOST}.json`);
|
|
6491
|
+
const overrides = existsSync40(hostPath) ? readJson(hostPath) : {};
|
|
6255
6492
|
const merged = deepMerge(base, overrides);
|
|
6256
6493
|
const settings = readJson(settingsPath);
|
|
6257
6494
|
const { ahead } = classifySettingsDrift(merged, settings);
|
|
6258
6495
|
const { promotable } = partitionByCaptureExclusion(ahead);
|
|
6259
6496
|
if (promotable.length === 0) return;
|
|
6260
|
-
const
|
|
6497
|
+
const { phrase, pronoun, verb } = describeSettings(promotable);
|
|
6261
6498
|
warn(
|
|
6262
|
-
`settings.json has
|
|
6499
|
+
`your settings.json has ${phrase} that ${verb} not yet in the repo; run 'nomad capture-settings' to save ${pronoun} (or 'nomad capture-settings --host' for host-specific values).`
|
|
6263
6500
|
);
|
|
6264
6501
|
} catch {
|
|
6265
6502
|
}
|
|
6266
6503
|
}
|
|
6267
6504
|
function guardGitlinks(repo) {
|
|
6268
|
-
const gitlinks = findGitlinks(
|
|
6505
|
+
const gitlinks = findGitlinks(join47(repo, "shared"));
|
|
6269
6506
|
if (gitlinks.length === 0) return;
|
|
6270
6507
|
for (const p of gitlinks) {
|
|
6271
6508
|
const rel = relative5(repo, p);
|
|
@@ -6331,7 +6568,7 @@ async function cmdPush(opts = {}) {
|
|
|
6331
6568
|
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
6332
6569
|
const repo = repoHome();
|
|
6333
6570
|
const backup = backupBase();
|
|
6334
|
-
if (!
|
|
6571
|
+
if (!existsSync40(repo)) die(`repo not cloned at ${repo}`);
|
|
6335
6572
|
const handle = acquireLock("push");
|
|
6336
6573
|
if (handle === null) process.exit(0);
|
|
6337
6574
|
try {
|
|
@@ -6354,8 +6591,8 @@ async function cmdPush(opts = {}) {
|
|
|
6354
6591
|
renderNoScanTree(st);
|
|
6355
6592
|
return;
|
|
6356
6593
|
}
|
|
6357
|
-
const mapPath =
|
|
6358
|
-
if (!
|
|
6594
|
+
const mapPath = join47(repo, "path-map.json");
|
|
6595
|
+
if (!existsSync40(mapPath)) {
|
|
6359
6596
|
if (dryRun) return runDryRunPreview(st, null, repo);
|
|
6360
6597
|
die("path-map.json missing, cannot enforce push allow-list");
|
|
6361
6598
|
}
|
|
@@ -6376,25 +6613,35 @@ async function cmdPush(opts = {}) {
|
|
|
6376
6613
|
}
|
|
6377
6614
|
|
|
6378
6615
|
// src/commands.update.ts
|
|
6379
|
-
import { execFileSync as
|
|
6616
|
+
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
6380
6617
|
init_utils();
|
|
6381
|
-
function readInstalledVersion(run =
|
|
6618
|
+
function readInstalledVersion(run = execFileSync19) {
|
|
6382
6619
|
try {
|
|
6383
6620
|
return run("nomad", ["--version"], { encoding: "utf8" }).toString().trim() || null;
|
|
6384
6621
|
} catch {
|
|
6385
6622
|
return null;
|
|
6386
6623
|
}
|
|
6387
6624
|
}
|
|
6388
|
-
function cmdUpdate(run =
|
|
6389
|
-
console.log(
|
|
6625
|
+
function cmdUpdate(currentVersion, run = execFileSync19) {
|
|
6626
|
+
console.log(`Updating claude-nomad v${currentVersion}...`);
|
|
6390
6627
|
try {
|
|
6391
|
-
run("npm", ["update", "-g", "claude-nomad"], {
|
|
6628
|
+
run("npm", ["update", "-g", "claude-nomad"], {
|
|
6629
|
+
encoding: "utf8",
|
|
6630
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
6631
|
+
// Now that output is captured rather than inherited, execFileSync buffers
|
|
6632
|
+
// it; lift the default 1 MiB ceiling so a noisy-but-successful npm run
|
|
6633
|
+
// (deprecation/funding spam) cannot throw ENOBUFS.
|
|
6634
|
+
maxBuffer: 64 * 1024 * 1024
|
|
6635
|
+
});
|
|
6392
6636
|
} catch (err) {
|
|
6393
6637
|
const e = err;
|
|
6394
6638
|
if (e.code === "ENOENT") {
|
|
6395
6639
|
throw new NomadFatal("npm not found on PATH; install Node.js/npm and retry.");
|
|
6396
6640
|
}
|
|
6397
|
-
|
|
6641
|
+
const detail = String(e.stderr ?? "").trim();
|
|
6642
|
+
const suffix = detail ? `
|
|
6643
|
+
${detail}` : "";
|
|
6644
|
+
throw new NomadFatal(`npm update -g claude-nomad failed: ${e.message}${suffix}`);
|
|
6398
6645
|
}
|
|
6399
6646
|
const version = readInstalledVersion(run);
|
|
6400
6647
|
if (version) {
|
|
@@ -6409,18 +6656,18 @@ init_config();
|
|
|
6409
6656
|
|
|
6410
6657
|
// src/diff.ts
|
|
6411
6658
|
init_config();
|
|
6412
|
-
import { existsSync as
|
|
6413
|
-
import { join as
|
|
6659
|
+
import { existsSync as existsSync41 } from "node:fs";
|
|
6660
|
+
import { join as join48 } from "node:path";
|
|
6414
6661
|
init_utils();
|
|
6415
6662
|
init_utils_fs();
|
|
6416
6663
|
init_utils_json();
|
|
6417
6664
|
function cmdDiff() {
|
|
6418
6665
|
try {
|
|
6419
6666
|
const repo = repoHome();
|
|
6420
|
-
if (!
|
|
6667
|
+
if (!existsSync41(repo)) die(`repo not cloned at ${repo}`);
|
|
6421
6668
|
const ts = freshBackupTs(backupBase());
|
|
6422
|
-
const mapPath =
|
|
6423
|
-
const map =
|
|
6669
|
+
const mapPath = join48(repo, "path-map.json");
|
|
6670
|
+
const map = existsSync41(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
6424
6671
|
computePreview(ts, map, "diff");
|
|
6425
6672
|
} catch (err) {
|
|
6426
6673
|
if (err instanceof NomadFatal) {
|
|
@@ -6434,19 +6681,19 @@ function cmdDiff() {
|
|
|
6434
6681
|
|
|
6435
6682
|
// src/init.ts
|
|
6436
6683
|
init_config();
|
|
6437
|
-
import { existsSync as
|
|
6438
|
-
import { join as
|
|
6684
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync11, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6685
|
+
import { join as join50 } from "node:path";
|
|
6439
6686
|
|
|
6440
6687
|
// src/init.gh-onboard.ts
|
|
6441
6688
|
init_config();
|
|
6442
|
-
import { execFileSync as
|
|
6689
|
+
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
6443
6690
|
init_utils();
|
|
6444
6691
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
6445
6692
|
function isValidRepoName(name) {
|
|
6446
6693
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
6447
6694
|
}
|
|
6448
6695
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
6449
|
-
function ensureOriginRepo(repoName, run =
|
|
6696
|
+
function ensureOriginRepo(repoName, run = execFileSync20) {
|
|
6450
6697
|
if (!isValidRepoName(repoName)) {
|
|
6451
6698
|
die(
|
|
6452
6699
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -6517,33 +6764,33 @@ init_config();
|
|
|
6517
6764
|
init_utils();
|
|
6518
6765
|
init_utils_fs();
|
|
6519
6766
|
init_utils_json();
|
|
6520
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as
|
|
6521
|
-
import { join as
|
|
6767
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync42, rmSync as rmSync15, statSync as statSync10 } from "node:fs";
|
|
6768
|
+
import { join as join49 } from "node:path";
|
|
6522
6769
|
function snapshotIntoShared(map) {
|
|
6523
6770
|
const repo = repoHome();
|
|
6524
6771
|
const claude = claudeHome();
|
|
6525
6772
|
for (const name of allSharedLinks(map)) {
|
|
6526
|
-
const src =
|
|
6527
|
-
if (!
|
|
6528
|
-
const dst =
|
|
6529
|
-
if (
|
|
6530
|
-
const gk =
|
|
6531
|
-
if (
|
|
6773
|
+
const src = join49(claude, name);
|
|
6774
|
+
if (!existsSync42(src)) continue;
|
|
6775
|
+
const dst = join49(repo, "shared", name);
|
|
6776
|
+
if (statSync10(src).isDirectory()) {
|
|
6777
|
+
const gk = join49(dst, ".gitkeep");
|
|
6778
|
+
if (existsSync42(gk)) rmSync15(gk);
|
|
6532
6779
|
cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
6533
6780
|
} else {
|
|
6534
6781
|
copyFileSync2(src, dst);
|
|
6535
6782
|
}
|
|
6536
6783
|
log(`snapshotted shared/${name} from ${src}`);
|
|
6537
6784
|
}
|
|
6538
|
-
const userSettings =
|
|
6539
|
-
if (
|
|
6785
|
+
const userSettings = join49(claude, "settings.json");
|
|
6786
|
+
if (existsSync42(userSettings)) {
|
|
6540
6787
|
let parsed;
|
|
6541
6788
|
try {
|
|
6542
6789
|
parsed = readJson(userSettings);
|
|
6543
6790
|
} catch (err) {
|
|
6544
6791
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
6545
6792
|
}
|
|
6546
|
-
const hostFile =
|
|
6793
|
+
const hostFile = join49(repo, "hosts", `${HOST}.json`);
|
|
6547
6794
|
writeJsonAtomic(hostFile, parsed);
|
|
6548
6795
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
6549
6796
|
}
|
|
@@ -6556,17 +6803,20 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
|
|
|
6556
6803
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
6557
6804
|
function preflightConflict(repoHome2) {
|
|
6558
6805
|
const candidates = [
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6806
|
+
join50(repoHome2, "shared", "settings.base.json"),
|
|
6807
|
+
join50(repoHome2, "shared", "CLAUDE.md"),
|
|
6808
|
+
join50(repoHome2, "path-map.json"),
|
|
6809
|
+
join50(repoHome2, "hosts"),
|
|
6810
|
+
join50(repoHome2, "shared")
|
|
6564
6811
|
];
|
|
6565
6812
|
for (const c of candidates) {
|
|
6566
|
-
if (
|
|
6813
|
+
if (existsSync43(c)) return c;
|
|
6567
6814
|
}
|
|
6568
6815
|
return null;
|
|
6569
6816
|
}
|
|
6817
|
+
function isAlreadyInitialized(repoHome2) {
|
|
6818
|
+
return preflightConflict(repoHome2) !== null;
|
|
6819
|
+
}
|
|
6570
6820
|
function cmdInit(opts = {}) {
|
|
6571
6821
|
const snapshot = opts.snapshot === true;
|
|
6572
6822
|
const keepActions = opts.keepActions === true;
|
|
@@ -6578,25 +6828,25 @@ function cmdInit(opts = {}) {
|
|
|
6578
6828
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
6579
6829
|
}
|
|
6580
6830
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
6581
|
-
mkdirSync11(
|
|
6582
|
-
mkdirSync11(
|
|
6831
|
+
mkdirSync11(join50(repo, "shared"), { recursive: true });
|
|
6832
|
+
mkdirSync11(join50(repo, "hosts"), { recursive: true });
|
|
6583
6833
|
for (const name of SHARED_KEEP_DIRS) {
|
|
6584
|
-
mkdirSync11(
|
|
6834
|
+
mkdirSync11(join50(repo, "shared", name), { recursive: true });
|
|
6585
6835
|
}
|
|
6586
|
-
const userClaudeMd =
|
|
6587
|
-
if (!snapshot || !
|
|
6588
|
-
writeFileSync6(
|
|
6836
|
+
const userClaudeMd = join50(claude, "CLAUDE.md");
|
|
6837
|
+
if (!snapshot || !existsSync43(userClaudeMd)) {
|
|
6838
|
+
writeFileSync6(join50(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
6589
6839
|
item("created shared/CLAUDE.md");
|
|
6590
6840
|
}
|
|
6591
6841
|
for (const name of SHARED_KEEP_DIRS) {
|
|
6592
|
-
writeFileSync6(
|
|
6842
|
+
writeFileSync6(join50(repo, "shared", name, ".gitkeep"), "");
|
|
6593
6843
|
item(`created shared/${name}/.gitkeep`);
|
|
6594
6844
|
}
|
|
6595
|
-
writeFileSync6(
|
|
6845
|
+
writeFileSync6(join50(repo, "hosts", ".gitkeep"), "");
|
|
6596
6846
|
item("created hosts/.gitkeep");
|
|
6597
|
-
writeJsonAtomic(
|
|
6847
|
+
writeJsonAtomic(join50(repo, "shared", "settings.base.json"), {});
|
|
6598
6848
|
item("created shared/settings.base.json");
|
|
6599
|
-
writeJsonAtomic(
|
|
6849
|
+
writeJsonAtomic(join50(repo, "path-map.json"), { projects: {} });
|
|
6600
6850
|
item("created path-map.json");
|
|
6601
6851
|
if (snapshot) {
|
|
6602
6852
|
snapshotIntoShared({ projects: {} });
|
|
@@ -6659,6 +6909,47 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
6659
6909
|
}
|
|
6660
6910
|
}
|
|
6661
6911
|
|
|
6912
|
+
// src/init.prompt.ts
|
|
6913
|
+
init_config();
|
|
6914
|
+
init_utils();
|
|
6915
|
+
import { existsSync as existsSync44, readdirSync as readdirSync16, statSync as statSync11 } from "node:fs";
|
|
6916
|
+
import { join as join51 } from "node:path";
|
|
6917
|
+
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
6918
|
+
function nonEmptyExists(path) {
|
|
6919
|
+
if (!existsSync44(path)) return false;
|
|
6920
|
+
try {
|
|
6921
|
+
if (statSync11(path).isDirectory()) return readdirSync16(path).length > 0;
|
|
6922
|
+
return true;
|
|
6923
|
+
} catch {
|
|
6924
|
+
return false;
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6927
|
+
function hasExistingClaudeConfig(claudeHome2) {
|
|
6928
|
+
if (existsSync44(join51(claudeHome2, "settings.json"))) return true;
|
|
6929
|
+
return SHARED_LINKS.some((name) => nonEmptyExists(join51(claudeHome2, name)));
|
|
6930
|
+
}
|
|
6931
|
+
async function confirmSnapshotDefault(claudeHome2) {
|
|
6932
|
+
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
6933
|
+
log(
|
|
6934
|
+
`tip: ${claudeHome2} already has config; re-run 'nomad init --snapshot' to seed the repo from it.`
|
|
6935
|
+
);
|
|
6936
|
+
return false;
|
|
6937
|
+
}
|
|
6938
|
+
log(`Found existing config in ${claudeHome2}.`);
|
|
6939
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
6940
|
+
try {
|
|
6941
|
+
const answer = await rl.question("Seed the new repo from it? [Y/n] ");
|
|
6942
|
+
return !/^n(o)?$/i.test(answer.trim());
|
|
6943
|
+
} finally {
|
|
6944
|
+
rl.close();
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6947
|
+
async function resolveSnapshotChoice(flagSnapshot, claudeHome2, confirm = confirmSnapshotDefault) {
|
|
6948
|
+
if (flagSnapshot) return true;
|
|
6949
|
+
if (!hasExistingClaudeConfig(claudeHome2)) return false;
|
|
6950
|
+
return confirm(claudeHome2);
|
|
6951
|
+
}
|
|
6952
|
+
|
|
6662
6953
|
// src/nomad.dispatch.helpers.ts
|
|
6663
6954
|
var REJECT = { ok: false, advance: 0 };
|
|
6664
6955
|
function applyBool(seen, set) {
|
|
@@ -6918,7 +7209,7 @@ function parsePushArgs(argv) {
|
|
|
6918
7209
|
// package.json
|
|
6919
7210
|
var package_default = {
|
|
6920
7211
|
name: "claude-nomad",
|
|
6921
|
-
version: "0.
|
|
7212
|
+
version: "0.55.0",
|
|
6922
7213
|
type: "module",
|
|
6923
7214
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
6924
7215
|
keywords: [
|
|
@@ -7050,7 +7341,8 @@ var DEFAULT_HELP = [
|
|
|
7050
7341
|
" init",
|
|
7051
7342
|
"Create a private GitHub repo via gh (if none exists), scaffold shared/, hosts/, path-map."
|
|
7052
7343
|
),
|
|
7053
|
-
row(" --snapshot", "
|
|
7344
|
+
row(" --snapshot", "Seed shared/ from the current ~/.claude/. Without this flag, init"),
|
|
7345
|
+
cont("offers the same seed interactively when it detects existing config."),
|
|
7054
7346
|
row(" --keep-actions", "Skip auto-disabling GitHub Actions on the private repo."),
|
|
7055
7347
|
row(
|
|
7056
7348
|
" --repo <name>",
|
|
@@ -7064,6 +7356,8 @@ var DEFAULT_HELP = [
|
|
|
7064
7356
|
cont("`nomad push` would stage (a temp copy, never the live dir)."),
|
|
7065
7357
|
row(" --check-schema", "Flag settings.json keys absent from the live published"),
|
|
7066
7358
|
cont("Claude Code settings schema (needs network; degrades offline)."),
|
|
7359
|
+
row(" --check-remote", "Verify origin/main has shared/ and a valid path-map.json"),
|
|
7360
|
+
cont("(reads locally-cached remote ref; skips with a warning if unavailable)."),
|
|
7067
7361
|
row(" --resume-cmd <id>", "Print `cd <abspath> && claude --resume <id>` for a session id"),
|
|
7068
7362
|
cont("from ~/.claude/projects/."),
|
|
7069
7363
|
"",
|
|
@@ -7126,7 +7420,8 @@ var DEFAULT_HELP = [
|
|
|
7126
7420
|
"",
|
|
7127
7421
|
row(" --version", "Print the installed CLI version as bare semver to stdout; exits 0."),
|
|
7128
7422
|
"",
|
|
7129
|
-
"
|
|
7423
|
+
"You edit the sync repo (~/claude-nomad/); nomad regenerates ~/.claude/ on every",
|
|
7424
|
+
"pull. Run `nomad doctor` to validate your setup. Edit shared/ or hosts/<HOST>.json",
|
|
7130
7425
|
"in the repo, never ~/.claude/settings.json directly (it is regenerated on",
|
|
7131
7426
|
"every pull)."
|
|
7132
7427
|
].join("\n");
|
|
@@ -7135,15 +7430,15 @@ var DEFAULT_HELP = [
|
|
|
7135
7430
|
init_config();
|
|
7136
7431
|
init_utils();
|
|
7137
7432
|
init_utils_json();
|
|
7138
|
-
import { existsSync as
|
|
7139
|
-
import { join as
|
|
7433
|
+
import { existsSync as existsSync45, readFileSync as readFileSync14, readdirSync as readdirSync17 } from "node:fs";
|
|
7434
|
+
import { join as join52 } from "node:path";
|
|
7140
7435
|
function resumeCmd(sessionId) {
|
|
7141
7436
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
7142
7437
|
fail(`invalid session id: ${sessionId}`);
|
|
7143
7438
|
process.exit(1);
|
|
7144
7439
|
}
|
|
7145
|
-
const projectsRoot =
|
|
7146
|
-
if (!
|
|
7440
|
+
const projectsRoot = join52(claudeHome(), "projects");
|
|
7441
|
+
if (!existsSync45(projectsRoot)) {
|
|
7147
7442
|
fail(`${projectsRoot} does not exist`);
|
|
7148
7443
|
process.exit(1);
|
|
7149
7444
|
}
|
|
@@ -7157,8 +7452,8 @@ function resumeCmd(sessionId) {
|
|
|
7157
7452
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
7158
7453
|
process.exit(1);
|
|
7159
7454
|
}
|
|
7160
|
-
const mapPath =
|
|
7161
|
-
if (!
|
|
7455
|
+
const mapPath = join52(repoHome(), "path-map.json");
|
|
7456
|
+
if (!existsSync45(mapPath)) {
|
|
7162
7457
|
fail("path-map.json missing");
|
|
7163
7458
|
process.exit(1);
|
|
7164
7459
|
}
|
|
@@ -7180,9 +7475,9 @@ function resumeCmd(sessionId) {
|
|
|
7180
7475
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
7181
7476
|
}
|
|
7182
7477
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
7183
|
-
for (const dir of
|
|
7184
|
-
const candidate =
|
|
7185
|
-
if (
|
|
7478
|
+
for (const dir of readdirSync17(projectsRoot)) {
|
|
7479
|
+
const candidate = join52(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
7480
|
+
if (existsSync45(candidate)) return candidate;
|
|
7186
7481
|
}
|
|
7187
7482
|
return null;
|
|
7188
7483
|
}
|
|
@@ -7265,8 +7560,9 @@ try {
|
|
|
7265
7560
|
console.error("usage: nomad init [--snapshot] [--keep-actions] [--repo <name>]");
|
|
7266
7561
|
process.exit(1);
|
|
7267
7562
|
}
|
|
7563
|
+
const snapshot = isAlreadyInitialized(repoHome()) ? initArgs.snapshot : await resolveSnapshotChoice(initArgs.snapshot, claudeHome());
|
|
7268
7564
|
cmdInit({
|
|
7269
|
-
snapshot
|
|
7565
|
+
snapshot,
|
|
7270
7566
|
keepActions: initArgs.keepActions,
|
|
7271
7567
|
repoName: initArgs.repoName
|
|
7272
7568
|
});
|
|
@@ -7284,7 +7580,7 @@ try {
|
|
|
7284
7580
|
console.error("usage: nomad update");
|
|
7285
7581
|
process.exit(1);
|
|
7286
7582
|
}
|
|
7287
|
-
cmdUpdate();
|
|
7583
|
+
cmdUpdate(package_default.version);
|
|
7288
7584
|
break;
|
|
7289
7585
|
}
|
|
7290
7586
|
case "adopt": {
|
|
@@ -7323,7 +7619,7 @@ try {
|
|
|
7323
7619
|
const parsed = parseDoctorArgs(process.argv.slice(3));
|
|
7324
7620
|
if (parsed.kind === "error") {
|
|
7325
7621
|
console.error(
|
|
7326
|
-
"usage: nomad doctor [--check-shared] [--check-schema] [--verbose|--all|-v] | --resume-cmd <session-id>"
|
|
7622
|
+
"usage: nomad doctor [--check-shared] [--check-schema] [--check-remote] [--verbose|--all|-v] | --resume-cmd <session-id>"
|
|
7327
7623
|
);
|
|
7328
7624
|
process.exit(1);
|
|
7329
7625
|
} else if (parsed.kind === "resume") {
|
|
@@ -7332,6 +7628,7 @@ try {
|
|
|
7332
7628
|
cmdDoctor({
|
|
7333
7629
|
checkShared: parsed.checkShared,
|
|
7334
7630
|
checkSchema: parsed.checkSchema,
|
|
7631
|
+
checkRemote: parsed.checkRemote,
|
|
7335
7632
|
verbose: parsed.verbose
|
|
7336
7633
|
});
|
|
7337
7634
|
}
|