claude-nomad 0.62.1 → 0.62.3
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 +16 -0
- package/README.md +4 -3
- package/dist/nomad.mjs +232 -141
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.62.3](https://github.com/funkadelic/claude-nomad/compare/v0.62.2...v0.62.3) (2026-07-21)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
* abort when an autostash pop conflicts during rebase ([#448](https://github.com/funkadelic/claude-nomad/issues/448)) ([a22b3df](https://github.com/funkadelic/claude-nomad/commit/a22b3dfdc95478ba09a6cf6bf9994c9d8d9ac416))
|
|
9
|
+
* **pull:** stop --force-remote applying conflict-markered config ([#452](https://github.com/funkadelic/claude-nomad/issues/452)) ([5612ac9](https://github.com/funkadelic/claude-nomad/commit/5612ac9c25e25118a73aaa465d2ccd9b16604e8a))
|
|
10
|
+
* retain never-pushed local skills on pull ([#449](https://github.com/funkadelic/claude-nomad/issues/449)) ([0faf4f7](https://github.com/funkadelic/claude-nomad/commit/0faf4f726d2982ba9e4b6da9be58b17b838bcd37))
|
|
11
|
+
|
|
12
|
+
## [0.62.2](https://github.com/funkadelic/claude-nomad/compare/v0.62.1...v0.62.2) (2026-07-21)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
* **push:** close the interactive-push secret-gate fail-open ([#446](https://github.com/funkadelic/claude-nomad/issues/446)) ([5de8975](https://github.com/funkadelic/claude-nomad/commit/5de8975f83f5e99711041ef6025fe69933b1ab15))
|
|
18
|
+
|
|
3
19
|
## [0.62.1](https://github.com/funkadelic/claude-nomad/compare/v0.62.0...v0.62.1) (2026-07-20)
|
|
4
20
|
|
|
5
21
|
|
package/README.md
CHANGED
|
@@ -253,9 +253,10 @@ operation, parks stranded commits on a `nomad/stranded-<ts>` branch, resets to `
|
|
|
253
253
|
re-pulls; refuses if stranded or dirty tracked changes touch synced config), and a repo where the
|
|
254
254
|
rebase was torn down but the git index still has unmerged entries with no active rebase or merge in
|
|
255
255
|
progress (clears the stuck index via `git reset --mixed HEAD`, preserving working-tree edits,
|
|
256
|
-
surfaces any orphaned autostash entry, then re-pulls
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
surfaces any orphaned autostash entry, then re-pulls; stops after the index repair if any conflicted
|
|
257
|
+
file still carries conflict markers, so they are never copied into your live config). Run
|
|
258
|
+
`nomad doctor` first if you are unsure which state you are in; the Repository section names the
|
|
259
|
+
specific problem and points at the right fix.
|
|
259
260
|
|
|
260
261
|
If an external tool (such as Claude Code or GSD) wrote new keys into your `~/.claude/settings.json`
|
|
261
262
|
that are not yet in your shared repo, run `nomad capture-settings` to promote them before the next
|
package/dist/nomad.mjs
CHANGED
|
@@ -779,7 +779,7 @@ function classifyWedge(repo) {
|
|
|
779
779
|
return unmergedIndexPresent(repo) ? "unmerged-index" : null;
|
|
780
780
|
}
|
|
781
781
|
function unmergedIndexRunbookText(resumeCmd2) {
|
|
782
|
-
return `repo has an unmerged index with no active rebase or merge in progress (torn-down rebase left stage-2/3 entries behind).
|
|
782
|
+
return `repo has an unmerged index with no active rebase or merge in progress (torn-down rebase or merge left stage-2/3 entries behind).
|
|
783
783
|
|
|
784
784
|
Manual recovery:
|
|
785
785
|
1. git reset --mixed HEAD (clears the stuck index; preserves working-tree files)
|
|
@@ -915,6 +915,43 @@ var init_push_gitleaks_config = __esm({
|
|
|
915
915
|
}
|
|
916
916
|
});
|
|
917
917
|
|
|
918
|
+
// src/autostash-guard.ts
|
|
919
|
+
function autostashConflictRunbookText(resumeCmd2, stashRetained) {
|
|
920
|
+
const head = `${resumeCmd2} reported success, but the autostash pop it ran internally conflicted. The working tree now holds conflict markers and the index is unmerged. Nothing has been copied to ~/.claude/ yet, and there is no rebase or merge in progress to abort.`;
|
|
921
|
+
if (stashRetained) {
|
|
922
|
+
return `${head}
|
|
923
|
+
|
|
924
|
+
Recovery (the pre-conflict content is retained in the stash; nothing is lost):
|
|
925
|
+
1. git stash list (confirm stash@{0}: autostash is present)
|
|
926
|
+
2. git show 'stash@{0}:<path>' (view the pre-conflict content; does not re-trigger the conflict)
|
|
927
|
+
3. git reset --hard HEAD (discard the markered working tree)
|
|
928
|
+
4. re-apply the content from step 2 into <path>, as needed
|
|
929
|
+
5. git stash drop (once you are done with the stash entry)
|
|
930
|
+
6. ${resumeCmd2}`;
|
|
931
|
+
}
|
|
932
|
+
return `${head}
|
|
933
|
+
|
|
934
|
+
No autostash entry was found to recover from, so the working tree is the only copy of this edit. Resolve the conflict markers by hand in the affected file(s), then:
|
|
935
|
+
1. git add <path> (mark resolved)
|
|
936
|
+
2. ${resumeCmd2}
|
|
937
|
+
|
|
938
|
+
Do not run "git reset --hard": with no stash entry to fall back on, that would discard your only copy of the edit.`;
|
|
939
|
+
}
|
|
940
|
+
function assertNoAutostashConflict(repo, resumeCmd2) {
|
|
941
|
+
if (!unmergedIndexPresent(repo)) return;
|
|
942
|
+
throw new NomadFatal(autostashConflictRunbookText(resumeCmd2, orphanedAutostashPresent(repo)), {
|
|
943
|
+
code: EXIT.CONFLICT
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
var init_autostash_guard = __esm({
|
|
947
|
+
"src/autostash-guard.ts"() {
|
|
948
|
+
"use strict";
|
|
949
|
+
init_commands_pull_wedge();
|
|
950
|
+
init_exit_codes();
|
|
951
|
+
init_utils();
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
|
|
918
955
|
// src/push-checks.ts
|
|
919
956
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
920
957
|
import { readdirSync as readdirSync7, rmSync as rmSync7 } from "node:fs";
|
|
@@ -1014,11 +1051,13 @@ function rebaseBeforePush(repo) {
|
|
|
1014
1051
|
`rebase failed; if a conflict was reported, resolve it in ${repo} and run "git rebase --continue" (or "git rebase --abort" to give up). Re-run nomad push after resolution.`
|
|
1015
1052
|
);
|
|
1016
1053
|
}
|
|
1054
|
+
assertNoAutostashConflict(repo, "nomad push");
|
|
1017
1055
|
}
|
|
1018
1056
|
var init_push_checks = __esm({
|
|
1019
1057
|
"src/push-checks.ts"() {
|
|
1020
1058
|
"use strict";
|
|
1021
1059
|
init_push_gitleaks_config();
|
|
1060
|
+
init_autostash_guard();
|
|
1022
1061
|
init_commands_pull_wedge();
|
|
1023
1062
|
init_exit_codes();
|
|
1024
1063
|
init_utils();
|
|
@@ -1644,9 +1683,9 @@ function cpSyncGuarded(src, dst, filter, label) {
|
|
|
1644
1683
|
throw err;
|
|
1645
1684
|
}
|
|
1646
1685
|
}
|
|
1647
|
-
function prunePreservingBy(src, dst, isPreserved) {
|
|
1686
|
+
function prunePreservingBy(src, dst, isPreserved, isRootPreserved) {
|
|
1648
1687
|
for (const name of readdirSync(dst)) {
|
|
1649
|
-
if (isPreserved(name)) continue;
|
|
1688
|
+
if (isPreserved(name) || isRootPreserved?.(name) === true) continue;
|
|
1650
1689
|
const dstPath = join2(dst, name);
|
|
1651
1690
|
const srcStat = lstatSync(join2(src, name), { throwIfNoEntry: false });
|
|
1652
1691
|
if (srcStat === void 0) {
|
|
@@ -1661,7 +1700,7 @@ function prunePreservingBy(src, dst, isPreserved) {
|
|
|
1661
1700
|
}
|
|
1662
1701
|
}
|
|
1663
1702
|
}
|
|
1664
|
-
function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
|
|
1703
|
+
function copyExtrasFilteredPreservingBy(src, dst, isPreserved, isRootPreserved) {
|
|
1665
1704
|
const dstStat = lstatSync(dst, { throwIfNoEntry: false });
|
|
1666
1705
|
if (dstStat !== void 0) {
|
|
1667
1706
|
if (dstStat.isDirectory()) {
|
|
@@ -1671,7 +1710,7 @@ function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
|
|
|
1671
1710
|
`copyExtrasFilteredPreservingBy: type mismatch copying ${quoted(src)} -> ${quoted(dst)}: the repo entry is a file but the local entry is a directory; run nomad pull --force-remote to recover`
|
|
1672
1711
|
);
|
|
1673
1712
|
}
|
|
1674
|
-
prunePreservingBy(src, dst, isPreserved);
|
|
1713
|
+
prunePreservingBy(src, dst, isPreserved, isRootPreserved);
|
|
1675
1714
|
} else {
|
|
1676
1715
|
rmSync(dst, { recursive: true, force: true });
|
|
1677
1716
|
}
|
|
@@ -5364,6 +5403,21 @@ import { dirname as dirname11, join as join42, sep as sep6 } from "node:path";
|
|
|
5364
5403
|
init_config();
|
|
5365
5404
|
import { existsSync as existsSync33, lstatSync as lstatSync12, mkdirSync as mkdirSync11, readdirSync as readdirSync14, rmSync as rmSync14 } from "node:fs";
|
|
5366
5405
|
import { join as join41 } from "node:path";
|
|
5406
|
+
|
|
5407
|
+
// src/skills-sync.tracked.ts
|
|
5408
|
+
init_utils();
|
|
5409
|
+
import { basename as basename3 } from "node:path";
|
|
5410
|
+
function trackedRootSkillsAt(ref, repo) {
|
|
5411
|
+
try {
|
|
5412
|
+
const raw = gitCaptureRaw(["ls-tree", "--name-only", "-z", ref, "--", "shared/skills/"], repo);
|
|
5413
|
+
const entries = raw.split("\0").filter((entry) => entry !== "");
|
|
5414
|
+
return new Set(entries.map((entry) => basename3(entry)));
|
|
5415
|
+
} catch {
|
|
5416
|
+
return /* @__PURE__ */ new Set();
|
|
5417
|
+
}
|
|
5418
|
+
}
|
|
5419
|
+
|
|
5420
|
+
// src/skills-sync.ts
|
|
5367
5421
|
init_utils_fs();
|
|
5368
5422
|
function isGsdOwned(name) {
|
|
5369
5423
|
return name.startsWith(GSD_PREFIX);
|
|
@@ -5379,20 +5433,21 @@ function copySkillsPush(src, dst) {
|
|
|
5379
5433
|
]);
|
|
5380
5434
|
copyExtrasFiltered(src, dst, blockSet);
|
|
5381
5435
|
}
|
|
5382
|
-
function copySkillsPull(src, dst) {
|
|
5383
|
-
copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
|
|
5436
|
+
function copySkillsPull(src, dst, isRootPreserved) {
|
|
5437
|
+
copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded, isRootPreserved);
|
|
5384
5438
|
}
|
|
5385
|
-
function syncSkillsPull(ts) {
|
|
5439
|
+
function syncSkillsPull(ts, prePostHeads) {
|
|
5386
5440
|
const sharedSkills = join41(repoHome(), "shared", "skills");
|
|
5387
5441
|
if (!existsSync33(sharedSkills)) return;
|
|
5388
5442
|
const localSkills = join41(claudeHome(), "skills");
|
|
5443
|
+
backupBeforeWrite(localSkills, ts);
|
|
5389
5444
|
const dstStat = lstatSync12(localSkills, { throwIfNoEntry: false });
|
|
5390
5445
|
if (dstStat?.isSymbolicLink() === true) {
|
|
5391
|
-
backupBeforeWrite(localSkills, ts);
|
|
5392
5446
|
rmSync14(localSkills, { recursive: true, force: true });
|
|
5393
5447
|
mkdirSync11(localSkills, { recursive: true });
|
|
5394
5448
|
}
|
|
5395
|
-
|
|
5449
|
+
const tracked = prePostHeads === void 0 ? /* @__PURE__ */ new Set() : trackedRootSkillsAt(prePostHeads.pre, repoHome());
|
|
5450
|
+
copySkillsPull(sharedSkills, localSkills, (name) => !tracked.has(name));
|
|
5396
5451
|
}
|
|
5397
5452
|
function syncSkillsPush() {
|
|
5398
5453
|
const localSkills = join41(claudeHome(), "skills");
|
|
@@ -5726,6 +5781,9 @@ function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
|
5726
5781
|
throw err;
|
|
5727
5782
|
}
|
|
5728
5783
|
}
|
|
5784
|
+
function leakBlockedFatal(recovery) {
|
|
5785
|
+
return new NomadFatal(recovery ?? "gitleaks detected secrets", { code: EXIT.LEAK_BLOCKED });
|
|
5786
|
+
}
|
|
5729
5787
|
function makeRealPrompt() {
|
|
5730
5788
|
return async (prompt) => {
|
|
5731
5789
|
const rl = createInterface2({
|
|
@@ -5772,9 +5830,10 @@ async function resolveLeakFindings(verdict, ts, map, deps = {}) {
|
|
|
5772
5830
|
);
|
|
5773
5831
|
}
|
|
5774
5832
|
if (!isTTYCheck()) {
|
|
5775
|
-
throw
|
|
5776
|
-
|
|
5777
|
-
|
|
5833
|
+
throw leakBlockedFatal(current.recovery);
|
|
5834
|
+
}
|
|
5835
|
+
if (current.leak && current.findings.length === 0) {
|
|
5836
|
+
throw leakBlockedFatal(current.recovery);
|
|
5778
5837
|
}
|
|
5779
5838
|
const prompt = makePromptFn();
|
|
5780
5839
|
printLegend();
|
|
@@ -5789,6 +5848,7 @@ async function resolveLeakFindings(verdict, ts, map, deps = {}) {
|
|
|
5789
5848
|
gitOrFatal(["add", "-A"], "git add", repo);
|
|
5790
5849
|
current = scanVerdict(repo);
|
|
5791
5850
|
}
|
|
5851
|
+
if (current.leak) throw leakBlockedFatal(current.recovery);
|
|
5792
5852
|
return current;
|
|
5793
5853
|
}
|
|
5794
5854
|
|
|
@@ -6379,8 +6439,9 @@ function unstageOne(rel, repo) {
|
|
|
6379
6439
|
}
|
|
6380
6440
|
|
|
6381
6441
|
// src/commands.pull.ts
|
|
6382
|
-
|
|
6383
|
-
import {
|
|
6442
|
+
init_autostash_guard();
|
|
6443
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync14 } from "node:fs";
|
|
6444
|
+
import { join as join53 } from "node:path";
|
|
6384
6445
|
|
|
6385
6446
|
// src/commands.push.sections.ts
|
|
6386
6447
|
init_color();
|
|
@@ -6817,8 +6878,8 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
6817
6878
|
|
|
6818
6879
|
// src/preview.ts
|
|
6819
6880
|
init_config();
|
|
6820
|
-
import { existsSync as
|
|
6821
|
-
import { join as
|
|
6881
|
+
import { existsSync as existsSync42 } from "node:fs";
|
|
6882
|
+
import { join as join52 } from "node:path";
|
|
6822
6883
|
|
|
6823
6884
|
// node_modules/diff/libesm/diff/base.js
|
|
6824
6885
|
var Diff = class {
|
|
@@ -7091,6 +7152,26 @@ function diffLinesToUnified(oldStr, newStr) {
|
|
|
7091
7152
|
return lines;
|
|
7092
7153
|
}
|
|
7093
7154
|
|
|
7155
|
+
// src/preview.skills.ts
|
|
7156
|
+
init_config();
|
|
7157
|
+
import { existsSync as existsSync41, readdirSync as readdirSync17 } from "node:fs";
|
|
7158
|
+
import { join as join51 } from "node:path";
|
|
7159
|
+
function buildSkillsPreviewSection() {
|
|
7160
|
+
const s = section("Skills");
|
|
7161
|
+
const sharedSkills = join51(repoHome(), "shared", "skills");
|
|
7162
|
+
if (!existsSync41(sharedSkills)) return s;
|
|
7163
|
+
const sharedNames = readdirSync17(sharedSkills, { encoding: "utf8" }).filter((name) => !isSkillExcluded(name)).sort((a, b) => a.localeCompare(b, "en"));
|
|
7164
|
+
for (const name of sharedNames) addItem(s, name);
|
|
7165
|
+
const localSkills = join51(claudeHome(), "skills");
|
|
7166
|
+
const localOnly = existsSync41(localSkills) ? readdirSync17(localSkills, { encoding: "utf8" }).filter(
|
|
7167
|
+
(name) => !isSkillExcluded(name) && !sharedNames.includes(name)
|
|
7168
|
+
).length : 0;
|
|
7169
|
+
if (localOnly > 0) {
|
|
7170
|
+
addItem(s, `${localOnly} local-only present, not in repo (push to reconcile)`);
|
|
7171
|
+
}
|
|
7172
|
+
return s;
|
|
7173
|
+
}
|
|
7174
|
+
|
|
7094
7175
|
// src/preview.ts
|
|
7095
7176
|
init_utils_json();
|
|
7096
7177
|
var CANONICAL_ORDER_NOTE = "settings.json will be rewritten in canonical key order; no value changes";
|
|
@@ -7104,7 +7185,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
7104
7185
|
return lines.join("\n");
|
|
7105
7186
|
}
|
|
7106
7187
|
function readJsonOrNull(path) {
|
|
7107
|
-
if (!
|
|
7188
|
+
if (!existsSync42(path)) return null;
|
|
7108
7189
|
try {
|
|
7109
7190
|
return readJson(path);
|
|
7110
7191
|
} catch {
|
|
@@ -7118,12 +7199,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
7118
7199
|
}
|
|
7119
7200
|
const notes = [];
|
|
7120
7201
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
7121
|
-
if (hostOverrides === null &&
|
|
7202
|
+
if (hostOverrides === null && existsSync42(hostPath)) {
|
|
7122
7203
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
7123
7204
|
}
|
|
7124
7205
|
const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
|
|
7125
7206
|
const current = readJsonOrNull(settingsPath);
|
|
7126
|
-
if (current === null &&
|
|
7207
|
+
if (current === null && existsSync42(settingsPath)) {
|
|
7127
7208
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
7128
7209
|
}
|
|
7129
7210
|
const strippedCurrent = stripGsdHookEntries(current ?? {});
|
|
@@ -7165,9 +7246,9 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
7165
7246
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
7166
7247
|
});
|
|
7167
7248
|
const settingsResult = previewSettings(
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7249
|
+
join52(repo, "shared", "settings.base.json"),
|
|
7250
|
+
join52(repo, "hosts", `${HOST}.json`),
|
|
7251
|
+
join52(claude, "settings.json")
|
|
7171
7252
|
);
|
|
7172
7253
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
7173
7254
|
const sessions = section("Sessions");
|
|
@@ -7179,10 +7260,11 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
7179
7260
|
if (localOnly > 0) {
|
|
7180
7261
|
addItem(sessions, `${localOnly} local-only present, not in repo (push to reconcile)`);
|
|
7181
7262
|
}
|
|
7263
|
+
const skills = buildSkillsPreviewSection();
|
|
7182
7264
|
const extras = section("Extras");
|
|
7183
7265
|
let extrasSkipped = 0;
|
|
7184
7266
|
let extrasUnmapped = 0;
|
|
7185
|
-
if (
|
|
7267
|
+
if (existsSync42(join52(repo, "path-map.json")) && existsSync42(join52(repo, "shared", "extras"))) {
|
|
7186
7268
|
const extrasResult = remapExtrasPull(ts, { dryRun: true });
|
|
7187
7269
|
for (const entry of extrasResult.wouldPull) {
|
|
7188
7270
|
addItem(extras, entry);
|
|
@@ -7195,7 +7277,7 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
7195
7277
|
summary,
|
|
7196
7278
|
summaryRow(verb, remapResult.unmapped + extrasUnmapped, 0, extrasSkipped, localOnly)
|
|
7197
7279
|
);
|
|
7198
|
-
renderTree([links, settingsSection, sessions, extras, summary]);
|
|
7280
|
+
renderTree([links, settingsSection, sessions, skills, extras, summary]);
|
|
7199
7281
|
return { unmapped: remapResult.unmapped, collisions: 0, localOnly };
|
|
7200
7282
|
}
|
|
7201
7283
|
|
|
@@ -7205,6 +7287,7 @@ init_commands_pull_wedge();
|
|
|
7205
7287
|
// src/commands.pull.recovery.ts
|
|
7206
7288
|
init_config();
|
|
7207
7289
|
init_commands_pull_wedge();
|
|
7290
|
+
init_exit_codes();
|
|
7208
7291
|
init_utils();
|
|
7209
7292
|
init_utils_fs();
|
|
7210
7293
|
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
@@ -7257,8 +7340,8 @@ function parsePorcelainZ(raw) {
|
|
|
7257
7340
|
}
|
|
7258
7341
|
return { tracked, untracked };
|
|
7259
7342
|
}
|
|
7260
|
-
function parseDirtyPaths(repo) {
|
|
7261
|
-
return parsePorcelainZ(gitStatusPorcelainZ(repo));
|
|
7343
|
+
function parseDirtyPaths(repo, opts = {}) {
|
|
7344
|
+
return parsePorcelainZ(gitStatusPorcelainZ(repo, opts));
|
|
7262
7345
|
}
|
|
7263
7346
|
function buildRecoverySummary(branchName, strandedLog, untracked) {
|
|
7264
7347
|
const strandedLines = strandedLog.split("\n").filter(Boolean).map((l) => ` ${l}`).join("\n");
|
|
@@ -7285,18 +7368,24 @@ function freshStrandedBranch(repo) {
|
|
|
7285
7368
|
return `${base}-${n}`;
|
|
7286
7369
|
}
|
|
7287
7370
|
function recoverUnmergedIndex(repo) {
|
|
7371
|
+
const conflicted = new Set(
|
|
7372
|
+
gitCapture(["diff", "--diff-filter=U", "--name-only", "-z"], repo).split("\0").filter(Boolean)
|
|
7373
|
+
);
|
|
7288
7374
|
gitOrFatal(["reset", "--mixed", "HEAD"], "git reset --mixed HEAD", repo);
|
|
7289
|
-
const
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
"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")
|
|
7293
|
-
);
|
|
7294
|
-
}
|
|
7375
|
+
const { tracked, untracked } = parseDirtyPaths(repo, { untrackedAll: true });
|
|
7376
|
+
const present = /* @__PURE__ */ new Set([...tracked, ...untracked]);
|
|
7377
|
+
const residual = [...conflicted].filter((p) => present.has(p));
|
|
7295
7378
|
if (orphanedAutostashPresent(repo)) {
|
|
7296
7379
|
log(
|
|
7297
7380
|
'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"'
|
|
7298
7381
|
);
|
|
7299
7382
|
}
|
|
7383
|
+
if (residual.length > 0) {
|
|
7384
|
+
die(
|
|
7385
|
+
"index cleared, but these files still carry unresolved conflict content from the torn-down rebase or merge:\n" + residual.map((p) => ` ${p}`).join("\n") + '\n\nThe repo is no longer wedged, so nothing else is blocked. Nothing was applied to ~/.claude/: pulling now would publish that content to your live config.\n\nResolve each file above (remove any <<<<<<< / ======= / >>>>>>> markers and keep the content you want; a file left untracked was deleted upstream, so keep or delete it deliberately), commit or checkout the result, then re-run "nomad pull".',
|
|
7386
|
+
{ code: EXIT.CONFLICT }
|
|
7387
|
+
);
|
|
7388
|
+
}
|
|
7300
7389
|
}
|
|
7301
7390
|
function recoverForceRemote(mode, repo) {
|
|
7302
7391
|
if (mode === "merge") {
|
|
@@ -7350,7 +7439,7 @@ function capturePrePostHeads(repo, rebase) {
|
|
|
7350
7439
|
function buildWetPullSections(ts, map, prePostHeads) {
|
|
7351
7440
|
applySharedLinks(ts, map);
|
|
7352
7441
|
const { label } = regenerateSettings(ts);
|
|
7353
|
-
syncSkillsPull(ts);
|
|
7442
|
+
syncSkillsPull(ts, prePostHeads);
|
|
7354
7443
|
const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
|
|
7355
7444
|
const extrasResult = remapExtrasPull(ts, { prePostHeads });
|
|
7356
7445
|
const localOnly = scanLocalOnly();
|
|
@@ -7397,7 +7486,7 @@ function runPullCore(opts = {}) {
|
|
|
7397
7486
|
const ts = freshBackupTs(backup);
|
|
7398
7487
|
handleWedge(repo, forceRemote);
|
|
7399
7488
|
if (!dryRun) {
|
|
7400
|
-
const backupRoot =
|
|
7489
|
+
const backupRoot = join53(backup, ts);
|
|
7401
7490
|
try {
|
|
7402
7491
|
mkdirSync14(backupRoot, { recursive: true });
|
|
7403
7492
|
} catch (err) {
|
|
@@ -7412,8 +7501,9 @@ function runPullCore(opts = {}) {
|
|
|
7412
7501
|
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
7413
7502
|
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
7414
7503
|
});
|
|
7415
|
-
|
|
7416
|
-
const
|
|
7504
|
+
assertNoAutostashConflict(repo, "nomad pull");
|
|
7505
|
+
const mapPath = join53(repo, "path-map.json");
|
|
7506
|
+
const map = existsSync43(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
7417
7507
|
const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
|
|
7418
7508
|
if (dryRun) {
|
|
7419
7509
|
computePreview(ts, map, "pull");
|
|
@@ -7439,8 +7529,8 @@ function runPullCore(opts = {}) {
|
|
|
7439
7529
|
}
|
|
7440
7530
|
function cmdPull(opts = {}) {
|
|
7441
7531
|
const repo = repoHome();
|
|
7442
|
-
if (!
|
|
7443
|
-
if (!
|
|
7532
|
+
if (!existsSync43(repo)) die(`repo not cloned at ${repo}`);
|
|
7533
|
+
if (!existsSync43(join53(repo, "shared", "settings.base.json"))) {
|
|
7444
7534
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
7445
7535
|
}
|
|
7446
7536
|
const handle = acquireLock("pull");
|
|
@@ -7462,13 +7552,13 @@ function cmdPull(opts = {}) {
|
|
|
7462
7552
|
|
|
7463
7553
|
// src/commands.push.ts
|
|
7464
7554
|
init_config();
|
|
7465
|
-
import { existsSync as
|
|
7466
|
-
import { join as
|
|
7555
|
+
import { existsSync as existsSync47 } from "node:fs";
|
|
7556
|
+
import { join as join58 } from "node:path";
|
|
7467
7557
|
|
|
7468
7558
|
// src/commands.push.selection.ts
|
|
7469
7559
|
init_config();
|
|
7470
|
-
import { existsSync as
|
|
7471
|
-
import { join as
|
|
7560
|
+
import { existsSync as existsSync44, statSync as statSync10 } from "node:fs";
|
|
7561
|
+
import { join as join54 } from "node:path";
|
|
7472
7562
|
init_utils_json();
|
|
7473
7563
|
function buildCurrentMap(map) {
|
|
7474
7564
|
const current = {};
|
|
@@ -7477,8 +7567,8 @@ function buildCurrentMap(map) {
|
|
|
7477
7567
|
for (const [, hostMap] of Object.entries(map.projects)) {
|
|
7478
7568
|
const localPath = hostMap[HOST];
|
|
7479
7569
|
if (!localPath) continue;
|
|
7480
|
-
const localDir =
|
|
7481
|
-
if (!
|
|
7570
|
+
const localDir = join54(claude, "projects", encodePath(localPath));
|
|
7571
|
+
if (!existsSync44(localDir)) continue;
|
|
7482
7572
|
for (const f of enumerateSourceFiles(localDir)) {
|
|
7483
7573
|
const st = statSync10(f);
|
|
7484
7574
|
current[f] = { size: st.size, mtime: st.mtimeMs };
|
|
@@ -7509,7 +7599,7 @@ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
|
|
|
7509
7599
|
};
|
|
7510
7600
|
}
|
|
7511
7601
|
function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
|
|
7512
|
-
const map =
|
|
7602
|
+
const map = existsSync44(mapPath) ? readPathMap(mapPath) : null;
|
|
7513
7603
|
const { selection, newManifest } = computePushSelection(
|
|
7514
7604
|
map,
|
|
7515
7605
|
old,
|
|
@@ -7611,14 +7701,14 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
7611
7701
|
|
|
7612
7702
|
// src/commands.push.settings.ts
|
|
7613
7703
|
init_config();
|
|
7614
|
-
import { existsSync as
|
|
7615
|
-
import { join as
|
|
7704
|
+
import { existsSync as existsSync45 } from "node:fs";
|
|
7705
|
+
import { join as join55 } from "node:path";
|
|
7616
7706
|
init_utils();
|
|
7617
7707
|
init_utils_fs();
|
|
7618
7708
|
init_utils_json();
|
|
7619
7709
|
function stripGsdHooksFromBase(repo, backup) {
|
|
7620
|
-
const basePath =
|
|
7621
|
-
if (!
|
|
7710
|
+
const basePath = join55(repo, "shared", "settings.base.json");
|
|
7711
|
+
if (!existsSync45(basePath)) return;
|
|
7622
7712
|
let base;
|
|
7623
7713
|
try {
|
|
7624
7714
|
base = readJson(basePath);
|
|
@@ -7632,14 +7722,14 @@ function stripGsdHooksFromBase(repo, backup) {
|
|
|
7632
7722
|
writeJsonAtomic(basePath, stripped);
|
|
7633
7723
|
}
|
|
7634
7724
|
function reportSettingsAheadDrift(repo) {
|
|
7635
|
-
const basePath =
|
|
7636
|
-
if (!
|
|
7637
|
-
const settingsPath =
|
|
7638
|
-
if (!
|
|
7725
|
+
const basePath = join55(repo, "shared", "settings.base.json");
|
|
7726
|
+
if (!existsSync45(basePath)) return;
|
|
7727
|
+
const settingsPath = join55(claudeHome(), "settings.json");
|
|
7728
|
+
if (!existsSync45(settingsPath)) return;
|
|
7639
7729
|
try {
|
|
7640
7730
|
const base = readJson(basePath);
|
|
7641
|
-
const hostPath =
|
|
7642
|
-
const overrides =
|
|
7731
|
+
const hostPath = join55(repo, "hosts", `${HOST}.json`);
|
|
7732
|
+
const overrides = existsSync45(hostPath) ? readJson(hostPath) : {};
|
|
7643
7733
|
const merged = deepMerge(base, overrides);
|
|
7644
7734
|
const settings = readJson(settingsPath);
|
|
7645
7735
|
const { ahead } = classifySettingsDrift(merged, settings);
|
|
@@ -7656,9 +7746,9 @@ function reportSettingsAheadDrift(repo) {
|
|
|
7656
7746
|
// src/commands.push.guards.ts
|
|
7657
7747
|
init_push_checks();
|
|
7658
7748
|
init_utils();
|
|
7659
|
-
import { join as
|
|
7749
|
+
import { join as join56, relative as relative7 } from "node:path";
|
|
7660
7750
|
function guardGitlinks(repo) {
|
|
7661
|
-
const gitlinks = findGitlinks(
|
|
7751
|
+
const gitlinks = findGitlinks(join56(repo, "shared"));
|
|
7662
7752
|
if (gitlinks.length === 0) return;
|
|
7663
7753
|
for (const p of gitlinks) {
|
|
7664
7754
|
const rel = relative7(repo, p).replaceAll("\\", "/");
|
|
@@ -7768,9 +7858,9 @@ init_color();
|
|
|
7768
7858
|
init_config();
|
|
7769
7859
|
init_config_sharedDirs_guard();
|
|
7770
7860
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
7771
|
-
import { copyFileSync, existsSync as
|
|
7861
|
+
import { copyFileSync, existsSync as existsSync46, mkdirSync as mkdirSync15, readdirSync as readdirSync18, rmSync as rmSync17 } from "node:fs";
|
|
7772
7862
|
import { homedir as homedir7 } from "node:os";
|
|
7773
|
-
import { join as
|
|
7863
|
+
import { join as join57, relative as relative8, sep as sep10 } from "node:path";
|
|
7774
7864
|
init_push_leak_verdict();
|
|
7775
7865
|
init_push_gitleaks();
|
|
7776
7866
|
init_utils_fs();
|
|
@@ -7782,7 +7872,7 @@ function stageSessionDir(localDir, dstDir, changed) {
|
|
|
7782
7872
|
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
7783
7873
|
if (matching.length === 0) return false;
|
|
7784
7874
|
for (const src of matching) {
|
|
7785
|
-
copyFileAtomic(src,
|
|
7875
|
+
copyFileAtomic(src, join57(dstDir, relative8(localDir, src)));
|
|
7786
7876
|
}
|
|
7787
7877
|
return true;
|
|
7788
7878
|
}
|
|
@@ -7798,14 +7888,14 @@ function stageSessions(tmpRoot, map, changed) {
|
|
|
7798
7888
|
if (!p || p === "TBD") continue;
|
|
7799
7889
|
reverse.set(encodePath(p), logical);
|
|
7800
7890
|
}
|
|
7801
|
-
const localProjects =
|
|
7802
|
-
if (!
|
|
7891
|
+
const localProjects = join57(claudeHome(), "projects");
|
|
7892
|
+
if (!existsSync46(localProjects)) return 0;
|
|
7803
7893
|
let staged = 0;
|
|
7804
|
-
for (const dir of
|
|
7894
|
+
for (const dir of readdirSync18(localProjects)) {
|
|
7805
7895
|
const logical = reverse.get(dir);
|
|
7806
7896
|
if (!logical) continue;
|
|
7807
|
-
const localDir =
|
|
7808
|
-
const dstDir =
|
|
7897
|
+
const localDir = join57(localProjects, dir);
|
|
7898
|
+
const dstDir = join57(tmpRoot, "shared", "projects", logical);
|
|
7809
7899
|
if (stageSessionDir(localDir, dstDir, changed)) staged++;
|
|
7810
7900
|
}
|
|
7811
7901
|
return staged;
|
|
@@ -7821,9 +7911,9 @@ function stageExtras(tmpRoot, map) {
|
|
|
7821
7911
|
if (!localRoot || localRoot === "TBD") continue;
|
|
7822
7912
|
for (const dirname13 of dirnames) {
|
|
7823
7913
|
if (!whitelist.includes(dirname13)) continue;
|
|
7824
|
-
const src =
|
|
7825
|
-
if (!
|
|
7826
|
-
const dst =
|
|
7914
|
+
const src = join57(localRoot, dirname13);
|
|
7915
|
+
if (!existsSync46(src)) continue;
|
|
7916
|
+
const dst = join57(tmpRoot, "shared", "extras", logical, dirname13);
|
|
7827
7917
|
copyExtras(src, dst);
|
|
7828
7918
|
staged++;
|
|
7829
7919
|
}
|
|
@@ -7831,19 +7921,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
7831
7921
|
return staged;
|
|
7832
7922
|
}
|
|
7833
7923
|
function previewPushLeaks(map, opts = {}) {
|
|
7834
|
-
const cacheDir =
|
|
7924
|
+
const cacheDir = join57(homedir7(), ".cache", "claude-nomad");
|
|
7835
7925
|
mkdirSync15(cacheDir, { recursive: true });
|
|
7836
7926
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes4(4).toString("hex")}`;
|
|
7837
|
-
const tmpRoot =
|
|
7927
|
+
const tmpRoot = join57(cacheDir, `push-preview-tree-${stamp}`);
|
|
7838
7928
|
try {
|
|
7839
7929
|
const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
|
|
7840
7930
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
7841
7931
|
if (sessionCount + extrasCount === 0) {
|
|
7842
7932
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
7843
7933
|
}
|
|
7844
|
-
const ignoreFile =
|
|
7845
|
-
if (
|
|
7846
|
-
copyFileSync(ignoreFile,
|
|
7934
|
+
const ignoreFile = join57(repoHome(), ".gitleaksignore");
|
|
7935
|
+
if (existsSync46(ignoreFile)) {
|
|
7936
|
+
copyFileSync(ignoreFile, join57(tmpRoot, ".gitleaksignore"));
|
|
7847
7937
|
}
|
|
7848
7938
|
let findings;
|
|
7849
7939
|
try {
|
|
@@ -7884,6 +7974,7 @@ async function commitAndPush(st, ts, map, resolution, repo, newManifest, render)
|
|
|
7884
7974
|
if (!render) log("push (leak recovery)");
|
|
7885
7975
|
renderPushTree(st, verdict);
|
|
7886
7976
|
verdict = await resolveLeakFindings(verdict, ts, map, resolution);
|
|
7977
|
+
if (verdict.leak) throw leakBlockedFatal(verdict.recovery);
|
|
7887
7978
|
}
|
|
7888
7979
|
gitOrFatal(["commit", "-m", `chore: sync from ${HOST}`], "git commit", repo);
|
|
7889
7980
|
withSpinner("Pushing", () => gitOrFatal(["push"], "git push", repo));
|
|
@@ -7969,7 +8060,7 @@ async function runPushCore(opts = {}) {
|
|
|
7969
8060
|
const scannerVersion = probeGitleaks();
|
|
7970
8061
|
const configHash = computeConfigHash();
|
|
7971
8062
|
const old = readManifest(manifestPath());
|
|
7972
|
-
const mapPath =
|
|
8063
|
+
const mapPath = join58(repo, "path-map.json");
|
|
7973
8064
|
const { map, selection, newManifest } = loadSelectionForPush(
|
|
7974
8065
|
mapPath,
|
|
7975
8066
|
old,
|
|
@@ -8020,7 +8111,7 @@ async function cmdPush(opts = {}) {
|
|
|
8020
8111
|
const allowRule = opts.allowRule;
|
|
8021
8112
|
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
8022
8113
|
const repo = repoHome();
|
|
8023
|
-
if (!
|
|
8114
|
+
if (!existsSync47(repo)) die(`repo not cloned at ${repo}`);
|
|
8024
8115
|
const handle = acquireLock("push");
|
|
8025
8116
|
if (handle === null) process.exit(0);
|
|
8026
8117
|
try {
|
|
@@ -8038,8 +8129,8 @@ async function cmdPush(opts = {}) {
|
|
|
8038
8129
|
}
|
|
8039
8130
|
|
|
8040
8131
|
// src/commands.sync.ts
|
|
8041
|
-
import { existsSync as
|
|
8042
|
-
import { join as
|
|
8132
|
+
import { existsSync as existsSync48 } from "node:fs";
|
|
8133
|
+
import { join as join59 } from "node:path";
|
|
8043
8134
|
init_config();
|
|
8044
8135
|
init_color();
|
|
8045
8136
|
init_utils();
|
|
@@ -8167,8 +8258,8 @@ async function runSyncWet(verbose) {
|
|
|
8167
8258
|
}
|
|
8168
8259
|
async function runSyncDryRun(repo, backup) {
|
|
8169
8260
|
const ts = freshBackupTs(backup);
|
|
8170
|
-
const mapPath =
|
|
8171
|
-
const map =
|
|
8261
|
+
const mapPath = join59(repo, "path-map.json");
|
|
8262
|
+
const map = existsSync48(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
8172
8263
|
computePreview(ts, map, "pull");
|
|
8173
8264
|
log("push preview below is computed against pre-pull state (a real sync pushes after pull)");
|
|
8174
8265
|
await runPushCore({ dryRun: true });
|
|
@@ -8178,8 +8269,8 @@ async function cmdSync(opts = {}) {
|
|
|
8178
8269
|
const verbose = opts.verbose === true;
|
|
8179
8270
|
const repo = repoHome();
|
|
8180
8271
|
const backup = backupBase();
|
|
8181
|
-
if (!
|
|
8182
|
-
if (!
|
|
8272
|
+
if (!existsSync48(repo)) die(`repo not cloned at ${repo}`);
|
|
8273
|
+
if (!existsSync48(join59(repo, "shared", "settings.base.json"))) {
|
|
8183
8274
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
8184
8275
|
}
|
|
8185
8276
|
const handle = acquireLock("sync");
|
|
@@ -8252,8 +8343,8 @@ init_config();
|
|
|
8252
8343
|
|
|
8253
8344
|
// src/crash-report.write.ts
|
|
8254
8345
|
init_config();
|
|
8255
|
-
import { mkdirSync as mkdirSync16, readdirSync as
|
|
8256
|
-
import { join as
|
|
8346
|
+
import { mkdirSync as mkdirSync16, readdirSync as readdirSync19, statSync as statSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
|
|
8347
|
+
import { join as join61 } from "node:path";
|
|
8257
8348
|
|
|
8258
8349
|
// src/crash-report.ts
|
|
8259
8350
|
var CRASH_MAX_STACK_LINES = 50;
|
|
@@ -8332,15 +8423,15 @@ function buildCrashReport(input) {
|
|
|
8332
8423
|
// src/crash-report.redact.ts
|
|
8333
8424
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync18, writeFileSync as writeFileSync10 } from "node:fs";
|
|
8334
8425
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
8335
|
-
import { join as
|
|
8426
|
+
import { join as join60 } from "node:path";
|
|
8336
8427
|
init_push_gitleaks_scan();
|
|
8337
8428
|
var CRASH_SCAN_TIMEOUT_MS = 3e3;
|
|
8338
8429
|
var SCAN_UNAVAILABLE_ADVISORY = "\n\n[gitleaks value-based scan unavailable; only structural redaction applied. Review before sharing this file publicly.]\n";
|
|
8339
8430
|
function redactWithGitleaks(text, scan = scanFile) {
|
|
8340
8431
|
let dir;
|
|
8341
8432
|
try {
|
|
8342
|
-
dir = mkdtempSync2(
|
|
8343
|
-
const tmp =
|
|
8433
|
+
dir = mkdtempSync2(join60(tmpdir2(), "nomad-crash-scan-"));
|
|
8434
|
+
const tmp = join60(dir, "crash.txt");
|
|
8344
8435
|
writeFileSync10(tmp, text, { mode: 384 });
|
|
8345
8436
|
const findings = scan(tmp, false, CRASH_SCAN_TIMEOUT_MS);
|
|
8346
8437
|
if (findings === null) return text + SCAN_UNAVAILABLE_ADVISORY;
|
|
@@ -8363,9 +8454,9 @@ init_utils();
|
|
|
8363
8454
|
var CRASH_RETENTION_KEEP = 20;
|
|
8364
8455
|
function listCrashFiles(dir = crashDir()) {
|
|
8365
8456
|
try {
|
|
8366
|
-
return
|
|
8457
|
+
return readdirSync19(dir).flatMap((name) => {
|
|
8367
8458
|
try {
|
|
8368
|
-
return [{ name, mtimeMs: statSync11(
|
|
8459
|
+
return [{ name, mtimeMs: statSync11(join61(dir, name)).mtimeMs }];
|
|
8369
8460
|
} catch {
|
|
8370
8461
|
return [];
|
|
8371
8462
|
}
|
|
@@ -8379,14 +8470,14 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
8379
8470
|
const targets = prunableByCount(files, keep);
|
|
8380
8471
|
for (const name of targets) {
|
|
8381
8472
|
try {
|
|
8382
|
-
unlinkSync2(
|
|
8473
|
+
unlinkSync2(join61(dir, name));
|
|
8383
8474
|
} catch {
|
|
8384
8475
|
}
|
|
8385
8476
|
}
|
|
8386
8477
|
}
|
|
8387
8478
|
function writeCrashReport(text, dir = crashDir()) {
|
|
8388
8479
|
mkdirSync16(dir, { recursive: true, mode: 448 });
|
|
8389
|
-
const path =
|
|
8480
|
+
const path = join61(dir, `crash-${nowTimestamp()}-${process.pid}.txt`);
|
|
8390
8481
|
writeFileSync11(path, text, { mode: 384 });
|
|
8391
8482
|
pruneCrashDir(dir);
|
|
8392
8483
|
return path;
|
|
@@ -8417,18 +8508,18 @@ function handleCrash(err, argv, opts) {
|
|
|
8417
8508
|
|
|
8418
8509
|
// src/diff.ts
|
|
8419
8510
|
init_config();
|
|
8420
|
-
import { existsSync as
|
|
8421
|
-
import { join as
|
|
8511
|
+
import { existsSync as existsSync49 } from "node:fs";
|
|
8512
|
+
import { join as join62 } from "node:path";
|
|
8422
8513
|
init_utils();
|
|
8423
8514
|
init_utils_fs();
|
|
8424
8515
|
init_utils_json();
|
|
8425
8516
|
function cmdDiff() {
|
|
8426
8517
|
try {
|
|
8427
8518
|
const repo = repoHome();
|
|
8428
|
-
if (!
|
|
8519
|
+
if (!existsSync49(repo)) die(`repo not cloned at ${repo}`);
|
|
8429
8520
|
const ts = freshBackupTs(backupBase());
|
|
8430
|
-
const mapPath =
|
|
8431
|
-
const map =
|
|
8521
|
+
const mapPath = join62(repo, "path-map.json");
|
|
8522
|
+
const map = existsSync49(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
8432
8523
|
divergenceCheckExtras(ts);
|
|
8433
8524
|
computePreview(ts, map, "diff");
|
|
8434
8525
|
} catch (err) {
|
|
@@ -8443,8 +8534,8 @@ function cmdDiff() {
|
|
|
8443
8534
|
|
|
8444
8535
|
// src/init.ts
|
|
8445
8536
|
init_config();
|
|
8446
|
-
import { existsSync as
|
|
8447
|
-
import { join as
|
|
8537
|
+
import { existsSync as existsSync51, mkdirSync as mkdirSync17, writeFileSync as writeFileSync12 } from "node:fs";
|
|
8538
|
+
import { join as join64 } from "node:path";
|
|
8448
8539
|
|
|
8449
8540
|
// src/init.gh-onboard.ts
|
|
8450
8541
|
init_config();
|
|
@@ -8526,33 +8617,33 @@ init_config();
|
|
|
8526
8617
|
init_utils();
|
|
8527
8618
|
init_utils_fs();
|
|
8528
8619
|
init_utils_json();
|
|
8529
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync10, existsSync as
|
|
8530
|
-
import { join as
|
|
8620
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync10, existsSync as existsSync50, rmSync as rmSync19, statSync as statSync12 } from "node:fs";
|
|
8621
|
+
import { join as join63 } from "node:path";
|
|
8531
8622
|
function snapshotIntoShared(map) {
|
|
8532
8623
|
const repo = repoHome();
|
|
8533
8624
|
const claude = claudeHome();
|
|
8534
8625
|
for (const name of allSharedLinks(map)) {
|
|
8535
|
-
const src =
|
|
8536
|
-
if (!
|
|
8537
|
-
const dst =
|
|
8626
|
+
const src = join63(claude, name);
|
|
8627
|
+
if (!existsSync50(src)) continue;
|
|
8628
|
+
const dst = join63(repo, "shared", name);
|
|
8538
8629
|
if (statSync12(src).isDirectory()) {
|
|
8539
|
-
const gk =
|
|
8540
|
-
if (
|
|
8630
|
+
const gk = join63(dst, ".gitkeep");
|
|
8631
|
+
if (existsSync50(gk)) rmSync19(gk);
|
|
8541
8632
|
cpSync10(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
8542
8633
|
} else {
|
|
8543
8634
|
copyFileSync2(src, dst);
|
|
8544
8635
|
}
|
|
8545
8636
|
log(`snapshotted shared/${name} from ${src}`);
|
|
8546
8637
|
}
|
|
8547
|
-
const userSettings =
|
|
8548
|
-
if (
|
|
8638
|
+
const userSettings = join63(claude, "settings.json");
|
|
8639
|
+
if (existsSync50(userSettings)) {
|
|
8549
8640
|
let parsed;
|
|
8550
8641
|
try {
|
|
8551
8642
|
parsed = readJson(userSettings);
|
|
8552
8643
|
} catch (err) {
|
|
8553
8644
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
8554
8645
|
}
|
|
8555
|
-
const hostFile =
|
|
8646
|
+
const hostFile = join63(repo, "hosts", `${HOST}.json`);
|
|
8556
8647
|
writeJsonAtomic(hostFile, parsed);
|
|
8557
8648
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
8558
8649
|
}
|
|
@@ -8566,14 +8657,14 @@ var GITATTRIBUTES = "# nomad: sync content is byte-managed, disable all line-end
|
|
|
8566
8657
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
8567
8658
|
function preflightConflict(repoHome2) {
|
|
8568
8659
|
const candidates = [
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8660
|
+
join64(repoHome2, "shared", "settings.base.json"),
|
|
8661
|
+
join64(repoHome2, "shared", "CLAUDE.md"),
|
|
8662
|
+
join64(repoHome2, "path-map.json"),
|
|
8663
|
+
join64(repoHome2, "hosts"),
|
|
8664
|
+
join64(repoHome2, "shared")
|
|
8574
8665
|
];
|
|
8575
8666
|
for (const c of candidates) {
|
|
8576
|
-
if (
|
|
8667
|
+
if (existsSync51(c)) return c;
|
|
8577
8668
|
}
|
|
8578
8669
|
return null;
|
|
8579
8670
|
}
|
|
@@ -8591,27 +8682,27 @@ function cmdInit(opts = {}) {
|
|
|
8591
8682
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
8592
8683
|
}
|
|
8593
8684
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
8594
|
-
mkdirSync17(
|
|
8595
|
-
mkdirSync17(
|
|
8685
|
+
mkdirSync17(join64(repo, "shared"), { recursive: true });
|
|
8686
|
+
mkdirSync17(join64(repo, "hosts"), { recursive: true });
|
|
8596
8687
|
for (const name of SHARED_KEEP_DIRS) {
|
|
8597
|
-
mkdirSync17(
|
|
8688
|
+
mkdirSync17(join64(repo, "shared", name), { recursive: true });
|
|
8598
8689
|
}
|
|
8599
|
-
const userClaudeMd =
|
|
8600
|
-
if (!snapshot || !
|
|
8601
|
-
writeFileSync12(
|
|
8690
|
+
const userClaudeMd = join64(claude, "CLAUDE.md");
|
|
8691
|
+
if (!snapshot || !existsSync51(userClaudeMd)) {
|
|
8692
|
+
writeFileSync12(join64(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
8602
8693
|
item("created shared/CLAUDE.md");
|
|
8603
8694
|
}
|
|
8604
8695
|
for (const name of SHARED_KEEP_DIRS) {
|
|
8605
|
-
writeFileSync12(
|
|
8696
|
+
writeFileSync12(join64(repo, "shared", name, ".gitkeep"), "");
|
|
8606
8697
|
item(`created shared/${name}/.gitkeep`);
|
|
8607
8698
|
}
|
|
8608
|
-
writeFileSync12(
|
|
8699
|
+
writeFileSync12(join64(repo, "hosts", ".gitkeep"), "");
|
|
8609
8700
|
item("created hosts/.gitkeep");
|
|
8610
|
-
writeJsonAtomic(
|
|
8701
|
+
writeJsonAtomic(join64(repo, "shared", "settings.base.json"), {});
|
|
8611
8702
|
item("created shared/settings.base.json");
|
|
8612
|
-
writeJsonAtomic(
|
|
8703
|
+
writeJsonAtomic(join64(repo, "path-map.json"), { projects: {} });
|
|
8613
8704
|
item("created path-map.json");
|
|
8614
|
-
writeFileSync12(
|
|
8705
|
+
writeFileSync12(join64(repo, ".gitattributes"), GITATTRIBUTES);
|
|
8615
8706
|
item("created .gitattributes");
|
|
8616
8707
|
if (snapshot) {
|
|
8617
8708
|
snapshotIntoShared({ projects: {} });
|
|
@@ -8677,21 +8768,21 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
8677
8768
|
// src/init.prompt.ts
|
|
8678
8769
|
init_config();
|
|
8679
8770
|
init_utils();
|
|
8680
|
-
import { existsSync as
|
|
8681
|
-
import { join as
|
|
8771
|
+
import { existsSync as existsSync52, readdirSync as readdirSync20, statSync as statSync13 } from "node:fs";
|
|
8772
|
+
import { join as join65 } from "node:path";
|
|
8682
8773
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
8683
8774
|
function nonEmptyExists(path) {
|
|
8684
|
-
if (!
|
|
8775
|
+
if (!existsSync52(path)) return false;
|
|
8685
8776
|
try {
|
|
8686
|
-
if (statSync13(path).isDirectory()) return
|
|
8777
|
+
if (statSync13(path).isDirectory()) return readdirSync20(path).length > 0;
|
|
8687
8778
|
return true;
|
|
8688
8779
|
} catch {
|
|
8689
8780
|
return false;
|
|
8690
8781
|
}
|
|
8691
8782
|
}
|
|
8692
8783
|
function hasExistingClaudeConfig(claudeHome2) {
|
|
8693
|
-
if (
|
|
8694
|
-
return SHARED_LINKS.some((name) => nonEmptyExists(
|
|
8784
|
+
if (existsSync52(join65(claudeHome2, "settings.json"))) return true;
|
|
8785
|
+
return SHARED_LINKS.some((name) => nonEmptyExists(join65(claudeHome2, name)));
|
|
8695
8786
|
}
|
|
8696
8787
|
async function confirmSnapshotDefault(claudeHome2) {
|
|
8697
8788
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
@@ -8998,7 +9089,7 @@ function parseSyncArgs(argv) {
|
|
|
8998
9089
|
// package.json
|
|
8999
9090
|
var package_default = {
|
|
9000
9091
|
name: "claude-nomad",
|
|
9001
|
-
version: "0.62.
|
|
9092
|
+
version: "0.62.3",
|
|
9002
9093
|
type: "module",
|
|
9003
9094
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
9004
9095
|
keywords: [
|
|
@@ -9239,15 +9330,15 @@ var DEFAULT_HELP = [
|
|
|
9239
9330
|
init_config();
|
|
9240
9331
|
init_utils();
|
|
9241
9332
|
init_utils_json();
|
|
9242
|
-
import { existsSync as
|
|
9243
|
-
import { join as
|
|
9333
|
+
import { existsSync as existsSync53, readFileSync as readFileSync20, readdirSync as readdirSync21 } from "node:fs";
|
|
9334
|
+
import { join as join66 } from "node:path";
|
|
9244
9335
|
function resumeCmd(sessionId) {
|
|
9245
9336
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
9246
9337
|
fail(`invalid session id: ${sessionId}`);
|
|
9247
9338
|
process.exit(1);
|
|
9248
9339
|
}
|
|
9249
|
-
const projectsRoot =
|
|
9250
|
-
if (!
|
|
9340
|
+
const projectsRoot = join66(claudeHome(), "projects");
|
|
9341
|
+
if (!existsSync53(projectsRoot)) {
|
|
9251
9342
|
fail(`${projectsRoot} does not exist`);
|
|
9252
9343
|
process.exit(1);
|
|
9253
9344
|
}
|
|
@@ -9261,8 +9352,8 @@ function resumeCmd(sessionId) {
|
|
|
9261
9352
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
9262
9353
|
process.exit(1);
|
|
9263
9354
|
}
|
|
9264
|
-
const mapPath =
|
|
9265
|
-
if (!
|
|
9355
|
+
const mapPath = join66(repoHome(), "path-map.json");
|
|
9356
|
+
if (!existsSync53(mapPath)) {
|
|
9266
9357
|
fail("path-map.json missing");
|
|
9267
9358
|
process.exit(1);
|
|
9268
9359
|
}
|
|
@@ -9284,9 +9375,9 @@ function resumeCmd(sessionId) {
|
|
|
9284
9375
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
9285
9376
|
}
|
|
9286
9377
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
9287
|
-
for (const dir of
|
|
9288
|
-
const candidate =
|
|
9289
|
-
if (
|
|
9378
|
+
for (const dir of readdirSync21(projectsRoot)) {
|
|
9379
|
+
const candidate = join66(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
9380
|
+
if (existsSync53(candidate)) return candidate;
|
|
9290
9381
|
}
|
|
9291
9382
|
return null;
|
|
9292
9383
|
}
|
package/package.json
CHANGED