claude-nomad 0.48.0 → 0.50.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 +22 -0
- package/README.md +40 -9
- package/dist/nomad.mjs +604 -210
- package/package.json +1 -1
package/dist/nomad.mjs
CHANGED
|
@@ -162,6 +162,13 @@ var init_color = __esm({
|
|
|
162
162
|
|
|
163
163
|
// src/utils.ts
|
|
164
164
|
import { execFileSync } from "node:child_process";
|
|
165
|
+
function gitCaptureRaw(args, cwd) {
|
|
166
|
+
return execFileSync("git", args, {
|
|
167
|
+
cwd,
|
|
168
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
169
|
+
maxBuffer: 64 * 1024 * 1024
|
|
170
|
+
}).toString();
|
|
171
|
+
}
|
|
165
172
|
function gitOrFatal(args, context, cwd) {
|
|
166
173
|
try {
|
|
167
174
|
execFileSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -387,7 +394,7 @@ function allSharedLinks(map) {
|
|
|
387
394
|
}
|
|
388
395
|
return [...SHARED_LINKS, ...extras];
|
|
389
396
|
}
|
|
390
|
-
var SETTINGS_SCHEMA_URL, NPM_REGISTRY_LATEST_URL, GITLEAKS_PINNED_VERSION, HOST, SHARED_LINKS, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, PUSH_ALLOWED_STATIC;
|
|
397
|
+
var SETTINGS_SCHEMA_URL, NPM_REGISTRY_LATEST_URL, GITLEAKS_PINNED_VERSION, HOST, SHARED_LINKS, GSD_PREFIX, GSD_DROPPED_NAMES, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, PUSH_ALLOWED_STATIC;
|
|
391
398
|
var init_config = __esm({
|
|
392
399
|
"src/config.ts"() {
|
|
393
400
|
"use strict";
|
|
@@ -399,15 +406,9 @@ var init_config = __esm({
|
|
|
399
406
|
NPM_REGISTRY_LATEST_URL = "https://registry.npmjs.org/claude-nomad/latest";
|
|
400
407
|
GITLEAKS_PINNED_VERSION = "8.30.1";
|
|
401
408
|
HOST = (process.env.NOMAD_HOST || hostname()).toLowerCase();
|
|
402
|
-
SHARED_LINKS = [
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
"skills",
|
|
406
|
-
"commands",
|
|
407
|
-
"rules",
|
|
408
|
-
"my-statusline.cjs",
|
|
409
|
-
"hooks"
|
|
410
|
-
];
|
|
409
|
+
SHARED_LINKS = ["CLAUDE.md", "commands", "rules", "my-statusline.cjs"];
|
|
410
|
+
GSD_PREFIX = "gsd-";
|
|
411
|
+
GSD_DROPPED_NAMES = ["hooks", "agents"];
|
|
411
412
|
SUPPORTED_EXTRAS = [".planning", "CLAUDE.md", ".claude"];
|
|
412
413
|
ALWAYS_NEVER_SYNC = /* @__PURE__ */ new Set([
|
|
413
414
|
".claude.json",
|
|
@@ -420,12 +421,10 @@ var init_config = __esm({
|
|
|
420
421
|
"shared/CLAUDE.md",
|
|
421
422
|
"shared/my-statusline.cjs",
|
|
422
423
|
"shared/settings.base.json",
|
|
423
|
-
"shared/agents/",
|
|
424
424
|
"shared/skills/",
|
|
425
425
|
"shared/commands/",
|
|
426
426
|
"shared/rules/",
|
|
427
427
|
"shared/.gitignore",
|
|
428
|
-
"shared/hooks/",
|
|
429
428
|
"hosts/",
|
|
430
429
|
"path-map.json",
|
|
431
430
|
".gitleaksignore",
|
|
@@ -571,6 +570,69 @@ var init_utils_fs = __esm({
|
|
|
571
570
|
}
|
|
572
571
|
});
|
|
573
572
|
|
|
573
|
+
// src/commands.pull.wedge.ts
|
|
574
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
575
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
576
|
+
import { join as join11 } from "node:path";
|
|
577
|
+
function detectWedge(repo) {
|
|
578
|
+
const g = join11(repo, ".git");
|
|
579
|
+
if (existsSync10(join11(g, "rebase-merge")) || existsSync10(join11(g, "rebase-apply"))) return "rebase";
|
|
580
|
+
if (existsSync10(join11(g, "MERGE_HEAD"))) return "merge";
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
function unmergedIndexPresent(repo) {
|
|
584
|
+
let raw;
|
|
585
|
+
try {
|
|
586
|
+
raw = execFileSync2("git", ["diff", "--diff-filter=U", "--name-only", "-z"], {
|
|
587
|
+
cwd: repo,
|
|
588
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
589
|
+
maxBuffer: 64 * 1024 * 1024
|
|
590
|
+
}).toString();
|
|
591
|
+
} catch {
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
return raw.split("\0").some(Boolean);
|
|
595
|
+
}
|
|
596
|
+
function classifyWedge(repo) {
|
|
597
|
+
const mode = detectWedge(repo);
|
|
598
|
+
if (mode !== null) return mode;
|
|
599
|
+
return unmergedIndexPresent(repo) ? "unmerged-index" : null;
|
|
600
|
+
}
|
|
601
|
+
function unmergedIndexRunbookText(resumeCmd2) {
|
|
602
|
+
return `repo has an unmerged index with no active rebase or merge in progress (torn-down rebase left stage-2/3 entries behind).
|
|
603
|
+
|
|
604
|
+
Manual recovery:
|
|
605
|
+
1. git reset --mixed HEAD (clears the stuck index; preserves working-tree files)
|
|
606
|
+
2. git stash list (look for an orphaned autostash entry)
|
|
607
|
+
git stash pop (restore the autostash) or
|
|
608
|
+
git stash drop (discard it)
|
|
609
|
+
3. ${resumeCmd2}
|
|
610
|
+
|
|
611
|
+
Auto-recover: run 'nomad pull --force-remote' to apply step 1 automatically
|
|
612
|
+
(see FAQ: "Every pull fails with unmerged files")`;
|
|
613
|
+
}
|
|
614
|
+
function wedgeMarkerRunbookText(state) {
|
|
615
|
+
return `repo is ${state} from a previous failed pull; run 'nomad pull --force-remote' to auto-recover, or resolve manually (see FAQ: "Every pull fails with unmerged files")`;
|
|
616
|
+
}
|
|
617
|
+
function orphanedAutostashPresent(repo) {
|
|
618
|
+
let raw;
|
|
619
|
+
try {
|
|
620
|
+
raw = execFileSync2("git", ["stash", "list"], {
|
|
621
|
+
cwd: repo,
|
|
622
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
623
|
+
maxBuffer: 64 * 1024 * 1024
|
|
624
|
+
}).toString();
|
|
625
|
+
} catch {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
return raw.split("\n").some((line) => /:\s*autostash$/.test(line));
|
|
629
|
+
}
|
|
630
|
+
var init_commands_pull_wedge = __esm({
|
|
631
|
+
"src/commands.pull.wedge.ts"() {
|
|
632
|
+
"use strict";
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
|
|
574
636
|
// src/push-gitleaks.config.ts
|
|
575
637
|
import { existsSync as existsSync11, mkdtempSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
576
638
|
import { tmpdir } from "node:os";
|
|
@@ -637,7 +699,7 @@ var init_push_gitleaks_config = __esm({
|
|
|
637
699
|
});
|
|
638
700
|
|
|
639
701
|
// src/push-checks.ts
|
|
640
|
-
import { execFileSync as
|
|
702
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
641
703
|
import { readdirSync as readdirSync4, rmSync as rmSync4 } from "node:fs";
|
|
642
704
|
import { homedir as homedir2, platform } from "node:os";
|
|
643
705
|
import { join as join13 } from "node:path";
|
|
@@ -699,7 +761,7 @@ function probeGitleaks() {
|
|
|
699
761
|
const args = ["version"];
|
|
700
762
|
if (toml !== null) args.push("--config", toml);
|
|
701
763
|
try {
|
|
702
|
-
return
|
|
764
|
+
return execFileSync3("gitleaks", args, { stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
703
765
|
} catch (err) {
|
|
704
766
|
const e = err;
|
|
705
767
|
if (e.code === "ENOENT") throw new NomadFatal(gitleaksInstallHint());
|
|
@@ -708,9 +770,18 @@ function probeGitleaks() {
|
|
|
708
770
|
if (tempPath !== null) rmSync4(tempPath, { recursive: true, force: true });
|
|
709
771
|
}
|
|
710
772
|
}
|
|
773
|
+
function wedgePreflight(wedge) {
|
|
774
|
+
if (wedge === "unmerged-index") {
|
|
775
|
+
return unmergedIndexRunbookText("nomad push");
|
|
776
|
+
}
|
|
777
|
+
const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
|
|
778
|
+
return wedgeMarkerRunbookText(state);
|
|
779
|
+
}
|
|
711
780
|
function rebaseBeforePush(repo) {
|
|
781
|
+
const wedge = classifyWedge(repo);
|
|
782
|
+
if (wedge !== null) throw new NomadFatal(wedgePreflight(wedge));
|
|
712
783
|
try {
|
|
713
|
-
|
|
784
|
+
execFileSync3("git", ["pull", "--rebase", "--autostash"], {
|
|
714
785
|
cwd: repo,
|
|
715
786
|
stdio: ["ignore", "pipe", "pipe"]
|
|
716
787
|
});
|
|
@@ -726,12 +797,13 @@ var init_push_checks = __esm({
|
|
|
726
797
|
"src/push-checks.ts"() {
|
|
727
798
|
"use strict";
|
|
728
799
|
init_push_gitleaks_config();
|
|
800
|
+
init_commands_pull_wedge();
|
|
729
801
|
init_utils();
|
|
730
802
|
}
|
|
731
803
|
});
|
|
732
804
|
|
|
733
805
|
// src/push-gitleaks.scan.ts
|
|
734
|
-
import { execFileSync as
|
|
806
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
735
807
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync5 } from "node:fs";
|
|
736
808
|
import { homedir as homedir3 } from "node:os";
|
|
737
809
|
import { join as join17 } from "node:path";
|
|
@@ -761,9 +833,9 @@ function scanStagedTree(repoDir, forwardStreams = false) {
|
|
|
761
833
|
if (toml !== null) args.push("--config", toml);
|
|
762
834
|
const opts = { cwd: repoDir, stdio: ["ignore", "pipe", "pipe"] };
|
|
763
835
|
try {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
836
|
+
execFileSync6("git", ["init", "-q"], opts);
|
|
837
|
+
execFileSync6("git", ["add", "-A"], opts);
|
|
838
|
+
execFileSync6("gitleaks", args, opts);
|
|
767
839
|
return [];
|
|
768
840
|
} catch (err) {
|
|
769
841
|
const e = err;
|
|
@@ -795,7 +867,7 @@ function scanFile(filePath, forwardStreams = false) {
|
|
|
795
867
|
if (toml !== null) args.push("--config", toml);
|
|
796
868
|
const opts = { stdio: ["ignore", "pipe", "pipe"] };
|
|
797
869
|
try {
|
|
798
|
-
|
|
870
|
+
execFileSync6("gitleaks", args, opts);
|
|
799
871
|
return [];
|
|
800
872
|
} catch (err) {
|
|
801
873
|
const e = err;
|
|
@@ -1618,6 +1690,23 @@ function reportSharedLinks(section2, map) {
|
|
|
1618
1690
|
if (fail2) process.exitCode = 1;
|
|
1619
1691
|
}
|
|
1620
1692
|
}
|
|
1693
|
+
function reportDroppedNamesMigration(section2) {
|
|
1694
|
+
const claude = claudeHome();
|
|
1695
|
+
for (const name of GSD_DROPPED_NAMES) {
|
|
1696
|
+
const p = join8(claude, name);
|
|
1697
|
+
let stat;
|
|
1698
|
+
try {
|
|
1699
|
+
stat = lstatSync5(p);
|
|
1700
|
+
} catch {
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
if (!stat.isSymbolicLink()) continue;
|
|
1704
|
+
addItem(
|
|
1705
|
+
section2,
|
|
1706
|
+
`${yellow(warnGlyph)} ${name}: gsd now owns this dir per-host (was a nomad symlink); run \`rm ~/.claude/${name}\` and let gsd reinstall a real dir`
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1621
1710
|
|
|
1622
1711
|
// src/commands.doctor.checks.settings.ts
|
|
1623
1712
|
init_color();
|
|
@@ -1788,26 +1877,15 @@ function reportNeverSync(section2) {
|
|
|
1788
1877
|
// src/commands.doctor.checks.repository.ts
|
|
1789
1878
|
init_color();
|
|
1790
1879
|
init_config();
|
|
1791
|
-
import { execFileSync as
|
|
1880
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
1792
1881
|
import { existsSync as existsSync12 } from "node:fs";
|
|
1793
1882
|
import { join as join14, relative as relative2 } from "node:path";
|
|
1794
|
-
|
|
1795
|
-
// src/commands.pull.wedge.ts
|
|
1796
|
-
import { existsSync as existsSync10 } from "node:fs";
|
|
1797
|
-
import { join as join11 } from "node:path";
|
|
1798
|
-
function detectWedge(repo) {
|
|
1799
|
-
const g = join11(repo, ".git");
|
|
1800
|
-
if (existsSync10(join11(g, "rebase-merge")) || existsSync10(join11(g, "rebase-apply"))) return "rebase";
|
|
1801
|
-
if (existsSync10(join11(g, "MERGE_HEAD"))) return "merge";
|
|
1802
|
-
return null;
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
// src/commands.doctor.checks.repository.ts
|
|
1883
|
+
init_commands_pull_wedge();
|
|
1806
1884
|
init_push_checks();
|
|
1807
1885
|
init_utils();
|
|
1808
1886
|
function reportGitleaksProbe(section2) {
|
|
1809
1887
|
try {
|
|
1810
|
-
|
|
1888
|
+
execFileSync4("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
1811
1889
|
return true;
|
|
1812
1890
|
} catch (err) {
|
|
1813
1891
|
if (err.code === "ENOENT") {
|
|
@@ -1843,7 +1921,7 @@ function reportGitlinks(section2) {
|
|
|
1843
1921
|
}
|
|
1844
1922
|
function reportRemote(section2) {
|
|
1845
1923
|
try {
|
|
1846
|
-
const url =
|
|
1924
|
+
const url = execFileSync4("git", ["remote", "get-url", "origin"], {
|
|
1847
1925
|
cwd: repoHome(),
|
|
1848
1926
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1849
1927
|
}).toString().trim();
|
|
@@ -1866,14 +1944,31 @@ function reportRebaseClean(section2) {
|
|
|
1866
1944
|
}
|
|
1867
1945
|
function reportRebaseState(section2) {
|
|
1868
1946
|
try {
|
|
1869
|
-
const wedge =
|
|
1870
|
-
if (wedge
|
|
1947
|
+
const wedge = classifyWedge(repoHome());
|
|
1948
|
+
if (wedge === null) return;
|
|
1949
|
+
if (wedge === "unmerged-index") {
|
|
1950
|
+
addItem(
|
|
1951
|
+
section2,
|
|
1952
|
+
`${red(failGlyph)} repo has an unmerged index with no active rebase: run 'nomad pull --force-remote' to auto-recover`
|
|
1953
|
+
);
|
|
1954
|
+
} else {
|
|
1871
1955
|
const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
|
|
1872
1956
|
addItem(
|
|
1873
1957
|
section2,
|
|
1874
1958
|
`${red(failGlyph)} repo is ${state}: run 'nomad pull --force-remote' to auto-recover`
|
|
1875
1959
|
);
|
|
1876
|
-
|
|
1960
|
+
}
|
|
1961
|
+
process.exitCode = 1;
|
|
1962
|
+
} catch {
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
function reportOrphanedAutostash(sec) {
|
|
1966
|
+
try {
|
|
1967
|
+
if (orphanedAutostashPresent(repoHome())) {
|
|
1968
|
+
addItem(
|
|
1969
|
+
sec,
|
|
1970
|
+
`${yellow(warnGlyph)} repo has an orphaned autostash entry: run 'git stash pop' to restore or 'git stash drop' to discard`
|
|
1971
|
+
);
|
|
1877
1972
|
}
|
|
1878
1973
|
} catch {
|
|
1879
1974
|
}
|
|
@@ -1920,7 +2015,7 @@ function reportBackupsCheck(section2, backupBase2 = backupBase()) {
|
|
|
1920
2015
|
if (count > DOCTOR_BACKUP_COUNT_WARN || sizeMb > DOCTOR_BACKUP_SIZE_WARN_MB) {
|
|
1921
2016
|
addItem(
|
|
1922
2017
|
section2,
|
|
1923
|
-
`${yellow(warnGlyph)} backups: ${count} dirs / ${sizeMb.toFixed(1)} MB (run 'nomad clean --backups')`
|
|
2018
|
+
`${yellow(warnGlyph)} backups: ${count} dirs / ${sizeMb.toFixed(1)} MB (run 'nomad clean --backups --keep <N>'; bare --backups only prunes dirs older than 14d)`
|
|
1924
2019
|
);
|
|
1925
2020
|
}
|
|
1926
2021
|
}
|
|
@@ -1932,9 +2027,9 @@ import { join as join16 } from "node:path";
|
|
|
1932
2027
|
init_config();
|
|
1933
2028
|
|
|
1934
2029
|
// src/http-fetch.ts
|
|
1935
|
-
import { execFileSync as
|
|
2030
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
1936
2031
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
1937
|
-
function fetchUrl(url, run =
|
|
2032
|
+
function fetchUrl(url, run = execFileSync5) {
|
|
1938
2033
|
try {
|
|
1939
2034
|
return run("curl", ["-fsSL", "-m", "3", url], {
|
|
1940
2035
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -1995,7 +2090,7 @@ function reportCheckSchema(section2) {
|
|
|
1995
2090
|
// src/commands.doctor.check-shared.ts
|
|
1996
2091
|
init_color();
|
|
1997
2092
|
import { randomBytes } from "node:crypto";
|
|
1998
|
-
import { execFileSync as
|
|
2093
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
1999
2094
|
import { existsSync as existsSync16, mkdirSync as mkdirSync4, readdirSync as readdirSync7, rmSync as rmSync7 } from "node:fs";
|
|
2000
2095
|
import { homedir as homedir4 } from "node:os";
|
|
2001
2096
|
import { join as join20 } from "node:path";
|
|
@@ -2265,7 +2360,7 @@ function buildScanTree(tmpRoot) {
|
|
|
2265
2360
|
}
|
|
2266
2361
|
function probeGitleaksForScan() {
|
|
2267
2362
|
try {
|
|
2268
|
-
|
|
2363
|
+
execFileSync7("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
2269
2364
|
return "ok";
|
|
2270
2365
|
} catch (err) {
|
|
2271
2366
|
if (err.code === "ENOENT") return "missing";
|
|
@@ -3669,7 +3764,7 @@ function withSpinner(label, fn, deps) {
|
|
|
3669
3764
|
|
|
3670
3765
|
// src/commands.doctor.gitleaks-version.ts
|
|
3671
3766
|
init_color();
|
|
3672
|
-
import { execFileSync as
|
|
3767
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
3673
3768
|
import { existsSync as existsSync26 } from "node:fs";
|
|
3674
3769
|
import { join as join32 } from "node:path";
|
|
3675
3770
|
init_config();
|
|
@@ -3692,7 +3787,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
3692
3787
|
return null;
|
|
3693
3788
|
}
|
|
3694
3789
|
}
|
|
3695
|
-
function reportGitleaksVersionCheck(section2, run =
|
|
3790
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync8, tomlExists = existsSync26) {
|
|
3696
3791
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
3697
3792
|
if (raw === null) return;
|
|
3698
3793
|
const local = majorMinorOf(raw);
|
|
@@ -3712,7 +3807,7 @@ function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists =
|
|
|
3712
3807
|
|
|
3713
3808
|
// src/commands.doctor.checks.deps.ts
|
|
3714
3809
|
init_color();
|
|
3715
|
-
import { execFileSync as
|
|
3810
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
3716
3811
|
var VERSION_TOKEN = /(\d{1,9}\.\d{1,9}\.\d{1,9})/;
|
|
3717
3812
|
var PROBE_TIMEOUT_MS = 3e3;
|
|
3718
3813
|
var FETCHER_BASE = "HTTP fetcher";
|
|
@@ -3749,7 +3844,7 @@ function reportFetcherRow(section2, run) {
|
|
|
3749
3844
|
);
|
|
3750
3845
|
}
|
|
3751
3846
|
}
|
|
3752
|
-
function reportOptionalDeps(section2, run =
|
|
3847
|
+
function reportOptionalDeps(section2, run = execFileSync9) {
|
|
3753
3848
|
const gh = probeOptionalDep("gh", run);
|
|
3754
3849
|
if (gh.status === "present") {
|
|
3755
3850
|
addItem(section2, `${green(okGlyph)} gh: ${gh.version ?? "present"}`);
|
|
@@ -3764,11 +3859,11 @@ function reportOptionalDeps(section2, run = execFileSync8) {
|
|
|
3764
3859
|
|
|
3765
3860
|
// src/commands.doctor.actions-drift.ts
|
|
3766
3861
|
init_color();
|
|
3767
|
-
import { execFileSync as
|
|
3862
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
3768
3863
|
init_config();
|
|
3769
3864
|
|
|
3770
3865
|
// src/gh-actions.ts
|
|
3771
|
-
import { execFileSync as
|
|
3866
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
3772
3867
|
var GH_TIMEOUT_MS = 5e3;
|
|
3773
3868
|
function parseGitHubRemote(remoteUrl) {
|
|
3774
3869
|
const normalized = remoteUrl.trim().replace(/\/$/, "");
|
|
@@ -3776,7 +3871,7 @@ function parseGitHubRemote(remoteUrl) {
|
|
|
3776
3871
|
if (m === null) return null;
|
|
3777
3872
|
return { owner: m[1], repo: m[2] };
|
|
3778
3873
|
}
|
|
3779
|
-
function ghAuthStatus(run =
|
|
3874
|
+
function ghAuthStatus(run = execFileSync10) {
|
|
3780
3875
|
try {
|
|
3781
3876
|
run("gh", ["auth", "status"], {
|
|
3782
3877
|
stdio: ["ignore", "ignore", "ignore"],
|
|
@@ -3790,7 +3885,7 @@ function ghAuthStatus(run = execFileSync9) {
|
|
|
3790
3885
|
return "gh-probe-error";
|
|
3791
3886
|
}
|
|
3792
3887
|
}
|
|
3793
|
-
function isRepoPrivate(ref, run =
|
|
3888
|
+
function isRepoPrivate(ref, run = execFileSync10) {
|
|
3794
3889
|
const out = run("gh", ["repo", "view", `${ref.owner}/${ref.repo}`, "--json", "isPrivate"], {
|
|
3795
3890
|
stdio: ["ignore", "pipe", "ignore"],
|
|
3796
3891
|
timeout: GH_TIMEOUT_MS
|
|
@@ -3798,7 +3893,7 @@ function isRepoPrivate(ref, run = execFileSync9) {
|
|
|
3798
3893
|
const parsed = JSON.parse(out);
|
|
3799
3894
|
return parsed.isPrivate === true;
|
|
3800
3895
|
}
|
|
3801
|
-
function isActionsEnabled(ref, run =
|
|
3896
|
+
function isActionsEnabled(ref, run = execFileSync10) {
|
|
3802
3897
|
const out = run(
|
|
3803
3898
|
"gh",
|
|
3804
3899
|
["api", `repos/${ref.owner}/${ref.repo}/actions/permissions`, "--jq", ".enabled"],
|
|
@@ -3806,7 +3901,7 @@ function isActionsEnabled(ref, run = execFileSync9) {
|
|
|
3806
3901
|
).toString().trim();
|
|
3807
3902
|
return out === "true";
|
|
3808
3903
|
}
|
|
3809
|
-
function disableActions(ref, run =
|
|
3904
|
+
function disableActions(ref, run = execFileSync10) {
|
|
3810
3905
|
run(
|
|
3811
3906
|
"gh",
|
|
3812
3907
|
[
|
|
@@ -3820,7 +3915,7 @@ function disableActions(ref, run = execFileSync9) {
|
|
|
3820
3915
|
{ stdio: ["ignore", "ignore", "pipe"], timeout: GH_TIMEOUT_MS }
|
|
3821
3916
|
);
|
|
3822
3917
|
}
|
|
3823
|
-
function readOriginRemote(cwd, run =
|
|
3918
|
+
function readOriginRemote(cwd, run = execFileSync10) {
|
|
3824
3919
|
return run("git", ["remote", "get-url", "origin"], {
|
|
3825
3920
|
cwd,
|
|
3826
3921
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -3828,7 +3923,7 @@ function readOriginRemote(cwd, run = execFileSync9) {
|
|
|
3828
3923
|
}
|
|
3829
3924
|
|
|
3830
3925
|
// src/commands.doctor.actions-drift.ts
|
|
3831
|
-
function reportActionsDrift(section2, run =
|
|
3926
|
+
function reportActionsDrift(section2, run = execFileSync11) {
|
|
3832
3927
|
let remote;
|
|
3833
3928
|
try {
|
|
3834
3929
|
remote = readOriginRemote(repoHome(), run);
|
|
@@ -3896,6 +3991,7 @@ function gatherDoctorSections(opts) {
|
|
|
3896
3991
|
const rawMap = existsSync27(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
3897
3992
|
const map = rawMap ?? { projects: {} };
|
|
3898
3993
|
reportSharedLinks(links, map);
|
|
3994
|
+
reportDroppedNamesMigration(links);
|
|
3899
3995
|
const hooksScan = section("Hook targets");
|
|
3900
3996
|
reportHooksTargetCheck(hooksScan);
|
|
3901
3997
|
reportHookScopeCheck(hooksScan);
|
|
@@ -3915,6 +4011,7 @@ function gatherDoctorSections(opts) {
|
|
|
3915
4011
|
reportRemote(repository);
|
|
3916
4012
|
reportRebaseClean(repository);
|
|
3917
4013
|
reportRebaseState(repository);
|
|
4014
|
+
reportOrphanedAutostash(repository);
|
|
3918
4015
|
reportActionsDrift(repository);
|
|
3919
4016
|
const nomadVersion = section("Nomad Version");
|
|
3920
4017
|
reportVersionCheck(nomadVersion);
|
|
@@ -3958,15 +4055,15 @@ function cmdDoctor(opts = {}) {
|
|
|
3958
4055
|
|
|
3959
4056
|
// src/commands.drop-session.ts
|
|
3960
4057
|
init_config();
|
|
3961
|
-
import { execFileSync as
|
|
4058
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
3962
4059
|
import { existsSync as existsSync29, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
|
|
3963
4060
|
import { join as join35, relative as relative4 } from "node:path";
|
|
3964
4061
|
|
|
3965
4062
|
// src/commands.drop-session.git.ts
|
|
3966
|
-
import { execFileSync as
|
|
4063
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
3967
4064
|
function expandStagedDir(dirRel, repo) {
|
|
3968
4065
|
try {
|
|
3969
|
-
const out =
|
|
4066
|
+
const out = execFileSync12("git", ["ls-files", "-z", "--", dirRel], {
|
|
3970
4067
|
cwd: repo,
|
|
3971
4068
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3972
4069
|
});
|
|
@@ -3977,7 +4074,7 @@ function expandStagedDir(dirRel, repo) {
|
|
|
3977
4074
|
}
|
|
3978
4075
|
function isTrackedInHead(rel, repo) {
|
|
3979
4076
|
try {
|
|
3980
|
-
|
|
4077
|
+
execFileSync12("git", ["cat-file", "-e", `HEAD:${rel}`], {
|
|
3981
4078
|
cwd: repo,
|
|
3982
4079
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3983
4080
|
});
|
|
@@ -3988,7 +4085,7 @@ function isTrackedInHead(rel, repo) {
|
|
|
3988
4085
|
}
|
|
3989
4086
|
function isInIndex(rel, repo) {
|
|
3990
4087
|
try {
|
|
3991
|
-
const out =
|
|
4088
|
+
const out = execFileSync12("git", ["ls-files", "--", rel], {
|
|
3992
4089
|
cwd: repo,
|
|
3993
4090
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3994
4091
|
});
|
|
@@ -4093,12 +4190,12 @@ function unstageOne(rel, repo) {
|
|
|
4093
4190
|
}
|
|
4094
4191
|
try {
|
|
4095
4192
|
if (isTrackedInHead(rel, repo)) {
|
|
4096
|
-
|
|
4193
|
+
execFileSync13("git", ["restore", "--staged", "--worktree", "--", rel], {
|
|
4097
4194
|
cwd: repo,
|
|
4098
4195
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4099
4196
|
});
|
|
4100
4197
|
} else {
|
|
4101
|
-
|
|
4198
|
+
execFileSync13("git", ["rm", "--cached", "-f", "--", rel], {
|
|
4102
4199
|
cwd: repo,
|
|
4103
4200
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4104
4201
|
});
|
|
@@ -4112,8 +4209,8 @@ function unstageOne(rel, repo) {
|
|
|
4112
4209
|
}
|
|
4113
4210
|
|
|
4114
4211
|
// src/commands.pull.ts
|
|
4115
|
-
import { existsSync as
|
|
4116
|
-
import { join as
|
|
4212
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync9 } from "node:fs";
|
|
4213
|
+
import { join as join43 } from "node:path";
|
|
4117
4214
|
|
|
4118
4215
|
// src/commands.push.sections.ts
|
|
4119
4216
|
init_color();
|
|
@@ -4209,11 +4306,11 @@ init_config();
|
|
|
4209
4306
|
// src/extras-sync.ts
|
|
4210
4307
|
init_config();
|
|
4211
4308
|
import { existsSync as existsSync32 } from "node:fs";
|
|
4212
|
-
import { join as
|
|
4309
|
+
import { join as join39 } from "node:path";
|
|
4213
4310
|
|
|
4214
4311
|
// src/extras-sync.diff.ts
|
|
4215
4312
|
init_utils();
|
|
4216
|
-
import { execFileSync as
|
|
4313
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
4217
4314
|
function labelDiffLine(line) {
|
|
4218
4315
|
const tab = line.indexOf(" ");
|
|
4219
4316
|
if (tab === -1) return line;
|
|
@@ -4228,7 +4325,7 @@ function parseDiffOutput(stdout) {
|
|
|
4228
4325
|
}
|
|
4229
4326
|
function listDivergingFiles(a, b) {
|
|
4230
4327
|
try {
|
|
4231
|
-
const stdout =
|
|
4328
|
+
const stdout = execFileSync14("git", ["diff", "--no-index", "--name-status", a, b], {
|
|
4232
4329
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4233
4330
|
}).toString();
|
|
4234
4331
|
return parseDiffOutput(stdout);
|
|
@@ -4297,21 +4394,39 @@ function* eachExtrasTarget(v, counts) {
|
|
|
4297
4394
|
counts.unmapped++;
|
|
4298
4395
|
continue;
|
|
4299
4396
|
}
|
|
4300
|
-
for (const
|
|
4301
|
-
if (!whitelist.includes(
|
|
4397
|
+
for (const dirname8 of dirnames) {
|
|
4398
|
+
if (!whitelist.includes(dirname8)) {
|
|
4302
4399
|
counts.skipped++;
|
|
4303
4400
|
continue;
|
|
4304
4401
|
}
|
|
4305
|
-
yield { logical, localRoot, dirname:
|
|
4402
|
+
yield { logical, localRoot, dirname: dirname8 };
|
|
4306
4403
|
}
|
|
4307
4404
|
}
|
|
4308
4405
|
}
|
|
4406
|
+
function copyExtrasOverlayFiltered(src, dst, blockSet) {
|
|
4407
|
+
try {
|
|
4408
|
+
cpSync6(src, dst, {
|
|
4409
|
+
recursive: true,
|
|
4410
|
+
force: true,
|
|
4411
|
+
verbatimSymlinks: true,
|
|
4412
|
+
filter: (srcEntry) => srcEntry === src || !blockSet.has(basename(srcEntry))
|
|
4413
|
+
});
|
|
4414
|
+
} catch (err) {
|
|
4415
|
+
const e = err;
|
|
4416
|
+
if (e.code === "EINVAL" || e.code === "ENOTEMPTY" || e.code === "ERR_FS_CP_NON_DIR_TO_DIR" || e.code === "ERR_FS_CP_DIR_TO_NON_DIR") {
|
|
4417
|
+
throw new NomadFatal(
|
|
4418
|
+
`copyExtrasOverlayFiltered: type collision copying ${JSON.stringify(src)} -> ${JSON.stringify(dst)} (${e.path ?? "unknown path"}): a file/directory type changed upstream; run nomad pull --force-remote to recover`
|
|
4419
|
+
);
|
|
4420
|
+
}
|
|
4421
|
+
throw err;
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4309
4424
|
function copyExtras(src, dst) {
|
|
4310
4425
|
rmSync10(dst, { recursive: true, force: true });
|
|
4311
4426
|
cpSync6(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
4312
4427
|
}
|
|
4313
|
-
function extrasDenySet(
|
|
4314
|
-
return
|
|
4428
|
+
function extrasDenySet(dirname8) {
|
|
4429
|
+
return dirname8 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
4315
4430
|
}
|
|
4316
4431
|
function copyExtrasFiltered(src, dst, blockSet) {
|
|
4317
4432
|
rmSync10(dst, { recursive: true, force: true });
|
|
@@ -4352,6 +4467,36 @@ function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
|
4352
4467
|
filter: (srcEntry) => srcEntry === src || !blockSet.has(basename(srcEntry))
|
|
4353
4468
|
});
|
|
4354
4469
|
}
|
|
4470
|
+
function prunePreservingBy(src, dst, isPreserved) {
|
|
4471
|
+
for (const name of readdirSync11(dst)) {
|
|
4472
|
+
if (isPreserved(name)) continue;
|
|
4473
|
+
const dstPath = join36(dst, name);
|
|
4474
|
+
const srcStat = lstatSync8(join36(src, name), { throwIfNoEntry: false });
|
|
4475
|
+
if (srcStat === void 0) {
|
|
4476
|
+
rmSync10(dstPath, { recursive: true, force: true });
|
|
4477
|
+
continue;
|
|
4478
|
+
}
|
|
4479
|
+
const dstStat = lstatSync8(dstPath);
|
|
4480
|
+
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
4481
|
+
prunePreservingBy(join36(src, name), dstPath, isPreserved);
|
|
4482
|
+
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
4483
|
+
rmSync10(dstPath, { recursive: true, force: true });
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
|
|
4488
|
+
const dstStat = lstatSync8(dst, { throwIfNoEntry: false });
|
|
4489
|
+
if (dstStat !== void 0) {
|
|
4490
|
+
if (dstStat.isDirectory()) prunePreservingBy(src, dst, isPreserved);
|
|
4491
|
+
else rmSync10(dst, { recursive: true, force: true });
|
|
4492
|
+
}
|
|
4493
|
+
cpSync6(src, dst, {
|
|
4494
|
+
recursive: true,
|
|
4495
|
+
force: true,
|
|
4496
|
+
verbatimSymlinks: true,
|
|
4497
|
+
filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
|
|
4498
|
+
});
|
|
4499
|
+
}
|
|
4355
4500
|
|
|
4356
4501
|
// src/extras-sync.ts
|
|
4357
4502
|
init_utils();
|
|
@@ -4359,9 +4504,85 @@ init_utils_json();
|
|
|
4359
4504
|
|
|
4360
4505
|
// src/extras-sync.remap.ts
|
|
4361
4506
|
init_config();
|
|
4362
|
-
import { existsSync as existsSync31, mkdirSync as mkdirSync7 } from "node:fs";
|
|
4363
|
-
import { join as
|
|
4507
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync7, readdirSync as readdirSync12, realpathSync as realpathSync4, rmSync as rmSync11 } from "node:fs";
|
|
4508
|
+
import { dirname as dirname7, join as join38, sep as sep6 } from "node:path";
|
|
4509
|
+
|
|
4510
|
+
// src/extras-sync.planning-diff.ts
|
|
4511
|
+
import { join as join37, normalize as normalize2, sep as sep5 } from "node:path";
|
|
4512
|
+
init_utils();
|
|
4513
|
+
function processRecord(fields, i, changed, deleted) {
|
|
4514
|
+
const status = fields[i];
|
|
4515
|
+
const next = i + 1;
|
|
4516
|
+
if (status.startsWith("R")) {
|
|
4517
|
+
const oldPath = fields[next];
|
|
4518
|
+
const newPath = fields[next + 1];
|
|
4519
|
+
if (oldPath) deleted.push(oldPath);
|
|
4520
|
+
if (newPath) changed.push(newPath);
|
|
4521
|
+
return next + 2;
|
|
4522
|
+
}
|
|
4523
|
+
if (status.startsWith("C")) {
|
|
4524
|
+
const dstPath = fields[next + 1];
|
|
4525
|
+
if (dstPath) changed.push(dstPath);
|
|
4526
|
+
return next + 2;
|
|
4527
|
+
}
|
|
4528
|
+
const path = fields[next];
|
|
4529
|
+
if (path) {
|
|
4530
|
+
if (status === "D") {
|
|
4531
|
+
deleted.push(path);
|
|
4532
|
+
} else {
|
|
4533
|
+
changed.push(path);
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
return next + 1;
|
|
4537
|
+
}
|
|
4538
|
+
function parsePlanningDiff(raw) {
|
|
4539
|
+
const changed = [];
|
|
4540
|
+
const deleted = [];
|
|
4541
|
+
if (raw === "") {
|
|
4542
|
+
return { changed, deleted };
|
|
4543
|
+
}
|
|
4544
|
+
const fields = raw.split("\0");
|
|
4545
|
+
let i = 0;
|
|
4546
|
+
while (i < fields.length) {
|
|
4547
|
+
const status = fields[i];
|
|
4548
|
+
if (!status) {
|
|
4549
|
+
i++;
|
|
4550
|
+
continue;
|
|
4551
|
+
}
|
|
4552
|
+
i = processRecord(fields, i, changed, deleted);
|
|
4553
|
+
}
|
|
4554
|
+
return { changed, deleted };
|
|
4555
|
+
}
|
|
4556
|
+
function planningDeleteTargets(opts) {
|
|
4557
|
+
const { raw, logical, localRoot } = opts;
|
|
4558
|
+
assertSafeLogical(logical);
|
|
4559
|
+
assertSafeLocalRoot(localRoot, logical);
|
|
4560
|
+
const { deleted } = parsePlanningDiff(raw);
|
|
4561
|
+
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
4562
|
+
const prefix = logicalPrefix + ".planning/";
|
|
4563
|
+
const planningRoot = join37(localRoot, ".planning");
|
|
4564
|
+
const planningRootBoundary = planningRoot + sep5;
|
|
4565
|
+
const targets = [];
|
|
4566
|
+
for (const repoPath of deleted) {
|
|
4567
|
+
if (!repoPath.startsWith(prefix)) {
|
|
4568
|
+
continue;
|
|
4569
|
+
}
|
|
4570
|
+
const remainder = repoPath.slice(logicalPrefix.length);
|
|
4571
|
+
const candidate = join37(localRoot, remainder);
|
|
4572
|
+
const resolved = normalize2(candidate);
|
|
4573
|
+
if (!resolved.startsWith(planningRootBoundary)) {
|
|
4574
|
+
throw new NomadFatal(
|
|
4575
|
+
`planningDeleteTargets: resolved path ${JSON.stringify(resolved)} escapes localRoot/.planning for logical ${JSON.stringify(logical)} -- refusing delete`
|
|
4576
|
+
);
|
|
4577
|
+
}
|
|
4578
|
+
targets.push(resolved);
|
|
4579
|
+
}
|
|
4580
|
+
return targets;
|
|
4581
|
+
}
|
|
4582
|
+
|
|
4583
|
+
// src/extras-sync.remap.ts
|
|
4364
4584
|
init_utils_fs();
|
|
4585
|
+
init_utils();
|
|
4365
4586
|
function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
4366
4587
|
const counts = { unmapped: 0, skipped: 0 };
|
|
4367
4588
|
const done = [];
|
|
@@ -4380,56 +4601,139 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
4380
4601
|
}
|
|
4381
4602
|
return { ...counts, done, would };
|
|
4382
4603
|
}
|
|
4604
|
+
function pruneEmptyAncestors(target, planningRoot) {
|
|
4605
|
+
let dir = dirname7(target);
|
|
4606
|
+
while (dir !== planningRoot && dir.startsWith(planningRoot + sep6)) {
|
|
4607
|
+
try {
|
|
4608
|
+
if (readdirSync12(dir).length > 0) break;
|
|
4609
|
+
rmSync11(dir, { recursive: true, force: true });
|
|
4610
|
+
} catch {
|
|
4611
|
+
break;
|
|
4612
|
+
}
|
|
4613
|
+
dir = dirname7(dir);
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
function tryRealpath(dir) {
|
|
4617
|
+
try {
|
|
4618
|
+
return realpathSync4(dir);
|
|
4619
|
+
} catch {
|
|
4620
|
+
return void 0;
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
function isInsidePlanningRoot(parentReal, rootReal) {
|
|
4624
|
+
return parentReal === rootReal || parentReal.startsWith(rootReal + sep6);
|
|
4625
|
+
}
|
|
4626
|
+
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
4627
|
+
if (existsSync31(repoCounterpart)) return;
|
|
4628
|
+
const parentReal = tryRealpath(dirname7(target));
|
|
4629
|
+
if (parentReal === void 0) return;
|
|
4630
|
+
const rootReal = tryRealpath(planningRoot);
|
|
4631
|
+
if (rootReal === void 0) return;
|
|
4632
|
+
if (!isInsidePlanningRoot(parentReal, rootReal)) return;
|
|
4633
|
+
rmSync11(target, { recursive: true, force: true });
|
|
4634
|
+
pruneEmptyAncestors(target, planningRoot);
|
|
4635
|
+
}
|
|
4636
|
+
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
4637
|
+
const repoExtras = join38(repo, "shared", "extras");
|
|
4638
|
+
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
4639
|
+
if (t.dirname !== ".planning") continue;
|
|
4640
|
+
let raw;
|
|
4641
|
+
try {
|
|
4642
|
+
raw = gitCaptureRaw(
|
|
4643
|
+
[
|
|
4644
|
+
"diff",
|
|
4645
|
+
"--name-status",
|
|
4646
|
+
"-z",
|
|
4647
|
+
prePostHeads.pre,
|
|
4648
|
+
prePostHeads.post,
|
|
4649
|
+
"--",
|
|
4650
|
+
`shared/extras/${t.logical}/.planning/`
|
|
4651
|
+
],
|
|
4652
|
+
repo
|
|
4653
|
+
);
|
|
4654
|
+
} catch (err) {
|
|
4655
|
+
const e = err;
|
|
4656
|
+
if (e.stderr) process.stderr.write(e.stderr);
|
|
4657
|
+
throw new NomadFatal(
|
|
4658
|
+
`git diff failed while propagating .planning deletes for ${t.logical}; run nomad pull --force-remote to recover`
|
|
4659
|
+
);
|
|
4660
|
+
}
|
|
4661
|
+
const targets = planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot });
|
|
4662
|
+
if (targets.length === 0) continue;
|
|
4663
|
+
backupExtrasWrite(join38(t.localRoot, t.dirname), ts, t.localRoot);
|
|
4664
|
+
const planningRoot = join38(t.localRoot, ".planning");
|
|
4665
|
+
for (const target of targets) {
|
|
4666
|
+
const relToLocal = target.slice(t.localRoot.length + sep6.length);
|
|
4667
|
+
deletePlanningTarget(target, planningRoot, join38(repoExtras, t.logical, relToLocal));
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4383
4671
|
function remapExtrasPush(ts, opts = {}) {
|
|
4384
4672
|
const dryRun = opts.dryRun === true;
|
|
4385
4673
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
4386
4674
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
4387
4675
|
const repo = repoHome();
|
|
4388
|
-
const repoExtras =
|
|
4676
|
+
const repoExtras = join38(repo, "shared", "extras");
|
|
4389
4677
|
if (!dryRun) mkdirSync7(repoExtras, { recursive: true });
|
|
4390
4678
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
4391
4679
|
v,
|
|
4392
4680
|
dryRun,
|
|
4393
|
-
({ localRoot, logical, dirname:
|
|
4394
|
-
src:
|
|
4395
|
-
dst:
|
|
4681
|
+
({ localRoot, logical, dirname: dirname8 }) => ({
|
|
4682
|
+
src: join38(localRoot, dirname8),
|
|
4683
|
+
dst: join38(repoExtras, logical, dirname8)
|
|
4396
4684
|
}),
|
|
4397
4685
|
(dst) => backupRepoWrite(dst, ts, repo),
|
|
4398
|
-
// Push
|
|
4399
|
-
//
|
|
4400
|
-
|
|
4686
|
+
// Push copy routing per extra type:
|
|
4687
|
+
// `.planning`: copyExtrasOverlayFiltered (no rmSync; deny-set filtered).
|
|
4688
|
+
// Repo-only files survive; local edits propagate (overlay overwrites).
|
|
4689
|
+
// The filter prevents ALWAYS_NEVER_SYNC files from landing in the repo
|
|
4690
|
+
// working tree before the allow-list gate fires, eliminating the
|
|
4691
|
+
// "residue wedges repeat push" regression (WR-02). The allow-list gate
|
|
4692
|
+
// (enforceAllowList / blockSetFor in commands.push.allowlist.ts)
|
|
4693
|
+
// remains the hard security boundary.
|
|
4694
|
+
// All others: copyExtrasFiltered with per-extra denylist.
|
|
4695
|
+
(src, dst, dirname8) => dirname8 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname8)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname8))
|
|
4401
4696
|
);
|
|
4402
4697
|
return { unmapped, skipped, pushed: done, wouldPush: would };
|
|
4403
4698
|
}
|
|
4404
4699
|
function remapExtrasPull(ts, opts = {}) {
|
|
4405
4700
|
const dryRun = opts.dryRun === true;
|
|
4701
|
+
const { prePostHeads } = opts;
|
|
4406
4702
|
const v = loadValidatedExtras({
|
|
4407
4703
|
requireRepoExtras: true,
|
|
4408
4704
|
missingMsg: "no path-map or repo extras dir; skipping extras remap"
|
|
4409
4705
|
});
|
|
4410
4706
|
if (v === null) return { unmapped: 0, skipped: 0, pulled: [], wouldPull: [] };
|
|
4411
|
-
const
|
|
4707
|
+
const repo = repoHome();
|
|
4412
4708
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
4413
4709
|
v,
|
|
4414
4710
|
dryRun,
|
|
4415
|
-
({ localRoot, logical, dirname:
|
|
4416
|
-
src:
|
|
4417
|
-
dst:
|
|
4711
|
+
({ localRoot, logical, dirname: dirname8 }) => ({
|
|
4712
|
+
src: join38(repo, "shared", "extras", logical, dirname8),
|
|
4713
|
+
dst: join38(localRoot, dirname8)
|
|
4418
4714
|
}),
|
|
4419
4715
|
// Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
|
|
4420
4716
|
// localRoot so the backup tree mirrors the project layout.
|
|
4421
4717
|
(dst, localRoot) => backupExtrasWrite(dst, ts, localRoot),
|
|
4422
|
-
// Pull
|
|
4423
|
-
//
|
|
4424
|
-
//
|
|
4425
|
-
//
|
|
4426
|
-
//
|
|
4427
|
-
//
|
|
4428
|
-
//
|
|
4429
|
-
//
|
|
4430
|
-
|
|
4431
|
-
|
|
4718
|
+
// Pull routing per extra type:
|
|
4719
|
+
// `.claude`: copyExtrasFilteredPreserving preserves host-local deny-set
|
|
4720
|
+
// files (e.g. settings.local.json) while mirror-pruning synced entries.
|
|
4721
|
+
// `.planning`: copyExtrasOverlayFiltered (no rmSync; deny-set filtered)
|
|
4722
|
+
// keeps local-only files; the delete pass below propagates upstream
|
|
4723
|
+
// removals via the git-diff D set. The filter is defense-in-depth
|
|
4724
|
+
// against a repo poisoned out-of-band.
|
|
4725
|
+
// All others: copyExtras (exact mirror; rarely carry host-local files).
|
|
4726
|
+
(src, dst, dirname8) => {
|
|
4727
|
+
if (dirname8 === ".claude")
|
|
4728
|
+
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname8));
|
|
4729
|
+
if (dirname8 === ".planning")
|
|
4730
|
+
return copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname8));
|
|
4731
|
+
return copyExtras(src, dst);
|
|
4732
|
+
}
|
|
4432
4733
|
);
|
|
4734
|
+
if (!dryRun && prePostHeads !== void 0) {
|
|
4735
|
+
propagatePlanningDeletes(v, ts, prePostHeads, repo);
|
|
4736
|
+
}
|
|
4433
4737
|
return { unmapped, skipped, pulled: done, wouldPull: would };
|
|
4434
4738
|
}
|
|
4435
4739
|
|
|
@@ -4438,17 +4742,17 @@ function divergenceCheckExtras(ts) {
|
|
|
4438
4742
|
const v = loadValidatedExtras({});
|
|
4439
4743
|
if (v === null) return;
|
|
4440
4744
|
const counts = { unmapped: 0, skipped: 0 };
|
|
4441
|
-
const backupRoot =
|
|
4745
|
+
const backupRoot = join39(backupBase(), ts, "extras");
|
|
4442
4746
|
const repo = repoHome();
|
|
4443
|
-
for (const { logical, localRoot, dirname:
|
|
4444
|
-
const local =
|
|
4445
|
-
const repoEntry =
|
|
4747
|
+
for (const { logical, localRoot, dirname: dirname8 } of eachExtrasTarget(v, counts)) {
|
|
4748
|
+
const local = join39(localRoot, dirname8);
|
|
4749
|
+
const repoEntry = join39(repo, "shared", "extras", logical, dirname8);
|
|
4446
4750
|
if (!existsSync32(local) || !existsSync32(repoEntry)) continue;
|
|
4447
4751
|
const diff = listDivergingFiles(local, repoEntry);
|
|
4448
4752
|
if (diff.length === 0) continue;
|
|
4449
|
-
const projectBackupRoot =
|
|
4753
|
+
const projectBackupRoot = join39(backupRoot, encodePath(localRoot));
|
|
4450
4754
|
warn(
|
|
4451
|
-
`local ${
|
|
4755
|
+
`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}/)`
|
|
4452
4756
|
);
|
|
4453
4757
|
for (const f of diff) warn(` ${f}`);
|
|
4454
4758
|
}
|
|
@@ -4459,8 +4763,8 @@ init_config();
|
|
|
4459
4763
|
init_utils();
|
|
4460
4764
|
init_utils_fs();
|
|
4461
4765
|
init_utils_json();
|
|
4462
|
-
import { existsSync as existsSync33, lstatSync as lstatSync9, rmSync as
|
|
4463
|
-
import { join as
|
|
4766
|
+
import { existsSync as existsSync33, lstatSync as lstatSync9, rmSync as rmSync12 } from "node:fs";
|
|
4767
|
+
import { join as join40 } from "node:path";
|
|
4464
4768
|
function emitAutoMove(onPreview, linkPath, ts, name) {
|
|
4465
4769
|
if (onPreview) {
|
|
4466
4770
|
onPreview({ kind: "auto-move", from: linkPath, to: `backup/${ts}/${name}` });
|
|
@@ -4480,8 +4784,8 @@ function isAlreadySymlink(linkPath) {
|
|
|
4480
4784
|
}
|
|
4481
4785
|
function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
|
|
4482
4786
|
for (const name of linkNames) {
|
|
4483
|
-
const linkPath =
|
|
4484
|
-
const target =
|
|
4787
|
+
const linkPath = join40(claude, name);
|
|
4788
|
+
const target = join40(repo, "shared", name);
|
|
4485
4789
|
if (!existsSync33(linkPath)) continue;
|
|
4486
4790
|
if (lstatSync9(linkPath).isSymbolicLink()) continue;
|
|
4487
4791
|
if (!existsSync33(target)) continue;
|
|
@@ -4490,7 +4794,7 @@ function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
|
|
|
4490
4794
|
continue;
|
|
4491
4795
|
}
|
|
4492
4796
|
backupBeforeWrite(linkPath, ts);
|
|
4493
|
-
|
|
4797
|
+
rmSync12(linkPath, { recursive: true, force: true });
|
|
4494
4798
|
}
|
|
4495
4799
|
}
|
|
4496
4800
|
function applySharedLinks(ts, map, opts = {}) {
|
|
@@ -4500,9 +4804,9 @@ function applySharedLinks(ts, map, opts = {}) {
|
|
|
4500
4804
|
const linkNames = allSharedLinks(map);
|
|
4501
4805
|
runAutoMovePasses(linkNames, claude, repo, ts, dryRun, opts.onPreview);
|
|
4502
4806
|
for (const name of linkNames) {
|
|
4503
|
-
const target =
|
|
4807
|
+
const target = join40(repo, "shared", name);
|
|
4504
4808
|
if (!existsSync33(target)) continue;
|
|
4505
|
-
const linkPath =
|
|
4809
|
+
const linkPath = join40(claude, name);
|
|
4506
4810
|
if (isAlreadySymlink(linkPath)) continue;
|
|
4507
4811
|
if (dryRun) {
|
|
4508
4812
|
emitCreate(opts.onPreview, linkPath, target);
|
|
@@ -4515,8 +4819,8 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
4515
4819
|
const dryRun = opts.dryRun === true;
|
|
4516
4820
|
const repo = repoHome();
|
|
4517
4821
|
const claude = claudeHome();
|
|
4518
|
-
const basePath =
|
|
4519
|
-
const hostPath =
|
|
4822
|
+
const basePath = join40(repo, "shared", "settings.base.json");
|
|
4823
|
+
const hostPath = join40(repo, "hosts", `${HOST}.json`);
|
|
4520
4824
|
if (!existsSync33(basePath)) {
|
|
4521
4825
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
4522
4826
|
}
|
|
@@ -4524,7 +4828,7 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
4524
4828
|
const hasOverrides = existsSync33(hostPath);
|
|
4525
4829
|
const overrides = hasOverrides ? readJson(hostPath) : {};
|
|
4526
4830
|
const merged = deepMerge(base, overrides);
|
|
4527
|
-
const settingsPath =
|
|
4831
|
+
const settingsPath = join40(claude, "settings.json");
|
|
4528
4832
|
if (!hasOverrides && existsSync33(settingsPath)) {
|
|
4529
4833
|
try {
|
|
4530
4834
|
const existing = readJson(settingsPath);
|
|
@@ -4549,10 +4853,53 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
4549
4853
|
return { label: overrideLabel };
|
|
4550
4854
|
}
|
|
4551
4855
|
|
|
4856
|
+
// src/skills-sync.ts
|
|
4857
|
+
init_config();
|
|
4858
|
+
import { existsSync as existsSync34, lstatSync as lstatSync10, mkdirSync as mkdirSync8, readdirSync as readdirSync13, rmSync as rmSync13 } from "node:fs";
|
|
4859
|
+
import { join as join41 } from "node:path";
|
|
4860
|
+
init_utils_fs();
|
|
4861
|
+
function isGsdOwned(name) {
|
|
4862
|
+
return name.startsWith(GSD_PREFIX);
|
|
4863
|
+
}
|
|
4864
|
+
function isSkillExcluded(name) {
|
|
4865
|
+
return isGsdOwned(name) || ALWAYS_NEVER_SYNC.has(name);
|
|
4866
|
+
}
|
|
4867
|
+
function copySkillsPush(src, dst) {
|
|
4868
|
+
const srcNames = readdirSync13(src, { encoding: "utf8" });
|
|
4869
|
+
const blockSet = /* @__PURE__ */ new Set([
|
|
4870
|
+
...srcNames.filter((n) => isGsdOwned(n)),
|
|
4871
|
+
...ALWAYS_NEVER_SYNC
|
|
4872
|
+
]);
|
|
4873
|
+
copyExtrasFiltered(src, dst, blockSet);
|
|
4874
|
+
}
|
|
4875
|
+
function copySkillsPull(src, dst) {
|
|
4876
|
+
copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
|
|
4877
|
+
}
|
|
4878
|
+
function syncSkillsPull(ts) {
|
|
4879
|
+
const sharedSkills = join41(repoHome(), "shared", "skills");
|
|
4880
|
+
if (!existsSync34(sharedSkills)) return;
|
|
4881
|
+
const localSkills = join41(claudeHome(), "skills");
|
|
4882
|
+
const dstStat = lstatSync10(localSkills, { throwIfNoEntry: false });
|
|
4883
|
+
if (dstStat?.isSymbolicLink() === true) {
|
|
4884
|
+
backupBeforeWrite(localSkills, ts);
|
|
4885
|
+
rmSync13(localSkills, { recursive: true, force: true });
|
|
4886
|
+
mkdirSync8(localSkills, { recursive: true });
|
|
4887
|
+
}
|
|
4888
|
+
copySkillsPull(sharedSkills, localSkills);
|
|
4889
|
+
}
|
|
4890
|
+
function syncSkillsPush() {
|
|
4891
|
+
const localSkills = join41(claudeHome(), "skills");
|
|
4892
|
+
const stat = lstatSync10(localSkills, { throwIfNoEntry: false });
|
|
4893
|
+
if (stat === void 0) return;
|
|
4894
|
+
if (stat.isSymbolicLink()) return;
|
|
4895
|
+
const sharedSkills = join41(repoHome(), "shared", "skills");
|
|
4896
|
+
copySkillsPush(localSkills, sharedSkills);
|
|
4897
|
+
}
|
|
4898
|
+
|
|
4552
4899
|
// src/preview.ts
|
|
4553
4900
|
init_config();
|
|
4554
|
-
import { existsSync as
|
|
4555
|
-
import { join as
|
|
4901
|
+
import { existsSync as existsSync35 } from "node:fs";
|
|
4902
|
+
import { join as join42 } from "node:path";
|
|
4556
4903
|
|
|
4557
4904
|
// node_modules/diff/libesm/diff/base.js
|
|
4558
4905
|
var Diff = class {
|
|
@@ -4838,7 +5185,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
4838
5185
|
return lines.join("\n");
|
|
4839
5186
|
}
|
|
4840
5187
|
function readJsonOrNull(path) {
|
|
4841
|
-
if (!
|
|
5188
|
+
if (!existsSync35(path)) return null;
|
|
4842
5189
|
try {
|
|
4843
5190
|
return readJson(path);
|
|
4844
5191
|
} catch {
|
|
@@ -4852,12 +5199,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
4852
5199
|
}
|
|
4853
5200
|
const notes = [];
|
|
4854
5201
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
4855
|
-
if (hostOverrides === null &&
|
|
5202
|
+
if (hostOverrides === null && existsSync35(hostPath)) {
|
|
4856
5203
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
4857
5204
|
}
|
|
4858
5205
|
const merged = deepMerge(base, hostOverrides ?? {});
|
|
4859
5206
|
const current = readJsonOrNull(settingsPath);
|
|
4860
|
-
if (current === null &&
|
|
5207
|
+
if (current === null && existsSync35(settingsPath)) {
|
|
4861
5208
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
4862
5209
|
}
|
|
4863
5210
|
const rawEqual = JSON.stringify(current ?? {}, null, 2) === JSON.stringify(merged, null, 2);
|
|
@@ -4897,9 +5244,9 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
4897
5244
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
4898
5245
|
});
|
|
4899
5246
|
const settingsResult = previewSettings(
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
5247
|
+
join42(repo, "shared", "settings.base.json"),
|
|
5248
|
+
join42(repo, "hosts", `${HOST}.json`),
|
|
5249
|
+
join42(claude, "settings.json")
|
|
4903
5250
|
);
|
|
4904
5251
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
4905
5252
|
const sessions = section("Sessions");
|
|
@@ -4913,13 +5260,21 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
4913
5260
|
return { unmapped: remapResult.unmapped, collisions: 0 };
|
|
4914
5261
|
}
|
|
4915
5262
|
|
|
5263
|
+
// src/commands.pull.ts
|
|
5264
|
+
init_commands_pull_wedge();
|
|
5265
|
+
|
|
4916
5266
|
// src/commands.pull.recovery.ts
|
|
4917
5267
|
init_config();
|
|
4918
|
-
|
|
5268
|
+
init_commands_pull_wedge();
|
|
4919
5269
|
init_utils();
|
|
4920
5270
|
init_utils_fs();
|
|
5271
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
4921
5272
|
function gitCapture(args, cwd) {
|
|
4922
|
-
return
|
|
5273
|
+
return execFileSync15("git", args, {
|
|
5274
|
+
cwd,
|
|
5275
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5276
|
+
maxBuffer: 64 * 1024 * 1024
|
|
5277
|
+
}).toString().trim();
|
|
4923
5278
|
}
|
|
4924
5279
|
function isSyncedConfig(path) {
|
|
4925
5280
|
return PUSH_ALLOWED_STATIC.some(
|
|
@@ -4990,6 +5345,20 @@ function freshStrandedBranch(repo) {
|
|
|
4990
5345
|
while (exists(`${base}-${n}`)) n++;
|
|
4991
5346
|
return `${base}-${n}`;
|
|
4992
5347
|
}
|
|
5348
|
+
function recoverUnmergedIndex(repo) {
|
|
5349
|
+
gitOrFatal(["reset", "--mixed", "HEAD"], "git reset --mixed HEAD", repo);
|
|
5350
|
+
const dirty = gitCapture(["diff", "--name-only", "-z"], repo).split("\0").filter(Boolean);
|
|
5351
|
+
if (dirty.length > 0) {
|
|
5352
|
+
log(
|
|
5353
|
+
"index cleared, but these files still carry conflict content from the torn-down rebase; review and resolve before the next pull:\n" + dirty.map((p) => ` ${p}`).join("\n")
|
|
5354
|
+
);
|
|
5355
|
+
}
|
|
5356
|
+
if (orphanedAutostashPresent(repo)) {
|
|
5357
|
+
log(
|
|
5358
|
+
'orphaned autostash preserved in the stash list; run "git stash pop" to restore or "git stash drop" to discard it, then re-run "nomad pull"'
|
|
5359
|
+
);
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
4993
5362
|
function recoverForceRemote(mode, repo) {
|
|
4994
5363
|
if (mode === "merge") {
|
|
4995
5364
|
gitOrFatal(["merge", "--abort"], "git merge --abort", repo);
|
|
@@ -5023,11 +5392,26 @@ function recoverForceRemote(mode, repo) {
|
|
|
5023
5392
|
init_utils();
|
|
5024
5393
|
init_utils_fs();
|
|
5025
5394
|
init_utils_json();
|
|
5026
|
-
function
|
|
5395
|
+
function captureHead(repo) {
|
|
5396
|
+
try {
|
|
5397
|
+
return gitCaptureRaw(["rev-parse", "HEAD"], repo).trim();
|
|
5398
|
+
} catch {
|
|
5399
|
+
return void 0;
|
|
5400
|
+
}
|
|
5401
|
+
}
|
|
5402
|
+
function capturePrePostHeads(repo, rebase) {
|
|
5403
|
+
const pre = captureHead(repo);
|
|
5404
|
+
rebase();
|
|
5405
|
+
const post = captureHead(repo);
|
|
5406
|
+
if (pre === void 0 || post === void 0) return void 0;
|
|
5407
|
+
return { pre, post };
|
|
5408
|
+
}
|
|
5409
|
+
function applyWetPull(ts, map, prePostHeads) {
|
|
5027
5410
|
applySharedLinks(ts, map);
|
|
5028
5411
|
const { label } = regenerateSettings(ts);
|
|
5412
|
+
syncSkillsPull(ts);
|
|
5029
5413
|
const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
|
|
5030
|
-
const extrasResult = remapExtrasPull(ts);
|
|
5414
|
+
const extrasResult = remapExtrasPull(ts, { prePostHeads });
|
|
5031
5415
|
const summary = section("Summary");
|
|
5032
5416
|
addItem(
|
|
5033
5417
|
summary,
|
|
@@ -5041,24 +5425,30 @@ function applyWetPull(ts, map) {
|
|
|
5041
5425
|
]);
|
|
5042
5426
|
}
|
|
5043
5427
|
function handleWedge(repo, forceRemote) {
|
|
5044
|
-
const wedge =
|
|
5428
|
+
const wedge = classifyWedge(repo);
|
|
5045
5429
|
if (wedge === null) return;
|
|
5430
|
+
if (wedge === "unmerged-index") {
|
|
5431
|
+
if (forceRemote) {
|
|
5432
|
+
recoverUnmergedIndex(repo);
|
|
5433
|
+
} else {
|
|
5434
|
+
die(unmergedIndexRunbookText("nomad pull"));
|
|
5435
|
+
}
|
|
5436
|
+
return;
|
|
5437
|
+
}
|
|
5046
5438
|
if (forceRemote) {
|
|
5047
5439
|
recoverForceRemote(wedge, repo);
|
|
5048
5440
|
return;
|
|
5049
5441
|
}
|
|
5050
5442
|
const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
|
|
5051
|
-
die(
|
|
5052
|
-
`repo is ${state} from a previous failed pull; run 'nomad pull --force-remote' to auto-recover, or resolve manually (see FAQ: "Every pull fails with unmerged files")`
|
|
5053
|
-
);
|
|
5443
|
+
die(wedgeMarkerRunbookText(state));
|
|
5054
5444
|
}
|
|
5055
5445
|
function cmdPull(opts = {}) {
|
|
5056
5446
|
const dryRun = opts.dryRun === true;
|
|
5057
5447
|
const forceRemote = opts.forceRemote === true;
|
|
5058
5448
|
const repo = repoHome();
|
|
5059
5449
|
const backup = backupBase();
|
|
5060
|
-
if (!
|
|
5061
|
-
if (!
|
|
5450
|
+
if (!existsSync36(repo)) die(`repo not cloned at ${repo}`);
|
|
5451
|
+
if (!existsSync36(join43(repo, "shared", "settings.base.json"))) {
|
|
5062
5452
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
5063
5453
|
}
|
|
5064
5454
|
const handle = acquireLock("pull");
|
|
@@ -5067,9 +5457,9 @@ function cmdPull(opts = {}) {
|
|
|
5067
5457
|
const ts = freshBackupTs(backup);
|
|
5068
5458
|
handleWedge(repo, forceRemote);
|
|
5069
5459
|
if (!dryRun) {
|
|
5070
|
-
const backupRoot =
|
|
5460
|
+
const backupRoot = join43(backup, ts);
|
|
5071
5461
|
try {
|
|
5072
|
-
|
|
5462
|
+
mkdirSync9(backupRoot, { recursive: true });
|
|
5073
5463
|
} catch (err) {
|
|
5074
5464
|
die(`could not create backup dir: ${err.message}`);
|
|
5075
5465
|
}
|
|
@@ -5077,15 +5467,17 @@ function cmdPull(opts = {}) {
|
|
|
5077
5467
|
log(
|
|
5078
5468
|
dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
|
|
5079
5469
|
);
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5470
|
+
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
5471
|
+
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
5472
|
+
});
|
|
5473
|
+
const mapPath = join43(repo, "path-map.json");
|
|
5474
|
+
const map = existsSync36(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
5083
5475
|
divergenceCheckExtras(ts);
|
|
5084
5476
|
if (dryRun) {
|
|
5085
5477
|
computePreview(ts, map, "pull");
|
|
5086
5478
|
log("dry-run complete; no mutation");
|
|
5087
5479
|
} else {
|
|
5088
|
-
applyWetPull(ts, map);
|
|
5480
|
+
applyWetPull(ts, map, prePostHeads);
|
|
5089
5481
|
}
|
|
5090
5482
|
} catch (err) {
|
|
5091
5483
|
if (err instanceof NomadFatal) {
|
|
@@ -5101,8 +5493,8 @@ function cmdPull(opts = {}) {
|
|
|
5101
5493
|
|
|
5102
5494
|
// src/commands.push.ts
|
|
5103
5495
|
init_config();
|
|
5104
|
-
import { existsSync as
|
|
5105
|
-
import { join as
|
|
5496
|
+
import { existsSync as existsSync38 } from "node:fs";
|
|
5497
|
+
import { join as join45, relative as relative5 } from "node:path";
|
|
5106
5498
|
|
|
5107
5499
|
// src/commands.push.allowlist.ts
|
|
5108
5500
|
init_config();
|
|
@@ -5181,7 +5573,7 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
5181
5573
|
|
|
5182
5574
|
// src/push-global-config.ts
|
|
5183
5575
|
init_config();
|
|
5184
|
-
import { execFileSync as
|
|
5576
|
+
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
5185
5577
|
var STATUS_LABELS = {
|
|
5186
5578
|
A: "add",
|
|
5187
5579
|
M: "modify",
|
|
@@ -5208,6 +5600,7 @@ function buildPrefixSets(hostname2) {
|
|
|
5208
5600
|
}
|
|
5209
5601
|
}
|
|
5210
5602
|
exactPrefixes.add("shared/settings.base.json");
|
|
5603
|
+
dirPrefixes.push("shared/skills");
|
|
5211
5604
|
exactPrefixes.add(`hosts/${hostname2}.json`);
|
|
5212
5605
|
return { exactPrefixes, dirPrefixes };
|
|
5213
5606
|
}
|
|
@@ -5220,7 +5613,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
|
|
|
5220
5613
|
}
|
|
5221
5614
|
function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
|
|
5222
5615
|
const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
|
|
5223
|
-
const raw =
|
|
5616
|
+
const raw = execFileSync16("git", args, {
|
|
5224
5617
|
cwd: repoHome2,
|
|
5225
5618
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5226
5619
|
}).toString();
|
|
@@ -5259,9 +5652,9 @@ init_color();
|
|
|
5259
5652
|
init_config();
|
|
5260
5653
|
init_config_sharedDirs_guard();
|
|
5261
5654
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
5262
|
-
import { copyFileSync, existsSync as
|
|
5655
|
+
import { copyFileSync, existsSync as existsSync37, mkdirSync as mkdirSync10, readdirSync as readdirSync14, rmSync as rmSync14 } from "node:fs";
|
|
5263
5656
|
import { homedir as homedir5 } from "node:os";
|
|
5264
|
-
import { join as
|
|
5657
|
+
import { join as join44 } from "node:path";
|
|
5265
5658
|
init_push_leak_verdict();
|
|
5266
5659
|
init_push_gitleaks();
|
|
5267
5660
|
init_utils_fs();
|
|
@@ -5276,13 +5669,13 @@ function stageSessions(tmpRoot, map) {
|
|
|
5276
5669
|
if (!p || p === "TBD") continue;
|
|
5277
5670
|
reverse.set(encodePath(p), logical);
|
|
5278
5671
|
}
|
|
5279
|
-
const localProjects =
|
|
5280
|
-
if (!
|
|
5672
|
+
const localProjects = join44(claudeHome(), "projects");
|
|
5673
|
+
if (!existsSync37(localProjects)) return 0;
|
|
5281
5674
|
let staged = 0;
|
|
5282
|
-
for (const dir of
|
|
5675
|
+
for (const dir of readdirSync14(localProjects)) {
|
|
5283
5676
|
const logical = reverse.get(dir);
|
|
5284
5677
|
if (!logical) continue;
|
|
5285
|
-
copyDirJsonlOnly(
|
|
5678
|
+
copyDirJsonlOnly(join44(localProjects, dir), join44(tmpRoot, "shared", "projects", logical));
|
|
5286
5679
|
staged++;
|
|
5287
5680
|
}
|
|
5288
5681
|
return staged;
|
|
@@ -5296,11 +5689,11 @@ function stageExtras(tmpRoot, map) {
|
|
|
5296
5689
|
assertSafeLogical(logical);
|
|
5297
5690
|
const localRoot = map.projects[logical]?.[HOST];
|
|
5298
5691
|
if (!localRoot || localRoot === "TBD") continue;
|
|
5299
|
-
for (const
|
|
5300
|
-
if (!whitelist.includes(
|
|
5301
|
-
const src =
|
|
5302
|
-
if (!
|
|
5303
|
-
const dst =
|
|
5692
|
+
for (const dirname8 of dirnames) {
|
|
5693
|
+
if (!whitelist.includes(dirname8)) continue;
|
|
5694
|
+
const src = join44(localRoot, dirname8);
|
|
5695
|
+
if (!existsSync37(src)) continue;
|
|
5696
|
+
const dst = join44(tmpRoot, "shared", "extras", logical, dirname8);
|
|
5304
5697
|
copyExtras(src, dst);
|
|
5305
5698
|
staged++;
|
|
5306
5699
|
}
|
|
@@ -5308,19 +5701,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
5308
5701
|
return staged;
|
|
5309
5702
|
}
|
|
5310
5703
|
function previewPushLeaks(map) {
|
|
5311
|
-
const cacheDir =
|
|
5312
|
-
|
|
5704
|
+
const cacheDir = join44(homedir5(), ".cache", "claude-nomad");
|
|
5705
|
+
mkdirSync10(cacheDir, { recursive: true });
|
|
5313
5706
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
5314
|
-
const tmpRoot =
|
|
5707
|
+
const tmpRoot = join44(cacheDir, `push-preview-tree-${stamp}`);
|
|
5315
5708
|
try {
|
|
5316
5709
|
const sessionCount = stageSessions(tmpRoot, map);
|
|
5317
5710
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
5318
5711
|
if (sessionCount + extrasCount === 0) {
|
|
5319
5712
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
5320
5713
|
}
|
|
5321
|
-
const ignoreFile =
|
|
5322
|
-
if (
|
|
5323
|
-
copyFileSync(ignoreFile,
|
|
5714
|
+
const ignoreFile = join44(repoHome(), ".gitleaksignore");
|
|
5715
|
+
if (existsSync37(ignoreFile)) {
|
|
5716
|
+
copyFileSync(ignoreFile, join44(tmpRoot, ".gitleaksignore"));
|
|
5324
5717
|
}
|
|
5325
5718
|
let findings;
|
|
5326
5719
|
try {
|
|
@@ -5333,7 +5726,7 @@ function previewPushLeaks(map) {
|
|
|
5333
5726
|
}
|
|
5334
5727
|
return verdictFromFindings(findings);
|
|
5335
5728
|
} finally {
|
|
5336
|
-
|
|
5729
|
+
rmSync14(tmpRoot, { recursive: true, force: true });
|
|
5337
5730
|
}
|
|
5338
5731
|
}
|
|
5339
5732
|
|
|
@@ -5342,7 +5735,7 @@ init_utils();
|
|
|
5342
5735
|
init_utils_fs();
|
|
5343
5736
|
init_utils_json();
|
|
5344
5737
|
function guardGitlinks(repo) {
|
|
5345
|
-
const gitlinks = findGitlinks(
|
|
5738
|
+
const gitlinks = findGitlinks(join45(repo, "shared"));
|
|
5346
5739
|
if (gitlinks.length === 0) return;
|
|
5347
5740
|
for (const p of gitlinks) {
|
|
5348
5741
|
const rel = relative5(repo, p);
|
|
@@ -5398,7 +5791,7 @@ async function cmdPush(opts = {}) {
|
|
|
5398
5791
|
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
5399
5792
|
const repo = repoHome();
|
|
5400
5793
|
const backup = backupBase();
|
|
5401
|
-
if (!
|
|
5794
|
+
if (!existsSync38(repo)) die(`repo not cloned at ${repo}`);
|
|
5402
5795
|
const handle = acquireLock("push");
|
|
5403
5796
|
if (handle === null) process.exit(0);
|
|
5404
5797
|
try {
|
|
@@ -5408,6 +5801,7 @@ async function cmdPush(opts = {}) {
|
|
|
5408
5801
|
const ts = freshBackupTs(backup);
|
|
5409
5802
|
const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun }));
|
|
5410
5803
|
const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
|
|
5804
|
+
if (!dryRun) syncSkillsPush();
|
|
5411
5805
|
const st = { dryRun, remap, extras, globalConfig: [] };
|
|
5412
5806
|
guardGitlinks(repo);
|
|
5413
5807
|
const status = gitStatusPorcelainZ(repo, { untrackedAll: true });
|
|
@@ -5416,8 +5810,8 @@ async function cmdPush(opts = {}) {
|
|
|
5416
5810
|
renderNoScanTree(st);
|
|
5417
5811
|
return;
|
|
5418
5812
|
}
|
|
5419
|
-
const mapPath =
|
|
5420
|
-
if (!
|
|
5813
|
+
const mapPath = join45(repo, "path-map.json");
|
|
5814
|
+
if (!existsSync38(mapPath)) {
|
|
5421
5815
|
if (dryRun) return runDryRunPreview(st, null, repo);
|
|
5422
5816
|
die("path-map.json missing, cannot enforce push allow-list");
|
|
5423
5817
|
}
|
|
@@ -5438,16 +5832,16 @@ async function cmdPush(opts = {}) {
|
|
|
5438
5832
|
}
|
|
5439
5833
|
|
|
5440
5834
|
// src/commands.update.ts
|
|
5441
|
-
import { execFileSync as
|
|
5835
|
+
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
5442
5836
|
init_utils();
|
|
5443
|
-
function readInstalledVersion(run =
|
|
5837
|
+
function readInstalledVersion(run = execFileSync17) {
|
|
5444
5838
|
try {
|
|
5445
5839
|
return run("nomad", ["--version"], { encoding: "utf8" }).toString().trim() || null;
|
|
5446
5840
|
} catch {
|
|
5447
5841
|
return null;
|
|
5448
5842
|
}
|
|
5449
5843
|
}
|
|
5450
|
-
function cmdUpdate(run =
|
|
5844
|
+
function cmdUpdate(run = execFileSync17) {
|
|
5451
5845
|
console.log("Updating claude-nomad CLI via npm...");
|
|
5452
5846
|
try {
|
|
5453
5847
|
run("npm", ["update", "-g", "claude-nomad"], { stdio: "inherit" });
|
|
@@ -5471,18 +5865,18 @@ init_config();
|
|
|
5471
5865
|
|
|
5472
5866
|
// src/diff.ts
|
|
5473
5867
|
init_config();
|
|
5474
|
-
import { existsSync as
|
|
5475
|
-
import { join as
|
|
5868
|
+
import { existsSync as existsSync39 } from "node:fs";
|
|
5869
|
+
import { join as join46 } from "node:path";
|
|
5476
5870
|
init_utils();
|
|
5477
5871
|
init_utils_fs();
|
|
5478
5872
|
init_utils_json();
|
|
5479
5873
|
function cmdDiff() {
|
|
5480
5874
|
try {
|
|
5481
5875
|
const repo = repoHome();
|
|
5482
|
-
if (!
|
|
5876
|
+
if (!existsSync39(repo)) die(`repo not cloned at ${repo}`);
|
|
5483
5877
|
const ts = freshBackupTs(backupBase());
|
|
5484
|
-
const mapPath =
|
|
5485
|
-
const map =
|
|
5878
|
+
const mapPath = join46(repo, "path-map.json");
|
|
5879
|
+
const map = existsSync39(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
5486
5880
|
computePreview(ts, map, "diff");
|
|
5487
5881
|
} catch (err) {
|
|
5488
5882
|
if (err instanceof NomadFatal) {
|
|
@@ -5496,19 +5890,19 @@ function cmdDiff() {
|
|
|
5496
5890
|
|
|
5497
5891
|
// src/init.ts
|
|
5498
5892
|
init_config();
|
|
5499
|
-
import { existsSync as
|
|
5500
|
-
import { join as
|
|
5893
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync11, writeFileSync as writeFileSync6 } from "node:fs";
|
|
5894
|
+
import { join as join48 } from "node:path";
|
|
5501
5895
|
|
|
5502
5896
|
// src/init.gh-onboard.ts
|
|
5503
5897
|
init_config();
|
|
5504
|
-
import { execFileSync as
|
|
5898
|
+
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
5505
5899
|
init_utils();
|
|
5506
5900
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
5507
5901
|
function isValidRepoName(name) {
|
|
5508
5902
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
5509
5903
|
}
|
|
5510
5904
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
5511
|
-
function ensureOriginRepo(repoName, run =
|
|
5905
|
+
function ensureOriginRepo(repoName, run = execFileSync18) {
|
|
5512
5906
|
if (!isValidRepoName(repoName)) {
|
|
5513
5907
|
die(
|
|
5514
5908
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -5579,33 +5973,33 @@ init_config();
|
|
|
5579
5973
|
init_utils();
|
|
5580
5974
|
init_utils_fs();
|
|
5581
5975
|
init_utils_json();
|
|
5582
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as
|
|
5583
|
-
import { join as
|
|
5976
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync40, rmSync as rmSync15, statSync as statSync9 } from "node:fs";
|
|
5977
|
+
import { join as join47 } from "node:path";
|
|
5584
5978
|
function snapshotIntoShared(map) {
|
|
5585
5979
|
const repo = repoHome();
|
|
5586
5980
|
const claude = claudeHome();
|
|
5587
5981
|
for (const name of allSharedLinks(map)) {
|
|
5588
|
-
const src =
|
|
5589
|
-
if (!
|
|
5590
|
-
const dst =
|
|
5982
|
+
const src = join47(claude, name);
|
|
5983
|
+
if (!existsSync40(src)) continue;
|
|
5984
|
+
const dst = join47(repo, "shared", name);
|
|
5591
5985
|
if (statSync9(src).isDirectory()) {
|
|
5592
|
-
const gk =
|
|
5593
|
-
if (
|
|
5986
|
+
const gk = join47(dst, ".gitkeep");
|
|
5987
|
+
if (existsSync40(gk)) rmSync15(gk);
|
|
5594
5988
|
cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
5595
5989
|
} else {
|
|
5596
5990
|
copyFileSync2(src, dst);
|
|
5597
5991
|
}
|
|
5598
5992
|
log(`snapshotted shared/${name} from ${src}`);
|
|
5599
5993
|
}
|
|
5600
|
-
const userSettings =
|
|
5601
|
-
if (
|
|
5994
|
+
const userSettings = join47(claude, "settings.json");
|
|
5995
|
+
if (existsSync40(userSettings)) {
|
|
5602
5996
|
let parsed;
|
|
5603
5997
|
try {
|
|
5604
5998
|
parsed = readJson(userSettings);
|
|
5605
5999
|
} catch (err) {
|
|
5606
6000
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
5607
6001
|
}
|
|
5608
|
-
const hostFile =
|
|
6002
|
+
const hostFile = join47(repo, "hosts", `${HOST}.json`);
|
|
5609
6003
|
writeJsonAtomic(hostFile, parsed);
|
|
5610
6004
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
5611
6005
|
}
|
|
@@ -5618,14 +6012,14 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
|
|
|
5618
6012
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
5619
6013
|
function preflightConflict(repoHome2) {
|
|
5620
6014
|
const candidates = [
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
6015
|
+
join48(repoHome2, "shared", "settings.base.json"),
|
|
6016
|
+
join48(repoHome2, "shared", "CLAUDE.md"),
|
|
6017
|
+
join48(repoHome2, "path-map.json"),
|
|
6018
|
+
join48(repoHome2, "hosts"),
|
|
6019
|
+
join48(repoHome2, "shared")
|
|
5626
6020
|
];
|
|
5627
6021
|
for (const c of candidates) {
|
|
5628
|
-
if (
|
|
6022
|
+
if (existsSync41(c)) return c;
|
|
5629
6023
|
}
|
|
5630
6024
|
return null;
|
|
5631
6025
|
}
|
|
@@ -5634,31 +6028,31 @@ function cmdInit(opts = {}) {
|
|
|
5634
6028
|
const keepActions = opts.keepActions === true;
|
|
5635
6029
|
const repo = repoHome();
|
|
5636
6030
|
const claude = claudeHome();
|
|
5637
|
-
|
|
6031
|
+
mkdirSync11(repo, { recursive: true });
|
|
5638
6032
|
const conflict = preflightConflict(repo);
|
|
5639
6033
|
if (conflict !== null) {
|
|
5640
6034
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
5641
6035
|
}
|
|
5642
6036
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
5643
|
-
|
|
5644
|
-
|
|
6037
|
+
mkdirSync11(join48(repo, "shared"), { recursive: true });
|
|
6038
|
+
mkdirSync11(join48(repo, "hosts"), { recursive: true });
|
|
5645
6039
|
for (const name of SHARED_KEEP_DIRS) {
|
|
5646
|
-
|
|
6040
|
+
mkdirSync11(join48(repo, "shared", name), { recursive: true });
|
|
5647
6041
|
}
|
|
5648
|
-
const userClaudeMd =
|
|
5649
|
-
if (!snapshot || !
|
|
5650
|
-
writeFileSync6(
|
|
6042
|
+
const userClaudeMd = join48(claude, "CLAUDE.md");
|
|
6043
|
+
if (!snapshot || !existsSync41(userClaudeMd)) {
|
|
6044
|
+
writeFileSync6(join48(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
5651
6045
|
item("created shared/CLAUDE.md");
|
|
5652
6046
|
}
|
|
5653
6047
|
for (const name of SHARED_KEEP_DIRS) {
|
|
5654
|
-
writeFileSync6(
|
|
6048
|
+
writeFileSync6(join48(repo, "shared", name, ".gitkeep"), "");
|
|
5655
6049
|
item(`created shared/${name}/.gitkeep`);
|
|
5656
6050
|
}
|
|
5657
|
-
writeFileSync6(
|
|
6051
|
+
writeFileSync6(join48(repo, "hosts", ".gitkeep"), "");
|
|
5658
6052
|
item("created hosts/.gitkeep");
|
|
5659
|
-
writeJsonAtomic(
|
|
6053
|
+
writeJsonAtomic(join48(repo, "shared", "settings.base.json"), {});
|
|
5660
6054
|
item("created shared/settings.base.json");
|
|
5661
|
-
writeJsonAtomic(
|
|
6055
|
+
writeJsonAtomic(join48(repo, "path-map.json"), { projects: {} });
|
|
5662
6056
|
item("created path-map.json");
|
|
5663
6057
|
if (snapshot) {
|
|
5664
6058
|
snapshotIntoShared({ projects: {} });
|
|
@@ -5959,7 +6353,7 @@ function parsePushArgs(argv) {
|
|
|
5959
6353
|
// package.json
|
|
5960
6354
|
var package_default = {
|
|
5961
6355
|
name: "claude-nomad",
|
|
5962
|
-
version: "0.
|
|
6356
|
+
version: "0.50.0",
|
|
5963
6357
|
type: "module",
|
|
5964
6358
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
5965
6359
|
keywords: [
|
|
@@ -6160,15 +6554,15 @@ var DEFAULT_HELP = [
|
|
|
6160
6554
|
init_config();
|
|
6161
6555
|
init_utils();
|
|
6162
6556
|
init_utils_json();
|
|
6163
|
-
import { existsSync as
|
|
6164
|
-
import { join as
|
|
6557
|
+
import { existsSync as existsSync42, readFileSync as readFileSync14, readdirSync as readdirSync15 } from "node:fs";
|
|
6558
|
+
import { join as join49 } from "node:path";
|
|
6165
6559
|
function resumeCmd(sessionId) {
|
|
6166
6560
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
6167
6561
|
fail(`invalid session id: ${sessionId}`);
|
|
6168
6562
|
process.exit(1);
|
|
6169
6563
|
}
|
|
6170
|
-
const projectsRoot =
|
|
6171
|
-
if (!
|
|
6564
|
+
const projectsRoot = join49(claudeHome(), "projects");
|
|
6565
|
+
if (!existsSync42(projectsRoot)) {
|
|
6172
6566
|
fail(`${projectsRoot} does not exist`);
|
|
6173
6567
|
process.exit(1);
|
|
6174
6568
|
}
|
|
@@ -6182,8 +6576,8 @@ function resumeCmd(sessionId) {
|
|
|
6182
6576
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
6183
6577
|
process.exit(1);
|
|
6184
6578
|
}
|
|
6185
|
-
const mapPath =
|
|
6186
|
-
if (!
|
|
6579
|
+
const mapPath = join49(repoHome(), "path-map.json");
|
|
6580
|
+
if (!existsSync42(mapPath)) {
|
|
6187
6581
|
fail("path-map.json missing");
|
|
6188
6582
|
process.exit(1);
|
|
6189
6583
|
}
|
|
@@ -6205,9 +6599,9 @@ function resumeCmd(sessionId) {
|
|
|
6205
6599
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
6206
6600
|
}
|
|
6207
6601
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
6208
|
-
for (const dir of
|
|
6209
|
-
const candidate =
|
|
6210
|
-
if (
|
|
6602
|
+
for (const dir of readdirSync15(projectsRoot)) {
|
|
6603
|
+
const candidate = join49(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
6604
|
+
if (existsSync42(candidate)) return candidate;
|
|
6211
6605
|
}
|
|
6212
6606
|
return null;
|
|
6213
6607
|
}
|