claude-nomad 0.53.2 → 0.54.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 +19 -0
- package/README.md +14 -9
- package/dist/nomad.mjs +550 -367
- 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
|
|
782
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
783
783
|
import { readdirSync as readdirSync4, 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();
|
|
@@ -825,7 +825,7 @@ function findGitlinks(dir) {
|
|
|
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;
|
|
@@ -2063,8 +2063,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2063
2063
|
}
|
|
2064
2064
|
|
|
2065
2065
|
// src/commands.doctor.ts
|
|
2066
|
-
import { existsSync as
|
|
2067
|
-
import { join as
|
|
2066
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
2067
|
+
import { join as join36 } from "node:path";
|
|
2068
2068
|
|
|
2069
2069
|
// src/commands.doctor.checks.repo.ts
|
|
2070
2070
|
init_color();
|
|
@@ -2391,6 +2391,18 @@ function reportUnmappedProjects(section2, map) {
|
|
|
2391
2391
|
addChildItem(section2, dim(dir));
|
|
2392
2392
|
}
|
|
2393
2393
|
}
|
|
2394
|
+
function reportCurrentHostPathsMissing(section2, map) {
|
|
2395
|
+
for (const [name, hosts] of Object.entries(map.projects)) {
|
|
2396
|
+
const abspath = hosts[HOST];
|
|
2397
|
+
if (!abspath || abspath === "TBD") continue;
|
|
2398
|
+
if (!existsSync11(abspath)) {
|
|
2399
|
+
addItem(
|
|
2400
|
+
section2,
|
|
2401
|
+
`${yellow(warnGlyph)} path-map: ${name} local path missing on ${HOST}: ${blue(abspath)}`
|
|
2402
|
+
);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2394
2406
|
function reportPathCollisions(section2, map) {
|
|
2395
2407
|
const seen = /* @__PURE__ */ new Map();
|
|
2396
2408
|
let collisionCount = 0;
|
|
@@ -2429,6 +2441,7 @@ function reportPathMap(section2) {
|
|
|
2429
2441
|
return;
|
|
2430
2442
|
}
|
|
2431
2443
|
reportMappedProjects(section2, map);
|
|
2444
|
+
reportCurrentHostPathsMissing(section2, map);
|
|
2432
2445
|
reportUnmappedProjects(section2, map);
|
|
2433
2446
|
reportPathCollisions(section2, map);
|
|
2434
2447
|
}
|
|
@@ -2441,18 +2454,103 @@ function reportNeverSync(section2) {
|
|
|
2441
2454
|
);
|
|
2442
2455
|
}
|
|
2443
2456
|
|
|
2457
|
+
// src/commands.doctor.checks.skills.ts
|
|
2458
|
+
init_color();
|
|
2459
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
2460
|
+
import { join as join14 } from "node:path";
|
|
2461
|
+
init_config();
|
|
2462
|
+
|
|
2463
|
+
// src/extras-sync.diff.ts
|
|
2464
|
+
init_utils();
|
|
2465
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2466
|
+
function labelDiffLine(line) {
|
|
2467
|
+
const tab = line.indexOf(" ");
|
|
2468
|
+
if (tab === -1) return line;
|
|
2469
|
+
const status = line.slice(0, tab);
|
|
2470
|
+
const path = line.slice(tab + 1);
|
|
2471
|
+
if (status === "D") return `${path} (local only)`;
|
|
2472
|
+
if (status === "A") return `${path} (repo only)`;
|
|
2473
|
+
return path;
|
|
2474
|
+
}
|
|
2475
|
+
function parseDiffOutput(stdout) {
|
|
2476
|
+
return stdout.split("\n").filter((line) => line.length > 0).map(labelDiffLine);
|
|
2477
|
+
}
|
|
2478
|
+
function listDivergingFiles(a, b) {
|
|
2479
|
+
try {
|
|
2480
|
+
const stdout = execFileSync2("git", ["diff", "--no-index", "--name-status", a, b], {
|
|
2481
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2482
|
+
}).toString();
|
|
2483
|
+
return parseDiffOutput(stdout);
|
|
2484
|
+
} catch (err) {
|
|
2485
|
+
const e = err;
|
|
2486
|
+
if (e.status === 1 && e.stdout !== void 0) {
|
|
2487
|
+
return parseDiffOutput(e.stdout.toString());
|
|
2488
|
+
}
|
|
2489
|
+
if (e.code === "ENOENT") {
|
|
2490
|
+
warn(`git not on PATH; divergence check skipped for ${a}`);
|
|
2491
|
+
return [];
|
|
2492
|
+
}
|
|
2493
|
+
warn(`divergence check failed for ${a}: ${e.message ?? String(err)}`);
|
|
2494
|
+
return [];
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
// src/commands.doctor.checks.skills.ts
|
|
2499
|
+
function stripSideIndicator(line) {
|
|
2500
|
+
if (line.endsWith(" (local only)")) return line.slice(0, -" (local only)".length);
|
|
2501
|
+
if (line.endsWith(" (repo only)")) return line.slice(0, -" (repo only)".length);
|
|
2502
|
+
return line;
|
|
2503
|
+
}
|
|
2504
|
+
function isGsdDiffLine(line, localBase, sharedBase) {
|
|
2505
|
+
const bare = stripSideIndicator(line);
|
|
2506
|
+
let relative6;
|
|
2507
|
+
if (bare.startsWith(localBase + "/")) {
|
|
2508
|
+
relative6 = bare.slice(localBase.length + 1);
|
|
2509
|
+
} else if (bare.startsWith(sharedBase + "/")) {
|
|
2510
|
+
relative6 = bare.slice(sharedBase.length + 1);
|
|
2511
|
+
} else {
|
|
2512
|
+
relative6 = bare;
|
|
2513
|
+
}
|
|
2514
|
+
return relative6.split("/")[0].startsWith(GSD_PREFIX);
|
|
2515
|
+
}
|
|
2516
|
+
function reportSkillsDivergence(section2) {
|
|
2517
|
+
const sharedSkills = join14(repoHome(), "shared", "skills");
|
|
2518
|
+
const localSkills = join14(claudeHome(), "skills");
|
|
2519
|
+
if (!existsSync12(sharedSkills)) {
|
|
2520
|
+
addItem(section2, `${dim(infoGlyph)} skills: no shared/skills/ to compare`);
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2523
|
+
if (!existsSync12(localSkills)) {
|
|
2524
|
+
addItem(section2, `${dim(infoGlyph)} skills: no local skills/ to compare`);
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
const diff = listDivergingFiles(localSkills, sharedSkills);
|
|
2528
|
+
const relevant = diff.filter((line) => !isGsdDiffLine(line, localSkills, sharedSkills));
|
|
2529
|
+
if (relevant.length === 0) {
|
|
2530
|
+
addItem(section2, `${green(okGlyph)} skills: in sync with shared/skills/`);
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
addItem(
|
|
2534
|
+
section2,
|
|
2535
|
+
`${yellow(warnGlyph)} skills: ${relevant.length} file(s) diverge from shared/skills/`
|
|
2536
|
+
);
|
|
2537
|
+
for (const f of relevant) {
|
|
2538
|
+
addChildItem(section2, f);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2444
2542
|
// src/commands.doctor.checks.git-state.ts
|
|
2445
2543
|
init_color();
|
|
2446
2544
|
init_config();
|
|
2447
|
-
import { execFileSync as
|
|
2448
|
-
import { existsSync as
|
|
2449
|
-
import { join as
|
|
2545
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
2546
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
2547
|
+
import { join as join18, relative as relative2 } from "node:path";
|
|
2450
2548
|
init_commands_pull_wedge();
|
|
2451
2549
|
init_push_checks();
|
|
2452
2550
|
init_utils();
|
|
2453
2551
|
function reportGitleaksProbe(section2) {
|
|
2454
2552
|
try {
|
|
2455
|
-
|
|
2553
|
+
execFileSync5("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
2456
2554
|
return true;
|
|
2457
2555
|
} catch (err) {
|
|
2458
2556
|
if (err.code === "ENOENT") {
|
|
@@ -2469,8 +2567,8 @@ function reportGitleaksProbe(section2) {
|
|
|
2469
2567
|
}
|
|
2470
2568
|
function reportGitlinks(section2) {
|
|
2471
2569
|
const repo = repoHome();
|
|
2472
|
-
const sharedDir =
|
|
2473
|
-
if (
|
|
2570
|
+
const sharedDir = join18(repo, "shared");
|
|
2571
|
+
if (existsSync15(sharedDir)) {
|
|
2474
2572
|
const gitlinks = findGitlinks(sharedDir);
|
|
2475
2573
|
for (const p of gitlinks) {
|
|
2476
2574
|
const rel = relative2(repo, p);
|
|
@@ -2488,7 +2586,7 @@ function reportGitlinks(section2) {
|
|
|
2488
2586
|
}
|
|
2489
2587
|
function reportRemote(section2) {
|
|
2490
2588
|
try {
|
|
2491
|
-
const url =
|
|
2589
|
+
const url = execFileSync5("git", ["remote", "get-url", "origin"], {
|
|
2492
2590
|
cwd: repoHome(),
|
|
2493
2591
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2494
2592
|
}).toString().trim();
|
|
@@ -2540,11 +2638,36 @@ function reportOrphanedAutostash(sec) {
|
|
|
2540
2638
|
} catch {
|
|
2541
2639
|
}
|
|
2542
2640
|
}
|
|
2641
|
+
function reportGitIdentity(section2) {
|
|
2642
|
+
const repo = repoHome();
|
|
2643
|
+
const missing = [];
|
|
2644
|
+
for (const field of ["user.name", "user.email"]) {
|
|
2645
|
+
try {
|
|
2646
|
+
const val = execFileSync5("git", ["config", field], {
|
|
2647
|
+
cwd: repo,
|
|
2648
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2649
|
+
}).toString().trim();
|
|
2650
|
+
if (!val) missing.push(field);
|
|
2651
|
+
} catch (err) {
|
|
2652
|
+
if (err.status === 1) missing.push(field);
|
|
2653
|
+
else return;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
if (missing.length > 0) {
|
|
2657
|
+
const hint = missing.map((f) => `git config ${f} "..."`).join(" / ");
|
|
2658
|
+
addItem(
|
|
2659
|
+
section2,
|
|
2660
|
+
`${yellow(warnGlyph)} git identity: ${missing.join(", ")} not set (run: ${hint})`
|
|
2661
|
+
);
|
|
2662
|
+
} else {
|
|
2663
|
+
addItem(section2, `${green(okGlyph)} git identity: user.name and user.email configured`);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2543
2666
|
|
|
2544
2667
|
// src/commands.doctor.checks.backups.ts
|
|
2545
2668
|
init_color();
|
|
2546
|
-
import { existsSync as
|
|
2547
|
-
import { join as
|
|
2669
|
+
import { existsSync as existsSync16, lstatSync as lstatSync7, readdirSync as readdirSync5 } from "node:fs";
|
|
2670
|
+
import { join as join19 } from "node:path";
|
|
2548
2671
|
init_config();
|
|
2549
2672
|
var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
|
|
2550
2673
|
function safeReaddir(dir) {
|
|
@@ -2560,7 +2683,7 @@ var BYTES_PER_MB = 1024 * 1024;
|
|
|
2560
2683
|
function dirSizeBytes(dir) {
|
|
2561
2684
|
let bytes = 0;
|
|
2562
2685
|
for (const entry of safeReaddir(dir)) {
|
|
2563
|
-
const full =
|
|
2686
|
+
const full = join19(dir, entry);
|
|
2564
2687
|
const st = lstatSync7(full, { throwIfNoEntry: false });
|
|
2565
2688
|
if (!st) continue;
|
|
2566
2689
|
if (st.isSymbolicLink()) continue;
|
|
@@ -2571,11 +2694,11 @@ function dirSizeBytes(dir) {
|
|
|
2571
2694
|
}
|
|
2572
2695
|
function totalSizeMb(backupBase2, dirs) {
|
|
2573
2696
|
let bytes = 0;
|
|
2574
|
-
for (const name of dirs) bytes += dirSizeBytes(
|
|
2697
|
+
for (const name of dirs) bytes += dirSizeBytes(join19(backupBase2, name));
|
|
2575
2698
|
return bytes / BYTES_PER_MB;
|
|
2576
2699
|
}
|
|
2577
2700
|
function reportBackupsCheck(section2, backupBase2 = backupBase()) {
|
|
2578
|
-
if (!
|
|
2701
|
+
if (!existsSync16(backupBase2)) return;
|
|
2579
2702
|
const dirs = safeReaddir(backupBase2).filter((n) => TS_SHAPE2.test(n));
|
|
2580
2703
|
const count = dirs.length;
|
|
2581
2704
|
const sizeMb = totalSizeMb(backupBase2, dirs);
|
|
@@ -2587,16 +2710,83 @@ function reportBackupsCheck(section2, backupBase2 = backupBase()) {
|
|
|
2587
2710
|
}
|
|
2588
2711
|
}
|
|
2589
2712
|
|
|
2713
|
+
// src/commands.doctor.check-remote.ts
|
|
2714
|
+
init_color();
|
|
2715
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
2716
|
+
init_config();
|
|
2717
|
+
init_utils_json();
|
|
2718
|
+
var GIT_REMOTE_TIMEOUT_MS = 3e3;
|
|
2719
|
+
function readRemotePathMap(sec, repo) {
|
|
2720
|
+
let rawJson;
|
|
2721
|
+
try {
|
|
2722
|
+
rawJson = execFileSync6("git", ["show", "origin/main:path-map.json"], {
|
|
2723
|
+
cwd: repo,
|
|
2724
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2725
|
+
timeout: GIT_REMOTE_TIMEOUT_MS
|
|
2726
|
+
}).toString();
|
|
2727
|
+
} catch {
|
|
2728
|
+
addItem(
|
|
2729
|
+
sec,
|
|
2730
|
+
`${yellow(warnGlyph)} remote check skipped (could not read path-map.json from origin/main)`
|
|
2731
|
+
);
|
|
2732
|
+
return false;
|
|
2733
|
+
}
|
|
2734
|
+
let map;
|
|
2735
|
+
try {
|
|
2736
|
+
map = JSON.parse(rawJson);
|
|
2737
|
+
} catch {
|
|
2738
|
+
addItem(sec, `${yellow(warnGlyph)} remote: path-map.json at origin/main is malformed JSON`);
|
|
2739
|
+
return false;
|
|
2740
|
+
}
|
|
2741
|
+
const shapeError = validatePathMapShape(map);
|
|
2742
|
+
if (shapeError !== null) {
|
|
2743
|
+
addItem(
|
|
2744
|
+
sec,
|
|
2745
|
+
`${yellow(warnGlyph)} remote: path-map.json at origin/main has invalid shape: ${shapeError}`
|
|
2746
|
+
);
|
|
2747
|
+
return false;
|
|
2748
|
+
}
|
|
2749
|
+
return true;
|
|
2750
|
+
}
|
|
2751
|
+
function reportCheckRemote(section2) {
|
|
2752
|
+
const repo = repoHome();
|
|
2753
|
+
let names;
|
|
2754
|
+
try {
|
|
2755
|
+
const out = execFileSync6("git", ["ls-tree", "--name-only", "origin/main"], {
|
|
2756
|
+
cwd: repo,
|
|
2757
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2758
|
+
timeout: GIT_REMOTE_TIMEOUT_MS
|
|
2759
|
+
}).toString().trim();
|
|
2760
|
+
names = out.split("\n").filter(Boolean);
|
|
2761
|
+
} catch {
|
|
2762
|
+
addItem(
|
|
2763
|
+
section2,
|
|
2764
|
+
`${yellow(warnGlyph)} remote check skipped (origin/main unavailable or git error)`
|
|
2765
|
+
);
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
if (!names.includes("shared")) {
|
|
2769
|
+
addItem(section2, `${yellow(warnGlyph)} remote: shared/ not found in origin/main`);
|
|
2770
|
+
return;
|
|
2771
|
+
}
|
|
2772
|
+
if (!names.includes("path-map.json")) {
|
|
2773
|
+
addItem(section2, `${yellow(warnGlyph)} remote: path-map.json not found in origin/main`);
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
if (!readRemotePathMap(section2, repo)) return;
|
|
2777
|
+
addItem(section2, `${green(okGlyph)} remote: origin/main has shared/ and a valid path-map.json`);
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2590
2780
|
// src/commands.doctor.check-schema.ts
|
|
2591
2781
|
init_color();
|
|
2592
|
-
import { existsSync as
|
|
2593
|
-
import { join as
|
|
2782
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
2783
|
+
import { join as join20 } from "node:path";
|
|
2594
2784
|
init_config();
|
|
2595
2785
|
|
|
2596
2786
|
// src/http-fetch.ts
|
|
2597
|
-
import { execFileSync as
|
|
2787
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
2598
2788
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
2599
|
-
function fetchUrl(url, run =
|
|
2789
|
+
function fetchUrl(url, run = execFileSync7) {
|
|
2600
2790
|
try {
|
|
2601
2791
|
return run("curl", ["-fsSL", "-m", "3", url], {
|
|
2602
2792
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -2627,8 +2817,8 @@ function fetchSchemaKeys() {
|
|
|
2627
2817
|
}
|
|
2628
2818
|
}
|
|
2629
2819
|
function reportCheckSchema(section2) {
|
|
2630
|
-
const settingsPath =
|
|
2631
|
-
if (!
|
|
2820
|
+
const settingsPath = join20(claudeHome(), "settings.json");
|
|
2821
|
+
if (!existsSync17(settingsPath)) {
|
|
2632
2822
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
|
|
2633
2823
|
return;
|
|
2634
2824
|
}
|
|
@@ -2657,19 +2847,19 @@ function reportCheckSchema(section2) {
|
|
|
2657
2847
|
// src/commands.doctor.check-shared.ts
|
|
2658
2848
|
init_color();
|
|
2659
2849
|
import { randomBytes } from "node:crypto";
|
|
2660
|
-
import { execFileSync as
|
|
2661
|
-
import { existsSync as
|
|
2850
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
2851
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync5, readdirSync as readdirSync7, rmSync as rmSync8 } from "node:fs";
|
|
2662
2852
|
import { homedir as homedir4 } from "node:os";
|
|
2663
|
-
import { join as
|
|
2853
|
+
import { join as join24 } from "node:path";
|
|
2664
2854
|
|
|
2665
2855
|
// src/commands.doctor.check-shared.scan.ts
|
|
2666
2856
|
init_color();
|
|
2667
|
-
import { join as
|
|
2857
|
+
import { join as join22 } from "node:path";
|
|
2668
2858
|
init_config();
|
|
2669
2859
|
init_push_gitleaks();
|
|
2670
2860
|
function scrubPath(logical, sid, logicalToEncoded) {
|
|
2671
2861
|
const encoded = logicalToEncoded.get(logical) ?? logical;
|
|
2672
|
-
return
|
|
2862
|
+
return join22(claudeHome(), "projects", encoded, `${sid}.jsonl`);
|
|
2673
2863
|
}
|
|
2674
2864
|
function reportSessionFindings(section2, bySession) {
|
|
2675
2865
|
for (const [sid, counts] of bySession) {
|
|
@@ -2757,8 +2947,8 @@ init_config();
|
|
|
2757
2947
|
|
|
2758
2948
|
// src/remap.ts
|
|
2759
2949
|
init_config_sharedDirs_guard();
|
|
2760
|
-
import { cpSync as cpSync4, existsSync as
|
|
2761
|
-
import { join as
|
|
2950
|
+
import { cpSync as cpSync4, existsSync as existsSync18, mkdirSync as mkdirSync4, readdirSync as readdirSync6, renameSync as renameSync3, rmSync as rmSync7, statSync as statSync4 } from "node:fs";
|
|
2951
|
+
import { join as join23, relative as relative3, sep as sep3 } from "node:path";
|
|
2762
2952
|
|
|
2763
2953
|
// src/extras-sync.guards.ts
|
|
2764
2954
|
init_utils();
|
|
@@ -2819,15 +3009,15 @@ function remapPull(ts, opts = {}) {
|
|
|
2819
3009
|
const wouldPull = [];
|
|
2820
3010
|
const repo = repoHome();
|
|
2821
3011
|
const claude = claudeHome();
|
|
2822
|
-
const mapPath =
|
|
2823
|
-
const repoProjects =
|
|
2824
|
-
if (!
|
|
3012
|
+
const mapPath = join23(repo, "path-map.json");
|
|
3013
|
+
const repoProjects = join23(repo, "shared", "projects");
|
|
3014
|
+
if (!existsSync18(mapPath) || !existsSync18(repoProjects)) {
|
|
2825
3015
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
2826
3016
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
2827
3017
|
return { unmapped: 0, pulled, wouldPull };
|
|
2828
3018
|
}
|
|
2829
3019
|
const map = readPathMap(mapPath);
|
|
2830
|
-
const localProjects =
|
|
3020
|
+
const localProjects = join23(claude, "projects");
|
|
2831
3021
|
if (!dryRun) mkdirSync4(localProjects, { recursive: true });
|
|
2832
3022
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
2833
3023
|
assertSafeLogical(logical);
|
|
@@ -2837,9 +3027,9 @@ function remapPull(ts, opts = {}) {
|
|
|
2837
3027
|
continue;
|
|
2838
3028
|
}
|
|
2839
3029
|
assertSafeLocalRoot(localPath, logical);
|
|
2840
|
-
const src =
|
|
2841
|
-
if (!
|
|
2842
|
-
const dst =
|
|
3030
|
+
const src = join23(repoProjects, logical);
|
|
3031
|
+
if (!existsSync18(src)) continue;
|
|
3032
|
+
const dst = join23(localProjects, encodePath(localPath));
|
|
2843
3033
|
if (dryRun) {
|
|
2844
3034
|
wouldPull.push(logical);
|
|
2845
3035
|
emitPreview(
|
|
@@ -2886,16 +3076,16 @@ function remapPush(ts, opts = {}) {
|
|
|
2886
3076
|
const wouldPush = [];
|
|
2887
3077
|
const repo = repoHome();
|
|
2888
3078
|
const claude = claudeHome();
|
|
2889
|
-
const mapPath =
|
|
2890
|
-
if (!
|
|
3079
|
+
const mapPath = join23(repo, "path-map.json");
|
|
3080
|
+
if (!existsSync18(mapPath)) {
|
|
2891
3081
|
log("no path-map.json; skipping session export");
|
|
2892
3082
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
2893
3083
|
}
|
|
2894
3084
|
const map = readPathMap(mapPath);
|
|
2895
|
-
const localProjects =
|
|
2896
|
-
const repoProjects =
|
|
3085
|
+
const localProjects = join23(claude, "projects");
|
|
3086
|
+
const repoProjects = join23(repo, "shared", "projects");
|
|
2897
3087
|
const reverse = buildReverseMap(map);
|
|
2898
|
-
if (!
|
|
3088
|
+
if (!existsSync18(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
2899
3089
|
if (!dryRun) mkdirSync4(repoProjects, { recursive: true });
|
|
2900
3090
|
for (const dir of readdirSync6(localProjects)) {
|
|
2901
3091
|
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
@@ -2904,13 +3094,13 @@ function remapPush(ts, opts = {}) {
|
|
|
2904
3094
|
unmapped++;
|
|
2905
3095
|
continue;
|
|
2906
3096
|
}
|
|
2907
|
-
const repoDst =
|
|
3097
|
+
const repoDst = join23(repoProjects, logical);
|
|
2908
3098
|
if (dryRun) {
|
|
2909
3099
|
wouldPush.push(logical);
|
|
2910
3100
|
continue;
|
|
2911
3101
|
}
|
|
2912
3102
|
backupRepoWrite(repoDst, ts, repo);
|
|
2913
|
-
copyDirJsonlOnly(
|
|
3103
|
+
copyDirJsonlOnly(join23(localProjects, dir), repoDst);
|
|
2914
3104
|
pushed.push(logical);
|
|
2915
3105
|
}
|
|
2916
3106
|
return { unmapped, collisions: 0, pushed, wouldPush };
|
|
@@ -2923,8 +3113,8 @@ function buildScanTree(tmpRoot) {
|
|
|
2923
3113
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
2924
3114
|
let staged = 0;
|
|
2925
3115
|
const repo = repoHome();
|
|
2926
|
-
const mapPath =
|
|
2927
|
-
if (!
|
|
3116
|
+
const mapPath = join24(repo, "path-map.json");
|
|
3117
|
+
if (!existsSync19(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
2928
3118
|
let map;
|
|
2929
3119
|
try {
|
|
2930
3120
|
map = readJson(mapPath);
|
|
@@ -2941,12 +3131,12 @@ function buildScanTree(tmpRoot) {
|
|
|
2941
3131
|
if (!p || p === "TBD") continue;
|
|
2942
3132
|
reverse.set(encodePath(p), logical);
|
|
2943
3133
|
}
|
|
2944
|
-
const localProjects =
|
|
2945
|
-
if (!
|
|
3134
|
+
const localProjects = join24(claudeHome(), "projects");
|
|
3135
|
+
if (!existsSync19(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
2946
3136
|
for (const dir of readdirSync7(localProjects)) {
|
|
2947
3137
|
const logical = reverse.get(dir);
|
|
2948
3138
|
if (!logical) continue;
|
|
2949
|
-
copyDirJsonlOnly(
|
|
3139
|
+
copyDirJsonlOnly(join24(localProjects, dir), join24(tmpRoot, "shared", "projects", logical));
|
|
2950
3140
|
logicalToEncoded.set(logical, dir);
|
|
2951
3141
|
staged++;
|
|
2952
3142
|
}
|
|
@@ -2954,7 +3144,7 @@ function buildScanTree(tmpRoot) {
|
|
|
2954
3144
|
}
|
|
2955
3145
|
function probeGitleaksForScan() {
|
|
2956
3146
|
try {
|
|
2957
|
-
|
|
3147
|
+
execFileSync9("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
2958
3148
|
return "ok";
|
|
2959
3149
|
} catch (err) {
|
|
2960
3150
|
if (err.code === "ENOENT") return "missing";
|
|
@@ -2977,11 +3167,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
2977
3167
|
}
|
|
2978
3168
|
function reportCheckShared(section2, gitleaksReady) {
|
|
2979
3169
|
if (!ensureGitleaksReady(section2, gitleaksReady)) return;
|
|
2980
|
-
const cacheDir =
|
|
3170
|
+
const cacheDir = join24(homedir4(), ".cache", "claude-nomad");
|
|
2981
3171
|
mkdirSync5(cacheDir, { recursive: true });
|
|
2982
3172
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
2983
|
-
const reportPath =
|
|
2984
|
-
const tmpRoot =
|
|
3173
|
+
const reportPath = join24(cacheDir, `check-shared-${stamp}.json`);
|
|
3174
|
+
const tmpRoot = join24(cacheDir, `check-shared-tree-${stamp}`);
|
|
2985
3175
|
try {
|
|
2986
3176
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
2987
3177
|
if (malformed) {
|
|
@@ -3002,8 +3192,8 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
3002
3192
|
|
|
3003
3193
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
3004
3194
|
init_color();
|
|
3005
|
-
import { existsSync as
|
|
3006
|
-
import { dirname as dirname3, extname, join as
|
|
3195
|
+
import { existsSync as existsSync20, readFileSync as readFileSync6, readdirSync as readdirSync8, realpathSync as realpathSync2 } from "node:fs";
|
|
3196
|
+
import { dirname as dirname3, extname, join as join25 } from "node:path";
|
|
3007
3197
|
init_config();
|
|
3008
3198
|
function typeFromPackageJson(pkgPath) {
|
|
3009
3199
|
try {
|
|
@@ -3025,8 +3215,8 @@ function effectiveType(hookPath) {
|
|
|
3025
3215
|
}
|
|
3026
3216
|
let dir = dirname3(real);
|
|
3027
3217
|
for (; ; ) {
|
|
3028
|
-
const pkg =
|
|
3029
|
-
if (
|
|
3218
|
+
const pkg = join25(dir, "package.json");
|
|
3219
|
+
if (existsSync20(pkg)) return typeFromPackageJson(pkg);
|
|
3030
3220
|
const parent = dirname3(dir);
|
|
3031
3221
|
if (parent === dir) return "cjs";
|
|
3032
3222
|
dir = parent;
|
|
@@ -3068,15 +3258,15 @@ function safeRead(path) {
|
|
|
3068
3258
|
}
|
|
3069
3259
|
}
|
|
3070
3260
|
function reportHookScopeCheck(section2) {
|
|
3071
|
-
const hooksDir =
|
|
3072
|
-
if (!
|
|
3261
|
+
const hooksDir = join25(claudeHome(), "hooks");
|
|
3262
|
+
if (!existsSync20(hooksDir)) {
|
|
3073
3263
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
3074
3264
|
return;
|
|
3075
3265
|
}
|
|
3076
3266
|
let anyWarn = false;
|
|
3077
3267
|
for (const name of safeReaddir2(hooksDir)) {
|
|
3078
3268
|
if (extname(name) !== ".js") continue;
|
|
3079
|
-
const abs =
|
|
3269
|
+
const abs = join25(hooksDir, name);
|
|
3080
3270
|
const eff = effectiveType(abs);
|
|
3081
3271
|
if (eff === null) continue;
|
|
3082
3272
|
const src = safeRead(abs);
|
|
@@ -3096,8 +3286,8 @@ function reportHookScopeCheck(section2) {
|
|
|
3096
3286
|
|
|
3097
3287
|
// src/commands.doctor.checks.hooks.ts
|
|
3098
3288
|
init_color();
|
|
3099
|
-
import { existsSync as
|
|
3100
|
-
import { join as
|
|
3289
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
3290
|
+
import { join as join26 } from "node:path";
|
|
3101
3291
|
init_config();
|
|
3102
3292
|
function expandHome(token) {
|
|
3103
3293
|
const h2 = home();
|
|
@@ -3136,7 +3326,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3136
3326
|
for (const group of groups) {
|
|
3137
3327
|
for (const cmd of commandsFromGroup(group)) {
|
|
3138
3328
|
for (const resolved of claudePathsIn(cmd)) {
|
|
3139
|
-
if (
|
|
3329
|
+
if (existsSync21(resolved)) continue;
|
|
3140
3330
|
addItem(
|
|
3141
3331
|
section2,
|
|
3142
3332
|
`${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
|
|
@@ -3149,8 +3339,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3149
3339
|
return anyFail;
|
|
3150
3340
|
}
|
|
3151
3341
|
function reportHooksTargetCheck(section2) {
|
|
3152
|
-
const settingsPath =
|
|
3153
|
-
if (!
|
|
3342
|
+
const settingsPath = join26(claudeHome(), "settings.json");
|
|
3343
|
+
if (!existsSync21(settingsPath)) {
|
|
3154
3344
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
3155
3345
|
return;
|
|
3156
3346
|
}
|
|
@@ -3173,12 +3363,12 @@ function reportHooksTargetCheck(section2) {
|
|
|
3173
3363
|
|
|
3174
3364
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
3175
3365
|
init_color();
|
|
3176
|
-
import { existsSync as
|
|
3177
|
-
import { join as
|
|
3366
|
+
import { existsSync as existsSync23, readFileSync as readFileSync7 } from "node:fs";
|
|
3367
|
+
import { join as join28 } from "node:path";
|
|
3178
3368
|
|
|
3179
3369
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
3180
|
-
import { closeSync as closeSync3, existsSync as
|
|
3181
|
-
import { dirname as dirname4, join as
|
|
3370
|
+
import { closeSync as closeSync3, existsSync as existsSync22, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
|
|
3371
|
+
import { dirname as dirname4, join as join27, resolve as resolve2 } from "node:path";
|
|
3182
3372
|
function suppressedRanges(src) {
|
|
3183
3373
|
const ranges = [];
|
|
3184
3374
|
const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
|
|
@@ -3210,12 +3400,12 @@ function topRelativeSpecifiers(src) {
|
|
|
3210
3400
|
}
|
|
3211
3401
|
function specifierIsMissing(specifier, baseDir) {
|
|
3212
3402
|
const base = resolve2(baseDir, specifier);
|
|
3213
|
-
if (
|
|
3403
|
+
if (existsSync22(base)) return false;
|
|
3214
3404
|
for (const ext of [".js", ".cjs", ".mjs"]) {
|
|
3215
|
-
if (
|
|
3405
|
+
if (existsSync22(base + ext)) return false;
|
|
3216
3406
|
}
|
|
3217
3407
|
for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
|
|
3218
|
-
if (
|
|
3408
|
+
if (existsSync22(join27(base, idx))) return false;
|
|
3219
3409
|
}
|
|
3220
3410
|
return true;
|
|
3221
3411
|
}
|
|
@@ -3270,8 +3460,8 @@ function commandTokens(command) {
|
|
|
3270
3460
|
return tokens;
|
|
3271
3461
|
}
|
|
3272
3462
|
function readPathMapSafe() {
|
|
3273
|
-
const mapPath =
|
|
3274
|
-
if (!
|
|
3463
|
+
const mapPath = join28(repoHome(), "path-map.json");
|
|
3464
|
+
if (!existsSync23(mapPath)) return { projects: {} };
|
|
3275
3465
|
try {
|
|
3276
3466
|
return JSON.parse(readFileSync7(mapPath, "utf8"));
|
|
3277
3467
|
} catch {
|
|
@@ -3344,8 +3534,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
3344
3534
|
return anyWarn;
|
|
3345
3535
|
}
|
|
3346
3536
|
function reportPreserveSymlinksCheck(section2) {
|
|
3347
|
-
const settingsPath =
|
|
3348
|
-
if (!
|
|
3537
|
+
const settingsPath = join28(claudeHome(), "settings.json");
|
|
3538
|
+
if (!existsSync23(settingsPath)) {
|
|
3349
3539
|
addItem(
|
|
3350
3540
|
section2,
|
|
3351
3541
|
`${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
|
|
@@ -3378,8 +3568,8 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
3378
3568
|
|
|
3379
3569
|
// src/commands.doctor.checks.settings-drift.ts
|
|
3380
3570
|
init_color();
|
|
3381
|
-
import { existsSync as
|
|
3382
|
-
import { join as
|
|
3571
|
+
import { existsSync as existsSync24, readFileSync as readFileSync8 } from "node:fs";
|
|
3572
|
+
import { join as join29 } from "node:path";
|
|
3383
3573
|
init_config();
|
|
3384
3574
|
init_utils_json();
|
|
3385
3575
|
function diffMergedSettings(merged, settings) {
|
|
@@ -3397,7 +3587,7 @@ function tryReadJson(filePath) {
|
|
|
3397
3587
|
}
|
|
3398
3588
|
}
|
|
3399
3589
|
function reportHooksBaseSelfCleanNote(section2) {
|
|
3400
|
-
const basePath =
|
|
3590
|
+
const basePath = join29(repoHome(), "shared", "settings.base.json");
|
|
3401
3591
|
const base = tryReadJson(basePath);
|
|
3402
3592
|
if (base === null) return;
|
|
3403
3593
|
if (!baseHasGsdHookEntries(base)) return;
|
|
@@ -3410,14 +3600,14 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3410
3600
|
const claude = claudeHome();
|
|
3411
3601
|
const repo = repoHome();
|
|
3412
3602
|
const host = HOST;
|
|
3413
|
-
const settingsPath =
|
|
3414
|
-
const basePath =
|
|
3415
|
-
const hostPath =
|
|
3416
|
-
if (!
|
|
3603
|
+
const settingsPath = join29(claude, "settings.json");
|
|
3604
|
+
const basePath = join29(repo, "shared", "settings.base.json");
|
|
3605
|
+
const hostPath = join29(repo, "hosts", `${host}.json`);
|
|
3606
|
+
if (!existsSync24(settingsPath)) {
|
|
3417
3607
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
3418
3608
|
return;
|
|
3419
3609
|
}
|
|
3420
|
-
if (!
|
|
3610
|
+
if (!existsSync24(basePath)) {
|
|
3421
3611
|
addItem(
|
|
3422
3612
|
section2,
|
|
3423
3613
|
`${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
|
|
@@ -3436,7 +3626,7 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3436
3626
|
if (settings === null) {
|
|
3437
3627
|
return;
|
|
3438
3628
|
}
|
|
3439
|
-
const hostExists =
|
|
3629
|
+
const hostExists = existsSync24(hostPath);
|
|
3440
3630
|
const hostObj = hostExists ? tryReadJson(hostPath) : null;
|
|
3441
3631
|
if (hostExists && hostObj === null) {
|
|
3442
3632
|
addItem(
|
|
@@ -3594,14 +3784,14 @@ function reportNodeEngineCheck(section2) {
|
|
|
3594
3784
|
|
|
3595
3785
|
// src/spinner.ts
|
|
3596
3786
|
init_color();
|
|
3597
|
-
import { existsSync as
|
|
3787
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
3598
3788
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3599
3789
|
import { Worker } from "node:worker_threads";
|
|
3600
3790
|
|
|
3601
3791
|
// src/commands.push.recovery.ts
|
|
3602
3792
|
init_config();
|
|
3603
3793
|
import { readFileSync as readFileSync13, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3604
|
-
import { join as
|
|
3794
|
+
import { join as join34 } from "node:path";
|
|
3605
3795
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
3606
3796
|
|
|
3607
3797
|
// src/commands.push.recovery.actions.ts
|
|
@@ -3612,25 +3802,25 @@ import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "nod
|
|
|
3612
3802
|
// src/commands.push.recovery.redact.ts
|
|
3613
3803
|
init_config();
|
|
3614
3804
|
init_config_sharedDirs_guard();
|
|
3615
|
-
import { cpSync as cpSync5, existsSync as
|
|
3616
|
-
import { dirname as dirname6, join as
|
|
3805
|
+
import { cpSync as cpSync5, existsSync as existsSync27, mkdirSync as mkdirSync6, statSync as statSync7 } from "node:fs";
|
|
3806
|
+
import { dirname as dirname6, join as join32, sep as sep4 } from "node:path";
|
|
3617
3807
|
|
|
3618
3808
|
// src/commands.redact.ts
|
|
3619
3809
|
init_config();
|
|
3620
|
-
import { existsSync as
|
|
3621
|
-
import { dirname as dirname5, join as
|
|
3810
|
+
import { existsSync as existsSync26, statSync as statSync6 } from "node:fs";
|
|
3811
|
+
import { dirname as dirname5, join as join31 } from "node:path";
|
|
3622
3812
|
|
|
3623
3813
|
// src/commands.redact.subtree.ts
|
|
3624
|
-
import { existsSync as
|
|
3625
|
-
import { join as
|
|
3814
|
+
import { existsSync as existsSync25, lstatSync as lstatSync8, readFileSync as readFileSync11, readdirSync as readdirSync9, statSync as statSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3815
|
+
import { join as join30 } from "node:path";
|
|
3626
3816
|
init_utils_fs();
|
|
3627
3817
|
init_utils();
|
|
3628
3818
|
function collectFiles(dir, out) {
|
|
3629
|
-
if (!
|
|
3819
|
+
if (!existsSync25(dir)) return;
|
|
3630
3820
|
const st = lstatSync8(dir);
|
|
3631
3821
|
if (!st.isDirectory()) return;
|
|
3632
3822
|
for (const entry of readdirSync9(dir)) {
|
|
3633
|
-
const abs =
|
|
3823
|
+
const abs = join30(dir, entry);
|
|
3634
3824
|
const lst = lstatSync8(abs);
|
|
3635
3825
|
if (lst.isSymbolicLink()) continue;
|
|
3636
3826
|
if (lst.isDirectory()) {
|
|
@@ -3682,10 +3872,10 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
3682
3872
|
|
|
3683
3873
|
// src/commands.pushed-history.ts
|
|
3684
3874
|
init_utils();
|
|
3685
|
-
import { execFileSync as
|
|
3875
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
3686
3876
|
function pushedRef(repo) {
|
|
3687
3877
|
try {
|
|
3688
|
-
const ref =
|
|
3878
|
+
const ref = execFileSync10("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
|
|
3689
3879
|
cwd: repo,
|
|
3690
3880
|
stdio: ["ignore", "pipe", "ignore"]
|
|
3691
3881
|
}).toString().trim();
|
|
@@ -3698,7 +3888,7 @@ function sessionInPushedHistory(id, repo) {
|
|
|
3698
3888
|
const ref = pushedRef(repo);
|
|
3699
3889
|
if (ref === null) return false;
|
|
3700
3890
|
try {
|
|
3701
|
-
const out =
|
|
3891
|
+
const out = execFileSync10(
|
|
3702
3892
|
"git",
|
|
3703
3893
|
[
|
|
3704
3894
|
"log",
|
|
@@ -3735,15 +3925,15 @@ init_utils_json();
|
|
|
3735
3925
|
init_utils();
|
|
3736
3926
|
function resolveLiveTranscript(id) {
|
|
3737
3927
|
try {
|
|
3738
|
-
const mapPath =
|
|
3739
|
-
if (!
|
|
3928
|
+
const mapPath = join31(repoHome(), "path-map.json");
|
|
3929
|
+
if (!existsSync26(mapPath)) return null;
|
|
3740
3930
|
const projects = readJson(mapPath).projects;
|
|
3741
3931
|
const claude = claudeHome();
|
|
3742
3932
|
for (const hostMap of Object.values(projects)) {
|
|
3743
3933
|
const abs = hostMap[HOST];
|
|
3744
3934
|
if (abs === void 0) continue;
|
|
3745
|
-
const live =
|
|
3746
|
-
if (
|
|
3935
|
+
const live = join31(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
3936
|
+
if (existsSync26(live)) return live;
|
|
3747
3937
|
}
|
|
3748
3938
|
return null;
|
|
3749
3939
|
} catch {
|
|
@@ -3763,17 +3953,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
3763
3953
|
}
|
|
3764
3954
|
const repo = repoHome();
|
|
3765
3955
|
const backup = backupBase();
|
|
3766
|
-
if (!
|
|
3956
|
+
if (!existsSync26(repo)) die(`repo not cloned at ${repo}`);
|
|
3767
3957
|
const handle = acquireLock("redact");
|
|
3768
3958
|
if (handle === null) process.exit(0);
|
|
3769
3959
|
try {
|
|
3770
3960
|
const localPath = resolveLiveTranscript(id);
|
|
3771
|
-
if (localPath === null || !
|
|
3961
|
+
if (localPath === null || !existsSync26(localPath)) {
|
|
3772
3962
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
3773
3963
|
process.exitCode = 1;
|
|
3774
3964
|
return;
|
|
3775
3965
|
}
|
|
3776
|
-
const sessionDir =
|
|
3966
|
+
const sessionDir = join31(dirname5(localPath), id);
|
|
3777
3967
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3778
3968
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync6(p).mtimeMs);
|
|
3779
3969
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -3889,8 +4079,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
3889
4079
|
assertSafeLogical(logical);
|
|
3890
4080
|
const abs = hostMap[HOST];
|
|
3891
4081
|
if (abs === void 0) continue;
|
|
3892
|
-
if (localPath.startsWith(
|
|
3893
|
-
return
|
|
4082
|
+
if (localPath.startsWith(join32(claude, "projects", encodePath(abs)) + sep4)) {
|
|
4083
|
+
return join32(repo, "shared", "projects", logical);
|
|
3894
4084
|
}
|
|
3895
4085
|
}
|
|
3896
4086
|
return null;
|
|
@@ -3900,7 +4090,7 @@ function preflightRedactable(f, map, nowMs) {
|
|
|
3900
4090
|
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
3901
4091
|
const localPath = resolveLiveTranscript(sid);
|
|
3902
4092
|
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
3903
|
-
const sessionDir =
|
|
4093
|
+
const sessionDir = join32(dirname6(localPath), sid);
|
|
3904
4094
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3905
4095
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
3906
4096
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -3930,7 +4120,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3930
4120
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
3931
4121
|
);
|
|
3932
4122
|
}
|
|
3933
|
-
const sessionDir =
|
|
4123
|
+
const sessionDir = join32(dirname6(localPath), sid);
|
|
3934
4124
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3935
4125
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
3936
4126
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -3964,9 +4154,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3964
4154
|
);
|
|
3965
4155
|
}
|
|
3966
4156
|
mkdirSync6(stagedProjectDir, { recursive: true });
|
|
3967
|
-
cpSync5(localPath,
|
|
3968
|
-
if (
|
|
3969
|
-
cpSync5(sessionDir,
|
|
4157
|
+
cpSync5(localPath, join32(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
4158
|
+
if (existsSync27(sessionDir)) {
|
|
4159
|
+
cpSync5(sessionDir, join32(stagedProjectDir, sid), { force: true, recursive: true });
|
|
3970
4160
|
}
|
|
3971
4161
|
return true;
|
|
3972
4162
|
}
|
|
@@ -3974,14 +4164,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3974
4164
|
// src/commands.push.recovery.drop.ts
|
|
3975
4165
|
init_config();
|
|
3976
4166
|
import { rmSync as rmSync9 } from "node:fs";
|
|
3977
|
-
import { join as
|
|
4167
|
+
import { join as join33 } from "node:path";
|
|
3978
4168
|
function dropSessionFromStaged(sid, map) {
|
|
3979
4169
|
const logicals = Object.keys(map.projects);
|
|
3980
4170
|
if (logicals.length === 0) return false;
|
|
3981
4171
|
const repo = repoHome();
|
|
3982
4172
|
for (const logical of logicals) {
|
|
3983
|
-
const jsonl =
|
|
3984
|
-
const dir =
|
|
4173
|
+
const jsonl = join33(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
4174
|
+
const dir = join33(repo, "shared", "projects", logical, sid);
|
|
3985
4175
|
rmSync9(jsonl, { force: true });
|
|
3986
4176
|
rmSync9(dir, { recursive: true, force: true });
|
|
3987
4177
|
}
|
|
@@ -4138,7 +4328,7 @@ function applyThenRescan(scanVerdict, repoHome2) {
|
|
|
4138
4328
|
return next;
|
|
4139
4329
|
}
|
|
4140
4330
|
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
4141
|
-
const ignPath =
|
|
4331
|
+
const ignPath = join34(repoHome2, ".gitleaksignore");
|
|
4142
4332
|
let before;
|
|
4143
4333
|
try {
|
|
4144
4334
|
before = readFileSync13(ignPath, "utf8");
|
|
@@ -4237,7 +4427,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
4237
4427
|
`);
|
|
4238
4428
|
}
|
|
4239
4429
|
function resolveWorkerPath(deps = {}) {
|
|
4240
|
-
const check = deps.existsSyncFn ??
|
|
4430
|
+
const check = deps.existsSyncFn ?? existsSync28;
|
|
4241
4431
|
const base = deps.baseUrl ?? import.meta.url;
|
|
4242
4432
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
4243
4433
|
if (check(mjs)) return mjs;
|
|
@@ -4303,9 +4493,9 @@ function withSpinner(label, fn, deps) {
|
|
|
4303
4493
|
|
|
4304
4494
|
// src/commands.doctor.gitleaks-version.ts
|
|
4305
4495
|
init_color();
|
|
4306
|
-
import { execFileSync as
|
|
4307
|
-
import { existsSync as
|
|
4308
|
-
import { join as
|
|
4496
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
4497
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
4498
|
+
import { join as join35 } from "node:path";
|
|
4309
4499
|
init_config();
|
|
4310
4500
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
4311
4501
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -4314,7 +4504,7 @@ function majorMinorOf(value) {
|
|
|
4314
4504
|
return m === null ? null : [m[1], m[2]];
|
|
4315
4505
|
}
|
|
4316
4506
|
function readGitleaksVersion(run, tomlExists) {
|
|
4317
|
-
const tomlPath =
|
|
4507
|
+
const tomlPath = join35(repoHome(), ".gitleaks.toml");
|
|
4318
4508
|
const args = ["version"];
|
|
4319
4509
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
4320
4510
|
try {
|
|
@@ -4326,7 +4516,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
4326
4516
|
return null;
|
|
4327
4517
|
}
|
|
4328
4518
|
}
|
|
4329
|
-
function reportGitleaksVersionCheck(section2, run =
|
|
4519
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync29) {
|
|
4330
4520
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
4331
4521
|
if (raw === null) return;
|
|
4332
4522
|
const local = majorMinorOf(raw);
|
|
@@ -4346,7 +4536,7 @@ function reportGitleaksVersionCheck(section2, run = execFileSync9, tomlExists =
|
|
|
4346
4536
|
|
|
4347
4537
|
// src/commands.doctor.checks.deps.ts
|
|
4348
4538
|
init_color();
|
|
4349
|
-
import { execFileSync as
|
|
4539
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
4350
4540
|
var VERSION_TOKEN = /(\d{1,9}\.\d{1,9}\.\d{1,9})/;
|
|
4351
4541
|
var PROBE_TIMEOUT_MS = 3e3;
|
|
4352
4542
|
var FETCHER_BASE = "HTTP fetcher";
|
|
@@ -4383,7 +4573,7 @@ function reportFetcherRow(section2, run) {
|
|
|
4383
4573
|
);
|
|
4384
4574
|
}
|
|
4385
4575
|
}
|
|
4386
|
-
function reportOptionalDeps(section2, run =
|
|
4576
|
+
function reportOptionalDeps(section2, run = execFileSync12) {
|
|
4387
4577
|
const gh = probeOptionalDep("gh", run);
|
|
4388
4578
|
if (gh.status === "present") {
|
|
4389
4579
|
addItem(section2, `${green(okGlyph)} gh: ${gh.version ?? "present"}`);
|
|
@@ -4398,11 +4588,11 @@ function reportOptionalDeps(section2, run = execFileSync10) {
|
|
|
4398
4588
|
|
|
4399
4589
|
// src/commands.doctor.actions-drift.ts
|
|
4400
4590
|
init_color();
|
|
4401
|
-
import { execFileSync as
|
|
4591
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
4402
4592
|
init_config();
|
|
4403
4593
|
|
|
4404
4594
|
// src/gh-actions.ts
|
|
4405
|
-
import { execFileSync as
|
|
4595
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
4406
4596
|
var GH_TIMEOUT_MS = 5e3;
|
|
4407
4597
|
function parseGitHubRemote(remoteUrl) {
|
|
4408
4598
|
const normalized = remoteUrl.trim().replace(/\/$/, "");
|
|
@@ -4410,7 +4600,7 @@ function parseGitHubRemote(remoteUrl) {
|
|
|
4410
4600
|
if (m === null) return null;
|
|
4411
4601
|
return { owner: m[1], repo: m[2] };
|
|
4412
4602
|
}
|
|
4413
|
-
function ghAuthStatus(run =
|
|
4603
|
+
function ghAuthStatus(run = execFileSync13) {
|
|
4414
4604
|
try {
|
|
4415
4605
|
run("gh", ["auth", "status"], {
|
|
4416
4606
|
stdio: ["ignore", "ignore", "ignore"],
|
|
@@ -4424,7 +4614,7 @@ function ghAuthStatus(run = execFileSync11) {
|
|
|
4424
4614
|
return "gh-probe-error";
|
|
4425
4615
|
}
|
|
4426
4616
|
}
|
|
4427
|
-
function isRepoPrivate(ref, run =
|
|
4617
|
+
function isRepoPrivate(ref, run = execFileSync13) {
|
|
4428
4618
|
const out = run("gh", ["repo", "view", `${ref.owner}/${ref.repo}`, "--json", "isPrivate"], {
|
|
4429
4619
|
stdio: ["ignore", "pipe", "ignore"],
|
|
4430
4620
|
timeout: GH_TIMEOUT_MS
|
|
@@ -4432,7 +4622,7 @@ function isRepoPrivate(ref, run = execFileSync11) {
|
|
|
4432
4622
|
const parsed = JSON.parse(out);
|
|
4433
4623
|
return parsed.isPrivate === true;
|
|
4434
4624
|
}
|
|
4435
|
-
function isActionsEnabled(ref, run =
|
|
4625
|
+
function isActionsEnabled(ref, run = execFileSync13) {
|
|
4436
4626
|
const out = run(
|
|
4437
4627
|
"gh",
|
|
4438
4628
|
["api", `repos/${ref.owner}/${ref.repo}/actions/permissions`, "--jq", ".enabled"],
|
|
@@ -4440,7 +4630,7 @@ function isActionsEnabled(ref, run = execFileSync11) {
|
|
|
4440
4630
|
).toString().trim();
|
|
4441
4631
|
return out === "true";
|
|
4442
4632
|
}
|
|
4443
|
-
function disableActions(ref, run =
|
|
4633
|
+
function disableActions(ref, run = execFileSync13) {
|
|
4444
4634
|
run(
|
|
4445
4635
|
"gh",
|
|
4446
4636
|
[
|
|
@@ -4454,7 +4644,7 @@ function disableActions(ref, run = execFileSync11) {
|
|
|
4454
4644
|
{ stdio: ["ignore", "ignore", "pipe"], timeout: GH_TIMEOUT_MS }
|
|
4455
4645
|
);
|
|
4456
4646
|
}
|
|
4457
|
-
function readOriginRemote(cwd, run =
|
|
4647
|
+
function readOriginRemote(cwd, run = execFileSync13) {
|
|
4458
4648
|
return run("git", ["remote", "get-url", "origin"], {
|
|
4459
4649
|
cwd,
|
|
4460
4650
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -4462,7 +4652,7 @@ function readOriginRemote(cwd, run = execFileSync11) {
|
|
|
4462
4652
|
}
|
|
4463
4653
|
|
|
4464
4654
|
// src/commands.doctor.actions-drift.ts
|
|
4465
|
-
function reportActionsDrift(section2, run =
|
|
4655
|
+
function reportActionsDrift(section2, run = execFileSync14) {
|
|
4466
4656
|
let remote;
|
|
4467
4657
|
try {
|
|
4468
4658
|
remote = readOriginRemote(repoHome(), run);
|
|
@@ -4519,7 +4709,13 @@ function buildVerdictSection(sections) {
|
|
|
4519
4709
|
|
|
4520
4710
|
// src/commands.doctor.compact.ts
|
|
4521
4711
|
init_color();
|
|
4522
|
-
var ALWAYS_FULL = /* @__PURE__ */ new Set([
|
|
4712
|
+
var ALWAYS_FULL = /* @__PURE__ */ new Set([
|
|
4713
|
+
"Nomad Version",
|
|
4714
|
+
"Summary",
|
|
4715
|
+
"Shared scan",
|
|
4716
|
+
"Schema scan",
|
|
4717
|
+
"Remote check"
|
|
4718
|
+
]);
|
|
4523
4719
|
function isProblem(item2) {
|
|
4524
4720
|
return item2.includes(failGlyph) || item2.includes(warnGlyph);
|
|
4525
4721
|
}
|
|
@@ -4542,8 +4738,8 @@ function gatherDoctorSections(opts) {
|
|
|
4542
4738
|
reportHostAndPaths(host);
|
|
4543
4739
|
reportRepoState(host);
|
|
4544
4740
|
const links = section("Shared links");
|
|
4545
|
-
const mapPath =
|
|
4546
|
-
const rawMap =
|
|
4741
|
+
const mapPath = join36(repoHome(), "path-map.json");
|
|
4742
|
+
const rawMap = existsSync30(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
4547
4743
|
const map = rawMap ?? { projects: {} };
|
|
4548
4744
|
reportSharedLinks(links, map);
|
|
4549
4745
|
reportDroppedNamesMigration(links);
|
|
@@ -4561,10 +4757,13 @@ function gatherDoctorSections(opts) {
|
|
|
4561
4757
|
reportPathMap(pathMap);
|
|
4562
4758
|
const neverSync = section("Never-sync");
|
|
4563
4759
|
reportNeverSync(neverSync);
|
|
4760
|
+
const skills = section("Skills");
|
|
4761
|
+
reportSkillsDivergence(skills);
|
|
4564
4762
|
const repository = section("Repository");
|
|
4565
4763
|
const gitleaksReady = reportGitleaksProbe(repository);
|
|
4566
4764
|
reportGitlinks(repository);
|
|
4567
4765
|
reportRemote(repository);
|
|
4766
|
+
reportGitIdentity(repository);
|
|
4568
4767
|
reportRebaseClean(repository);
|
|
4569
4768
|
reportRebaseState(repository);
|
|
4570
4769
|
reportOrphanedAutostash(repository);
|
|
@@ -4581,6 +4780,8 @@ function gatherDoctorSections(opts) {
|
|
|
4581
4780
|
if (opts.checkShared === true) reportCheckShared(sharedScan, gitleaksReady);
|
|
4582
4781
|
const schemaScan = section("Schema scan");
|
|
4583
4782
|
if (opts.checkSchema === true) reportCheckSchema(schemaScan);
|
|
4783
|
+
const remoteCheck = section("Remote check");
|
|
4784
|
+
if (opts.checkRemote === true) reportCheckRemote(remoteCheck);
|
|
4584
4785
|
const body = [
|
|
4585
4786
|
nomadVersion,
|
|
4586
4787
|
depVersions,
|
|
@@ -4590,10 +4791,12 @@ function gatherDoctorSections(opts) {
|
|
|
4590
4791
|
settings,
|
|
4591
4792
|
pathMap,
|
|
4592
4793
|
neverSync,
|
|
4794
|
+
skills,
|
|
4593
4795
|
repository,
|
|
4594
4796
|
housekeeping,
|
|
4595
4797
|
sharedScan,
|
|
4596
|
-
schemaScan
|
|
4798
|
+
schemaScan,
|
|
4799
|
+
remoteCheck
|
|
4597
4800
|
];
|
|
4598
4801
|
return [...body, buildVerdictSection(body)];
|
|
4599
4802
|
}
|
|
@@ -4618,27 +4821,29 @@ function parseDoctorArgs(args) {
|
|
|
4618
4821
|
}
|
|
4619
4822
|
let checkShared = false;
|
|
4620
4823
|
let checkSchema = false;
|
|
4824
|
+
let checkRemote = false;
|
|
4621
4825
|
let verbose = false;
|
|
4622
4826
|
for (const arg of args) {
|
|
4623
4827
|
if (arg === "--check-shared") checkShared = true;
|
|
4624
4828
|
else if (arg === "--check-schema") checkSchema = true;
|
|
4829
|
+
else if (arg === "--check-remote") checkRemote = true;
|
|
4625
4830
|
else if (arg === "--verbose" || arg === "--all" || arg === "-v") verbose = true;
|
|
4626
4831
|
else return { kind: "error" };
|
|
4627
4832
|
}
|
|
4628
|
-
return { kind: "run", checkShared, checkSchema, verbose };
|
|
4833
|
+
return { kind: "run", checkShared, checkSchema, checkRemote, verbose };
|
|
4629
4834
|
}
|
|
4630
4835
|
|
|
4631
4836
|
// src/commands.drop-session.ts
|
|
4632
4837
|
init_config();
|
|
4633
|
-
import { execFileSync as
|
|
4634
|
-
import { existsSync as
|
|
4635
|
-
import { join as
|
|
4838
|
+
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
4839
|
+
import { existsSync as existsSync32, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
|
|
4840
|
+
import { join as join38, relative as relative4 } from "node:path";
|
|
4636
4841
|
|
|
4637
4842
|
// src/commands.drop-session.git.ts
|
|
4638
|
-
import { execFileSync as
|
|
4843
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
4639
4844
|
function expandStagedDir(dirRel, repo) {
|
|
4640
4845
|
try {
|
|
4641
|
-
const out =
|
|
4846
|
+
const out = execFileSync15("git", ["ls-files", "-z", "--", dirRel], {
|
|
4642
4847
|
cwd: repo,
|
|
4643
4848
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4644
4849
|
});
|
|
@@ -4649,7 +4854,7 @@ function expandStagedDir(dirRel, repo) {
|
|
|
4649
4854
|
}
|
|
4650
4855
|
function isTrackedInHead(rel, repo) {
|
|
4651
4856
|
try {
|
|
4652
|
-
|
|
4857
|
+
execFileSync15("git", ["cat-file", "-e", `HEAD:${rel}`], {
|
|
4653
4858
|
cwd: repo,
|
|
4654
4859
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4655
4860
|
});
|
|
@@ -4660,7 +4865,7 @@ function isTrackedInHead(rel, repo) {
|
|
|
4660
4865
|
}
|
|
4661
4866
|
function isInIndex(rel, repo) {
|
|
4662
4867
|
try {
|
|
4663
|
-
const out =
|
|
4868
|
+
const out = execFileSync15("git", ["ls-files", "--", rel], {
|
|
4664
4869
|
cwd: repo,
|
|
4665
4870
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4666
4871
|
});
|
|
@@ -4674,8 +4879,8 @@ function isInIndex(rel, repo) {
|
|
|
4674
4879
|
init_config();
|
|
4675
4880
|
init_utils();
|
|
4676
4881
|
init_utils_json();
|
|
4677
|
-
import { existsSync as
|
|
4678
|
-
import { join as
|
|
4882
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
4883
|
+
import { join as join37 } from "node:path";
|
|
4679
4884
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
4680
4885
|
function reportScrubHint(id, matches) {
|
|
4681
4886
|
const live = resolveLiveTranscript2(id, matches);
|
|
@@ -4691,8 +4896,8 @@ function reportScrubHint(id, matches) {
|
|
|
4691
4896
|
}
|
|
4692
4897
|
function resolveLiveTranscript2(id, matches) {
|
|
4693
4898
|
try {
|
|
4694
|
-
const mapPath =
|
|
4695
|
-
if (!
|
|
4899
|
+
const mapPath = join37(repoHome(), "path-map.json");
|
|
4900
|
+
if (!existsSync31(mapPath)) return null;
|
|
4696
4901
|
const projects = readJson(mapPath).projects;
|
|
4697
4902
|
const claude = claudeHome();
|
|
4698
4903
|
for (const rel of matches) {
|
|
@@ -4700,8 +4905,8 @@ function resolveLiveTranscript2(id, matches) {
|
|
|
4700
4905
|
if (logical === void 0) continue;
|
|
4701
4906
|
const abs = projects[logical]?.[HOST];
|
|
4702
4907
|
if (abs === void 0) continue;
|
|
4703
|
-
const live =
|
|
4704
|
-
if (
|
|
4908
|
+
const live = join37(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
4909
|
+
if (existsSync31(live)) return live;
|
|
4705
4910
|
}
|
|
4706
4911
|
return null;
|
|
4707
4912
|
} catch {
|
|
@@ -4717,12 +4922,12 @@ function cmdDropSession(id) {
|
|
|
4717
4922
|
process.exit(1);
|
|
4718
4923
|
}
|
|
4719
4924
|
const repo = repoHome();
|
|
4720
|
-
if (!
|
|
4925
|
+
if (!existsSync32(repo)) die(`repo not cloned at ${repo}`);
|
|
4721
4926
|
const handle = acquireLock("drop-session");
|
|
4722
4927
|
if (handle === null) process.exit(0);
|
|
4723
4928
|
try {
|
|
4724
|
-
const repoProjects =
|
|
4725
|
-
if (!
|
|
4929
|
+
const repoProjects = join38(repo, "shared", "projects");
|
|
4930
|
+
if (!existsSync32(repoProjects)) {
|
|
4726
4931
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
4727
4932
|
}
|
|
4728
4933
|
const matches = collectMatches(repoProjects, id, repo);
|
|
@@ -4745,12 +4950,12 @@ function cmdDropSession(id) {
|
|
|
4745
4950
|
function collectMatches(repoProjects, id, repo) {
|
|
4746
4951
|
const matches = [];
|
|
4747
4952
|
for (const logical of readdirSync10(repoProjects)) {
|
|
4748
|
-
const candidate =
|
|
4749
|
-
if (
|
|
4953
|
+
const candidate = join38(repoProjects, logical, `${id}.jsonl`);
|
|
4954
|
+
if (existsSync32(candidate)) {
|
|
4750
4955
|
matches.push(relative4(repo, candidate));
|
|
4751
4956
|
}
|
|
4752
|
-
const dir =
|
|
4753
|
-
if (
|
|
4957
|
+
const dir = join38(repoProjects, logical, id);
|
|
4958
|
+
if (existsSync32(dir) && statSync8(dir).isDirectory()) {
|
|
4754
4959
|
const dirRel = relative4(repo, dir);
|
|
4755
4960
|
const staged = expandStagedDir(dirRel, repo);
|
|
4756
4961
|
if (staged.length > 0) matches.push(...staged);
|
|
@@ -4766,12 +4971,12 @@ function unstageOne(rel, repo) {
|
|
|
4766
4971
|
}
|
|
4767
4972
|
try {
|
|
4768
4973
|
if (isTrackedInHead(rel, repo)) {
|
|
4769
|
-
|
|
4974
|
+
execFileSync16("git", ["restore", "--staged", "--worktree", "--", rel], {
|
|
4770
4975
|
cwd: repo,
|
|
4771
4976
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4772
4977
|
});
|
|
4773
4978
|
} else {
|
|
4774
|
-
|
|
4979
|
+
execFileSync16("git", ["rm", "--cached", "-f", "--", rel], {
|
|
4775
4980
|
cwd: repo,
|
|
4776
4981
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4777
4982
|
});
|
|
@@ -4785,8 +4990,8 @@ function unstageOne(rel, repo) {
|
|
|
4785
4990
|
}
|
|
4786
4991
|
|
|
4787
4992
|
// src/commands.pull.ts
|
|
4788
|
-
import { existsSync as
|
|
4789
|
-
import { join as
|
|
4993
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync9 } from "node:fs";
|
|
4994
|
+
import { join as join45 } from "node:path";
|
|
4790
4995
|
|
|
4791
4996
|
// src/commands.push.sections.ts
|
|
4792
4997
|
init_color();
|
|
@@ -4881,55 +5086,20 @@ init_config();
|
|
|
4881
5086
|
|
|
4882
5087
|
// src/extras-sync.ts
|
|
4883
5088
|
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
|
-
}
|
|
5089
|
+
import { existsSync as existsSync35 } from "node:fs";
|
|
5090
|
+
import { join as join42 } from "node:path";
|
|
4921
5091
|
|
|
4922
5092
|
// src/extras-sync.core.ts
|
|
4923
5093
|
init_config();
|
|
4924
|
-
import { cpSync as cpSync6, existsSync as
|
|
4925
|
-
import { basename, join as
|
|
5094
|
+
import { cpSync as cpSync6, existsSync as existsSync33, lstatSync as lstatSync9, readdirSync as readdirSync11, rmSync as rmSync11 } from "node:fs";
|
|
5095
|
+
import { basename, join as join39 } from "node:path";
|
|
4926
5096
|
init_utils();
|
|
4927
5097
|
init_utils_json();
|
|
4928
5098
|
function loadValidatedExtras(opts) {
|
|
4929
5099
|
const repo = repoHome();
|
|
4930
|
-
const mapPath =
|
|
4931
|
-
const repoExtras =
|
|
4932
|
-
if (!
|
|
5100
|
+
const mapPath = join39(repo, "path-map.json");
|
|
5101
|
+
const repoExtras = join39(repo, "shared", "extras");
|
|
5102
|
+
if (!existsSync33(mapPath) || opts.requireRepoExtras === true && !existsSync33(repoExtras)) {
|
|
4933
5103
|
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
4934
5104
|
return null;
|
|
4935
5105
|
}
|
|
@@ -4961,16 +5131,16 @@ function* eachExtrasTarget(v, counts) {
|
|
|
4961
5131
|
}
|
|
4962
5132
|
}
|
|
4963
5133
|
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
4964
|
-
if (!
|
|
5134
|
+
if (!existsSync33(dst)) return;
|
|
4965
5135
|
for (const name of readdirSync11(src)) {
|
|
4966
5136
|
if (isExcluded(name)) continue;
|
|
4967
|
-
const dstPath =
|
|
5137
|
+
const dstPath = join39(dst, name);
|
|
4968
5138
|
const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
|
|
4969
5139
|
if (dstStat === void 0) continue;
|
|
4970
5140
|
if (dstStat.isSymbolicLink()) {
|
|
4971
5141
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
4972
|
-
} else if (dstStat.isDirectory() && lstatSync9(
|
|
4973
|
-
stripCollidingDstSymlinks(
|
|
5142
|
+
} else if (dstStat.isDirectory() && lstatSync9(join39(src, name)).isDirectory()) {
|
|
5143
|
+
stripCollidingDstSymlinks(join39(src, name), dstPath, isExcluded);
|
|
4974
5144
|
}
|
|
4975
5145
|
}
|
|
4976
5146
|
}
|
|
@@ -5012,15 +5182,15 @@ function copyExtrasFiltered(src, dst, blockSet) {
|
|
|
5012
5182
|
function prunePreservingDenied(src, dst, blockSet) {
|
|
5013
5183
|
for (const name of readdirSync11(dst)) {
|
|
5014
5184
|
if (isDeniedName(blockSet, name)) continue;
|
|
5015
|
-
const dstPath =
|
|
5016
|
-
const srcStat = lstatSync9(
|
|
5185
|
+
const dstPath = join39(dst, name);
|
|
5186
|
+
const srcStat = lstatSync9(join39(src, name), { throwIfNoEntry: false });
|
|
5017
5187
|
if (srcStat === void 0) {
|
|
5018
5188
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5019
5189
|
continue;
|
|
5020
5190
|
}
|
|
5021
5191
|
const dstStat = lstatSync9(dstPath);
|
|
5022
5192
|
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
5023
|
-
prunePreservingDenied(
|
|
5193
|
+
prunePreservingDenied(join39(src, name), dstPath, blockSet);
|
|
5024
5194
|
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
5025
5195
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5026
5196
|
}
|
|
@@ -5043,15 +5213,15 @@ function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
|
5043
5213
|
function prunePreservingBy(src, dst, isPreserved) {
|
|
5044
5214
|
for (const name of readdirSync11(dst)) {
|
|
5045
5215
|
if (isPreserved(name)) continue;
|
|
5046
|
-
const dstPath =
|
|
5047
|
-
const srcStat = lstatSync9(
|
|
5216
|
+
const dstPath = join39(dst, name);
|
|
5217
|
+
const srcStat = lstatSync9(join39(src, name), { throwIfNoEntry: false });
|
|
5048
5218
|
if (srcStat === void 0) {
|
|
5049
5219
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5050
5220
|
continue;
|
|
5051
5221
|
}
|
|
5052
5222
|
const dstStat = lstatSync9(dstPath);
|
|
5053
5223
|
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
5054
|
-
prunePreservingBy(
|
|
5224
|
+
prunePreservingBy(join39(src, name), dstPath, isPreserved);
|
|
5055
5225
|
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
5056
5226
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5057
5227
|
}
|
|
@@ -5078,11 +5248,11 @@ init_utils_json();
|
|
|
5078
5248
|
|
|
5079
5249
|
// src/extras-sync.remap.ts
|
|
5080
5250
|
init_config();
|
|
5081
|
-
import { existsSync as
|
|
5082
|
-
import { dirname as dirname7, join as
|
|
5251
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync7, readdirSync as readdirSync12, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
|
|
5252
|
+
import { dirname as dirname7, join as join41, sep as sep7 } from "node:path";
|
|
5083
5253
|
|
|
5084
5254
|
// src/extras-sync.planning-diff.ts
|
|
5085
|
-
import { join as
|
|
5255
|
+
import { join as join40, normalize as normalize2, sep as sep6 } from "node:path";
|
|
5086
5256
|
init_utils();
|
|
5087
5257
|
function processRecord(fields, i, changed, deleted) {
|
|
5088
5258
|
const status = fields[i];
|
|
@@ -5134,7 +5304,7 @@ function planningDeleteTargets(opts) {
|
|
|
5134
5304
|
const { deleted } = parsePlanningDiff(raw);
|
|
5135
5305
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
5136
5306
|
const prefix = logicalPrefix + ".planning/";
|
|
5137
|
-
const planningRoot =
|
|
5307
|
+
const planningRoot = join40(localRoot, ".planning");
|
|
5138
5308
|
const planningRootBoundary = planningRoot + sep6;
|
|
5139
5309
|
const targets = [];
|
|
5140
5310
|
for (const repoPath of deleted) {
|
|
@@ -5142,7 +5312,7 @@ function planningDeleteTargets(opts) {
|
|
|
5142
5312
|
continue;
|
|
5143
5313
|
}
|
|
5144
5314
|
const remainder = repoPath.slice(logicalPrefix.length);
|
|
5145
|
-
const candidate =
|
|
5315
|
+
const candidate = join40(localRoot, remainder);
|
|
5146
5316
|
const resolved = normalize2(candidate);
|
|
5147
5317
|
if (!resolved.startsWith(planningRootBoundary)) {
|
|
5148
5318
|
throw new NomadFatal(
|
|
@@ -5163,7 +5333,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
5163
5333
|
const would = [];
|
|
5164
5334
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
5165
5335
|
const { src, dst } = paths(t);
|
|
5166
|
-
if (!
|
|
5336
|
+
if (!existsSync34(src)) continue;
|
|
5167
5337
|
const item2 = `${t.logical}/${t.dirname}`;
|
|
5168
5338
|
if (dryRun) {
|
|
5169
5339
|
would.push(item2);
|
|
@@ -5198,7 +5368,7 @@ function isInsidePlanningRoot(parentReal, rootReal) {
|
|
|
5198
5368
|
return parentReal === rootReal || parentReal.startsWith(rootReal + sep7);
|
|
5199
5369
|
}
|
|
5200
5370
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
5201
|
-
if (
|
|
5371
|
+
if (existsSync34(repoCounterpart)) return;
|
|
5202
5372
|
const parentReal = tryRealpath(dirname7(target));
|
|
5203
5373
|
if (parentReal === void 0) return;
|
|
5204
5374
|
const rootReal = tryRealpath(planningRoot);
|
|
@@ -5208,7 +5378,7 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
|
5208
5378
|
pruneEmptyAncestors(target, planningRoot);
|
|
5209
5379
|
}
|
|
5210
5380
|
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
5211
|
-
const repoExtras =
|
|
5381
|
+
const repoExtras = join41(repo, "shared", "extras");
|
|
5212
5382
|
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
5213
5383
|
if (t.dirname !== ".planning") continue;
|
|
5214
5384
|
let raw;
|
|
@@ -5234,11 +5404,11 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
5234
5404
|
}
|
|
5235
5405
|
const targets = planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot });
|
|
5236
5406
|
if (targets.length === 0) continue;
|
|
5237
|
-
backupExtrasWrite(
|
|
5238
|
-
const planningRoot =
|
|
5407
|
+
backupExtrasWrite(join41(t.localRoot, t.dirname), ts, t.localRoot);
|
|
5408
|
+
const planningRoot = join41(t.localRoot, ".planning");
|
|
5239
5409
|
for (const target of targets) {
|
|
5240
5410
|
const relToLocal = target.slice(t.localRoot.length + sep7.length);
|
|
5241
|
-
deletePlanningTarget(target, planningRoot,
|
|
5411
|
+
deletePlanningTarget(target, planningRoot, join41(repoExtras, t.logical, relToLocal));
|
|
5242
5412
|
}
|
|
5243
5413
|
}
|
|
5244
5414
|
}
|
|
@@ -5247,14 +5417,14 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
5247
5417
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
5248
5418
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
5249
5419
|
const repo = repoHome();
|
|
5250
|
-
const repoExtras =
|
|
5420
|
+
const repoExtras = join41(repo, "shared", "extras");
|
|
5251
5421
|
if (!dryRun) mkdirSync7(repoExtras, { recursive: true });
|
|
5252
5422
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
5253
5423
|
v,
|
|
5254
5424
|
dryRun,
|
|
5255
5425
|
({ localRoot, logical, dirname: dirname8 }) => ({
|
|
5256
|
-
src:
|
|
5257
|
-
dst:
|
|
5426
|
+
src: join41(localRoot, dirname8),
|
|
5427
|
+
dst: join41(repoExtras, logical, dirname8)
|
|
5258
5428
|
}),
|
|
5259
5429
|
(dst) => backupRepoWrite(dst, ts, repo),
|
|
5260
5430
|
// Push copy routing per extra type:
|
|
@@ -5283,8 +5453,8 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
5283
5453
|
v,
|
|
5284
5454
|
dryRun,
|
|
5285
5455
|
({ localRoot, logical, dirname: dirname8 }) => ({
|
|
5286
|
-
src:
|
|
5287
|
-
dst:
|
|
5456
|
+
src: join41(repo, "shared", "extras", logical, dirname8),
|
|
5457
|
+
dst: join41(localRoot, dirname8)
|
|
5288
5458
|
}),
|
|
5289
5459
|
// Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
|
|
5290
5460
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -5316,15 +5486,15 @@ function divergenceCheckExtras(ts) {
|
|
|
5316
5486
|
const v = loadValidatedExtras({});
|
|
5317
5487
|
if (v === null) return;
|
|
5318
5488
|
const counts = { unmapped: 0, skipped: 0 };
|
|
5319
|
-
const backupRoot =
|
|
5489
|
+
const backupRoot = join42(backupBase(), ts, "extras");
|
|
5320
5490
|
const repo = repoHome();
|
|
5321
5491
|
for (const { logical, localRoot, dirname: dirname8 } of eachExtrasTarget(v, counts)) {
|
|
5322
|
-
const local =
|
|
5323
|
-
const repoEntry =
|
|
5324
|
-
if (!
|
|
5492
|
+
const local = join42(localRoot, dirname8);
|
|
5493
|
+
const repoEntry = join42(repo, "shared", "extras", logical, dirname8);
|
|
5494
|
+
if (!existsSync35(local) || !existsSync35(repoEntry)) continue;
|
|
5325
5495
|
const diff = listDivergingFiles(local, repoEntry);
|
|
5326
5496
|
if (diff.length === 0) continue;
|
|
5327
|
-
const projectBackupRoot =
|
|
5497
|
+
const projectBackupRoot = join42(backupRoot, encodePath(localRoot));
|
|
5328
5498
|
warn(
|
|
5329
5499
|
`local ${dirname8} for ${logical} diverges from origin in ${diff.length} file(s); next remapExtrasPull will merge changes (.planning overlays, .claude/.CLAUDE.md mirror; backups at ${projectBackupRoot}/)`
|
|
5330
5500
|
);
|
|
@@ -5334,8 +5504,8 @@ function divergenceCheckExtras(ts) {
|
|
|
5334
5504
|
|
|
5335
5505
|
// src/skills-sync.ts
|
|
5336
5506
|
init_config();
|
|
5337
|
-
import { existsSync as
|
|
5338
|
-
import { join as
|
|
5507
|
+
import { existsSync as existsSync36, lstatSync as lstatSync10, mkdirSync as mkdirSync8, readdirSync as readdirSync13, rmSync as rmSync13 } from "node:fs";
|
|
5508
|
+
import { join as join43 } from "node:path";
|
|
5339
5509
|
init_utils_fs();
|
|
5340
5510
|
function isGsdOwned(name) {
|
|
5341
5511
|
return name.startsWith(GSD_PREFIX);
|
|
@@ -5355,9 +5525,9 @@ function copySkillsPull(src, dst) {
|
|
|
5355
5525
|
copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
|
|
5356
5526
|
}
|
|
5357
5527
|
function syncSkillsPull(ts) {
|
|
5358
|
-
const sharedSkills =
|
|
5359
|
-
if (!
|
|
5360
|
-
const localSkills =
|
|
5528
|
+
const sharedSkills = join43(repoHome(), "shared", "skills");
|
|
5529
|
+
if (!existsSync36(sharedSkills)) return;
|
|
5530
|
+
const localSkills = join43(claudeHome(), "skills");
|
|
5361
5531
|
const dstStat = lstatSync10(localSkills, { throwIfNoEntry: false });
|
|
5362
5532
|
if (dstStat?.isSymbolicLink() === true) {
|
|
5363
5533
|
backupBeforeWrite(localSkills, ts);
|
|
@@ -5367,18 +5537,18 @@ function syncSkillsPull(ts) {
|
|
|
5367
5537
|
copySkillsPull(sharedSkills, localSkills);
|
|
5368
5538
|
}
|
|
5369
5539
|
function syncSkillsPush() {
|
|
5370
|
-
const localSkills =
|
|
5540
|
+
const localSkills = join43(claudeHome(), "skills");
|
|
5371
5541
|
const stat = lstatSync10(localSkills, { throwIfNoEntry: false });
|
|
5372
5542
|
if (stat === void 0) return;
|
|
5373
5543
|
if (stat.isSymbolicLink()) return;
|
|
5374
|
-
const sharedSkills =
|
|
5544
|
+
const sharedSkills = join43(repoHome(), "shared", "skills");
|
|
5375
5545
|
copySkillsPush(localSkills, sharedSkills);
|
|
5376
5546
|
}
|
|
5377
5547
|
|
|
5378
5548
|
// src/preview.ts
|
|
5379
5549
|
init_config();
|
|
5380
|
-
import { existsSync as
|
|
5381
|
-
import { join as
|
|
5550
|
+
import { existsSync as existsSync37 } from "node:fs";
|
|
5551
|
+
import { join as join44 } from "node:path";
|
|
5382
5552
|
|
|
5383
5553
|
// node_modules/diff/libesm/diff/base.js
|
|
5384
5554
|
var Diff = class {
|
|
@@ -5664,7 +5834,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
5664
5834
|
return lines.join("\n");
|
|
5665
5835
|
}
|
|
5666
5836
|
function readJsonOrNull(path) {
|
|
5667
|
-
if (!
|
|
5837
|
+
if (!existsSync37(path)) return null;
|
|
5668
5838
|
try {
|
|
5669
5839
|
return readJson(path);
|
|
5670
5840
|
} catch {
|
|
@@ -5678,12 +5848,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
5678
5848
|
}
|
|
5679
5849
|
const notes = [];
|
|
5680
5850
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
5681
|
-
if (hostOverrides === null &&
|
|
5851
|
+
if (hostOverrides === null && existsSync37(hostPath)) {
|
|
5682
5852
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
5683
5853
|
}
|
|
5684
5854
|
const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
|
|
5685
5855
|
const current = readJsonOrNull(settingsPath);
|
|
5686
|
-
if (current === null &&
|
|
5856
|
+
if (current === null && existsSync37(settingsPath)) {
|
|
5687
5857
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
5688
5858
|
}
|
|
5689
5859
|
const strippedCurrent = stripGsdHookEntries(current ?? {});
|
|
@@ -5724,9 +5894,9 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
5724
5894
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
5725
5895
|
});
|
|
5726
5896
|
const settingsResult = previewSettings(
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5897
|
+
join44(repo, "shared", "settings.base.json"),
|
|
5898
|
+
join44(repo, "hosts", `${HOST}.json`),
|
|
5899
|
+
join44(claude, "settings.json")
|
|
5730
5900
|
);
|
|
5731
5901
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
5732
5902
|
const sessions = section("Sessions");
|
|
@@ -5748,9 +5918,9 @@ init_config();
|
|
|
5748
5918
|
init_commands_pull_wedge();
|
|
5749
5919
|
init_utils();
|
|
5750
5920
|
init_utils_fs();
|
|
5751
|
-
import { execFileSync as
|
|
5921
|
+
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
5752
5922
|
function gitCapture(args, cwd) {
|
|
5753
|
-
return
|
|
5923
|
+
return execFileSync17("git", args, {
|
|
5754
5924
|
cwd,
|
|
5755
5925
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5756
5926
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -5927,8 +6097,8 @@ function cmdPull(opts = {}) {
|
|
|
5927
6097
|
const forceRemote = opts.forceRemote === true;
|
|
5928
6098
|
const repo = repoHome();
|
|
5929
6099
|
const backup = backupBase();
|
|
5930
|
-
if (!
|
|
5931
|
-
if (!
|
|
6100
|
+
if (!existsSync38(repo)) die(`repo not cloned at ${repo}`);
|
|
6101
|
+
if (!existsSync38(join45(repo, "shared", "settings.base.json"))) {
|
|
5932
6102
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
5933
6103
|
}
|
|
5934
6104
|
const handle = acquireLock("pull");
|
|
@@ -5937,7 +6107,7 @@ function cmdPull(opts = {}) {
|
|
|
5937
6107
|
const ts = freshBackupTs(backup);
|
|
5938
6108
|
handleWedge(repo, forceRemote);
|
|
5939
6109
|
if (!dryRun) {
|
|
5940
|
-
const backupRoot =
|
|
6110
|
+
const backupRoot = join45(backup, ts);
|
|
5941
6111
|
try {
|
|
5942
6112
|
mkdirSync9(backupRoot, { recursive: true });
|
|
5943
6113
|
} catch (err) {
|
|
@@ -5950,8 +6120,8 @@ function cmdPull(opts = {}) {
|
|
|
5950
6120
|
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
5951
6121
|
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
5952
6122
|
});
|
|
5953
|
-
const mapPath =
|
|
5954
|
-
const map =
|
|
6123
|
+
const mapPath = join45(repo, "path-map.json");
|
|
6124
|
+
const map = existsSync38(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
5955
6125
|
divergenceCheckExtras(ts);
|
|
5956
6126
|
if (dryRun) {
|
|
5957
6127
|
computePreview(ts, map, "pull");
|
|
@@ -5973,8 +6143,8 @@ function cmdPull(opts = {}) {
|
|
|
5973
6143
|
|
|
5974
6144
|
// src/commands.push.ts
|
|
5975
6145
|
init_config();
|
|
5976
|
-
import { existsSync as
|
|
5977
|
-
import { join as
|
|
6146
|
+
import { existsSync as existsSync40 } from "node:fs";
|
|
6147
|
+
import { join as join47, relative as relative5 } from "node:path";
|
|
5978
6148
|
|
|
5979
6149
|
// src/commands.push.allowlist.ts
|
|
5980
6150
|
init_config();
|
|
@@ -6067,7 +6237,7 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
6067
6237
|
|
|
6068
6238
|
// src/push-global-config.ts
|
|
6069
6239
|
init_config();
|
|
6070
|
-
import { execFileSync as
|
|
6240
|
+
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
6071
6241
|
var STATUS_LABELS = {
|
|
6072
6242
|
A: "add",
|
|
6073
6243
|
M: "modify",
|
|
@@ -6107,7 +6277,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
|
|
|
6107
6277
|
}
|
|
6108
6278
|
function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
|
|
6109
6279
|
const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
|
|
6110
|
-
const raw =
|
|
6280
|
+
const raw = execFileSync18("git", args, {
|
|
6111
6281
|
cwd: repoHome2,
|
|
6112
6282
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6113
6283
|
}).toString();
|
|
@@ -6146,9 +6316,9 @@ init_color();
|
|
|
6146
6316
|
init_config();
|
|
6147
6317
|
init_config_sharedDirs_guard();
|
|
6148
6318
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
6149
|
-
import { copyFileSync, existsSync as
|
|
6319
|
+
import { copyFileSync, existsSync as existsSync39, mkdirSync as mkdirSync10, readdirSync as readdirSync14, rmSync as rmSync14 } from "node:fs";
|
|
6150
6320
|
import { homedir as homedir5 } from "node:os";
|
|
6151
|
-
import { join as
|
|
6321
|
+
import { join as join46 } from "node:path";
|
|
6152
6322
|
init_push_leak_verdict();
|
|
6153
6323
|
init_push_gitleaks();
|
|
6154
6324
|
init_utils_fs();
|
|
@@ -6163,13 +6333,13 @@ function stageSessions(tmpRoot, map) {
|
|
|
6163
6333
|
if (!p || p === "TBD") continue;
|
|
6164
6334
|
reverse.set(encodePath(p), logical);
|
|
6165
6335
|
}
|
|
6166
|
-
const localProjects =
|
|
6167
|
-
if (!
|
|
6336
|
+
const localProjects = join46(claudeHome(), "projects");
|
|
6337
|
+
if (!existsSync39(localProjects)) return 0;
|
|
6168
6338
|
let staged = 0;
|
|
6169
6339
|
for (const dir of readdirSync14(localProjects)) {
|
|
6170
6340
|
const logical = reverse.get(dir);
|
|
6171
6341
|
if (!logical) continue;
|
|
6172
|
-
copyDirJsonlOnly(
|
|
6342
|
+
copyDirJsonlOnly(join46(localProjects, dir), join46(tmpRoot, "shared", "projects", logical));
|
|
6173
6343
|
staged++;
|
|
6174
6344
|
}
|
|
6175
6345
|
return staged;
|
|
@@ -6185,9 +6355,9 @@ function stageExtras(tmpRoot, map) {
|
|
|
6185
6355
|
if (!localRoot || localRoot === "TBD") continue;
|
|
6186
6356
|
for (const dirname8 of dirnames) {
|
|
6187
6357
|
if (!whitelist.includes(dirname8)) continue;
|
|
6188
|
-
const src =
|
|
6189
|
-
if (!
|
|
6190
|
-
const dst =
|
|
6358
|
+
const src = join46(localRoot, dirname8);
|
|
6359
|
+
if (!existsSync39(src)) continue;
|
|
6360
|
+
const dst = join46(tmpRoot, "shared", "extras", logical, dirname8);
|
|
6191
6361
|
copyExtras(src, dst);
|
|
6192
6362
|
staged++;
|
|
6193
6363
|
}
|
|
@@ -6195,19 +6365,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
6195
6365
|
return staged;
|
|
6196
6366
|
}
|
|
6197
6367
|
function previewPushLeaks(map) {
|
|
6198
|
-
const cacheDir =
|
|
6368
|
+
const cacheDir = join46(homedir5(), ".cache", "claude-nomad");
|
|
6199
6369
|
mkdirSync10(cacheDir, { recursive: true });
|
|
6200
6370
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
6201
|
-
const tmpRoot =
|
|
6371
|
+
const tmpRoot = join46(cacheDir, `push-preview-tree-${stamp}`);
|
|
6202
6372
|
try {
|
|
6203
6373
|
const sessionCount = stageSessions(tmpRoot, map);
|
|
6204
6374
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
6205
6375
|
if (sessionCount + extrasCount === 0) {
|
|
6206
6376
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
6207
6377
|
}
|
|
6208
|
-
const ignoreFile =
|
|
6209
|
-
if (
|
|
6210
|
-
copyFileSync(ignoreFile,
|
|
6378
|
+
const ignoreFile = join46(repoHome(), ".gitleaksignore");
|
|
6379
|
+
if (existsSync39(ignoreFile)) {
|
|
6380
|
+
copyFileSync(ignoreFile, join46(tmpRoot, ".gitleaksignore"));
|
|
6211
6381
|
}
|
|
6212
6382
|
let findings;
|
|
6213
6383
|
try {
|
|
@@ -6229,8 +6399,8 @@ init_utils();
|
|
|
6229
6399
|
init_utils_fs();
|
|
6230
6400
|
init_utils_json();
|
|
6231
6401
|
function stripGsdHooksFromBase(repo, backup) {
|
|
6232
|
-
const basePath =
|
|
6233
|
-
if (!
|
|
6402
|
+
const basePath = join47(repo, "shared", "settings.base.json");
|
|
6403
|
+
if (!existsSync40(basePath)) return;
|
|
6234
6404
|
let base;
|
|
6235
6405
|
try {
|
|
6236
6406
|
base = readJson(basePath);
|
|
@@ -6244,14 +6414,14 @@ function stripGsdHooksFromBase(repo, backup) {
|
|
|
6244
6414
|
writeJsonAtomic(basePath, stripped);
|
|
6245
6415
|
}
|
|
6246
6416
|
function reportSettingsAheadDrift(repo) {
|
|
6247
|
-
const basePath =
|
|
6248
|
-
if (!
|
|
6249
|
-
const settingsPath =
|
|
6250
|
-
if (!
|
|
6417
|
+
const basePath = join47(repo, "shared", "settings.base.json");
|
|
6418
|
+
if (!existsSync40(basePath)) return;
|
|
6419
|
+
const settingsPath = join47(claudeHome(), "settings.json");
|
|
6420
|
+
if (!existsSync40(settingsPath)) return;
|
|
6251
6421
|
try {
|
|
6252
6422
|
const base = readJson(basePath);
|
|
6253
|
-
const hostPath =
|
|
6254
|
-
const overrides =
|
|
6423
|
+
const hostPath = join47(repo, "hosts", `${HOST}.json`);
|
|
6424
|
+
const overrides = existsSync40(hostPath) ? readJson(hostPath) : {};
|
|
6255
6425
|
const merged = deepMerge(base, overrides);
|
|
6256
6426
|
const settings = readJson(settingsPath);
|
|
6257
6427
|
const { ahead } = classifySettingsDrift(merged, settings);
|
|
@@ -6265,7 +6435,7 @@ function reportSettingsAheadDrift(repo) {
|
|
|
6265
6435
|
}
|
|
6266
6436
|
}
|
|
6267
6437
|
function guardGitlinks(repo) {
|
|
6268
|
-
const gitlinks = findGitlinks(
|
|
6438
|
+
const gitlinks = findGitlinks(join47(repo, "shared"));
|
|
6269
6439
|
if (gitlinks.length === 0) return;
|
|
6270
6440
|
for (const p of gitlinks) {
|
|
6271
6441
|
const rel = relative5(repo, p);
|
|
@@ -6331,7 +6501,7 @@ async function cmdPush(opts = {}) {
|
|
|
6331
6501
|
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
6332
6502
|
const repo = repoHome();
|
|
6333
6503
|
const backup = backupBase();
|
|
6334
|
-
if (!
|
|
6504
|
+
if (!existsSync40(repo)) die(`repo not cloned at ${repo}`);
|
|
6335
6505
|
const handle = acquireLock("push");
|
|
6336
6506
|
if (handle === null) process.exit(0);
|
|
6337
6507
|
try {
|
|
@@ -6354,8 +6524,8 @@ async function cmdPush(opts = {}) {
|
|
|
6354
6524
|
renderNoScanTree(st);
|
|
6355
6525
|
return;
|
|
6356
6526
|
}
|
|
6357
|
-
const mapPath =
|
|
6358
|
-
if (!
|
|
6527
|
+
const mapPath = join47(repo, "path-map.json");
|
|
6528
|
+
if (!existsSync40(mapPath)) {
|
|
6359
6529
|
if (dryRun) return runDryRunPreview(st, null, repo);
|
|
6360
6530
|
die("path-map.json missing, cannot enforce push allow-list");
|
|
6361
6531
|
}
|
|
@@ -6376,25 +6546,35 @@ async function cmdPush(opts = {}) {
|
|
|
6376
6546
|
}
|
|
6377
6547
|
|
|
6378
6548
|
// src/commands.update.ts
|
|
6379
|
-
import { execFileSync as
|
|
6549
|
+
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
6380
6550
|
init_utils();
|
|
6381
|
-
function readInstalledVersion(run =
|
|
6551
|
+
function readInstalledVersion(run = execFileSync19) {
|
|
6382
6552
|
try {
|
|
6383
6553
|
return run("nomad", ["--version"], { encoding: "utf8" }).toString().trim() || null;
|
|
6384
6554
|
} catch {
|
|
6385
6555
|
return null;
|
|
6386
6556
|
}
|
|
6387
6557
|
}
|
|
6388
|
-
function cmdUpdate(run =
|
|
6389
|
-
console.log(
|
|
6558
|
+
function cmdUpdate(currentVersion, run = execFileSync19) {
|
|
6559
|
+
console.log(`Updating claude-nomad v${currentVersion}...`);
|
|
6390
6560
|
try {
|
|
6391
|
-
run("npm", ["update", "-g", "claude-nomad"], {
|
|
6561
|
+
run("npm", ["update", "-g", "claude-nomad"], {
|
|
6562
|
+
encoding: "utf8",
|
|
6563
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
6564
|
+
// Now that output is captured rather than inherited, execFileSync buffers
|
|
6565
|
+
// it; lift the default 1 MiB ceiling so a noisy-but-successful npm run
|
|
6566
|
+
// (deprecation/funding spam) cannot throw ENOBUFS.
|
|
6567
|
+
maxBuffer: 64 * 1024 * 1024
|
|
6568
|
+
});
|
|
6392
6569
|
} catch (err) {
|
|
6393
6570
|
const e = err;
|
|
6394
6571
|
if (e.code === "ENOENT") {
|
|
6395
6572
|
throw new NomadFatal("npm not found on PATH; install Node.js/npm and retry.");
|
|
6396
6573
|
}
|
|
6397
|
-
|
|
6574
|
+
const detail = String(e.stderr ?? "").trim();
|
|
6575
|
+
const suffix = detail ? `
|
|
6576
|
+
${detail}` : "";
|
|
6577
|
+
throw new NomadFatal(`npm update -g claude-nomad failed: ${e.message}${suffix}`);
|
|
6398
6578
|
}
|
|
6399
6579
|
const version = readInstalledVersion(run);
|
|
6400
6580
|
if (version) {
|
|
@@ -6409,18 +6589,18 @@ init_config();
|
|
|
6409
6589
|
|
|
6410
6590
|
// src/diff.ts
|
|
6411
6591
|
init_config();
|
|
6412
|
-
import { existsSync as
|
|
6413
|
-
import { join as
|
|
6592
|
+
import { existsSync as existsSync41 } from "node:fs";
|
|
6593
|
+
import { join as join48 } from "node:path";
|
|
6414
6594
|
init_utils();
|
|
6415
6595
|
init_utils_fs();
|
|
6416
6596
|
init_utils_json();
|
|
6417
6597
|
function cmdDiff() {
|
|
6418
6598
|
try {
|
|
6419
6599
|
const repo = repoHome();
|
|
6420
|
-
if (!
|
|
6600
|
+
if (!existsSync41(repo)) die(`repo not cloned at ${repo}`);
|
|
6421
6601
|
const ts = freshBackupTs(backupBase());
|
|
6422
|
-
const mapPath =
|
|
6423
|
-
const map =
|
|
6602
|
+
const mapPath = join48(repo, "path-map.json");
|
|
6603
|
+
const map = existsSync41(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
6424
6604
|
computePreview(ts, map, "diff");
|
|
6425
6605
|
} catch (err) {
|
|
6426
6606
|
if (err instanceof NomadFatal) {
|
|
@@ -6434,19 +6614,19 @@ function cmdDiff() {
|
|
|
6434
6614
|
|
|
6435
6615
|
// src/init.ts
|
|
6436
6616
|
init_config();
|
|
6437
|
-
import { existsSync as
|
|
6438
|
-
import { join as
|
|
6617
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync11, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6618
|
+
import { join as join50 } from "node:path";
|
|
6439
6619
|
|
|
6440
6620
|
// src/init.gh-onboard.ts
|
|
6441
6621
|
init_config();
|
|
6442
|
-
import { execFileSync as
|
|
6622
|
+
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
6443
6623
|
init_utils();
|
|
6444
6624
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
6445
6625
|
function isValidRepoName(name) {
|
|
6446
6626
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
6447
6627
|
}
|
|
6448
6628
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
6449
|
-
function ensureOriginRepo(repoName, run =
|
|
6629
|
+
function ensureOriginRepo(repoName, run = execFileSync20) {
|
|
6450
6630
|
if (!isValidRepoName(repoName)) {
|
|
6451
6631
|
die(
|
|
6452
6632
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -6517,33 +6697,33 @@ init_config();
|
|
|
6517
6697
|
init_utils();
|
|
6518
6698
|
init_utils_fs();
|
|
6519
6699
|
init_utils_json();
|
|
6520
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as
|
|
6521
|
-
import { join as
|
|
6700
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync42, rmSync as rmSync15, statSync as statSync9 } from "node:fs";
|
|
6701
|
+
import { join as join49 } from "node:path";
|
|
6522
6702
|
function snapshotIntoShared(map) {
|
|
6523
6703
|
const repo = repoHome();
|
|
6524
6704
|
const claude = claudeHome();
|
|
6525
6705
|
for (const name of allSharedLinks(map)) {
|
|
6526
|
-
const src =
|
|
6527
|
-
if (!
|
|
6528
|
-
const dst =
|
|
6706
|
+
const src = join49(claude, name);
|
|
6707
|
+
if (!existsSync42(src)) continue;
|
|
6708
|
+
const dst = join49(repo, "shared", name);
|
|
6529
6709
|
if (statSync9(src).isDirectory()) {
|
|
6530
|
-
const gk =
|
|
6531
|
-
if (
|
|
6710
|
+
const gk = join49(dst, ".gitkeep");
|
|
6711
|
+
if (existsSync42(gk)) rmSync15(gk);
|
|
6532
6712
|
cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
6533
6713
|
} else {
|
|
6534
6714
|
copyFileSync2(src, dst);
|
|
6535
6715
|
}
|
|
6536
6716
|
log(`snapshotted shared/${name} from ${src}`);
|
|
6537
6717
|
}
|
|
6538
|
-
const userSettings =
|
|
6539
|
-
if (
|
|
6718
|
+
const userSettings = join49(claude, "settings.json");
|
|
6719
|
+
if (existsSync42(userSettings)) {
|
|
6540
6720
|
let parsed;
|
|
6541
6721
|
try {
|
|
6542
6722
|
parsed = readJson(userSettings);
|
|
6543
6723
|
} catch (err) {
|
|
6544
6724
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
6545
6725
|
}
|
|
6546
|
-
const hostFile =
|
|
6726
|
+
const hostFile = join49(repo, "hosts", `${HOST}.json`);
|
|
6547
6727
|
writeJsonAtomic(hostFile, parsed);
|
|
6548
6728
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
6549
6729
|
}
|
|
@@ -6556,14 +6736,14 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
|
|
|
6556
6736
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
6557
6737
|
function preflightConflict(repoHome2) {
|
|
6558
6738
|
const candidates = [
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6739
|
+
join50(repoHome2, "shared", "settings.base.json"),
|
|
6740
|
+
join50(repoHome2, "shared", "CLAUDE.md"),
|
|
6741
|
+
join50(repoHome2, "path-map.json"),
|
|
6742
|
+
join50(repoHome2, "hosts"),
|
|
6743
|
+
join50(repoHome2, "shared")
|
|
6564
6744
|
];
|
|
6565
6745
|
for (const c of candidates) {
|
|
6566
|
-
if (
|
|
6746
|
+
if (existsSync43(c)) return c;
|
|
6567
6747
|
}
|
|
6568
6748
|
return null;
|
|
6569
6749
|
}
|
|
@@ -6578,25 +6758,25 @@ function cmdInit(opts = {}) {
|
|
|
6578
6758
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
6579
6759
|
}
|
|
6580
6760
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
6581
|
-
mkdirSync11(
|
|
6582
|
-
mkdirSync11(
|
|
6761
|
+
mkdirSync11(join50(repo, "shared"), { recursive: true });
|
|
6762
|
+
mkdirSync11(join50(repo, "hosts"), { recursive: true });
|
|
6583
6763
|
for (const name of SHARED_KEEP_DIRS) {
|
|
6584
|
-
mkdirSync11(
|
|
6764
|
+
mkdirSync11(join50(repo, "shared", name), { recursive: true });
|
|
6585
6765
|
}
|
|
6586
|
-
const userClaudeMd =
|
|
6587
|
-
if (!snapshot || !
|
|
6588
|
-
writeFileSync6(
|
|
6766
|
+
const userClaudeMd = join50(claude, "CLAUDE.md");
|
|
6767
|
+
if (!snapshot || !existsSync43(userClaudeMd)) {
|
|
6768
|
+
writeFileSync6(join50(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
6589
6769
|
item("created shared/CLAUDE.md");
|
|
6590
6770
|
}
|
|
6591
6771
|
for (const name of SHARED_KEEP_DIRS) {
|
|
6592
|
-
writeFileSync6(
|
|
6772
|
+
writeFileSync6(join50(repo, "shared", name, ".gitkeep"), "");
|
|
6593
6773
|
item(`created shared/${name}/.gitkeep`);
|
|
6594
6774
|
}
|
|
6595
|
-
writeFileSync6(
|
|
6775
|
+
writeFileSync6(join50(repo, "hosts", ".gitkeep"), "");
|
|
6596
6776
|
item("created hosts/.gitkeep");
|
|
6597
|
-
writeJsonAtomic(
|
|
6777
|
+
writeJsonAtomic(join50(repo, "shared", "settings.base.json"), {});
|
|
6598
6778
|
item("created shared/settings.base.json");
|
|
6599
|
-
writeJsonAtomic(
|
|
6779
|
+
writeJsonAtomic(join50(repo, "path-map.json"), { projects: {} });
|
|
6600
6780
|
item("created path-map.json");
|
|
6601
6781
|
if (snapshot) {
|
|
6602
6782
|
snapshotIntoShared({ projects: {} });
|
|
@@ -6918,7 +7098,7 @@ function parsePushArgs(argv) {
|
|
|
6918
7098
|
// package.json
|
|
6919
7099
|
var package_default = {
|
|
6920
7100
|
name: "claude-nomad",
|
|
6921
|
-
version: "0.
|
|
7101
|
+
version: "0.54.0",
|
|
6922
7102
|
type: "module",
|
|
6923
7103
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
6924
7104
|
keywords: [
|
|
@@ -7064,6 +7244,8 @@ var DEFAULT_HELP = [
|
|
|
7064
7244
|
cont("`nomad push` would stage (a temp copy, never the live dir)."),
|
|
7065
7245
|
row(" --check-schema", "Flag settings.json keys absent from the live published"),
|
|
7066
7246
|
cont("Claude Code settings schema (needs network; degrades offline)."),
|
|
7247
|
+
row(" --check-remote", "Verify origin/main has shared/ and a valid path-map.json"),
|
|
7248
|
+
cont("(reads locally-cached remote ref; skips with a warning if unavailable)."),
|
|
7067
7249
|
row(" --resume-cmd <id>", "Print `cd <abspath> && claude --resume <id>` for a session id"),
|
|
7068
7250
|
cont("from ~/.claude/projects/."),
|
|
7069
7251
|
"",
|
|
@@ -7135,15 +7317,15 @@ var DEFAULT_HELP = [
|
|
|
7135
7317
|
init_config();
|
|
7136
7318
|
init_utils();
|
|
7137
7319
|
init_utils_json();
|
|
7138
|
-
import { existsSync as
|
|
7139
|
-
import { join as
|
|
7320
|
+
import { existsSync as existsSync44, readFileSync as readFileSync14, readdirSync as readdirSync15 } from "node:fs";
|
|
7321
|
+
import { join as join51 } from "node:path";
|
|
7140
7322
|
function resumeCmd(sessionId) {
|
|
7141
7323
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
7142
7324
|
fail(`invalid session id: ${sessionId}`);
|
|
7143
7325
|
process.exit(1);
|
|
7144
7326
|
}
|
|
7145
|
-
const projectsRoot =
|
|
7146
|
-
if (!
|
|
7327
|
+
const projectsRoot = join51(claudeHome(), "projects");
|
|
7328
|
+
if (!existsSync44(projectsRoot)) {
|
|
7147
7329
|
fail(`${projectsRoot} does not exist`);
|
|
7148
7330
|
process.exit(1);
|
|
7149
7331
|
}
|
|
@@ -7157,8 +7339,8 @@ function resumeCmd(sessionId) {
|
|
|
7157
7339
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
7158
7340
|
process.exit(1);
|
|
7159
7341
|
}
|
|
7160
|
-
const mapPath =
|
|
7161
|
-
if (!
|
|
7342
|
+
const mapPath = join51(repoHome(), "path-map.json");
|
|
7343
|
+
if (!existsSync44(mapPath)) {
|
|
7162
7344
|
fail("path-map.json missing");
|
|
7163
7345
|
process.exit(1);
|
|
7164
7346
|
}
|
|
@@ -7181,8 +7363,8 @@ function resumeCmd(sessionId) {
|
|
|
7181
7363
|
}
|
|
7182
7364
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
7183
7365
|
for (const dir of readdirSync15(projectsRoot)) {
|
|
7184
|
-
const candidate =
|
|
7185
|
-
if (
|
|
7366
|
+
const candidate = join51(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
7367
|
+
if (existsSync44(candidate)) return candidate;
|
|
7186
7368
|
}
|
|
7187
7369
|
return null;
|
|
7188
7370
|
}
|
|
@@ -7284,7 +7466,7 @@ try {
|
|
|
7284
7466
|
console.error("usage: nomad update");
|
|
7285
7467
|
process.exit(1);
|
|
7286
7468
|
}
|
|
7287
|
-
cmdUpdate();
|
|
7469
|
+
cmdUpdate(package_default.version);
|
|
7288
7470
|
break;
|
|
7289
7471
|
}
|
|
7290
7472
|
case "adopt": {
|
|
@@ -7323,7 +7505,7 @@ try {
|
|
|
7323
7505
|
const parsed = parseDoctorArgs(process.argv.slice(3));
|
|
7324
7506
|
if (parsed.kind === "error") {
|
|
7325
7507
|
console.error(
|
|
7326
|
-
"usage: nomad doctor [--check-shared] [--check-schema] [--verbose|--all|-v] | --resume-cmd <session-id>"
|
|
7508
|
+
"usage: nomad doctor [--check-shared] [--check-schema] [--check-remote] [--verbose|--all|-v] | --resume-cmd <session-id>"
|
|
7327
7509
|
);
|
|
7328
7510
|
process.exit(1);
|
|
7329
7511
|
} else if (parsed.kind === "resume") {
|
|
@@ -7332,6 +7514,7 @@ try {
|
|
|
7332
7514
|
cmdDoctor({
|
|
7333
7515
|
checkShared: parsed.checkShared,
|
|
7334
7516
|
checkSchema: parsed.checkSchema,
|
|
7517
|
+
checkRemote: parsed.checkRemote,
|
|
7335
7518
|
verbose: parsed.verbose
|
|
7336
7519
|
});
|
|
7337
7520
|
}
|