claude-nomad 0.59.0 → 0.61.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/.gitleaks.toml +11 -2
- package/CHANGELOG.md +46 -0
- package/README.md +10 -6
- package/dist/nomad.mjs +874 -427
- package/package.json +1 -1
package/dist/nomad.mjs
CHANGED
|
@@ -1353,6 +1353,75 @@ function stripGsdHookEntries(settings) {
|
|
|
1353
1353
|
}
|
|
1354
1354
|
return out;
|
|
1355
1355
|
}
|
|
1356
|
+
function keepMatcherEntry(entry) {
|
|
1357
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
1358
|
+
const entryObj = entry;
|
|
1359
|
+
if (!Array.isArray(entryObj.hooks)) return null;
|
|
1360
|
+
const innerHooks = entryObj.hooks;
|
|
1361
|
+
const kept = innerHooks.filter((h2) => {
|
|
1362
|
+
if (h2 === null || typeof h2 !== "object" || Array.isArray(h2)) return false;
|
|
1363
|
+
const hookObj = h2;
|
|
1364
|
+
const cmd = hookObj.command;
|
|
1365
|
+
return isGsdHookEntry(typeof cmd === "string" ? cmd : "");
|
|
1366
|
+
});
|
|
1367
|
+
if (kept.length === 0) return null;
|
|
1368
|
+
return { ...entryObj, hooks: kept };
|
|
1369
|
+
}
|
|
1370
|
+
function keepEventMatchers(matchers) {
|
|
1371
|
+
if (!Array.isArray(matchers)) return null;
|
|
1372
|
+
const kept = [];
|
|
1373
|
+
for (const entry of matchers) {
|
|
1374
|
+
const result = keepMatcherEntry(entry);
|
|
1375
|
+
if (result !== null) kept.push(result);
|
|
1376
|
+
}
|
|
1377
|
+
return kept.length === 0 ? null : kept;
|
|
1378
|
+
}
|
|
1379
|
+
function keepGsdHookEntries(settings) {
|
|
1380
|
+
const hooksVal = settings.hooks;
|
|
1381
|
+
if (hooksVal === null || typeof hooksVal !== "object" || Array.isArray(hooksVal)) return {};
|
|
1382
|
+
const hooksObj = hooksVal;
|
|
1383
|
+
const keptHooks = {};
|
|
1384
|
+
for (const [event, matchers] of Object.entries(hooksObj)) {
|
|
1385
|
+
const kept = keepEventMatchers(matchers);
|
|
1386
|
+
if (kept !== null) keptHooks[event] = kept;
|
|
1387
|
+
}
|
|
1388
|
+
return Object.keys(keptHooks).length === 0 ? {} : { hooks: keptHooks };
|
|
1389
|
+
}
|
|
1390
|
+
function asPlainObject(value) {
|
|
1391
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1392
|
+
return value;
|
|
1393
|
+
}
|
|
1394
|
+
function canonicalMatcherKey(m) {
|
|
1395
|
+
const obj = asPlainObject(m);
|
|
1396
|
+
if (obj === null) return JSON.stringify(m);
|
|
1397
|
+
return JSON.stringify(
|
|
1398
|
+
Object.keys(obj).sort((a, b) => a.localeCompare(b)).map((k) => [k, obj[k]])
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
function unionMatcherArrays(baseMatchers, gsdMatchers) {
|
|
1402
|
+
const seen = new Set(baseMatchers.map(canonicalMatcherKey));
|
|
1403
|
+
const merged = [...baseMatchers];
|
|
1404
|
+
for (const m of gsdMatchers) {
|
|
1405
|
+
const key = canonicalMatcherKey(m);
|
|
1406
|
+
if (!seen.has(key)) {
|
|
1407
|
+
merged.push(m);
|
|
1408
|
+
seen.add(key);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
return merged;
|
|
1412
|
+
}
|
|
1413
|
+
function graftGsdHookEntries(base, gsdOnly) {
|
|
1414
|
+
const gsdHooks = asPlainObject(gsdOnly.hooks);
|
|
1415
|
+
if (gsdHooks === null || Object.keys(gsdHooks).length === 0) return base;
|
|
1416
|
+
const baseHooks = asPlainObject(base.hooks);
|
|
1417
|
+
const mergedHooks = baseHooks ? { ...baseHooks } : {};
|
|
1418
|
+
for (const [event, gsdMatchers] of Object.entries(gsdHooks)) {
|
|
1419
|
+
if (!Array.isArray(gsdMatchers)) continue;
|
|
1420
|
+
const baseMatchers = mergedHooks[event];
|
|
1421
|
+
mergedHooks[event] = Array.isArray(baseMatchers) ? unionMatcherArrays(baseMatchers, gsdMatchers) : gsdMatchers;
|
|
1422
|
+
}
|
|
1423
|
+
return { ...base, hooks: mergedHooks };
|
|
1424
|
+
}
|
|
1356
1425
|
function matcherHasGsdEntry(entry) {
|
|
1357
1426
|
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return false;
|
|
1358
1427
|
const entryObj = entry;
|
|
@@ -1617,12 +1686,12 @@ function* eachExtrasTarget(v, counts) {
|
|
|
1617
1686
|
counts.unmapped++;
|
|
1618
1687
|
continue;
|
|
1619
1688
|
}
|
|
1620
|
-
for (const
|
|
1621
|
-
if (!whitelist.includes(
|
|
1689
|
+
for (const dirname11 of dirnames) {
|
|
1690
|
+
if (!whitelist.includes(dirname11)) {
|
|
1622
1691
|
counts.skipped++;
|
|
1623
1692
|
continue;
|
|
1624
1693
|
}
|
|
1625
|
-
yield { logical, localRoot, dirname:
|
|
1694
|
+
yield { logical, localRoot, dirname: dirname11 };
|
|
1626
1695
|
}
|
|
1627
1696
|
}
|
|
1628
1697
|
}
|
|
@@ -1660,8 +1729,8 @@ function copyExtrasFileSkipDiverged(src, dst) {
|
|
|
1660
1729
|
}
|
|
1661
1730
|
copyExtras(src, dst);
|
|
1662
1731
|
}
|
|
1663
|
-
function extrasDenySet(
|
|
1664
|
-
return
|
|
1732
|
+
function extrasDenySet(dirname11) {
|
|
1733
|
+
return dirname11 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
1665
1734
|
}
|
|
1666
1735
|
function copyExtrasFiltered(src, dst, blockSet) {
|
|
1667
1736
|
rmSync2(dst, { recursive: true, force: true });
|
|
@@ -1804,6 +1873,34 @@ function syncSharedLinksPush(map) {
|
|
|
1804
1873
|
copyExtrasFiltered(localPath, join5(repo, "shared", name), ALWAYS_NEVER_SYNC);
|
|
1805
1874
|
}
|
|
1806
1875
|
}
|
|
1876
|
+
function readExistingSettings(settingsPath) {
|
|
1877
|
+
if (!existsSync4(settingsPath)) return { existing: {}, present: false, malformed: false };
|
|
1878
|
+
try {
|
|
1879
|
+
const parsed = readJson(settingsPath);
|
|
1880
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1881
|
+
return { existing: {}, present: true, malformed: true };
|
|
1882
|
+
}
|
|
1883
|
+
return { existing: parsed, present: true, malformed: false };
|
|
1884
|
+
} catch {
|
|
1885
|
+
return { existing: {}, present: true, malformed: true };
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
function emitDriftWarnings(merged, existing) {
|
|
1889
|
+
const drift = classifySettingsDrift(merged, existing);
|
|
1890
|
+
if (drift.behind.length > 0) {
|
|
1891
|
+
const { phrase, pronoun } = describeSettings(drift.behind);
|
|
1892
|
+
warn(
|
|
1893
|
+
`your settings.json is missing ${phrase} that the synced copy has; run 'nomad pull' to restore ${pronoun}.`
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
const { promotable } = partitionByCaptureExclusion(drift.ahead);
|
|
1897
|
+
if (promotable.length > 0) {
|
|
1898
|
+
const { phrase, pronoun, verb } = describeSettings(promotable);
|
|
1899
|
+
warn(
|
|
1900
|
+
`your settings.json has ${phrase} that ${verb} not yet synced; run 'nomad capture-settings' to save ${pronoun} to the repo before the next pull overwrites ${pronoun}.`
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1807
1904
|
function regenerateSettings(ts, opts = {}) {
|
|
1808
1905
|
const dryRun = opts.dryRun === true;
|
|
1809
1906
|
const suppressDriftWarn = opts.suppressDriftWarn === true;
|
|
@@ -1819,25 +1916,12 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
1819
1916
|
const overrides = hasOverrides ? readJson(hostPath) : {};
|
|
1820
1917
|
const merged = deepMerge(base, overrides);
|
|
1821
1918
|
const settingsPath = join5(claude, "settings.json");
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
const drift = classifySettingsDrift(merged, existing);
|
|
1826
|
-
if (drift.behind.length > 0) {
|
|
1827
|
-
const { phrase, pronoun } = describeSettings(drift.behind);
|
|
1828
|
-
warn(
|
|
1829
|
-
`your settings.json is missing ${phrase} that the synced copy has; run 'nomad pull' to restore ${pronoun}.`
|
|
1830
|
-
);
|
|
1831
|
-
}
|
|
1832
|
-
const { promotable } = partitionByCaptureExclusion(drift.ahead);
|
|
1833
|
-
if (promotable.length > 0) {
|
|
1834
|
-
const { phrase, pronoun, verb } = describeSettings(promotable);
|
|
1835
|
-
warn(
|
|
1836
|
-
`your settings.json has ${phrase} that ${verb} not yet synced; run 'nomad capture-settings' to save ${pronoun} to the repo before the next pull overwrites ${pronoun}.`
|
|
1837
|
-
);
|
|
1838
|
-
}
|
|
1839
|
-
} catch {
|
|
1919
|
+
const { existing, present, malformed } = readExistingSettings(settingsPath);
|
|
1920
|
+
if (!suppressDriftWarn && present) {
|
|
1921
|
+
if (malformed) {
|
|
1840
1922
|
warn("existing settings.json is malformed; skipping drift-check and regenerating.");
|
|
1923
|
+
} else {
|
|
1924
|
+
emitDriftWarnings(merged, existing);
|
|
1841
1925
|
}
|
|
1842
1926
|
}
|
|
1843
1927
|
const overrideLabel = hasOverrides ? `${HOST}.json` : "no host overrides";
|
|
@@ -1846,7 +1930,10 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
1846
1930
|
return { label: overrideLabel };
|
|
1847
1931
|
}
|
|
1848
1932
|
backupBeforeWrite(settingsPath, ts);
|
|
1849
|
-
writeJsonAtomic(
|
|
1933
|
+
writeJsonAtomic(
|
|
1934
|
+
settingsPath,
|
|
1935
|
+
graftGsdHookEntries(stripGsdHookEntries(merged), keepGsdHookEntries(existing))
|
|
1936
|
+
);
|
|
1850
1937
|
return { label: overrideLabel };
|
|
1851
1938
|
}
|
|
1852
1939
|
|
|
@@ -2471,8 +2558,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2471
2558
|
}
|
|
2472
2559
|
|
|
2473
2560
|
// src/commands.doctor.ts
|
|
2474
|
-
import { existsSync as
|
|
2475
|
-
import { join as
|
|
2561
|
+
import { existsSync as existsSync35 } from "node:fs";
|
|
2562
|
+
import { join as join42 } from "node:path";
|
|
2476
2563
|
|
|
2477
2564
|
// src/commands.doctor.checks.repo.ts
|
|
2478
2565
|
init_color();
|
|
@@ -3319,20 +3406,117 @@ function reportCheckSchema(section2) {
|
|
|
3319
3406
|
|
|
3320
3407
|
// src/commands.doctor.check-shared.ts
|
|
3321
3408
|
init_color();
|
|
3322
|
-
import { randomBytes } from "node:crypto";
|
|
3409
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
3410
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
3411
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync7, readdirSync as readdirSync11, rmSync as rmSync11 } from "node:fs";
|
|
3412
|
+
import { homedir as homedir5 } from "node:os";
|
|
3413
|
+
import { join as join28 } from "node:path";
|
|
3414
|
+
|
|
3415
|
+
// src/commands.doctor.check-shared.memory.ts
|
|
3416
|
+
init_color();
|
|
3323
3417
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
3324
|
-
import {
|
|
3418
|
+
import { randomBytes } from "node:crypto";
|
|
3419
|
+
import { mkdirSync as mkdirSync4, rmSync as rmSync9, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3325
3420
|
import { homedir as homedir4 } from "node:os";
|
|
3326
|
-
import { join as
|
|
3421
|
+
import { dirname as dirname3, join as join24 } from "node:path";
|
|
3422
|
+
init_config();
|
|
3423
|
+
init_push_gitleaks();
|
|
3424
|
+
init_utils_fs();
|
|
3425
|
+
var MEMORY_MD_PATH = /^shared\/projects\/([^/]+)\/memory\/[^/]+\.md$/;
|
|
3426
|
+
function buildMemoryScanTree(tmpRoot) {
|
|
3427
|
+
let paths;
|
|
3428
|
+
try {
|
|
3429
|
+
const out = execFileSync9(
|
|
3430
|
+
"git",
|
|
3431
|
+
["-C", repoHome(), "ls-tree", "-r", "-z", "--name-only", "HEAD", "--", "shared/projects"],
|
|
3432
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 1e4 }
|
|
3433
|
+
);
|
|
3434
|
+
paths = out.split("\0").filter((p) => p.length > 0);
|
|
3435
|
+
} catch {
|
|
3436
|
+
return 0;
|
|
3437
|
+
}
|
|
3438
|
+
const logicals = /* @__PURE__ */ new Set();
|
|
3439
|
+
for (const rel of paths) {
|
|
3440
|
+
const m = MEMORY_MD_PATH.exec(rel);
|
|
3441
|
+
if (m?.[1] === void 0) continue;
|
|
3442
|
+
let blob;
|
|
3443
|
+
try {
|
|
3444
|
+
blob = execFileSync9("git", ["-C", repoHome(), "cat-file", "blob", `HEAD:${rel}`], {
|
|
3445
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3446
|
+
maxBuffer: 67108864,
|
|
3447
|
+
timeout: 1e4
|
|
3448
|
+
});
|
|
3449
|
+
} catch {
|
|
3450
|
+
continue;
|
|
3451
|
+
}
|
|
3452
|
+
const dest = join24(tmpRoot, rel);
|
|
3453
|
+
mkdirSync4(dirname3(dest), { recursive: true });
|
|
3454
|
+
writeFileSync4(dest, blob);
|
|
3455
|
+
logicals.add(m[1]);
|
|
3456
|
+
}
|
|
3457
|
+
return logicals.size;
|
|
3458
|
+
}
|
|
3459
|
+
function reportMemoryFindings(section2, findings) {
|
|
3460
|
+
for (const f of findings) {
|
|
3461
|
+
addItem(section2, `${yellow(warnGlyph)} ${f.RuleID} in ${f.File}`);
|
|
3462
|
+
}
|
|
3463
|
+
addItem(
|
|
3464
|
+
section2,
|
|
3465
|
+
` ${dim("run `nomad push` and choose Redact in the recovery menu to scrub these")}`
|
|
3466
|
+
);
|
|
3467
|
+
}
|
|
3468
|
+
function reportCommittedMemory(section2) {
|
|
3469
|
+
let tmpRoot;
|
|
3470
|
+
try {
|
|
3471
|
+
const cacheDir = join24(homedir4(), ".cache", "claude-nomad");
|
|
3472
|
+
mkdirSync4(cacheDir, { recursive: true });
|
|
3473
|
+
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
3474
|
+
tmpRoot = join24(cacheDir, `check-shared-memory-tree-${stamp}`);
|
|
3475
|
+
mkdirSync4(tmpRoot, { recursive: true, mode: 448 });
|
|
3476
|
+
const staged = buildMemoryScanTree(tmpRoot);
|
|
3477
|
+
if (staged === 0) return;
|
|
3478
|
+
let findings;
|
|
3479
|
+
try {
|
|
3480
|
+
findings = scanStagedTree(tmpRoot);
|
|
3481
|
+
} catch (err) {
|
|
3482
|
+
addItem(
|
|
3483
|
+
section2,
|
|
3484
|
+
`${yellow(warnGlyph)} committed-memory scan skipped: ${err.message}`
|
|
3485
|
+
);
|
|
3486
|
+
return;
|
|
3487
|
+
}
|
|
3488
|
+
if (findings === null) {
|
|
3489
|
+
addItem(
|
|
3490
|
+
section2,
|
|
3491
|
+
`${yellow(warnGlyph)} committed-memory scan skipped: no parseable gitleaks report`
|
|
3492
|
+
);
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
if (findings.length === 0) return;
|
|
3496
|
+
reportMemoryFindings(section2, findings);
|
|
3497
|
+
} catch (err) {
|
|
3498
|
+
addItem(
|
|
3499
|
+
section2,
|
|
3500
|
+
`${yellow(warnGlyph)} committed-memory scan skipped: ${err.message}`
|
|
3501
|
+
);
|
|
3502
|
+
} finally {
|
|
3503
|
+
if (tmpRoot !== void 0) {
|
|
3504
|
+
try {
|
|
3505
|
+
rmSync9(tmpRoot, { recursive: true, force: true });
|
|
3506
|
+
} catch {
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3327
3511
|
|
|
3328
3512
|
// src/commands.doctor.check-shared.scan.ts
|
|
3329
3513
|
init_color();
|
|
3330
|
-
import { join as
|
|
3514
|
+
import { join as join25 } from "node:path";
|
|
3331
3515
|
init_config();
|
|
3332
3516
|
init_push_gitleaks();
|
|
3333
3517
|
function scrubPath(logical, sid, logicalToEncoded) {
|
|
3334
3518
|
const encoded = logicalToEncoded.get(logical) ?? logical;
|
|
3335
|
-
return
|
|
3519
|
+
return join25(claudeHome(), "projects", encoded, `${sid}.jsonl`);
|
|
3336
3520
|
}
|
|
3337
3521
|
function reportSessionFindings(section2, bySession) {
|
|
3338
3522
|
for (const [sid, counts] of bySession) {
|
|
@@ -3420,8 +3604,8 @@ init_config();
|
|
|
3420
3604
|
|
|
3421
3605
|
// src/remap.ts
|
|
3422
3606
|
init_config_sharedDirs_guard();
|
|
3423
|
-
import { cpSync as cpSync6, existsSync as existsSync21, lstatSync as lstatSync10, mkdirSync as
|
|
3424
|
-
import { dirname as
|
|
3607
|
+
import { cpSync as cpSync6, existsSync as existsSync21, lstatSync as lstatSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync10, rmSync as rmSync10, statSync as statSync5 } from "node:fs";
|
|
3608
|
+
import { dirname as dirname5, join as join27, relative as relative5, sep as sep4 } from "node:path";
|
|
3425
3609
|
init_config();
|
|
3426
3610
|
|
|
3427
3611
|
// src/push-manifest.ts
|
|
@@ -3429,8 +3613,8 @@ init_config();
|
|
|
3429
3613
|
init_push_gitleaks_config();
|
|
3430
3614
|
init_utils_fs();
|
|
3431
3615
|
import { createHash } from "node:crypto";
|
|
3432
|
-
import { existsSync as existsSync20, mkdirSync as
|
|
3433
|
-
import { dirname as
|
|
3616
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync5, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
|
|
3617
|
+
import { dirname as dirname4, join as join26 } from "node:path";
|
|
3434
3618
|
function isChanged(prev, cur, hash) {
|
|
3435
3619
|
if (prev === void 0) return true;
|
|
3436
3620
|
if (prev.size !== cur.size) return true;
|
|
@@ -3471,7 +3655,7 @@ function hashFile(absPath) {
|
|
|
3471
3655
|
}
|
|
3472
3656
|
function enumerateDir(dir, results) {
|
|
3473
3657
|
for (const entry of readdirSync9(dir)) {
|
|
3474
|
-
const fullPath =
|
|
3658
|
+
const fullPath = join26(dir, entry);
|
|
3475
3659
|
const st = statSync4(fullPath);
|
|
3476
3660
|
if (st.isDirectory()) {
|
|
3477
3661
|
enumerateDir(fullPath, results);
|
|
@@ -3483,7 +3667,7 @@ function enumerateDir(dir, results) {
|
|
|
3483
3667
|
function enumerateSourceFiles(projectDir) {
|
|
3484
3668
|
const results = [];
|
|
3485
3669
|
for (const entry of readdirSync9(projectDir)) {
|
|
3486
|
-
const fullPath =
|
|
3670
|
+
const fullPath = join26(projectDir, entry);
|
|
3487
3671
|
const st = statSync4(fullPath);
|
|
3488
3672
|
if (st.isDirectory()) {
|
|
3489
3673
|
enumerateDir(fullPath, results);
|
|
@@ -3502,8 +3686,8 @@ function computeConfigHash() {
|
|
|
3502
3686
|
const repo = repoHome();
|
|
3503
3687
|
const parts = [
|
|
3504
3688
|
fileIdentity(resolveTomlPath(repo)),
|
|
3505
|
-
fileIdentity(
|
|
3506
|
-
fileIdentity(
|
|
3689
|
+
fileIdentity(join26(repo, ".gitleaks.overlay.toml")),
|
|
3690
|
+
fileIdentity(join26(repo, ".gitleaksignore"))
|
|
3507
3691
|
];
|
|
3508
3692
|
return createHash("sha256").update(parts.join("\n")).digest("hex");
|
|
3509
3693
|
}
|
|
@@ -3523,7 +3707,7 @@ function readManifest(path) {
|
|
|
3523
3707
|
}
|
|
3524
3708
|
}
|
|
3525
3709
|
function writeManifest(path, manifest) {
|
|
3526
|
-
|
|
3710
|
+
mkdirSync5(dirname4(path), { recursive: true });
|
|
3527
3711
|
writeJsonAtomic(path, manifest);
|
|
3528
3712
|
}
|
|
3529
3713
|
function buildManifest(files, scannerVersion, configHash) {
|
|
@@ -3537,9 +3721,9 @@ init_utils_json();
|
|
|
3537
3721
|
var TMP_SUFFIX = ".nomad-tmp";
|
|
3538
3722
|
function atomicMirror(src, dst, options) {
|
|
3539
3723
|
const tmp = `${dst}${TMP_SUFFIX}`;
|
|
3540
|
-
|
|
3724
|
+
rmSync10(tmp, { recursive: true, force: true });
|
|
3541
3725
|
cpSync6(src, tmp, options);
|
|
3542
|
-
|
|
3726
|
+
rmSync10(dst, { recursive: true, force: true });
|
|
3543
3727
|
renameAtomicRetry(tmp, dst);
|
|
3544
3728
|
}
|
|
3545
3729
|
function overlaySessionDir(src, dst) {
|
|
@@ -3547,7 +3731,7 @@ function overlaySessionDir(src, dst) {
|
|
|
3547
3731
|
cpSyncGuarded(src, dst, void 0, "overlaySessionDir");
|
|
3548
3732
|
}
|
|
3549
3733
|
function copyFileAtomic(src, dst) {
|
|
3550
|
-
|
|
3734
|
+
mkdirSync6(dirname5(dst), { recursive: true });
|
|
3551
3735
|
const tmp = `${dst}.tmp.${process.pid}`;
|
|
3552
3736
|
cpSync6(src, tmp, { force: true, preserveTimestamps: true });
|
|
3553
3737
|
renameAtomicRetry(tmp, dst);
|
|
@@ -3565,12 +3749,12 @@ function applySelective(sel, localDir, repoDst) {
|
|
|
3565
3749
|
const prefix = `${localDir}${sep4}`;
|
|
3566
3750
|
for (const src of sel.changed) {
|
|
3567
3751
|
if (!src.startsWith(prefix)) continue;
|
|
3568
|
-
copyFileAtomic(src,
|
|
3752
|
+
copyFileAtomic(src, join27(repoDst, relative5(localDir, src)));
|
|
3569
3753
|
}
|
|
3570
3754
|
for (const src of sel.deleted) {
|
|
3571
3755
|
if (!src.startsWith(prefix)) continue;
|
|
3572
|
-
const dst =
|
|
3573
|
-
if (existsSync21(dst))
|
|
3756
|
+
const dst = join27(repoDst, relative5(localDir, src));
|
|
3757
|
+
if (existsSync21(dst)) rmSync10(dst);
|
|
3574
3758
|
}
|
|
3575
3759
|
}
|
|
3576
3760
|
function copyDirJsonlOnly(src, dst) {
|
|
@@ -3599,16 +3783,16 @@ function remapPull(ts, opts = {}) {
|
|
|
3599
3783
|
const wouldPull = [];
|
|
3600
3784
|
const repo = repoHome();
|
|
3601
3785
|
const claude = claudeHome();
|
|
3602
|
-
const mapPath =
|
|
3603
|
-
const repoProjects =
|
|
3786
|
+
const mapPath = join27(repo, "path-map.json");
|
|
3787
|
+
const repoProjects = join27(repo, "shared", "projects");
|
|
3604
3788
|
if (!existsSync21(mapPath) || !existsSync21(repoProjects)) {
|
|
3605
3789
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
3606
3790
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
3607
3791
|
return { unmapped: 0, pulled, wouldPull };
|
|
3608
3792
|
}
|
|
3609
3793
|
const map = readPathMap(mapPath);
|
|
3610
|
-
const localProjects =
|
|
3611
|
-
if (!dryRun)
|
|
3794
|
+
const localProjects = join27(claude, "projects");
|
|
3795
|
+
if (!dryRun) mkdirSync6(localProjects, { recursive: true });
|
|
3612
3796
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
3613
3797
|
assertSafeLogical(logical);
|
|
3614
3798
|
const localPath = hosts[HOST];
|
|
@@ -3617,9 +3801,9 @@ function remapPull(ts, opts = {}) {
|
|
|
3617
3801
|
continue;
|
|
3618
3802
|
}
|
|
3619
3803
|
assertSafeLocalRoot(localPath, logical);
|
|
3620
|
-
const src =
|
|
3804
|
+
const src = join27(repoProjects, logical);
|
|
3621
3805
|
if (!existsSync21(src)) continue;
|
|
3622
|
-
const dst =
|
|
3806
|
+
const dst = join27(localProjects, encodePath(localPath));
|
|
3623
3807
|
if (dryRun) {
|
|
3624
3808
|
wouldPull.push(logical);
|
|
3625
3809
|
emitPreview(
|
|
@@ -3638,8 +3822,8 @@ function remapPull(ts, opts = {}) {
|
|
|
3638
3822
|
function countLocalOnly(src, dst) {
|
|
3639
3823
|
let count = 0;
|
|
3640
3824
|
for (const name of readdirSync10(dst)) {
|
|
3641
|
-
const dstPath =
|
|
3642
|
-
const srcPath =
|
|
3825
|
+
const dstPath = join27(dst, name);
|
|
3826
|
+
const srcPath = join27(src, name);
|
|
3643
3827
|
if (lstatSync10(dstPath).isDirectory()) {
|
|
3644
3828
|
count += countLocalOnly(srcPath, dstPath);
|
|
3645
3829
|
} else if (lstatSync10(srcPath, { throwIfNoEntry: false }) === void 0) {
|
|
@@ -3651,20 +3835,20 @@ function countLocalOnly(src, dst) {
|
|
|
3651
3835
|
function scanLocalOnly() {
|
|
3652
3836
|
const repo = repoHome();
|
|
3653
3837
|
const claude = claudeHome();
|
|
3654
|
-
const mapPath =
|
|
3655
|
-
const repoProjects =
|
|
3838
|
+
const mapPath = join27(repo, "path-map.json");
|
|
3839
|
+
const repoProjects = join27(repo, "shared", "projects");
|
|
3656
3840
|
if (!existsSync21(mapPath) || !existsSync21(repoProjects)) return 0;
|
|
3657
3841
|
const map = readPathMap(mapPath);
|
|
3658
|
-
const localProjects =
|
|
3842
|
+
const localProjects = join27(claude, "projects");
|
|
3659
3843
|
let count = 0;
|
|
3660
3844
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
3661
3845
|
assertSafeLogical(logical);
|
|
3662
3846
|
const localPath = hosts[HOST];
|
|
3663
3847
|
if (!localPath || localPath === "TBD") continue;
|
|
3664
3848
|
assertSafeLocalRoot(localPath, logical);
|
|
3665
|
-
const dst =
|
|
3849
|
+
const dst = join27(localProjects, encodePath(localPath));
|
|
3666
3850
|
if (!existsSync21(dst)) continue;
|
|
3667
|
-
count += countLocalOnly(
|
|
3851
|
+
count += countLocalOnly(join27(repoProjects, logical), dst);
|
|
3668
3852
|
}
|
|
3669
3853
|
return count;
|
|
3670
3854
|
}
|
|
@@ -3699,17 +3883,17 @@ function remapPush(ts, opts = {}) {
|
|
|
3699
3883
|
const wouldPush = [];
|
|
3700
3884
|
const repo = repoHome();
|
|
3701
3885
|
const claude = claudeHome();
|
|
3702
|
-
const mapPath =
|
|
3886
|
+
const mapPath = join27(repo, "path-map.json");
|
|
3703
3887
|
if (!existsSync21(mapPath)) {
|
|
3704
3888
|
log("no path-map.json; skipping session export");
|
|
3705
3889
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
3706
3890
|
}
|
|
3707
3891
|
const map = readPathMap(mapPath);
|
|
3708
|
-
const localProjects =
|
|
3709
|
-
const repoProjects =
|
|
3892
|
+
const localProjects = join27(claude, "projects");
|
|
3893
|
+
const repoProjects = join27(repo, "shared", "projects");
|
|
3710
3894
|
const reverse = buildReverseMap(map);
|
|
3711
3895
|
if (!existsSync21(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
3712
|
-
if (!dryRun)
|
|
3896
|
+
if (!dryRun) mkdirSync6(repoProjects, { recursive: true });
|
|
3713
3897
|
for (const dir of readdirSync10(localProjects)) {
|
|
3714
3898
|
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
3715
3899
|
const logical = reverse.get(dir);
|
|
@@ -3717,8 +3901,8 @@ function remapPush(ts, opts = {}) {
|
|
|
3717
3901
|
unmapped++;
|
|
3718
3902
|
continue;
|
|
3719
3903
|
}
|
|
3720
|
-
const localDir =
|
|
3721
|
-
const repoDst =
|
|
3904
|
+
const localDir = join27(localProjects, dir);
|
|
3905
|
+
const repoDst = join27(repoProjects, logical);
|
|
3722
3906
|
if (skipForSelection(opts.selection, localDir)) continue;
|
|
3723
3907
|
if (dryRun) {
|
|
3724
3908
|
wouldPush.push(logical);
|
|
@@ -3742,7 +3926,7 @@ function buildScanTree(tmpRoot) {
|
|
|
3742
3926
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
3743
3927
|
let staged = 0;
|
|
3744
3928
|
const repo = repoHome();
|
|
3745
|
-
const mapPath =
|
|
3929
|
+
const mapPath = join28(repo, "path-map.json");
|
|
3746
3930
|
if (!existsSync22(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
3747
3931
|
let map;
|
|
3748
3932
|
try {
|
|
@@ -3760,12 +3944,12 @@ function buildScanTree(tmpRoot) {
|
|
|
3760
3944
|
if (!p || p === "TBD") continue;
|
|
3761
3945
|
reverse.set(encodePath(p), logical);
|
|
3762
3946
|
}
|
|
3763
|
-
const localProjects =
|
|
3947
|
+
const localProjects = join28(claudeHome(), "projects");
|
|
3764
3948
|
if (!existsSync22(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
3765
3949
|
for (const dir of readdirSync11(localProjects)) {
|
|
3766
3950
|
const logical = reverse.get(dir);
|
|
3767
3951
|
if (!logical) continue;
|
|
3768
|
-
copyDirJsonlOnly(
|
|
3952
|
+
copyDirJsonlOnly(join28(localProjects, dir), join28(tmpRoot, "shared", "projects", logical));
|
|
3769
3953
|
logicalToEncoded.set(logical, dir);
|
|
3770
3954
|
staged++;
|
|
3771
3955
|
}
|
|
@@ -3773,7 +3957,7 @@ function buildScanTree(tmpRoot) {
|
|
|
3773
3957
|
}
|
|
3774
3958
|
function probeGitleaksForScan() {
|
|
3775
3959
|
try {
|
|
3776
|
-
|
|
3960
|
+
execFileSync10("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
3777
3961
|
return "ok";
|
|
3778
3962
|
} catch (err) {
|
|
3779
3963
|
if (err.code === "ENOENT") return "missing";
|
|
@@ -3794,13 +3978,12 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
3794
3978
|
}
|
|
3795
3979
|
return true;
|
|
3796
3980
|
}
|
|
3797
|
-
function
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
const
|
|
3802
|
-
const
|
|
3803
|
-
const tmpRoot = join27(cacheDir, `check-shared-tree-${stamp}`);
|
|
3981
|
+
function runLocalPreviewScan(section2) {
|
|
3982
|
+
const cacheDir = join28(homedir5(), ".cache", "claude-nomad");
|
|
3983
|
+
mkdirSync7(cacheDir, { recursive: true });
|
|
3984
|
+
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
3985
|
+
const reportPath = join28(cacheDir, `check-shared-${stamp}.json`);
|
|
3986
|
+
const tmpRoot = join28(cacheDir, `check-shared-tree-${stamp}`);
|
|
3804
3987
|
try {
|
|
3805
3988
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
3806
3989
|
if (malformed) {
|
|
@@ -3814,15 +3997,26 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
3814
3997
|
}
|
|
3815
3998
|
scanAndReport(section2, tmpRoot, staged, logicalToEncoded);
|
|
3816
3999
|
} finally {
|
|
3817
|
-
|
|
3818
|
-
|
|
4000
|
+
rmSync11(reportPath, { force: true });
|
|
4001
|
+
rmSync11(tmpRoot, { recursive: true, force: true });
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
function reportCheckShared(section2, gitleaksReady) {
|
|
4005
|
+
if (!ensureGitleaksReady(section2, gitleaksReady)) return;
|
|
4006
|
+
try {
|
|
4007
|
+
runLocalPreviewScan(section2);
|
|
4008
|
+
} catch (err) {
|
|
4009
|
+
addItem(section2, `${red(failGlyph)} shared scan failed: ${err.message}`);
|
|
4010
|
+
process.exitCode = 1;
|
|
4011
|
+
} finally {
|
|
4012
|
+
reportCommittedMemory(section2);
|
|
3819
4013
|
}
|
|
3820
4014
|
}
|
|
3821
4015
|
|
|
3822
4016
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
3823
4017
|
init_color();
|
|
3824
4018
|
import { existsSync as existsSync23, readFileSync as readFileSync8, readdirSync as readdirSync12, realpathSync as realpathSync2 } from "node:fs";
|
|
3825
|
-
import { dirname as
|
|
4019
|
+
import { dirname as dirname6, extname, join as join29 } from "node:path";
|
|
3826
4020
|
init_config();
|
|
3827
4021
|
function typeFromPackageJson(pkgPath) {
|
|
3828
4022
|
try {
|
|
@@ -3842,11 +4036,11 @@ function effectiveType(hookPath) {
|
|
|
3842
4036
|
} catch {
|
|
3843
4037
|
return null;
|
|
3844
4038
|
}
|
|
3845
|
-
let dir =
|
|
4039
|
+
let dir = dirname6(real);
|
|
3846
4040
|
for (; ; ) {
|
|
3847
|
-
const pkg =
|
|
4041
|
+
const pkg = join29(dir, "package.json");
|
|
3848
4042
|
if (existsSync23(pkg)) return typeFromPackageJson(pkg);
|
|
3849
|
-
const parent =
|
|
4043
|
+
const parent = dirname6(dir);
|
|
3850
4044
|
if (parent === dir) return "cjs";
|
|
3851
4045
|
dir = parent;
|
|
3852
4046
|
}
|
|
@@ -3887,7 +4081,7 @@ function safeRead(path) {
|
|
|
3887
4081
|
}
|
|
3888
4082
|
}
|
|
3889
4083
|
function reportHookScopeCheck(section2) {
|
|
3890
|
-
const hooksDir =
|
|
4084
|
+
const hooksDir = join29(claudeHome(), "hooks");
|
|
3891
4085
|
if (!existsSync23(hooksDir)) {
|
|
3892
4086
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
3893
4087
|
return;
|
|
@@ -3895,7 +4089,7 @@ function reportHookScopeCheck(section2) {
|
|
|
3895
4089
|
let anyWarn = false;
|
|
3896
4090
|
for (const name of safeReaddir2(hooksDir)) {
|
|
3897
4091
|
if (extname(name) !== ".js") continue;
|
|
3898
|
-
const abs =
|
|
4092
|
+
const abs = join29(hooksDir, name);
|
|
3899
4093
|
const eff = effectiveType(abs);
|
|
3900
4094
|
if (eff === null) continue;
|
|
3901
4095
|
const src = safeRead(abs);
|
|
@@ -3916,7 +4110,7 @@ function reportHookScopeCheck(section2) {
|
|
|
3916
4110
|
// src/commands.doctor.checks.hooks.ts
|
|
3917
4111
|
init_color();
|
|
3918
4112
|
import { existsSync as existsSync24 } from "node:fs";
|
|
3919
|
-
import { join as
|
|
4113
|
+
import { join as join30 } from "node:path";
|
|
3920
4114
|
init_config();
|
|
3921
4115
|
function expandHome(token) {
|
|
3922
4116
|
const h2 = home();
|
|
@@ -3972,7 +4166,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3972
4166
|
return anyFail;
|
|
3973
4167
|
}
|
|
3974
4168
|
function reportHooksTargetCheck(section2) {
|
|
3975
|
-
const settingsPath =
|
|
4169
|
+
const settingsPath = join30(claudeHome(), "settings.json");
|
|
3976
4170
|
if (!existsSync24(settingsPath)) {
|
|
3977
4171
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
3978
4172
|
return;
|
|
@@ -3997,11 +4191,11 @@ function reportHooksTargetCheck(section2) {
|
|
|
3997
4191
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
3998
4192
|
init_color();
|
|
3999
4193
|
import { existsSync as existsSync26, readFileSync as readFileSync9 } from "node:fs";
|
|
4000
|
-
import { join as
|
|
4194
|
+
import { join as join32 } from "node:path";
|
|
4001
4195
|
|
|
4002
4196
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
4003
4197
|
import { closeSync as closeSync3, existsSync as existsSync25, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
|
|
4004
|
-
import { dirname as
|
|
4198
|
+
import { dirname as dirname7, join as join31, resolve as resolve2 } from "node:path";
|
|
4005
4199
|
function suppressedRanges(src) {
|
|
4006
4200
|
const ranges = [];
|
|
4007
4201
|
const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
|
|
@@ -4038,7 +4232,7 @@ function specifierIsMissing(specifier, baseDir) {
|
|
|
4038
4232
|
if (existsSync25(base + ext)) return false;
|
|
4039
4233
|
}
|
|
4040
4234
|
for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
|
|
4041
|
-
if (existsSync25(
|
|
4235
|
+
if (existsSync25(join31(base, idx))) return false;
|
|
4042
4236
|
}
|
|
4043
4237
|
return true;
|
|
4044
4238
|
}
|
|
@@ -4064,7 +4258,7 @@ function relativeRequireTargetsBroken(scriptPath) {
|
|
|
4064
4258
|
}
|
|
4065
4259
|
const specifiers = topRelativeSpecifiers(raw);
|
|
4066
4260
|
if (specifiers.length === 0) return false;
|
|
4067
|
-
const baseDir =
|
|
4261
|
+
const baseDir = dirname7(realPath);
|
|
4068
4262
|
for (const spec of specifiers) {
|
|
4069
4263
|
if (specifierIsMissing(spec, baseDir)) return true;
|
|
4070
4264
|
}
|
|
@@ -4093,7 +4287,7 @@ function commandTokens(command) {
|
|
|
4093
4287
|
return tokens;
|
|
4094
4288
|
}
|
|
4095
4289
|
function readPathMapSafe() {
|
|
4096
|
-
const mapPath =
|
|
4290
|
+
const mapPath = join32(repoHome(), "path-map.json");
|
|
4097
4291
|
if (!existsSync26(mapPath)) return { projects: {} };
|
|
4098
4292
|
try {
|
|
4099
4293
|
return JSON.parse(readFileSync9(mapPath, "utf8"));
|
|
@@ -4168,7 +4362,7 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
4168
4362
|
return anyWarn;
|
|
4169
4363
|
}
|
|
4170
4364
|
function reportPreserveSymlinksCheck(section2) {
|
|
4171
|
-
const settingsPath =
|
|
4365
|
+
const settingsPath = join32(claudeHome(), "settings.json");
|
|
4172
4366
|
if (!existsSync26(settingsPath)) {
|
|
4173
4367
|
addItem(
|
|
4174
4368
|
section2,
|
|
@@ -4203,7 +4397,7 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
4203
4397
|
// src/commands.doctor.checks.settings-drift.ts
|
|
4204
4398
|
init_color();
|
|
4205
4399
|
import { existsSync as existsSync27, readFileSync as readFileSync10 } from "node:fs";
|
|
4206
|
-
import { join as
|
|
4400
|
+
import { join as join33 } from "node:path";
|
|
4207
4401
|
init_config();
|
|
4208
4402
|
init_utils_json();
|
|
4209
4403
|
function diffMergedSettings(merged, settings) {
|
|
@@ -4221,7 +4415,7 @@ function tryReadJson(filePath) {
|
|
|
4221
4415
|
}
|
|
4222
4416
|
}
|
|
4223
4417
|
function reportHooksBaseSelfCleanNote(section2) {
|
|
4224
|
-
const basePath =
|
|
4418
|
+
const basePath = join33(repoHome(), "shared", "settings.base.json");
|
|
4225
4419
|
const base = tryReadJson(basePath);
|
|
4226
4420
|
if (base === null) return;
|
|
4227
4421
|
if (!baseHasGsdHookEntries(base)) return;
|
|
@@ -4234,9 +4428,9 @@ function reportSettingsDriftCheck(section2) {
|
|
|
4234
4428
|
const claude = claudeHome();
|
|
4235
4429
|
const repo = repoHome();
|
|
4236
4430
|
const host = HOST;
|
|
4237
|
-
const settingsPath =
|
|
4238
|
-
const basePath =
|
|
4239
|
-
const hostPath =
|
|
4431
|
+
const settingsPath = join33(claude, "settings.json");
|
|
4432
|
+
const basePath = join33(repo, "shared", "settings.base.json");
|
|
4433
|
+
const hostPath = join33(repo, "hosts", `${host}.json`);
|
|
4240
4434
|
if (!existsSync27(settingsPath)) {
|
|
4241
4435
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
4242
4436
|
return;
|
|
@@ -4418,7 +4612,7 @@ function reportNodeEngineCheck(section2) {
|
|
|
4418
4612
|
|
|
4419
4613
|
// src/commands.doctor.checks.longpaths.ts
|
|
4420
4614
|
init_color();
|
|
4421
|
-
import { execFileSync as
|
|
4615
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
4422
4616
|
init_config();
|
|
4423
4617
|
var PROBE_TIMEOUT_MS = 3e3;
|
|
4424
4618
|
var LONGPATHS_REG_KEY = String.raw`HKLM\SYSTEM\CurrentControlSet\Control\FileSystem`;
|
|
@@ -4452,7 +4646,7 @@ function addLongpathsRow(section2, label, enabled2, remediation) {
|
|
|
4452
4646
|
}
|
|
4453
4647
|
addItem(section2, `${yellow(warnGlyph)} ${label}: not enabled (${remediation})`);
|
|
4454
4648
|
}
|
|
4455
|
-
function reportLongPathsCheck(section2, run =
|
|
4649
|
+
function reportLongPathsCheck(section2, run = execFileSync11) {
|
|
4456
4650
|
if (process.platform !== "win32") return;
|
|
4457
4651
|
addLongpathsRow(
|
|
4458
4652
|
section2,
|
|
@@ -4474,15 +4668,15 @@ function reportSyncModality(section2) {
|
|
|
4474
4668
|
|
|
4475
4669
|
// src/commands.doctor.checks.crlf.ts
|
|
4476
4670
|
init_color();
|
|
4477
|
-
import { execFileSync as
|
|
4671
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
4478
4672
|
import { existsSync as existsSync28, readFileSync as readFileSync13 } from "node:fs";
|
|
4479
|
-
import { join as
|
|
4673
|
+
import { join as join34 } from "node:path";
|
|
4480
4674
|
init_config();
|
|
4481
4675
|
var PROBE_TIMEOUT_MS2 = 3e3;
|
|
4482
4676
|
var GUARD_LINE = /^\*\s+-text\b/;
|
|
4483
4677
|
function hasGitattributesGuard(repo) {
|
|
4484
4678
|
try {
|
|
4485
|
-
const path =
|
|
4679
|
+
const path = join34(repo, ".gitattributes");
|
|
4486
4680
|
if (!existsSync28(path)) return false;
|
|
4487
4681
|
const content = readFileSync13(path, "utf8");
|
|
4488
4682
|
return content.split("\n").map((line) => line.trim()).some((line) => line !== "" && !line.startsWith("#") && GUARD_LINE.test(line));
|
|
@@ -4513,7 +4707,7 @@ function addExposedRow(section2, repo, verdict) {
|
|
|
4513
4707
|
const remediation = `add a .gitattributes with a \`* -text\` line, or run \`git config core.autocrlf false\`, in ${repo}`;
|
|
4514
4708
|
addItem(section2, `${yellow(warnGlyph)} CRLF guard: ${risk}; ${remediation}`);
|
|
4515
4709
|
}
|
|
4516
|
-
function reportCrlfGuardCheck(section2, run =
|
|
4710
|
+
function reportCrlfGuardCheck(section2, run = execFileSync12) {
|
|
4517
4711
|
const repo = repoHome();
|
|
4518
4712
|
if (hasGitattributesGuard(repo)) {
|
|
4519
4713
|
addItem(section2, `${green(okGlyph)} CRLF guard: .gitattributes (* -text) present`);
|
|
@@ -4524,35 +4718,35 @@ function reportCrlfGuardCheck(section2, run = execFileSync11) {
|
|
|
4524
4718
|
|
|
4525
4719
|
// src/spinner.ts
|
|
4526
4720
|
init_color();
|
|
4527
|
-
import { existsSync as
|
|
4721
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
4528
4722
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
4529
4723
|
import { Worker } from "node:worker_threads";
|
|
4530
4724
|
|
|
4531
4725
|
// src/commands.push.recovery.ts
|
|
4532
4726
|
init_config();
|
|
4533
|
-
import { readFileSync as
|
|
4534
|
-
import { join as
|
|
4727
|
+
import { readFileSync as readFileSync17, rmSync as rmSync13, writeFileSync as writeFileSync7 } from "node:fs";
|
|
4728
|
+
import { join as join40 } from "node:path";
|
|
4535
4729
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
4536
4730
|
|
|
4537
4731
|
// src/commands.push.recovery.actions.ts
|
|
4538
4732
|
init_config();
|
|
4539
|
-
import { readFileSync as
|
|
4733
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
4540
4734
|
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep6 } from "node:path";
|
|
4541
4735
|
|
|
4542
4736
|
// src/commands.push.recovery.redact.ts
|
|
4543
4737
|
init_config();
|
|
4544
4738
|
init_config_sharedDirs_guard();
|
|
4545
|
-
import { cpSync as
|
|
4546
|
-
import { dirname as
|
|
4739
|
+
import { cpSync as cpSync8, existsSync as existsSync32, mkdirSync as mkdirSync9, statSync as statSync8 } from "node:fs";
|
|
4740
|
+
import { dirname as dirname9, join as join38, sep as sep5 } from "node:path";
|
|
4547
4741
|
|
|
4548
4742
|
// src/commands.redact.ts
|
|
4549
4743
|
init_config();
|
|
4550
4744
|
import { existsSync as existsSync30, statSync as statSync7 } from "node:fs";
|
|
4551
|
-
import { dirname as
|
|
4745
|
+
import { dirname as dirname8, join as join36 } from "node:path";
|
|
4552
4746
|
|
|
4553
4747
|
// src/commands.redact.subtree.ts
|
|
4554
|
-
import { existsSync as existsSync29, lstatSync as lstatSync11, readFileSync as readFileSync14, readdirSync as readdirSync13, statSync as statSync6, writeFileSync as
|
|
4555
|
-
import { join as
|
|
4748
|
+
import { existsSync as existsSync29, lstatSync as lstatSync11, readFileSync as readFileSync14, readdirSync as readdirSync13, statSync as statSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4749
|
+
import { join as join35 } from "node:path";
|
|
4556
4750
|
init_utils_fs();
|
|
4557
4751
|
init_utils();
|
|
4558
4752
|
function collectFiles(dir, out) {
|
|
@@ -4560,7 +4754,7 @@ function collectFiles(dir, out) {
|
|
|
4560
4754
|
const st = lstatSync11(dir);
|
|
4561
4755
|
if (!st.isDirectory()) return;
|
|
4562
4756
|
for (const entry of readdirSync13(dir)) {
|
|
4563
|
-
const abs =
|
|
4757
|
+
const abs = join35(dir, entry);
|
|
4564
4758
|
const lst = lstatSync11(abs);
|
|
4565
4759
|
if (lst.isSymbolicLink()) continue;
|
|
4566
4760
|
if (lst.isDirectory()) {
|
|
@@ -4604,7 +4798,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
4604
4798
|
`warning: no redaction applied to ${filePath}: finding match values were not located in the file. Inspect it manually; the push re-scan still blocks a real leak.`
|
|
4605
4799
|
);
|
|
4606
4800
|
}
|
|
4607
|
-
|
|
4801
|
+
writeFileSync5(filePath, after, "utf8");
|
|
4608
4802
|
}
|
|
4609
4803
|
}
|
|
4610
4804
|
return { total, dirty };
|
|
@@ -4612,10 +4806,10 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
4612
4806
|
|
|
4613
4807
|
// src/commands.pushed-history.ts
|
|
4614
4808
|
init_utils();
|
|
4615
|
-
import { execFileSync as
|
|
4809
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
4616
4810
|
function pushedRef(repo) {
|
|
4617
4811
|
try {
|
|
4618
|
-
const ref =
|
|
4812
|
+
const ref = execFileSync13("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
|
|
4619
4813
|
cwd: repo,
|
|
4620
4814
|
stdio: ["ignore", "pipe", "ignore"]
|
|
4621
4815
|
}).toString().trim();
|
|
@@ -4628,7 +4822,7 @@ function sessionInPushedHistory(id, repo) {
|
|
|
4628
4822
|
const ref = pushedRef(repo);
|
|
4629
4823
|
if (ref === null) return false;
|
|
4630
4824
|
try {
|
|
4631
|
-
const out =
|
|
4825
|
+
const out = execFileSync13(
|
|
4632
4826
|
"git",
|
|
4633
4827
|
[
|
|
4634
4828
|
"log",
|
|
@@ -4665,14 +4859,14 @@ init_utils_json();
|
|
|
4665
4859
|
init_utils();
|
|
4666
4860
|
function resolveLiveTranscript(id) {
|
|
4667
4861
|
try {
|
|
4668
|
-
const mapPath =
|
|
4862
|
+
const mapPath = join36(repoHome(), "path-map.json");
|
|
4669
4863
|
if (!existsSync30(mapPath)) return null;
|
|
4670
4864
|
const projects = readJson(mapPath).projects;
|
|
4671
4865
|
const claude = claudeHome();
|
|
4672
4866
|
for (const hostMap of Object.values(projects)) {
|
|
4673
4867
|
const abs = hostMap[HOST];
|
|
4674
4868
|
if (abs === void 0) continue;
|
|
4675
|
-
const live =
|
|
4869
|
+
const live = join36(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
4676
4870
|
if (existsSync30(live)) return live;
|
|
4677
4871
|
}
|
|
4678
4872
|
return null;
|
|
@@ -4703,7 +4897,7 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
4703
4897
|
process.exitCode = 1;
|
|
4704
4898
|
return;
|
|
4705
4899
|
}
|
|
4706
|
-
const sessionDir =
|
|
4900
|
+
const sessionDir = join36(dirname8(localPath), id);
|
|
4707
4901
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4708
4902
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
4709
4903
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4763,6 +4957,78 @@ init_utils();
|
|
|
4763
4957
|
|
|
4764
4958
|
// src/commands.push.recovery.seams.ts
|
|
4765
4959
|
init_push_gitleaks();
|
|
4960
|
+
|
|
4961
|
+
// src/commands.push.recovery.memory.ts
|
|
4962
|
+
init_config();
|
|
4963
|
+
init_config_sharedDirs_guard();
|
|
4964
|
+
import { cpSync as cpSync7, existsSync as existsSync31, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "node:fs";
|
|
4965
|
+
import { join as join37 } from "node:path";
|
|
4966
|
+
init_push_gitleaks_scan();
|
|
4967
|
+
init_utils_fs();
|
|
4968
|
+
init_utils_json();
|
|
4969
|
+
init_utils();
|
|
4970
|
+
var MEMORY_FINDING_PATH = /^shared\/projects\/([^/]+)\/memory\/([^/]+\.md)$/;
|
|
4971
|
+
var SAFE_MEMORY_FILENAME = /^[^/\\]+\.md$/;
|
|
4972
|
+
var MEMORY_DIR_PATH = /^shared\/projects\/[^/]+\/memory\//;
|
|
4973
|
+
function memoryFileFromFinding(f) {
|
|
4974
|
+
const m = MEMORY_FINDING_PATH.exec(f.File);
|
|
4975
|
+
if (m?.[1] === void 0 || m[2] === void 0) return null;
|
|
4976
|
+
return { logical: m[1], filename: m[2] };
|
|
4977
|
+
}
|
|
4978
|
+
function isMemoryFindingPath(f) {
|
|
4979
|
+
return MEMORY_DIR_PATH.test(f.File);
|
|
4980
|
+
}
|
|
4981
|
+
function resolveMemoryLocalPath(logical, filename, map) {
|
|
4982
|
+
assertSafeLogical(logical);
|
|
4983
|
+
if (!SAFE_MEMORY_FILENAME.test(filename) || filename.includes("..")) return null;
|
|
4984
|
+
const abs = map.projects[logical]?.[HOST];
|
|
4985
|
+
if (abs === void 0) return null;
|
|
4986
|
+
const localPath = join37(claudeHome(), "projects", encodePath(abs), "memory", filename);
|
|
4987
|
+
return existsSync31(localPath) ? localPath : null;
|
|
4988
|
+
}
|
|
4989
|
+
function preflightMemoryRedactable(f, map) {
|
|
4990
|
+
const parsed = memoryFileFromFinding(f);
|
|
4991
|
+
if (parsed === null) return "a finding is not a project-level memory file";
|
|
4992
|
+
const localPath = resolveMemoryLocalPath(parsed.logical, parsed.filename, map);
|
|
4993
|
+
if (localPath === null) {
|
|
4994
|
+
return `memory file ${parsed.logical}/memory/${parsed.filename}: local file not found or unmapped`;
|
|
4995
|
+
}
|
|
4996
|
+
return null;
|
|
4997
|
+
}
|
|
4998
|
+
function applyMemoryRedact(f, ts, map, scan = scanFile) {
|
|
4999
|
+
const refuse = (msg) => {
|
|
5000
|
+
log(msg);
|
|
5001
|
+
return false;
|
|
5002
|
+
};
|
|
5003
|
+
const parsed = memoryFileFromFinding(f);
|
|
5004
|
+
if (parsed === null) {
|
|
5005
|
+
return refuse("could not parse this finding as a project-level memory file; choose Skip.");
|
|
5006
|
+
}
|
|
5007
|
+
const { logical, filename } = parsed;
|
|
5008
|
+
const localPath = resolveMemoryLocalPath(logical, filename, map);
|
|
5009
|
+
if (localPath === null) {
|
|
5010
|
+
return refuse(
|
|
5011
|
+
`could not locate the local memory file for ${logical}/memory/${filename}; choose Skip.`
|
|
5012
|
+
);
|
|
5013
|
+
}
|
|
5014
|
+
const findings = scan(localPath);
|
|
5015
|
+
if (findings === null) {
|
|
5016
|
+
return refuse(`re-scan of ${logical}/memory/${filename} failed; choose Skip.`);
|
|
5017
|
+
}
|
|
5018
|
+
if (findings.length === 0) {
|
|
5019
|
+
return refuse(`nothing to redact in ${logical}/memory/${filename}; choose Skip.`);
|
|
5020
|
+
}
|
|
5021
|
+
backupBeforeWrite(localPath, ts);
|
|
5022
|
+
const before = readFileSync15(localPath, "utf8");
|
|
5023
|
+
const after = applyRedactions(before, findings);
|
|
5024
|
+
writeFileSync6(localPath, after, "utf8");
|
|
5025
|
+
const stagedMemoryDir = join37(repoHome(), "shared", "projects", logical, "memory");
|
|
5026
|
+
mkdirSync8(stagedMemoryDir, { recursive: true });
|
|
5027
|
+
cpSync7(localPath, join37(stagedMemoryDir, filename), { force: true });
|
|
5028
|
+
return true;
|
|
5029
|
+
}
|
|
5030
|
+
|
|
5031
|
+
// src/commands.push.recovery.seams.ts
|
|
4766
5032
|
var MASK_LEAD = 4;
|
|
4767
5033
|
var MASK_BODY = "************";
|
|
4768
5034
|
var CONTEXT_WINDOW = 40;
|
|
@@ -4771,8 +5037,10 @@ function findingKey(f) {
|
|
|
4771
5037
|
return `${f.File}:${f.StartLine}:${f.StartColumn}:${f.RuleID}`;
|
|
4772
5038
|
}
|
|
4773
5039
|
var VALID_SID = /^[A-Za-z0-9_-]+$/;
|
|
5040
|
+
var SUBTREE_PATH = /^shared\/projects\/[^/]+\/([^/]+)\/.+$/;
|
|
4774
5041
|
function sessionIdFromFinding(f) {
|
|
4775
|
-
|
|
5042
|
+
if (isMemoryFindingPath(f)) return null;
|
|
5043
|
+
const m = SESSION_PATH.exec(f.File) ?? SUBTREE_PATH.exec(f.File);
|
|
4776
5044
|
if (m === null) return null;
|
|
4777
5045
|
const sid = m[1];
|
|
4778
5046
|
return VALID_SID.test(sid) ? sid : null;
|
|
@@ -4819,8 +5087,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
4819
5087
|
assertSafeLogical(logical);
|
|
4820
5088
|
const abs = hostMap[HOST];
|
|
4821
5089
|
if (abs === void 0) continue;
|
|
4822
|
-
if (localPath.startsWith(
|
|
4823
|
-
return
|
|
5090
|
+
if (localPath.startsWith(join38(claude, "projects", encodePath(abs)) + sep5)) {
|
|
5091
|
+
return join38(repo, "shared", "projects", logical);
|
|
4824
5092
|
}
|
|
4825
5093
|
}
|
|
4826
5094
|
return null;
|
|
@@ -4830,7 +5098,7 @@ function preflightRedactable(f, map, nowMs) {
|
|
|
4830
5098
|
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
4831
5099
|
const localPath = resolveLiveTranscript(sid);
|
|
4832
5100
|
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
4833
|
-
const sessionDir =
|
|
5101
|
+
const sessionDir = join38(dirname9(localPath), sid);
|
|
4834
5102
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4835
5103
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4836
5104
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4860,7 +5128,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4860
5128
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
4861
5129
|
);
|
|
4862
5130
|
}
|
|
4863
|
-
const sessionDir =
|
|
5131
|
+
const sessionDir = join38(dirname9(localPath), sid);
|
|
4864
5132
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4865
5133
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4866
5134
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4893,27 +5161,27 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4893
5161
|
`nothing to redact in the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
4894
5162
|
);
|
|
4895
5163
|
}
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
if (
|
|
4899
|
-
|
|
5164
|
+
mkdirSync9(stagedProjectDir, { recursive: true });
|
|
5165
|
+
cpSync8(localPath, join38(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
5166
|
+
if (existsSync32(sessionDir)) {
|
|
5167
|
+
cpSync8(sessionDir, join38(stagedProjectDir, sid), { force: true, recursive: true });
|
|
4900
5168
|
}
|
|
4901
5169
|
return true;
|
|
4902
5170
|
}
|
|
4903
5171
|
|
|
4904
5172
|
// src/commands.push.recovery.drop.ts
|
|
4905
5173
|
init_config();
|
|
4906
|
-
import { rmSync as
|
|
4907
|
-
import { join as
|
|
5174
|
+
import { rmSync as rmSync12 } from "node:fs";
|
|
5175
|
+
import { join as join39 } from "node:path";
|
|
4908
5176
|
function dropSessionFromStaged(sid, map) {
|
|
4909
5177
|
const logicals = Object.keys(map.projects);
|
|
4910
5178
|
if (logicals.length === 0) return false;
|
|
4911
5179
|
const repo = repoHome();
|
|
4912
5180
|
for (const logical of logicals) {
|
|
4913
|
-
const jsonl =
|
|
4914
|
-
const dir =
|
|
4915
|
-
|
|
4916
|
-
|
|
5181
|
+
const jsonl = join39(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
5182
|
+
const dir = join39(repo, "shared", "projects", logical, sid);
|
|
5183
|
+
rmSync12(jsonl, { force: true });
|
|
5184
|
+
rmSync12(dir, { recursive: true, force: true });
|
|
4917
5185
|
}
|
|
4918
5186
|
return true;
|
|
4919
5187
|
}
|
|
@@ -4947,7 +5215,7 @@ function makeDefaultReadLine(repo) {
|
|
|
4947
5215
|
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep6)) {
|
|
4948
5216
|
return null;
|
|
4949
5217
|
}
|
|
4950
|
-
const content =
|
|
5218
|
+
const content = readFileSync16(target, "utf8");
|
|
4951
5219
|
const lines = content.split(/\r?\n/);
|
|
4952
5220
|
const idx = line - 1;
|
|
4953
5221
|
if (idx < 0 || idx >= lines.length) return null;
|
|
@@ -4970,6 +5238,22 @@ Finding: ${f.RuleID} in ${f.File} line ${f.StartLine}` + (sid === null ? "" : `
|
|
|
4970
5238
|
}
|
|
4971
5239
|
return actions;
|
|
4972
5240
|
}
|
|
5241
|
+
function dispatchMemory(f, action, ctx) {
|
|
5242
|
+
const parsed = memoryFileFromFinding(f);
|
|
5243
|
+
if (parsed === null) {
|
|
5244
|
+
if (isMemoryFindingPath(f)) {
|
|
5245
|
+
log(`memory path not auto-redactable: ${f.File}; scrub it by hand or choose Skip`);
|
|
5246
|
+
}
|
|
5247
|
+
return;
|
|
5248
|
+
}
|
|
5249
|
+
if (action === "drop") {
|
|
5250
|
+
log("memory files cannot be dropped; use Redact or Skip");
|
|
5251
|
+
return;
|
|
5252
|
+
}
|
|
5253
|
+
const memKey = `${parsed.logical}/${parsed.filename}`;
|
|
5254
|
+
if (ctx.redactedMemory.has(memKey)) return;
|
|
5255
|
+
if (applyMemoryRedact(f, ctx.ts, ctx.map, ctx.scan)) ctx.redactedMemory.add(memKey);
|
|
5256
|
+
}
|
|
4973
5257
|
function dispatchOne(f, ctx) {
|
|
4974
5258
|
const action = ctx.actions.get(findingKey(f)) ?? "skip";
|
|
4975
5259
|
if (action === "skip") return;
|
|
@@ -4979,7 +5263,10 @@ function dispatchOne(f, ctx) {
|
|
|
4979
5263
|
applyAllow(f, ctx.repo);
|
|
4980
5264
|
return;
|
|
4981
5265
|
}
|
|
4982
|
-
if (sid === null)
|
|
5266
|
+
if (sid === null) {
|
|
5267
|
+
dispatchMemory(f, action, ctx);
|
|
5268
|
+
return;
|
|
5269
|
+
}
|
|
4983
5270
|
if (action === "drop") {
|
|
4984
5271
|
ctx.droppedSids.add(sid);
|
|
4985
5272
|
if (ctx.drop(sid, ctx.map)) {
|
|
@@ -5004,20 +5291,46 @@ function dispatchActions(findings, actions, opts) {
|
|
|
5004
5291
|
scan,
|
|
5005
5292
|
drop,
|
|
5006
5293
|
redactedSids: /* @__PURE__ */ new Set(),
|
|
5007
|
-
droppedSids: /* @__PURE__ */ new Set()
|
|
5294
|
+
droppedSids: /* @__PURE__ */ new Set(),
|
|
5295
|
+
redactedMemory: /* @__PURE__ */ new Set()
|
|
5008
5296
|
};
|
|
5009
5297
|
for (const f of findings) {
|
|
5010
5298
|
dispatchOne(f, ctx);
|
|
5011
5299
|
}
|
|
5012
5300
|
}
|
|
5301
|
+
function redactAllDedupeKey(f) {
|
|
5302
|
+
const sid = sessionIdFromFinding(f);
|
|
5303
|
+
if (sid !== null) return sid;
|
|
5304
|
+
const parsed = memoryFileFromFinding(f);
|
|
5305
|
+
return parsed !== null ? `${parsed.logical}/${parsed.filename}` : findingKey(f);
|
|
5306
|
+
}
|
|
5307
|
+
function redactAllPreflightOne(f, map, nowMs) {
|
|
5308
|
+
if (sessionIdFromFinding(f) === null && memoryFileFromFinding(f) !== null) {
|
|
5309
|
+
return preflightMemoryRedactable(f, map);
|
|
5310
|
+
}
|
|
5311
|
+
return preflightRedactable(f, map, nowMs);
|
|
5312
|
+
}
|
|
5313
|
+
function redactAllOne(f, ts, map, nowMs, scan, redactedSids, redactedMemory) {
|
|
5314
|
+
const sid = sessionIdFromFinding(f);
|
|
5315
|
+
if (sid !== null) {
|
|
5316
|
+
if (redactedSids.has(sid)) return;
|
|
5317
|
+
if (applyRedact(f, ts, map, nowMs, scan)) redactedSids.add(sid);
|
|
5318
|
+
return;
|
|
5319
|
+
}
|
|
5320
|
+
const parsed = memoryFileFromFinding(f);
|
|
5321
|
+
if (parsed === null) return;
|
|
5322
|
+
const memKey = `${parsed.logical}/${parsed.filename}`;
|
|
5323
|
+
if (redactedMemory.has(memKey)) return;
|
|
5324
|
+
if (applyMemoryRedact(f, ts, map, scan)) redactedMemory.add(memKey);
|
|
5325
|
+
}
|
|
5013
5326
|
function redactAllFindings(findings, ts, map, nowMs, scan = scanFile) {
|
|
5014
5327
|
const refusals = [];
|
|
5015
5328
|
const preflighted = /* @__PURE__ */ new Set();
|
|
5016
5329
|
for (const f of findings) {
|
|
5017
|
-
const dedupeKey =
|
|
5330
|
+
const dedupeKey = redactAllDedupeKey(f);
|
|
5018
5331
|
if (preflighted.has(dedupeKey)) continue;
|
|
5019
5332
|
preflighted.add(dedupeKey);
|
|
5020
|
-
const reason =
|
|
5333
|
+
const reason = redactAllPreflightOne(f, map, nowMs);
|
|
5021
5334
|
if (reason !== null) refusals.push(reason);
|
|
5022
5335
|
}
|
|
5023
5336
|
if (refusals.length > 0) {
|
|
@@ -5028,10 +5341,9 @@ function redactAllFindings(findings, ts, map, nowMs, scan = scanFile) {
|
|
|
5028
5341
|
);
|
|
5029
5342
|
}
|
|
5030
5343
|
const redactedSids = /* @__PURE__ */ new Set();
|
|
5344
|
+
const redactedMemory = /* @__PURE__ */ new Set();
|
|
5031
5345
|
for (const f of findings) {
|
|
5032
|
-
|
|
5033
|
-
if (sid === null || redactedSids.has(sid)) continue;
|
|
5034
|
-
if (applyRedact(f, ts, map, nowMs, scan)) redactedSids.add(sid);
|
|
5346
|
+
redactAllOne(f, ts, map, nowMs, scan, redactedSids, redactedMemory);
|
|
5035
5347
|
}
|
|
5036
5348
|
}
|
|
5037
5349
|
|
|
@@ -5068,10 +5380,10 @@ function applyThenRescan(scanVerdict, repoHome2) {
|
|
|
5068
5380
|
return next;
|
|
5069
5381
|
}
|
|
5070
5382
|
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
5071
|
-
const ignPath =
|
|
5383
|
+
const ignPath = join40(repoHome2, ".gitleaksignore");
|
|
5072
5384
|
let before;
|
|
5073
5385
|
try {
|
|
5074
|
-
before =
|
|
5386
|
+
before = readFileSync17(ignPath, "utf8");
|
|
5075
5387
|
} catch {
|
|
5076
5388
|
before = null;
|
|
5077
5389
|
}
|
|
@@ -5079,8 +5391,8 @@ function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
|
5079
5391
|
try {
|
|
5080
5392
|
return applyThenRescan(scanVerdict, repoHome2);
|
|
5081
5393
|
} catch (err) {
|
|
5082
|
-
if (before === null)
|
|
5083
|
-
else
|
|
5394
|
+
if (before === null) rmSync13(ignPath, { force: true });
|
|
5395
|
+
else writeFileSync7(ignPath, before, "utf8");
|
|
5084
5396
|
throw err;
|
|
5085
5397
|
}
|
|
5086
5398
|
}
|
|
@@ -5167,7 +5479,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
5167
5479
|
`);
|
|
5168
5480
|
}
|
|
5169
5481
|
function resolveWorkerPath(deps = {}) {
|
|
5170
|
-
const check = deps.existsSyncFn ??
|
|
5482
|
+
const check = deps.existsSyncFn ?? existsSync33;
|
|
5171
5483
|
const base = deps.baseUrl ?? import.meta.url;
|
|
5172
5484
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
5173
5485
|
if (check(mjs)) return mjs;
|
|
@@ -5233,9 +5545,9 @@ function withSpinner(label, fn, deps) {
|
|
|
5233
5545
|
|
|
5234
5546
|
// src/commands.doctor.gitleaks-version.ts
|
|
5235
5547
|
init_color();
|
|
5236
|
-
import { execFileSync as
|
|
5237
|
-
import { existsSync as
|
|
5238
|
-
import { join as
|
|
5548
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
5549
|
+
import { existsSync as existsSync34 } from "node:fs";
|
|
5550
|
+
import { join as join41 } from "node:path";
|
|
5239
5551
|
init_config();
|
|
5240
5552
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
5241
5553
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -5244,7 +5556,7 @@ function majorMinorOf(value) {
|
|
|
5244
5556
|
return m === null ? null : [m[1], m[2]];
|
|
5245
5557
|
}
|
|
5246
5558
|
function readGitleaksVersion(run, tomlExists) {
|
|
5247
|
-
const tomlPath =
|
|
5559
|
+
const tomlPath = join41(repoHome(), ".gitleaks.toml");
|
|
5248
5560
|
const args = ["version"];
|
|
5249
5561
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
5250
5562
|
try {
|
|
@@ -5256,7 +5568,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
5256
5568
|
return null;
|
|
5257
5569
|
}
|
|
5258
5570
|
}
|
|
5259
|
-
function reportGitleaksVersionCheck(section2, run =
|
|
5571
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync14, tomlExists = existsSync34) {
|
|
5260
5572
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
5261
5573
|
if (raw === null) return;
|
|
5262
5574
|
const local = majorMinorOf(raw);
|
|
@@ -5276,7 +5588,7 @@ function reportGitleaksVersionCheck(section2, run = execFileSync13, tomlExists =
|
|
|
5276
5588
|
|
|
5277
5589
|
// src/commands.doctor.checks.deps.ts
|
|
5278
5590
|
init_color();
|
|
5279
|
-
import { execFileSync as
|
|
5591
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
5280
5592
|
var VERSION_TOKEN = /(\d{1,9}\.\d{1,9}\.\d{1,9})/;
|
|
5281
5593
|
var PROBE_TIMEOUT_MS3 = 3e3;
|
|
5282
5594
|
var FETCHER_BASE = "HTTP fetcher";
|
|
@@ -5313,7 +5625,7 @@ function reportFetcherRow(section2, run) {
|
|
|
5313
5625
|
);
|
|
5314
5626
|
}
|
|
5315
5627
|
}
|
|
5316
|
-
function reportOptionalDeps(section2, run =
|
|
5628
|
+
function reportOptionalDeps(section2, run = execFileSync15) {
|
|
5317
5629
|
const gh = probeOptionalDep("gh", run);
|
|
5318
5630
|
if (gh.status === "present") {
|
|
5319
5631
|
addItem(section2, `${green(okGlyph)} gh: ${gh.version ?? "present"}`);
|
|
@@ -5328,11 +5640,11 @@ function reportOptionalDeps(section2, run = execFileSync14) {
|
|
|
5328
5640
|
|
|
5329
5641
|
// src/commands.doctor.actions-drift.ts
|
|
5330
5642
|
init_color();
|
|
5331
|
-
import { execFileSync as
|
|
5643
|
+
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
5332
5644
|
init_config();
|
|
5333
5645
|
|
|
5334
5646
|
// src/gh-actions.ts
|
|
5335
|
-
import { execFileSync as
|
|
5647
|
+
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
5336
5648
|
var GH_TIMEOUT_MS = 5e3;
|
|
5337
5649
|
function parseGitHubRemote(remoteUrl) {
|
|
5338
5650
|
const normalized = remoteUrl.trim().replace(/\/$/, "");
|
|
@@ -5340,7 +5652,7 @@ function parseGitHubRemote(remoteUrl) {
|
|
|
5340
5652
|
if (m === null) return null;
|
|
5341
5653
|
return { owner: m[1], repo: m[2] };
|
|
5342
5654
|
}
|
|
5343
|
-
function ghAuthStatus(run =
|
|
5655
|
+
function ghAuthStatus(run = execFileSync16) {
|
|
5344
5656
|
try {
|
|
5345
5657
|
run("gh", ["auth", "status"], {
|
|
5346
5658
|
stdio: ["ignore", "ignore", "ignore"],
|
|
@@ -5354,7 +5666,7 @@ function ghAuthStatus(run = execFileSync15) {
|
|
|
5354
5666
|
return "gh-probe-error";
|
|
5355
5667
|
}
|
|
5356
5668
|
}
|
|
5357
|
-
function isRepoPrivate(ref, run =
|
|
5669
|
+
function isRepoPrivate(ref, run = execFileSync16) {
|
|
5358
5670
|
const out = run("gh", ["repo", "view", `${ref.owner}/${ref.repo}`, "--json", "isPrivate"], {
|
|
5359
5671
|
stdio: ["ignore", "pipe", "ignore"],
|
|
5360
5672
|
timeout: GH_TIMEOUT_MS
|
|
@@ -5362,7 +5674,7 @@ function isRepoPrivate(ref, run = execFileSync15) {
|
|
|
5362
5674
|
const parsed = JSON.parse(out);
|
|
5363
5675
|
return parsed.isPrivate === true;
|
|
5364
5676
|
}
|
|
5365
|
-
function isActionsEnabled(ref, run =
|
|
5677
|
+
function isActionsEnabled(ref, run = execFileSync16) {
|
|
5366
5678
|
const out = run(
|
|
5367
5679
|
"gh",
|
|
5368
5680
|
["api", `repos/${ref.owner}/${ref.repo}/actions/permissions`, "--jq", ".enabled"],
|
|
@@ -5370,7 +5682,7 @@ function isActionsEnabled(ref, run = execFileSync15) {
|
|
|
5370
5682
|
).toString().trim();
|
|
5371
5683
|
return out === "true";
|
|
5372
5684
|
}
|
|
5373
|
-
function disableActions(ref, run =
|
|
5685
|
+
function disableActions(ref, run = execFileSync16) {
|
|
5374
5686
|
run(
|
|
5375
5687
|
"gh",
|
|
5376
5688
|
[
|
|
@@ -5384,7 +5696,7 @@ function disableActions(ref, run = execFileSync15) {
|
|
|
5384
5696
|
{ stdio: ["ignore", "ignore", "pipe"], timeout: GH_TIMEOUT_MS }
|
|
5385
5697
|
);
|
|
5386
5698
|
}
|
|
5387
|
-
function readOriginRemote(cwd, run =
|
|
5699
|
+
function readOriginRemote(cwd, run = execFileSync16) {
|
|
5388
5700
|
return run("git", ["remote", "get-url", "origin"], {
|
|
5389
5701
|
cwd,
|
|
5390
5702
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -5392,7 +5704,7 @@ function readOriginRemote(cwd, run = execFileSync15) {
|
|
|
5392
5704
|
}
|
|
5393
5705
|
|
|
5394
5706
|
// src/commands.doctor.actions-drift.ts
|
|
5395
|
-
function reportActionsDrift(section2, run =
|
|
5707
|
+
function reportActionsDrift(section2, run = execFileSync17) {
|
|
5396
5708
|
let remote;
|
|
5397
5709
|
try {
|
|
5398
5710
|
remote = readOriginRemote(repoHome(), run);
|
|
@@ -5482,8 +5794,8 @@ function gatherDoctorSections(opts) {
|
|
|
5482
5794
|
reportLongPathsCheck(host);
|
|
5483
5795
|
reportCrlfGuardCheck(host);
|
|
5484
5796
|
const links = section("Shared links");
|
|
5485
|
-
const mapPath =
|
|
5486
|
-
const rawMap =
|
|
5797
|
+
const mapPath = join42(repoHome(), "path-map.json");
|
|
5798
|
+
const rawMap = existsSync35(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
5487
5799
|
const map = rawMap ?? { projects: {} };
|
|
5488
5800
|
reportSharedLinks(links, map);
|
|
5489
5801
|
reportDroppedNamesMigration(links);
|
|
@@ -5579,15 +5891,15 @@ function parseDoctorArgs(args) {
|
|
|
5579
5891
|
|
|
5580
5892
|
// src/commands.drop-session.ts
|
|
5581
5893
|
init_config();
|
|
5582
|
-
import { execFileSync as
|
|
5583
|
-
import { existsSync as
|
|
5584
|
-
import { join as
|
|
5894
|
+
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
5895
|
+
import { existsSync as existsSync37, readdirSync as readdirSync14, statSync as statSync9 } from "node:fs";
|
|
5896
|
+
import { join as join44, relative as relative6 } from "node:path";
|
|
5585
5897
|
|
|
5586
5898
|
// src/commands.drop-session.git.ts
|
|
5587
|
-
import { execFileSync as
|
|
5899
|
+
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
5588
5900
|
function expandStagedDir(dirRel, repo) {
|
|
5589
5901
|
try {
|
|
5590
|
-
const out =
|
|
5902
|
+
const out = execFileSync18("git", ["ls-files", "-z", "--", dirRel], {
|
|
5591
5903
|
cwd: repo,
|
|
5592
5904
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5593
5905
|
});
|
|
@@ -5599,7 +5911,7 @@ function expandStagedDir(dirRel, repo) {
|
|
|
5599
5911
|
function isTrackedInHead(rel, repo) {
|
|
5600
5912
|
try {
|
|
5601
5913
|
const treePath = process.platform === "win32" ? rel.replaceAll("\\", "/") : rel;
|
|
5602
|
-
|
|
5914
|
+
execFileSync18("git", ["cat-file", "-e", `HEAD:${treePath}`], {
|
|
5603
5915
|
cwd: repo,
|
|
5604
5916
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5605
5917
|
});
|
|
@@ -5610,7 +5922,7 @@ function isTrackedInHead(rel, repo) {
|
|
|
5610
5922
|
}
|
|
5611
5923
|
function isInIndex(rel, repo) {
|
|
5612
5924
|
try {
|
|
5613
|
-
const out =
|
|
5925
|
+
const out = execFileSync18("git", ["ls-files", "--", rel], {
|
|
5614
5926
|
cwd: repo,
|
|
5615
5927
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5616
5928
|
});
|
|
@@ -5624,8 +5936,8 @@ function isInIndex(rel, repo) {
|
|
|
5624
5936
|
init_config();
|
|
5625
5937
|
init_utils();
|
|
5626
5938
|
init_utils_json();
|
|
5627
|
-
import { existsSync as
|
|
5628
|
-
import { join as
|
|
5939
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
5940
|
+
import { join as join43 } from "node:path";
|
|
5629
5941
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
5630
5942
|
function reportScrubHint(id, matches) {
|
|
5631
5943
|
const live = resolveLiveTranscript2(id, matches);
|
|
@@ -5641,8 +5953,8 @@ function reportScrubHint(id, matches) {
|
|
|
5641
5953
|
}
|
|
5642
5954
|
function resolveLiveTranscript2(id, matches) {
|
|
5643
5955
|
try {
|
|
5644
|
-
const mapPath =
|
|
5645
|
-
if (!
|
|
5956
|
+
const mapPath = join43(repoHome(), "path-map.json");
|
|
5957
|
+
if (!existsSync36(mapPath)) return null;
|
|
5646
5958
|
const projects = readJson(mapPath).projects;
|
|
5647
5959
|
const claude = claudeHome();
|
|
5648
5960
|
for (const rel of matches) {
|
|
@@ -5650,8 +5962,8 @@ function resolveLiveTranscript2(id, matches) {
|
|
|
5650
5962
|
if (logical === void 0) continue;
|
|
5651
5963
|
const abs = projects[logical]?.[HOST];
|
|
5652
5964
|
if (abs === void 0) continue;
|
|
5653
|
-
const live =
|
|
5654
|
-
if (
|
|
5965
|
+
const live = join43(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
5966
|
+
if (existsSync36(live)) return live;
|
|
5655
5967
|
}
|
|
5656
5968
|
return null;
|
|
5657
5969
|
} catch {
|
|
@@ -5667,12 +5979,12 @@ function cmdDropSession(id) {
|
|
|
5667
5979
|
process.exit(1);
|
|
5668
5980
|
}
|
|
5669
5981
|
const repo = repoHome();
|
|
5670
|
-
if (!
|
|
5982
|
+
if (!existsSync37(repo)) die(`repo not cloned at ${repo}`);
|
|
5671
5983
|
const handle = acquireLock("drop-session");
|
|
5672
5984
|
if (handle === null) process.exit(0);
|
|
5673
5985
|
try {
|
|
5674
|
-
const repoProjects =
|
|
5675
|
-
if (!
|
|
5986
|
+
const repoProjects = join44(repo, "shared", "projects");
|
|
5987
|
+
if (!existsSync37(repoProjects)) {
|
|
5676
5988
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
5677
5989
|
}
|
|
5678
5990
|
const matches = collectMatches(repoProjects, id, repo);
|
|
@@ -5695,12 +6007,12 @@ function cmdDropSession(id) {
|
|
|
5695
6007
|
function collectMatches(repoProjects, id, repo) {
|
|
5696
6008
|
const matches = [];
|
|
5697
6009
|
for (const logical of readdirSync14(repoProjects)) {
|
|
5698
|
-
const candidate =
|
|
5699
|
-
if (
|
|
6010
|
+
const candidate = join44(repoProjects, logical, `${id}.jsonl`);
|
|
6011
|
+
if (existsSync37(candidate)) {
|
|
5700
6012
|
matches.push(relative6(repo, candidate));
|
|
5701
6013
|
}
|
|
5702
|
-
const dir =
|
|
5703
|
-
if (
|
|
6014
|
+
const dir = join44(repoProjects, logical, id);
|
|
6015
|
+
if (existsSync37(dir) && statSync9(dir).isDirectory()) {
|
|
5704
6016
|
const dirRel = relative6(repo, dir);
|
|
5705
6017
|
const staged = expandStagedDir(dirRel, repo);
|
|
5706
6018
|
if (staged.length > 0) matches.push(...staged);
|
|
@@ -5716,12 +6028,12 @@ function unstageOne(rel, repo) {
|
|
|
5716
6028
|
}
|
|
5717
6029
|
try {
|
|
5718
6030
|
if (isTrackedInHead(rel, repo)) {
|
|
5719
|
-
|
|
6031
|
+
execFileSync19("git", ["restore", "--staged", "--worktree", "--", rel], {
|
|
5720
6032
|
cwd: repo,
|
|
5721
6033
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5722
6034
|
});
|
|
5723
6035
|
} else {
|
|
5724
|
-
|
|
6036
|
+
execFileSync19("git", ["rm", "--cached", "-f", "--", rel], {
|
|
5725
6037
|
cwd: repo,
|
|
5726
6038
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5727
6039
|
});
|
|
@@ -5735,8 +6047,8 @@ function unstageOne(rel, repo) {
|
|
|
5735
6047
|
}
|
|
5736
6048
|
|
|
5737
6049
|
// src/commands.pull.ts
|
|
5738
|
-
import { existsSync as
|
|
5739
|
-
import { join as
|
|
6050
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync12 } from "node:fs";
|
|
6051
|
+
import { join as join50 } from "node:path";
|
|
5740
6052
|
|
|
5741
6053
|
// src/commands.push.sections.ts
|
|
5742
6054
|
init_color();
|
|
@@ -5818,19 +6130,25 @@ function summarySection(st) {
|
|
|
5818
6130
|
addItem(s, summaryRow("push", unmapped, st.remap.collisions, st.extras.skipped));
|
|
5819
6131
|
return s;
|
|
5820
6132
|
}
|
|
5821
|
-
function
|
|
6133
|
+
function buildPushTreeSections(st, verdict) {
|
|
5822
6134
|
const leakScan = section("Leak scan");
|
|
5823
6135
|
addItem(leakScan, verdict.verdictRow);
|
|
5824
|
-
|
|
6136
|
+
return [...syncedSections(st), leakScan, summarySection(st)];
|
|
5825
6137
|
}
|
|
5826
|
-
function
|
|
6138
|
+
function renderPushTree(st, verdict) {
|
|
6139
|
+
renderTree(buildPushTreeSections(st, verdict));
|
|
6140
|
+
}
|
|
6141
|
+
function buildNoScanSections(st, opts = {}) {
|
|
5827
6142
|
const sections = [];
|
|
5828
6143
|
if (opts.noMapHint === true) {
|
|
5829
6144
|
const pathMap = section("Path map");
|
|
5830
6145
|
addItem(pathMap, `${dim(infoGlyph)} no path-map.json (nothing to preview)`);
|
|
5831
6146
|
sections.push(pathMap);
|
|
5832
6147
|
}
|
|
5833
|
-
|
|
6148
|
+
return [...sections, ...syncedSections(st), summarySection(st)];
|
|
6149
|
+
}
|
|
6150
|
+
function renderNoScanTree(st, opts = {}) {
|
|
6151
|
+
renderTree(buildNoScanSections(st, opts));
|
|
5834
6152
|
}
|
|
5835
6153
|
|
|
5836
6154
|
// src/commands.pull.ts
|
|
@@ -5838,18 +6156,18 @@ init_config();
|
|
|
5838
6156
|
|
|
5839
6157
|
// src/extras-sync.ts
|
|
5840
6158
|
init_config();
|
|
5841
|
-
import { existsSync as
|
|
5842
|
-
import { join as
|
|
6159
|
+
import { existsSync as existsSync39, statSync as statSync10 } from "node:fs";
|
|
6160
|
+
import { join as join47 } from "node:path";
|
|
5843
6161
|
init_utils();
|
|
5844
6162
|
init_utils_json();
|
|
5845
6163
|
|
|
5846
6164
|
// src/extras-sync.remap.ts
|
|
5847
6165
|
init_config();
|
|
5848
|
-
import { existsSync as
|
|
5849
|
-
import { dirname as
|
|
6166
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync10, readdirSync as readdirSync15, readFileSync as readFileSync18, realpathSync as realpathSync4, rmSync as rmSync14 } from "node:fs";
|
|
6167
|
+
import { dirname as dirname10, join as join46, sep as sep8 } from "node:path";
|
|
5850
6168
|
|
|
5851
6169
|
// src/extras-sync.planning-diff.ts
|
|
5852
|
-
import { join as
|
|
6170
|
+
import { join as join45, normalize as normalize2, sep as sep7 } from "node:path";
|
|
5853
6171
|
init_utils();
|
|
5854
6172
|
function processRecord(fields, i, changed, deleted) {
|
|
5855
6173
|
const status = fields[i];
|
|
@@ -5901,7 +6219,7 @@ function planningDeleteTargets(opts) {
|
|
|
5901
6219
|
const { deleted } = parsePlanningDiff(raw);
|
|
5902
6220
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
5903
6221
|
const prefix = logicalPrefix + ".planning/";
|
|
5904
|
-
const planningRoot =
|
|
6222
|
+
const planningRoot = join45(localRoot, ".planning");
|
|
5905
6223
|
const planningRootBoundary = planningRoot + sep7;
|
|
5906
6224
|
const targets = [];
|
|
5907
6225
|
for (const repoPath of deleted) {
|
|
@@ -5909,7 +6227,7 @@ function planningDeleteTargets(opts) {
|
|
|
5909
6227
|
continue;
|
|
5910
6228
|
}
|
|
5911
6229
|
const remainder = repoPath.slice(logicalPrefix.length);
|
|
5912
|
-
const candidate =
|
|
6230
|
+
const candidate = join45(localRoot, remainder);
|
|
5913
6231
|
const resolved = normalize2(candidate);
|
|
5914
6232
|
if (!resolved.startsWith(planningRootBoundary)) {
|
|
5915
6233
|
throw new NomadFatal(
|
|
@@ -5930,7 +6248,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
5930
6248
|
const would = [];
|
|
5931
6249
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
5932
6250
|
const { src, dst } = paths(t);
|
|
5933
|
-
if (!
|
|
6251
|
+
if (!existsSync38(src)) continue;
|
|
5934
6252
|
const item2 = `${t.logical}/${t.dirname}`;
|
|
5935
6253
|
if (dryRun) {
|
|
5936
6254
|
would.push(item2);
|
|
@@ -5943,15 +6261,15 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
5943
6261
|
return { ...counts, done, would };
|
|
5944
6262
|
}
|
|
5945
6263
|
function pruneEmptyAncestors(target, planningRoot) {
|
|
5946
|
-
let dir =
|
|
6264
|
+
let dir = dirname10(target);
|
|
5947
6265
|
while (dir !== planningRoot && dir.startsWith(planningRoot + sep8)) {
|
|
5948
6266
|
try {
|
|
5949
6267
|
if (readdirSync15(dir).length > 0) break;
|
|
5950
|
-
|
|
6268
|
+
rmSync14(dir, { recursive: true, force: true });
|
|
5951
6269
|
} catch {
|
|
5952
6270
|
break;
|
|
5953
6271
|
}
|
|
5954
|
-
dir =
|
|
6272
|
+
dir = dirname10(dir);
|
|
5955
6273
|
}
|
|
5956
6274
|
}
|
|
5957
6275
|
function tryRealpath(dir) {
|
|
@@ -5965,20 +6283,20 @@ function isInsidePlanningRoot(parentReal, rootReal) {
|
|
|
5965
6283
|
return parentReal === rootReal || parentReal.startsWith(rootReal + sep8);
|
|
5966
6284
|
}
|
|
5967
6285
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
5968
|
-
if (
|
|
5969
|
-
const parentReal = tryRealpath(
|
|
6286
|
+
if (existsSync38(repoCounterpart)) return;
|
|
6287
|
+
const parentReal = tryRealpath(dirname10(target));
|
|
5970
6288
|
if (parentReal === void 0) return;
|
|
5971
6289
|
const rootReal = tryRealpath(planningRoot);
|
|
5972
6290
|
if (rootReal === void 0) return;
|
|
5973
6291
|
if (!isInsidePlanningRoot(parentReal, rootReal)) return;
|
|
5974
|
-
|
|
6292
|
+
rmSync14(target, { recursive: true, force: true });
|
|
5975
6293
|
pruneEmptyAncestors(target, planningRoot);
|
|
5976
6294
|
}
|
|
5977
6295
|
function localDivergesFromPreDelete(target, pre, repoRel, repo) {
|
|
5978
|
-
if (!
|
|
6296
|
+
if (!existsSync38(target)) return false;
|
|
5979
6297
|
try {
|
|
5980
6298
|
const preBlob = gitCaptureBuffer(["show", `${pre}:${repoRel}`], repo);
|
|
5981
|
-
return !
|
|
6299
|
+
return !readFileSync18(target).equals(preBlob);
|
|
5982
6300
|
} catch {
|
|
5983
6301
|
return true;
|
|
5984
6302
|
}
|
|
@@ -6020,7 +6338,7 @@ function keptDeletePreview(v, prePostHeads, repo) {
|
|
|
6020
6338
|
return kept;
|
|
6021
6339
|
}
|
|
6022
6340
|
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
6023
|
-
const repoExtras =
|
|
6341
|
+
const repoExtras = join46(repo, "shared", "extras");
|
|
6024
6342
|
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
6025
6343
|
if (t.dirname !== ".planning") continue;
|
|
6026
6344
|
let raw;
|
|
@@ -6035,14 +6353,14 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
6035
6353
|
}
|
|
6036
6354
|
const pairs = deletePairsFor(t, raw);
|
|
6037
6355
|
if (pairs.length === 0) continue;
|
|
6038
|
-
backupExtrasWrite(
|
|
6039
|
-
const planningRoot =
|
|
6356
|
+
backupExtrasWrite(join46(t.localRoot, t.dirname), ts, t.localRoot);
|
|
6357
|
+
const planningRoot = join46(t.localRoot, ".planning");
|
|
6040
6358
|
for (const { target, relToLocal, repoRel } of pairs) {
|
|
6041
6359
|
if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
|
|
6042
6360
|
warn(keptDeleteWarnLine(t.logical, relToLocal));
|
|
6043
6361
|
continue;
|
|
6044
6362
|
}
|
|
6045
|
-
deletePlanningTarget(target, planningRoot,
|
|
6363
|
+
deletePlanningTarget(target, planningRoot, join46(repoExtras, t.logical, relToLocal));
|
|
6046
6364
|
}
|
|
6047
6365
|
}
|
|
6048
6366
|
}
|
|
@@ -6051,14 +6369,14 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
6051
6369
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
6052
6370
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
6053
6371
|
const repo = repoHome();
|
|
6054
|
-
const repoExtras =
|
|
6055
|
-
if (!dryRun)
|
|
6372
|
+
const repoExtras = join46(repo, "shared", "extras");
|
|
6373
|
+
if (!dryRun) mkdirSync10(repoExtras, { recursive: true });
|
|
6056
6374
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
6057
6375
|
v,
|
|
6058
6376
|
dryRun,
|
|
6059
|
-
({ localRoot, logical, dirname:
|
|
6060
|
-
src:
|
|
6061
|
-
dst:
|
|
6377
|
+
({ localRoot, logical, dirname: dirname11 }) => ({
|
|
6378
|
+
src: join46(localRoot, dirname11),
|
|
6379
|
+
dst: join46(repoExtras, logical, dirname11)
|
|
6062
6380
|
}),
|
|
6063
6381
|
(dst) => backupRepoWrite(dst, ts, repo),
|
|
6064
6382
|
// Push copy routing per extra type:
|
|
@@ -6070,7 +6388,7 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
6070
6388
|
// (enforceAllowList / blockSetFor in commands.push.allowlist.ts)
|
|
6071
6389
|
// remains the hard security boundary.
|
|
6072
6390
|
// All others: copyExtrasFiltered with per-extra denylist.
|
|
6073
|
-
(src, dst,
|
|
6391
|
+
(src, dst, dirname11) => dirname11 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname11)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname11))
|
|
6074
6392
|
);
|
|
6075
6393
|
return { unmapped, skipped, pushed: done, wouldPush: would };
|
|
6076
6394
|
}
|
|
@@ -6086,9 +6404,9 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
6086
6404
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
6087
6405
|
v,
|
|
6088
6406
|
dryRun,
|
|
6089
|
-
({ localRoot, logical, dirname:
|
|
6090
|
-
src:
|
|
6091
|
-
dst:
|
|
6407
|
+
({ localRoot, logical, dirname: dirname11 }) => ({
|
|
6408
|
+
src: join46(repo, "shared", "extras", logical, dirname11),
|
|
6409
|
+
dst: join46(localRoot, dirname11)
|
|
6092
6410
|
}),
|
|
6093
6411
|
// Snapshot the host-side dst BEFORE the copy step touches it. Anchor on
|
|
6094
6412
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -6106,12 +6424,12 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
6106
6424
|
// copyExtrasFileSkipDiverged keeps a locally-edited file rather than
|
|
6107
6425
|
// clobbering it, so the divergence WARN's keep-local promise holds for
|
|
6108
6426
|
// every extra, not just `.planning`.
|
|
6109
|
-
(src, dst,
|
|
6110
|
-
if (
|
|
6111
|
-
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(
|
|
6112
|
-
if (
|
|
6427
|
+
(src, dst, dirname11) => {
|
|
6428
|
+
if (dirname11 === ".claude")
|
|
6429
|
+
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname11));
|
|
6430
|
+
if (dirname11 === ".planning") {
|
|
6113
6431
|
const divergedSet = new Set(listDivergingModified(dst, src));
|
|
6114
|
-
return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(
|
|
6432
|
+
return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname11), divergedSet);
|
|
6115
6433
|
}
|
|
6116
6434
|
return copyExtrasFileSkipDiverged(src, dst);
|
|
6117
6435
|
}
|
|
@@ -6135,20 +6453,20 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
6135
6453
|
const v = loadValidatedExtras({});
|
|
6136
6454
|
if (v === null) return 0;
|
|
6137
6455
|
const counts = { unmapped: 0, skipped: 0 };
|
|
6138
|
-
const backupRoot =
|
|
6456
|
+
const backupRoot = join47(backupBase(), ts, "extras");
|
|
6139
6457
|
const repo = repoHome();
|
|
6140
6458
|
let divergedCount = 0;
|
|
6141
|
-
for (const { logical, localRoot, dirname:
|
|
6142
|
-
const local =
|
|
6143
|
-
const repoEntry =
|
|
6144
|
-
if (!
|
|
6459
|
+
for (const { logical, localRoot, dirname: dirname11 } of eachExtrasTarget(v, counts)) {
|
|
6460
|
+
const local = join47(localRoot, dirname11);
|
|
6461
|
+
const repoEntry = join47(repo, "shared", "extras", logical, dirname11);
|
|
6462
|
+
if (!existsSync39(local) || !existsSync39(repoEntry)) continue;
|
|
6145
6463
|
const modified = listDivergingModified(local, repoEntry);
|
|
6146
6464
|
if (modified.length === 0) continue;
|
|
6147
6465
|
divergedCount += modified.length;
|
|
6148
|
-
const projectBackupRoot =
|
|
6466
|
+
const projectBackupRoot = join47(backupRoot, encodePath(localRoot));
|
|
6149
6467
|
warn(
|
|
6150
6468
|
divergenceWarnLine({
|
|
6151
|
-
dirname:
|
|
6469
|
+
dirname: dirname11,
|
|
6152
6470
|
logical,
|
|
6153
6471
|
isDir: statSync10(local).isDirectory(),
|
|
6154
6472
|
count: modified.length,
|
|
@@ -6167,8 +6485,8 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
6167
6485
|
|
|
6168
6486
|
// src/skills-sync.ts
|
|
6169
6487
|
init_config();
|
|
6170
|
-
import { existsSync as
|
|
6171
|
-
import { join as
|
|
6488
|
+
import { existsSync as existsSync40, lstatSync as lstatSync12, mkdirSync as mkdirSync11, readdirSync as readdirSync16, rmSync as rmSync15 } from "node:fs";
|
|
6489
|
+
import { join as join48 } from "node:path";
|
|
6172
6490
|
init_utils_fs();
|
|
6173
6491
|
function isGsdOwned(name) {
|
|
6174
6492
|
return name.startsWith(GSD_PREFIX);
|
|
@@ -6188,30 +6506,30 @@ function copySkillsPull(src, dst) {
|
|
|
6188
6506
|
copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
|
|
6189
6507
|
}
|
|
6190
6508
|
function syncSkillsPull(ts) {
|
|
6191
|
-
const sharedSkills =
|
|
6192
|
-
if (!
|
|
6193
|
-
const localSkills =
|
|
6509
|
+
const sharedSkills = join48(repoHome(), "shared", "skills");
|
|
6510
|
+
if (!existsSync40(sharedSkills)) return;
|
|
6511
|
+
const localSkills = join48(claudeHome(), "skills");
|
|
6194
6512
|
const dstStat = lstatSync12(localSkills, { throwIfNoEntry: false });
|
|
6195
6513
|
if (dstStat?.isSymbolicLink() === true) {
|
|
6196
6514
|
backupBeforeWrite(localSkills, ts);
|
|
6197
|
-
|
|
6198
|
-
|
|
6515
|
+
rmSync15(localSkills, { recursive: true, force: true });
|
|
6516
|
+
mkdirSync11(localSkills, { recursive: true });
|
|
6199
6517
|
}
|
|
6200
6518
|
copySkillsPull(sharedSkills, localSkills);
|
|
6201
6519
|
}
|
|
6202
6520
|
function syncSkillsPush() {
|
|
6203
|
-
const localSkills =
|
|
6521
|
+
const localSkills = join48(claudeHome(), "skills");
|
|
6204
6522
|
const stat = lstatSync12(localSkills, { throwIfNoEntry: false });
|
|
6205
6523
|
if (stat === void 0) return;
|
|
6206
6524
|
if (stat.isSymbolicLink()) return;
|
|
6207
|
-
const sharedSkills =
|
|
6525
|
+
const sharedSkills = join48(repoHome(), "shared", "skills");
|
|
6208
6526
|
copySkillsPush(localSkills, sharedSkills);
|
|
6209
6527
|
}
|
|
6210
6528
|
|
|
6211
6529
|
// src/preview.ts
|
|
6212
6530
|
init_config();
|
|
6213
|
-
import { existsSync as
|
|
6214
|
-
import { join as
|
|
6531
|
+
import { existsSync as existsSync41 } from "node:fs";
|
|
6532
|
+
import { join as join49 } from "node:path";
|
|
6215
6533
|
|
|
6216
6534
|
// node_modules/diff/libesm/diff/base.js
|
|
6217
6535
|
var Diff = class {
|
|
@@ -6497,7 +6815,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
6497
6815
|
return lines.join("\n");
|
|
6498
6816
|
}
|
|
6499
6817
|
function readJsonOrNull(path) {
|
|
6500
|
-
if (!
|
|
6818
|
+
if (!existsSync41(path)) return null;
|
|
6501
6819
|
try {
|
|
6502
6820
|
return readJson(path);
|
|
6503
6821
|
} catch {
|
|
@@ -6511,12 +6829,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
6511
6829
|
}
|
|
6512
6830
|
const notes = [];
|
|
6513
6831
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
6514
|
-
if (hostOverrides === null &&
|
|
6832
|
+
if (hostOverrides === null && existsSync41(hostPath)) {
|
|
6515
6833
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
6516
6834
|
}
|
|
6517
6835
|
const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
|
|
6518
6836
|
const current = readJsonOrNull(settingsPath);
|
|
6519
|
-
if (current === null &&
|
|
6837
|
+
if (current === null && existsSync41(settingsPath)) {
|
|
6520
6838
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
6521
6839
|
}
|
|
6522
6840
|
const strippedCurrent = stripGsdHookEntries(current ?? {});
|
|
@@ -6558,9 +6876,9 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
6558
6876
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
6559
6877
|
});
|
|
6560
6878
|
const settingsResult = previewSettings(
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6879
|
+
join49(repo, "shared", "settings.base.json"),
|
|
6880
|
+
join49(repo, "hosts", `${HOST}.json`),
|
|
6881
|
+
join49(claude, "settings.json")
|
|
6564
6882
|
);
|
|
6565
6883
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
6566
6884
|
const sessions = section("Sessions");
|
|
@@ -6575,7 +6893,7 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
6575
6893
|
const extras = section("Extras");
|
|
6576
6894
|
let extrasSkipped = 0;
|
|
6577
6895
|
let extrasUnmapped = 0;
|
|
6578
|
-
if (
|
|
6896
|
+
if (existsSync41(join49(repo, "path-map.json")) && existsSync41(join49(repo, "shared", "extras"))) {
|
|
6579
6897
|
const extrasResult = remapExtrasPull(ts, { dryRun: true });
|
|
6580
6898
|
for (const entry of extrasResult.wouldPull) {
|
|
6581
6899
|
addItem(extras, entry);
|
|
@@ -6600,9 +6918,9 @@ init_config();
|
|
|
6600
6918
|
init_commands_pull_wedge();
|
|
6601
6919
|
init_utils();
|
|
6602
6920
|
init_utils_fs();
|
|
6603
|
-
import { execFileSync as
|
|
6921
|
+
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
6604
6922
|
function gitCapture(args, cwd) {
|
|
6605
|
-
return
|
|
6923
|
+
return execFileSync20("git", args, {
|
|
6606
6924
|
cwd,
|
|
6607
6925
|
stdio: ["ignore", "pipe", "pipe"],
|
|
6608
6926
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -6746,17 +7064,9 @@ function buildWetPullSections(ts, map, prePostHeads) {
|
|
|
6746
7064
|
const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
|
|
6747
7065
|
const extrasResult = remapExtrasPull(ts, { prePostHeads });
|
|
6748
7066
|
const localOnly = scanLocalOnly();
|
|
7067
|
+
const unmapped = remapResult.unmapped + extrasResult.unmapped;
|
|
6749
7068
|
const summary = section(PULL_SUMMARY_HEADER);
|
|
6750
|
-
addItem(
|
|
6751
|
-
summary,
|
|
6752
|
-
summaryRow(
|
|
6753
|
-
"pull",
|
|
6754
|
-
remapResult.unmapped + extrasResult.unmapped,
|
|
6755
|
-
0,
|
|
6756
|
-
extrasResult.skipped,
|
|
6757
|
-
localOnly
|
|
6758
|
-
)
|
|
6759
|
-
);
|
|
7069
|
+
addItem(summary, summaryRow("pull", unmapped, 0, extrasResult.skipped, localOnly));
|
|
6760
7070
|
return {
|
|
6761
7071
|
sections: [
|
|
6762
7072
|
buildSettingsSection(label),
|
|
@@ -6764,7 +7074,10 @@ function buildWetPullSections(ts, map, prePostHeads) {
|
|
|
6764
7074
|
buildExtrasSection(extrasResult.pulled, extrasResult.skipped),
|
|
6765
7075
|
summary
|
|
6766
7076
|
],
|
|
6767
|
-
localOnly
|
|
7077
|
+
localOnly,
|
|
7078
|
+
settingsLabel: label,
|
|
7079
|
+
unmapped,
|
|
7080
|
+
extrasSkipped: extrasResult.skipped
|
|
6768
7081
|
};
|
|
6769
7082
|
}
|
|
6770
7083
|
function handleWedge(repo, forceRemote) {
|
|
@@ -6788,39 +7101,56 @@ function handleWedge(repo, forceRemote) {
|
|
|
6788
7101
|
function runPullCore(opts = {}) {
|
|
6789
7102
|
const dryRun = opts.dryRun === true;
|
|
6790
7103
|
const forceRemote = opts.forceRemote === true;
|
|
7104
|
+
const compose = opts.compose === true;
|
|
6791
7105
|
const repo = repoHome();
|
|
6792
7106
|
const backup = backupBase();
|
|
6793
7107
|
const ts = freshBackupTs(backup);
|
|
6794
7108
|
handleWedge(repo, forceRemote);
|
|
6795
7109
|
if (!dryRun) {
|
|
6796
|
-
const backupRoot =
|
|
7110
|
+
const backupRoot = join50(backup, ts);
|
|
6797
7111
|
try {
|
|
6798
|
-
|
|
7112
|
+
mkdirSync12(backupRoot, { recursive: true });
|
|
6799
7113
|
} catch (err) {
|
|
6800
7114
|
die(`could not create backup dir: ${err.message}`);
|
|
6801
7115
|
}
|
|
6802
7116
|
}
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
7117
|
+
if (!compose) {
|
|
7118
|
+
log(
|
|
7119
|
+
dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
|
|
7120
|
+
);
|
|
7121
|
+
}
|
|
6806
7122
|
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
6807
7123
|
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
6808
7124
|
});
|
|
6809
|
-
const mapPath =
|
|
6810
|
-
const map =
|
|
7125
|
+
const mapPath = join50(repo, "path-map.json");
|
|
7126
|
+
const map = existsSync42(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
6811
7127
|
const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
|
|
6812
7128
|
if (dryRun) {
|
|
6813
7129
|
computePreview(ts, map, "pull");
|
|
6814
7130
|
log("dry-run complete; no mutation");
|
|
6815
7131
|
return { tag: "dry" };
|
|
6816
7132
|
}
|
|
6817
|
-
const { sections, localOnly } = buildWetPullSections(
|
|
6818
|
-
|
|
7133
|
+
const { sections, localOnly, settingsLabel, unmapped, extrasSkipped } = buildWetPullSections(
|
|
7134
|
+
ts,
|
|
7135
|
+
map,
|
|
7136
|
+
prePostHeads
|
|
7137
|
+
);
|
|
7138
|
+
const incomingChanges = prePostHeads === void 0 ? true : prePostHeads.pre !== prePostHeads.post;
|
|
7139
|
+
return {
|
|
7140
|
+
tag: "wet",
|
|
7141
|
+
sections,
|
|
7142
|
+
localOnly,
|
|
7143
|
+
divergedKeptLocal,
|
|
7144
|
+
incomingChanges,
|
|
7145
|
+
settingsLabel,
|
|
7146
|
+
unmapped,
|
|
7147
|
+
extrasSkipped
|
|
7148
|
+
};
|
|
6819
7149
|
}
|
|
6820
7150
|
function cmdPull(opts = {}) {
|
|
6821
7151
|
const repo = repoHome();
|
|
6822
|
-
if (!
|
|
6823
|
-
if (!
|
|
7152
|
+
if (!existsSync42(repo)) die(`repo not cloned at ${repo}`);
|
|
7153
|
+
if (!existsSync42(join50(repo, "shared", "settings.base.json"))) {
|
|
6824
7154
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
6825
7155
|
}
|
|
6826
7156
|
const handle = acquireLock("pull");
|
|
@@ -6842,13 +7172,13 @@ function cmdPull(opts = {}) {
|
|
|
6842
7172
|
|
|
6843
7173
|
// src/commands.push.ts
|
|
6844
7174
|
init_config();
|
|
6845
|
-
import { existsSync as
|
|
6846
|
-
import { join as
|
|
7175
|
+
import { existsSync as existsSync46 } from "node:fs";
|
|
7176
|
+
import { join as join55 } from "node:path";
|
|
6847
7177
|
|
|
6848
7178
|
// src/commands.push.selection.ts
|
|
6849
7179
|
init_config();
|
|
6850
|
-
import { existsSync as
|
|
6851
|
-
import { join as
|
|
7180
|
+
import { existsSync as existsSync43, statSync as statSync11 } from "node:fs";
|
|
7181
|
+
import { join as join51 } from "node:path";
|
|
6852
7182
|
init_utils_json();
|
|
6853
7183
|
function buildCurrentMap(map) {
|
|
6854
7184
|
const current = {};
|
|
@@ -6857,8 +7187,8 @@ function buildCurrentMap(map) {
|
|
|
6857
7187
|
for (const [, hostMap] of Object.entries(map.projects)) {
|
|
6858
7188
|
const localPath = hostMap[HOST];
|
|
6859
7189
|
if (!localPath) continue;
|
|
6860
|
-
const localDir =
|
|
6861
|
-
if (!
|
|
7190
|
+
const localDir = join51(claude, "projects", encodePath(localPath));
|
|
7191
|
+
if (!existsSync43(localDir)) continue;
|
|
6862
7192
|
for (const f of enumerateSourceFiles(localDir)) {
|
|
6863
7193
|
const st = statSync11(f);
|
|
6864
7194
|
current[f] = { size: st.size, mtime: st.mtimeMs };
|
|
@@ -6889,7 +7219,7 @@ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
|
|
|
6889
7219
|
};
|
|
6890
7220
|
}
|
|
6891
7221
|
function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
|
|
6892
|
-
const map =
|
|
7222
|
+
const map = existsSync43(mapPath) ? readPathMap(mapPath) : null;
|
|
6893
7223
|
const { selection, newManifest } = computePushSelection(
|
|
6894
7224
|
map,
|
|
6895
7225
|
old,
|
|
@@ -6991,14 +7321,14 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
6991
7321
|
|
|
6992
7322
|
// src/commands.push.settings.ts
|
|
6993
7323
|
init_config();
|
|
6994
|
-
import { existsSync as
|
|
6995
|
-
import { join as
|
|
7324
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
7325
|
+
import { join as join52 } from "node:path";
|
|
6996
7326
|
init_utils();
|
|
6997
7327
|
init_utils_fs();
|
|
6998
7328
|
init_utils_json();
|
|
6999
7329
|
function stripGsdHooksFromBase(repo, backup) {
|
|
7000
|
-
const basePath =
|
|
7001
|
-
if (!
|
|
7330
|
+
const basePath = join52(repo, "shared", "settings.base.json");
|
|
7331
|
+
if (!existsSync44(basePath)) return;
|
|
7002
7332
|
let base;
|
|
7003
7333
|
try {
|
|
7004
7334
|
base = readJson(basePath);
|
|
@@ -7012,14 +7342,14 @@ function stripGsdHooksFromBase(repo, backup) {
|
|
|
7012
7342
|
writeJsonAtomic(basePath, stripped);
|
|
7013
7343
|
}
|
|
7014
7344
|
function reportSettingsAheadDrift(repo) {
|
|
7015
|
-
const basePath =
|
|
7016
|
-
if (!
|
|
7017
|
-
const settingsPath =
|
|
7018
|
-
if (!
|
|
7345
|
+
const basePath = join52(repo, "shared", "settings.base.json");
|
|
7346
|
+
if (!existsSync44(basePath)) return;
|
|
7347
|
+
const settingsPath = join52(claudeHome(), "settings.json");
|
|
7348
|
+
if (!existsSync44(settingsPath)) return;
|
|
7019
7349
|
try {
|
|
7020
7350
|
const base = readJson(basePath);
|
|
7021
|
-
const hostPath =
|
|
7022
|
-
const overrides =
|
|
7351
|
+
const hostPath = join52(repo, "hosts", `${HOST}.json`);
|
|
7352
|
+
const overrides = existsSync44(hostPath) ? readJson(hostPath) : {};
|
|
7023
7353
|
const merged = deepMerge(base, overrides);
|
|
7024
7354
|
const settings = readJson(settingsPath);
|
|
7025
7355
|
const { ahead } = classifySettingsDrift(merged, settings);
|
|
@@ -7036,9 +7366,9 @@ function reportSettingsAheadDrift(repo) {
|
|
|
7036
7366
|
// src/commands.push.guards.ts
|
|
7037
7367
|
init_push_checks();
|
|
7038
7368
|
init_utils();
|
|
7039
|
-
import { join as
|
|
7369
|
+
import { join as join53, relative as relative7 } from "node:path";
|
|
7040
7370
|
function guardGitlinks(repo) {
|
|
7041
|
-
const gitlinks = findGitlinks(
|
|
7371
|
+
const gitlinks = findGitlinks(join53(repo, "shared"));
|
|
7042
7372
|
if (gitlinks.length === 0) return;
|
|
7043
7373
|
for (const p of gitlinks) {
|
|
7044
7374
|
const rel = relative7(repo, p).replaceAll("\\", "/");
|
|
@@ -7070,7 +7400,7 @@ init_config();
|
|
|
7070
7400
|
|
|
7071
7401
|
// src/push-global-config.ts
|
|
7072
7402
|
init_config();
|
|
7073
|
-
import { execFileSync as
|
|
7403
|
+
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
7074
7404
|
var STATUS_LABELS = {
|
|
7075
7405
|
A: "add",
|
|
7076
7406
|
M: "modify",
|
|
@@ -7110,7 +7440,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
|
|
|
7110
7440
|
}
|
|
7111
7441
|
function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
|
|
7112
7442
|
const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
|
|
7113
|
-
const raw =
|
|
7443
|
+
const raw = execFileSync21("git", args, {
|
|
7114
7444
|
cwd: repoHome2,
|
|
7115
7445
|
stdio: ["ignore", "pipe", "pipe"]
|
|
7116
7446
|
}).toString();
|
|
@@ -7147,10 +7477,10 @@ init_push_leak_verdict();
|
|
|
7147
7477
|
init_color();
|
|
7148
7478
|
init_config();
|
|
7149
7479
|
init_config_sharedDirs_guard();
|
|
7150
|
-
import { randomBytes as
|
|
7151
|
-
import { copyFileSync, existsSync as
|
|
7152
|
-
import { homedir as
|
|
7153
|
-
import { join as
|
|
7480
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
7481
|
+
import { copyFileSync, existsSync as existsSync45, mkdirSync as mkdirSync13, readdirSync as readdirSync17, rmSync as rmSync16 } from "node:fs";
|
|
7482
|
+
import { homedir as homedir6 } from "node:os";
|
|
7483
|
+
import { join as join54, relative as relative8, sep as sep9 } from "node:path";
|
|
7154
7484
|
init_push_leak_verdict();
|
|
7155
7485
|
init_push_gitleaks();
|
|
7156
7486
|
init_utils_fs();
|
|
@@ -7162,7 +7492,7 @@ function stageSessionDir(localDir, dstDir, changed) {
|
|
|
7162
7492
|
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
7163
7493
|
if (matching.length === 0) return false;
|
|
7164
7494
|
for (const src of matching) {
|
|
7165
|
-
copyFileAtomic(src,
|
|
7495
|
+
copyFileAtomic(src, join54(dstDir, relative8(localDir, src)));
|
|
7166
7496
|
}
|
|
7167
7497
|
return true;
|
|
7168
7498
|
}
|
|
@@ -7178,14 +7508,14 @@ function stageSessions(tmpRoot, map, changed) {
|
|
|
7178
7508
|
if (!p || p === "TBD") continue;
|
|
7179
7509
|
reverse.set(encodePath(p), logical);
|
|
7180
7510
|
}
|
|
7181
|
-
const localProjects =
|
|
7182
|
-
if (!
|
|
7511
|
+
const localProjects = join54(claudeHome(), "projects");
|
|
7512
|
+
if (!existsSync45(localProjects)) return 0;
|
|
7183
7513
|
let staged = 0;
|
|
7184
7514
|
for (const dir of readdirSync17(localProjects)) {
|
|
7185
7515
|
const logical = reverse.get(dir);
|
|
7186
7516
|
if (!logical) continue;
|
|
7187
|
-
const localDir =
|
|
7188
|
-
const dstDir =
|
|
7517
|
+
const localDir = join54(localProjects, dir);
|
|
7518
|
+
const dstDir = join54(tmpRoot, "shared", "projects", logical);
|
|
7189
7519
|
if (stageSessionDir(localDir, dstDir, changed)) staged++;
|
|
7190
7520
|
}
|
|
7191
7521
|
return staged;
|
|
@@ -7199,11 +7529,11 @@ function stageExtras(tmpRoot, map) {
|
|
|
7199
7529
|
assertSafeLogical(logical);
|
|
7200
7530
|
const localRoot = map.projects[logical]?.[HOST];
|
|
7201
7531
|
if (!localRoot || localRoot === "TBD") continue;
|
|
7202
|
-
for (const
|
|
7203
|
-
if (!whitelist.includes(
|
|
7204
|
-
const src =
|
|
7205
|
-
if (!
|
|
7206
|
-
const dst =
|
|
7532
|
+
for (const dirname11 of dirnames) {
|
|
7533
|
+
if (!whitelist.includes(dirname11)) continue;
|
|
7534
|
+
const src = join54(localRoot, dirname11);
|
|
7535
|
+
if (!existsSync45(src)) continue;
|
|
7536
|
+
const dst = join54(tmpRoot, "shared", "extras", logical, dirname11);
|
|
7207
7537
|
copyExtras(src, dst);
|
|
7208
7538
|
staged++;
|
|
7209
7539
|
}
|
|
@@ -7211,19 +7541,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
7211
7541
|
return staged;
|
|
7212
7542
|
}
|
|
7213
7543
|
function previewPushLeaks(map, opts = {}) {
|
|
7214
|
-
const cacheDir =
|
|
7215
|
-
|
|
7216
|
-
const stamp = `${nowTimestamp()}-${process.pid}-${
|
|
7217
|
-
const tmpRoot =
|
|
7544
|
+
const cacheDir = join54(homedir6(), ".cache", "claude-nomad");
|
|
7545
|
+
mkdirSync13(cacheDir, { recursive: true });
|
|
7546
|
+
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes3(4).toString("hex")}`;
|
|
7547
|
+
const tmpRoot = join54(cacheDir, `push-preview-tree-${stamp}`);
|
|
7218
7548
|
try {
|
|
7219
7549
|
const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
|
|
7220
7550
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
7221
7551
|
if (sessionCount + extrasCount === 0) {
|
|
7222
7552
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
7223
7553
|
}
|
|
7224
|
-
const ignoreFile =
|
|
7225
|
-
if (
|
|
7226
|
-
copyFileSync(ignoreFile,
|
|
7554
|
+
const ignoreFile = join54(repoHome(), ".gitleaksignore");
|
|
7555
|
+
if (existsSync45(ignoreFile)) {
|
|
7556
|
+
copyFileSync(ignoreFile, join54(tmpRoot, ".gitleaksignore"));
|
|
7227
7557
|
}
|
|
7228
7558
|
let findings;
|
|
7229
7559
|
try {
|
|
@@ -7236,13 +7566,13 @@ function previewPushLeaks(map, opts = {}) {
|
|
|
7236
7566
|
}
|
|
7237
7567
|
return verdictFromFindings(findings);
|
|
7238
7568
|
} finally {
|
|
7239
|
-
|
|
7569
|
+
rmSync16(tmpRoot, { recursive: true, force: true });
|
|
7240
7570
|
}
|
|
7241
7571
|
}
|
|
7242
7572
|
|
|
7243
7573
|
// src/commands.push.steps.ts
|
|
7244
7574
|
init_utils();
|
|
7245
|
-
async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
|
|
7575
|
+
async function commitAndPush(st, ts, map, resolution, repo, newManifest, render) {
|
|
7246
7576
|
gitOrFatal(["add", "-A"], "git add", repo);
|
|
7247
7577
|
const staged = parsePorcelainZ2(gitStatusPorcelainZ(repo));
|
|
7248
7578
|
const toDrop = staged.filter((p) => isGsdDropped(p));
|
|
@@ -7250,13 +7580,18 @@ async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
|
|
|
7250
7580
|
gitOrFatal(["restore", "--staged", "--", ...toDrop], "git restore --staged", repo);
|
|
7251
7581
|
}
|
|
7252
7582
|
if (staged.length === toDrop.length) {
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7583
|
+
const sections2 = buildNoScanSections(st);
|
|
7584
|
+
if (render) {
|
|
7585
|
+
log("nothing to commit");
|
|
7586
|
+
renderTree(sections2);
|
|
7587
|
+
}
|
|
7588
|
+
return { outcome: "nothing", sections: sections2 };
|
|
7256
7589
|
}
|
|
7257
7590
|
st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: true });
|
|
7258
7591
|
let verdict = withSpinner("Scanning for secrets", () => scanPushVerdict(repo));
|
|
7592
|
+
const hadLeak = verdict.leak;
|
|
7259
7593
|
if (verdict.leak) {
|
|
7594
|
+
if (!render) log("push (leak recovery)");
|
|
7260
7595
|
renderPushTree(st, verdict);
|
|
7261
7596
|
verdict = await resolveLeakFindings(verdict, ts, map, resolution);
|
|
7262
7597
|
}
|
|
@@ -7267,8 +7602,13 @@ async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
|
|
|
7267
7602
|
} catch (err) {
|
|
7268
7603
|
warn(`could not write push manifest (next push will full-rescan): ${String(err)}`);
|
|
7269
7604
|
}
|
|
7270
|
-
|
|
7271
|
-
|
|
7605
|
+
if (hadLeak) {
|
|
7606
|
+
renderPushTree(st, verdict);
|
|
7607
|
+
return { outcome: "pushed", sections: [] };
|
|
7608
|
+
}
|
|
7609
|
+
const sections = buildPushTreeSections(st, verdict);
|
|
7610
|
+
if (render) renderTree(sections);
|
|
7611
|
+
return { outcome: "pushed", sections };
|
|
7272
7612
|
}
|
|
7273
7613
|
function runDryRunPreview(st, map, repo, selection) {
|
|
7274
7614
|
st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: false });
|
|
@@ -7285,20 +7625,61 @@ function runDryRunPreview(st, map, repo, selection) {
|
|
|
7285
7625
|
init_push_checks();
|
|
7286
7626
|
init_utils();
|
|
7287
7627
|
init_utils_fs();
|
|
7628
|
+
function aheadOfUpstream(repo) {
|
|
7629
|
+
try {
|
|
7630
|
+
const raw = gitCaptureRaw(["rev-list", "--count", "@{u}..HEAD"], repo);
|
|
7631
|
+
return Number.parseInt(raw.trim(), 10) > 0;
|
|
7632
|
+
} catch {
|
|
7633
|
+
return false;
|
|
7634
|
+
}
|
|
7635
|
+
}
|
|
7636
|
+
function emptyStatusResult(st, compose, repo) {
|
|
7637
|
+
if (compose) {
|
|
7638
|
+
return {
|
|
7639
|
+
tag: "nothing",
|
|
7640
|
+
sections: buildNoScanSections(st),
|
|
7641
|
+
aheadOfOrigin: aheadOfUpstream(repo),
|
|
7642
|
+
globalConfigCount: st.globalConfig.length,
|
|
7643
|
+
collisions: st.remap.collisions
|
|
7644
|
+
};
|
|
7645
|
+
}
|
|
7646
|
+
log("nothing to commit");
|
|
7647
|
+
renderNoScanTree(st);
|
|
7648
|
+
return { tag: "nothing" };
|
|
7649
|
+
}
|
|
7650
|
+
function toPushCoreResult(outcome, sections, compose, repo, st) {
|
|
7651
|
+
if (!compose) return { tag: outcome };
|
|
7652
|
+
const globalConfigCount = st.globalConfig.length;
|
|
7653
|
+
const collisions = st.remap.collisions;
|
|
7654
|
+
if (outcome === "nothing") {
|
|
7655
|
+
return {
|
|
7656
|
+
tag: "nothing",
|
|
7657
|
+
sections,
|
|
7658
|
+
aheadOfOrigin: aheadOfUpstream(repo),
|
|
7659
|
+
globalConfigCount,
|
|
7660
|
+
collisions
|
|
7661
|
+
};
|
|
7662
|
+
}
|
|
7663
|
+
return { tag: "pushed", sections, globalConfigCount, collisions };
|
|
7664
|
+
}
|
|
7288
7665
|
async function runPushCore(opts = {}) {
|
|
7289
7666
|
const dryRun = opts.dryRun === true;
|
|
7290
7667
|
const redactAll = opts.redactAll === true;
|
|
7291
7668
|
const allowAll = opts.allowAll === true;
|
|
7292
7669
|
const allowRule = opts.allowRule;
|
|
7293
7670
|
const fullScan = opts.fullScan === true;
|
|
7671
|
+
const compose = opts.compose === true;
|
|
7672
|
+
const render = !compose;
|
|
7294
7673
|
const repo = repoHome();
|
|
7295
7674
|
const backup = backupBase();
|
|
7296
|
-
|
|
7675
|
+
if (!compose) {
|
|
7676
|
+
console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
|
|
7677
|
+
}
|
|
7297
7678
|
reportSettingsAheadDrift(repo);
|
|
7298
7679
|
const scannerVersion = probeGitleaks();
|
|
7299
7680
|
const configHash = computeConfigHash();
|
|
7300
7681
|
const old = readManifest(manifestPath());
|
|
7301
|
-
const mapPath =
|
|
7682
|
+
const mapPath = join55(repo, "path-map.json");
|
|
7302
7683
|
const { map, selection, newManifest } = loadSelectionForPush(
|
|
7303
7684
|
mapPath,
|
|
7304
7685
|
old,
|
|
@@ -7318,11 +7699,7 @@ async function runPushCore(opts = {}) {
|
|
|
7318
7699
|
const st = { dryRun, remap, extras, globalConfig: [] };
|
|
7319
7700
|
guardGitlinks(repo);
|
|
7320
7701
|
const status = gitStatusPorcelainZ(repo, { untrackedAll: true });
|
|
7321
|
-
if (!dryRun && !status)
|
|
7322
|
-
log("nothing to commit");
|
|
7323
|
-
renderNoScanTree(st);
|
|
7324
|
-
return { tag: "nothing" };
|
|
7325
|
-
}
|
|
7702
|
+
if (!dryRun && !status) return emptyStatusResult(st, compose, repo);
|
|
7326
7703
|
if (map === null) {
|
|
7327
7704
|
if (dryRun) {
|
|
7328
7705
|
runDryRunPreview(st, null, repo, selection);
|
|
@@ -7335,15 +7712,16 @@ async function runPushCore(opts = {}) {
|
|
|
7335
7712
|
runDryRunPreview(st, map, repo, selection);
|
|
7336
7713
|
return { tag: "dry" };
|
|
7337
7714
|
}
|
|
7338
|
-
const outcome = await commitAndPush(
|
|
7715
|
+
const { outcome, sections } = await commitAndPush(
|
|
7339
7716
|
st,
|
|
7340
7717
|
ts,
|
|
7341
7718
|
map,
|
|
7342
7719
|
{ redactAll, allowAll, allowRule },
|
|
7343
7720
|
repo,
|
|
7344
|
-
newManifest
|
|
7721
|
+
newManifest,
|
|
7722
|
+
render
|
|
7345
7723
|
);
|
|
7346
|
-
return outcome
|
|
7724
|
+
return toPushCoreResult(outcome, sections, compose, repo, st);
|
|
7347
7725
|
}
|
|
7348
7726
|
async function cmdPush(opts = {}) {
|
|
7349
7727
|
const dryRun = opts.dryRun === true;
|
|
@@ -7352,7 +7730,7 @@ async function cmdPush(opts = {}) {
|
|
|
7352
7730
|
const allowRule = opts.allowRule;
|
|
7353
7731
|
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
7354
7732
|
const repo = repoHome();
|
|
7355
|
-
if (!
|
|
7733
|
+
if (!existsSync46(repo)) die(`repo not cloned at ${repo}`);
|
|
7356
7734
|
const handle = acquireLock("push");
|
|
7357
7735
|
if (handle === null) process.exit(0);
|
|
7358
7736
|
try {
|
|
@@ -7370,36 +7748,59 @@ async function cmdPush(opts = {}) {
|
|
|
7370
7748
|
}
|
|
7371
7749
|
|
|
7372
7750
|
// src/commands.sync.ts
|
|
7373
|
-
import { existsSync as
|
|
7374
|
-
import { join as
|
|
7751
|
+
import { existsSync as existsSync47 } from "node:fs";
|
|
7752
|
+
import { join as join56 } from "node:path";
|
|
7375
7753
|
init_config();
|
|
7754
|
+
init_color();
|
|
7376
7755
|
init_utils();
|
|
7377
7756
|
init_utils_fs();
|
|
7378
7757
|
init_utils_json();
|
|
7379
|
-
function pullHasNoSyncedItems(sections) {
|
|
7380
|
-
return sections.every(
|
|
7381
|
-
(s) => s.header === "Settings" || s.header === PULL_SUMMARY_HEADER || s.items.length === 0
|
|
7382
|
-
);
|
|
7383
|
-
}
|
|
7384
7758
|
function isNoopSync(pull, pushOutcome) {
|
|
7385
7759
|
if (!pushOutcome.ok) return false;
|
|
7386
7760
|
if (pull.localOnly !== 0 || pull.divergedKeptLocal !== 0) return false;
|
|
7387
7761
|
if (pushOutcome.result.tag !== "nothing") return false;
|
|
7388
|
-
|
|
7762
|
+
if (pushOutcome.result.aheadOfOrigin === true) return false;
|
|
7763
|
+
return !pull.incomingChanges;
|
|
7389
7764
|
}
|
|
7390
|
-
function
|
|
7391
|
-
const
|
|
7392
|
-
return
|
|
7765
|
+
function buildPullSummaryRow(pull) {
|
|
7766
|
+
const applied = pull.incomingChanges ? "upstream changes applied" : "no upstream changes";
|
|
7767
|
+
return `pull: ${applied}; settings regenerated (base + ${pull.settingsLabel})`;
|
|
7393
7768
|
}
|
|
7394
|
-
function
|
|
7395
|
-
|
|
7769
|
+
function countNoun(n, singular, plural) {
|
|
7770
|
+
return `${n} ${n === 1 ? singular : plural}`;
|
|
7771
|
+
}
|
|
7772
|
+
function pushedParenParts(pull, globalConfigCount) {
|
|
7773
|
+
const parts = [];
|
|
7774
|
+
if (pull.localOnly > 0) {
|
|
7775
|
+
parts.push(countNoun(pull.localOnly, "local-only session", "local-only sessions"));
|
|
7776
|
+
}
|
|
7396
7777
|
if (pull.divergedKeptLocal > 0) {
|
|
7397
|
-
|
|
7778
|
+
parts.push(countNoun(pull.divergedKeptLocal, "diverged extras file", "diverged extras files"));
|
|
7398
7779
|
}
|
|
7399
|
-
if (
|
|
7400
|
-
|
|
7780
|
+
if (globalConfigCount > 0) {
|
|
7781
|
+
parts.push(countNoun(globalConfigCount, "config file", "config files"));
|
|
7401
7782
|
}
|
|
7402
|
-
return
|
|
7783
|
+
return parts;
|
|
7784
|
+
}
|
|
7785
|
+
function buildPushSummaryRow(pull, result) {
|
|
7786
|
+
if (result.tag !== "pushed") return "push: nothing to push";
|
|
7787
|
+
const parts = pushedParenParts(pull, result.globalConfigCount ?? 0);
|
|
7788
|
+
return parts.length > 0 ? `push: pushed (${parts.join(", ")})` : "push: pushed";
|
|
7789
|
+
}
|
|
7790
|
+
function buildSkipAndCollisionRows(pull, result) {
|
|
7791
|
+
const rows = [];
|
|
7792
|
+
if (pull.unmapped > 0) {
|
|
7793
|
+
rows.push(`${dim(infoGlyph)} ${pull.unmapped} not in path-map (run nomad doctor to list)`);
|
|
7794
|
+
}
|
|
7795
|
+
if (pull.extrasSkipped > 0) {
|
|
7796
|
+
rows.push(`${dim(infoGlyph)} ${pull.extrasSkipped} extras skipped`);
|
|
7797
|
+
}
|
|
7798
|
+
const collisions = result.tag === "dry" ? void 0 : result.collisions;
|
|
7799
|
+
if (collisions !== void 0 && collisions > 0) {
|
|
7800
|
+
const phrase = countNoun(collisions, "collision", "collisions");
|
|
7801
|
+
rows.push(`${yellow(warnGlyph)} ${phrase} (run nomad doctor to list)`);
|
|
7802
|
+
}
|
|
7803
|
+
return rows;
|
|
7403
7804
|
}
|
|
7404
7805
|
function buildSyncSummarySection(pull, pushOutcome) {
|
|
7405
7806
|
const s = section("Sync summary");
|
|
@@ -7407,22 +7808,59 @@ function buildSyncSummarySection(pull, pushOutcome) {
|
|
|
7407
7808
|
addItem(s, `pull: applied, push: failed (${pushOutcome.message})`);
|
|
7408
7809
|
return s;
|
|
7409
7810
|
}
|
|
7410
|
-
|
|
7411
|
-
addItem(s,
|
|
7412
|
-
|
|
7811
|
+
const { result } = pushOutcome;
|
|
7812
|
+
addItem(s, buildPullSummaryRow(pull));
|
|
7813
|
+
addItem(s, buildPushSummaryRow(pull, result));
|
|
7814
|
+
if (result.tag === "nothing" && result.aheadOfOrigin === true) {
|
|
7815
|
+
addItem(s, "sync repo has unpushed commits");
|
|
7816
|
+
}
|
|
7817
|
+
for (const row2 of buildSkipAndCollisionRows(pull, result)) addItem(s, row2);
|
|
7413
7818
|
return s;
|
|
7414
7819
|
}
|
|
7415
|
-
|
|
7820
|
+
var SYNC_SECTION_ORDER = ["Settings", "Global config", "Sessions", "Extras", "Leak scan"];
|
|
7821
|
+
function isDroppedSummaryHeader(header) {
|
|
7822
|
+
return header === PULL_SUMMARY_HEADER || header === "Push summary";
|
|
7823
|
+
}
|
|
7824
|
+
function mergeSyncSections(pullSections, pushSections) {
|
|
7825
|
+
const byHeader = new Map(
|
|
7826
|
+
SYNC_SECTION_ORDER.map((header) => [header, section(header)])
|
|
7827
|
+
);
|
|
7828
|
+
for (const s of [...pullSections, ...pushSections]) {
|
|
7829
|
+
if (isDroppedSummaryHeader(s.header)) continue;
|
|
7830
|
+
let target = byHeader.get(s.header);
|
|
7831
|
+
if (target === void 0) {
|
|
7832
|
+
target = section(s.header);
|
|
7833
|
+
byHeader.set(s.header, target);
|
|
7834
|
+
}
|
|
7835
|
+
for (const item2 of s.items) {
|
|
7836
|
+
if (!target.items.includes(item2)) addItem(target, item2);
|
|
7837
|
+
}
|
|
7838
|
+
}
|
|
7839
|
+
return [...byHeader.values()].filter((s) => s.items.length > 0);
|
|
7840
|
+
}
|
|
7841
|
+
function pushSectionsOf(pushOutcome) {
|
|
7842
|
+
if (!pushOutcome.ok) return [];
|
|
7843
|
+
const result = pushOutcome.result;
|
|
7844
|
+
if (result.tag === "dry") return [];
|
|
7845
|
+
return result.sections ?? [];
|
|
7846
|
+
}
|
|
7847
|
+
function renderWetSync(pull, pushOutcome, verbose) {
|
|
7416
7848
|
if (isNoopSync(pull, pushOutcome)) {
|
|
7417
7849
|
ok("already in sync");
|
|
7418
7850
|
return;
|
|
7419
7851
|
}
|
|
7420
|
-
|
|
7421
|
-
|
|
7852
|
+
log(`sync on host=${HOST}`);
|
|
7853
|
+
const summary = buildSyncSummarySection(pull, pushOutcome);
|
|
7854
|
+
if (!verbose) {
|
|
7855
|
+
renderTree([summary]);
|
|
7856
|
+
return;
|
|
7857
|
+
}
|
|
7858
|
+
const merged = mergeSyncSections(pull.sections, pushSectionsOf(pushOutcome));
|
|
7859
|
+
renderTree([...merged, summary]);
|
|
7422
7860
|
}
|
|
7423
7861
|
async function runSyncPushHalf() {
|
|
7424
7862
|
try {
|
|
7425
|
-
const result = await runPushCore();
|
|
7863
|
+
const result = await runPushCore({ compose: true });
|
|
7426
7864
|
return { ok: true, result };
|
|
7427
7865
|
} catch (err) {
|
|
7428
7866
|
if (err instanceof NomadFatal) {
|
|
@@ -7432,25 +7870,26 @@ async function runSyncPushHalf() {
|
|
|
7432
7870
|
throw err;
|
|
7433
7871
|
}
|
|
7434
7872
|
}
|
|
7435
|
-
async function runSyncWet() {
|
|
7436
|
-
const pull = runPullCore();
|
|
7873
|
+
async function runSyncWet(verbose) {
|
|
7874
|
+
const pull = runPullCore({ compose: true });
|
|
7437
7875
|
const pushOutcome = await runSyncPushHalf();
|
|
7438
|
-
renderWetSync(pull, pushOutcome);
|
|
7876
|
+
renderWetSync(pull, pushOutcome, verbose);
|
|
7439
7877
|
}
|
|
7440
7878
|
async function runSyncDryRun(repo, backup) {
|
|
7441
7879
|
const ts = freshBackupTs(backup);
|
|
7442
|
-
const mapPath =
|
|
7443
|
-
const map =
|
|
7880
|
+
const mapPath = join56(repo, "path-map.json");
|
|
7881
|
+
const map = existsSync47(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
7444
7882
|
computePreview(ts, map, "pull");
|
|
7445
7883
|
log("push preview below is computed against pre-pull state (a real sync pushes after pull)");
|
|
7446
7884
|
await runPushCore({ dryRun: true });
|
|
7447
7885
|
}
|
|
7448
7886
|
async function cmdSync(opts = {}) {
|
|
7449
7887
|
const dryRun = opts.dryRun === true;
|
|
7888
|
+
const verbose = opts.verbose === true;
|
|
7450
7889
|
const repo = repoHome();
|
|
7451
7890
|
const backup = backupBase();
|
|
7452
|
-
if (!
|
|
7453
|
-
if (!
|
|
7891
|
+
if (!existsSync47(repo)) die(`repo not cloned at ${repo}`);
|
|
7892
|
+
if (!existsSync47(join56(repo, "shared", "settings.base.json"))) {
|
|
7454
7893
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
7455
7894
|
}
|
|
7456
7895
|
const handle = acquireLock("sync");
|
|
@@ -7459,7 +7898,7 @@ async function cmdSync(opts = {}) {
|
|
|
7459
7898
|
if (dryRun) {
|
|
7460
7899
|
await runSyncDryRun(repo, backup);
|
|
7461
7900
|
} else {
|
|
7462
|
-
await runSyncWet();
|
|
7901
|
+
await runSyncWet(verbose);
|
|
7463
7902
|
}
|
|
7464
7903
|
} catch (err) {
|
|
7465
7904
|
if (err instanceof NomadFatal) {
|
|
@@ -7474,9 +7913,9 @@ async function cmdSync(opts = {}) {
|
|
|
7474
7913
|
}
|
|
7475
7914
|
|
|
7476
7915
|
// src/commands.update.ts
|
|
7477
|
-
import { execFileSync as
|
|
7916
|
+
import { execFileSync as execFileSync22 } from "node:child_process";
|
|
7478
7917
|
init_utils();
|
|
7479
|
-
function readInstalledVersion(run =
|
|
7918
|
+
function readInstalledVersion(run = execFileSync22) {
|
|
7480
7919
|
const isWin = process.platform === "win32";
|
|
7481
7920
|
try {
|
|
7482
7921
|
return run(isWin ? "nomad.cmd" : "nomad", ["--version"], {
|
|
@@ -7487,7 +7926,7 @@ function readInstalledVersion(run = execFileSync21) {
|
|
|
7487
7926
|
return null;
|
|
7488
7927
|
}
|
|
7489
7928
|
}
|
|
7490
|
-
function cmdUpdate(currentVersion, run =
|
|
7929
|
+
function cmdUpdate(currentVersion, run = execFileSync22) {
|
|
7491
7930
|
console.log(`Updating claude-nomad v${currentVersion}...`);
|
|
7492
7931
|
const isWin = process.platform === "win32";
|
|
7493
7932
|
try {
|
|
@@ -7523,18 +7962,18 @@ init_config();
|
|
|
7523
7962
|
|
|
7524
7963
|
// src/diff.ts
|
|
7525
7964
|
init_config();
|
|
7526
|
-
import { existsSync as
|
|
7527
|
-
import { join as
|
|
7965
|
+
import { existsSync as existsSync48 } from "node:fs";
|
|
7966
|
+
import { join as join57 } from "node:path";
|
|
7528
7967
|
init_utils();
|
|
7529
7968
|
init_utils_fs();
|
|
7530
7969
|
init_utils_json();
|
|
7531
7970
|
function cmdDiff() {
|
|
7532
7971
|
try {
|
|
7533
7972
|
const repo = repoHome();
|
|
7534
|
-
if (!
|
|
7973
|
+
if (!existsSync48(repo)) die(`repo not cloned at ${repo}`);
|
|
7535
7974
|
const ts = freshBackupTs(backupBase());
|
|
7536
|
-
const mapPath =
|
|
7537
|
-
const map =
|
|
7975
|
+
const mapPath = join57(repo, "path-map.json");
|
|
7976
|
+
const map = existsSync48(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
7538
7977
|
divergenceCheckExtras(ts);
|
|
7539
7978
|
computePreview(ts, map, "diff");
|
|
7540
7979
|
} catch (err) {
|
|
@@ -7549,19 +7988,19 @@ function cmdDiff() {
|
|
|
7549
7988
|
|
|
7550
7989
|
// src/init.ts
|
|
7551
7990
|
init_config();
|
|
7552
|
-
import { existsSync as
|
|
7553
|
-
import { join as
|
|
7991
|
+
import { existsSync as existsSync50, mkdirSync as mkdirSync14, writeFileSync as writeFileSync8 } from "node:fs";
|
|
7992
|
+
import { join as join59 } from "node:path";
|
|
7554
7993
|
|
|
7555
7994
|
// src/init.gh-onboard.ts
|
|
7556
7995
|
init_config();
|
|
7557
|
-
import { execFileSync as
|
|
7996
|
+
import { execFileSync as execFileSync23 } from "node:child_process";
|
|
7558
7997
|
init_utils();
|
|
7559
7998
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
7560
7999
|
function isValidRepoName(name) {
|
|
7561
8000
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
7562
8001
|
}
|
|
7563
8002
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
7564
|
-
function ensureOriginRepo(repoName, run =
|
|
8003
|
+
function ensureOriginRepo(repoName, run = execFileSync23) {
|
|
7565
8004
|
if (!isValidRepoName(repoName)) {
|
|
7566
8005
|
die(
|
|
7567
8006
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -7632,33 +8071,33 @@ init_config();
|
|
|
7632
8071
|
init_utils();
|
|
7633
8072
|
init_utils_fs();
|
|
7634
8073
|
init_utils_json();
|
|
7635
|
-
import { copyFileSync as copyFileSync2, cpSync as
|
|
7636
|
-
import { join as
|
|
8074
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync9, existsSync as existsSync49, rmSync as rmSync17, statSync as statSync12 } from "node:fs";
|
|
8075
|
+
import { join as join58 } from "node:path";
|
|
7637
8076
|
function snapshotIntoShared(map) {
|
|
7638
8077
|
const repo = repoHome();
|
|
7639
8078
|
const claude = claudeHome();
|
|
7640
8079
|
for (const name of allSharedLinks(map)) {
|
|
7641
|
-
const src =
|
|
7642
|
-
if (!
|
|
7643
|
-
const dst =
|
|
8080
|
+
const src = join58(claude, name);
|
|
8081
|
+
if (!existsSync49(src)) continue;
|
|
8082
|
+
const dst = join58(repo, "shared", name);
|
|
7644
8083
|
if (statSync12(src).isDirectory()) {
|
|
7645
|
-
const gk =
|
|
7646
|
-
if (
|
|
7647
|
-
|
|
8084
|
+
const gk = join58(dst, ".gitkeep");
|
|
8085
|
+
if (existsSync49(gk)) rmSync17(gk);
|
|
8086
|
+
cpSync9(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
7648
8087
|
} else {
|
|
7649
8088
|
copyFileSync2(src, dst);
|
|
7650
8089
|
}
|
|
7651
8090
|
log(`snapshotted shared/${name} from ${src}`);
|
|
7652
8091
|
}
|
|
7653
|
-
const userSettings =
|
|
7654
|
-
if (
|
|
8092
|
+
const userSettings = join58(claude, "settings.json");
|
|
8093
|
+
if (existsSync49(userSettings)) {
|
|
7655
8094
|
let parsed;
|
|
7656
8095
|
try {
|
|
7657
8096
|
parsed = readJson(userSettings);
|
|
7658
8097
|
} catch (err) {
|
|
7659
8098
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
7660
8099
|
}
|
|
7661
|
-
const hostFile =
|
|
8100
|
+
const hostFile = join58(repo, "hosts", `${HOST}.json`);
|
|
7662
8101
|
writeJsonAtomic(hostFile, parsed);
|
|
7663
8102
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
7664
8103
|
}
|
|
@@ -7672,14 +8111,14 @@ var GITATTRIBUTES = "# nomad: sync content is byte-managed, disable all line-end
|
|
|
7672
8111
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
7673
8112
|
function preflightConflict(repoHome2) {
|
|
7674
8113
|
const candidates = [
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
8114
|
+
join59(repoHome2, "shared", "settings.base.json"),
|
|
8115
|
+
join59(repoHome2, "shared", "CLAUDE.md"),
|
|
8116
|
+
join59(repoHome2, "path-map.json"),
|
|
8117
|
+
join59(repoHome2, "hosts"),
|
|
8118
|
+
join59(repoHome2, "shared")
|
|
7680
8119
|
];
|
|
7681
8120
|
for (const c of candidates) {
|
|
7682
|
-
if (
|
|
8121
|
+
if (existsSync50(c)) return c;
|
|
7683
8122
|
}
|
|
7684
8123
|
return null;
|
|
7685
8124
|
}
|
|
@@ -7691,33 +8130,33 @@ function cmdInit(opts = {}) {
|
|
|
7691
8130
|
const keepActions = opts.keepActions === true;
|
|
7692
8131
|
const repo = repoHome();
|
|
7693
8132
|
const claude = claudeHome();
|
|
7694
|
-
|
|
8133
|
+
mkdirSync14(repo, { recursive: true });
|
|
7695
8134
|
const conflict = preflightConflict(repo);
|
|
7696
8135
|
if (conflict !== null) {
|
|
7697
8136
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
7698
8137
|
}
|
|
7699
8138
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
7700
|
-
|
|
7701
|
-
|
|
8139
|
+
mkdirSync14(join59(repo, "shared"), { recursive: true });
|
|
8140
|
+
mkdirSync14(join59(repo, "hosts"), { recursive: true });
|
|
7702
8141
|
for (const name of SHARED_KEEP_DIRS) {
|
|
7703
|
-
|
|
8142
|
+
mkdirSync14(join59(repo, "shared", name), { recursive: true });
|
|
7704
8143
|
}
|
|
7705
|
-
const userClaudeMd =
|
|
7706
|
-
if (!snapshot || !
|
|
7707
|
-
|
|
8144
|
+
const userClaudeMd = join59(claude, "CLAUDE.md");
|
|
8145
|
+
if (!snapshot || !existsSync50(userClaudeMd)) {
|
|
8146
|
+
writeFileSync8(join59(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
7708
8147
|
item("created shared/CLAUDE.md");
|
|
7709
8148
|
}
|
|
7710
8149
|
for (const name of SHARED_KEEP_DIRS) {
|
|
7711
|
-
|
|
8150
|
+
writeFileSync8(join59(repo, "shared", name, ".gitkeep"), "");
|
|
7712
8151
|
item(`created shared/${name}/.gitkeep`);
|
|
7713
8152
|
}
|
|
7714
|
-
|
|
8153
|
+
writeFileSync8(join59(repo, "hosts", ".gitkeep"), "");
|
|
7715
8154
|
item("created hosts/.gitkeep");
|
|
7716
|
-
writeJsonAtomic(
|
|
8155
|
+
writeJsonAtomic(join59(repo, "shared", "settings.base.json"), {});
|
|
7717
8156
|
item("created shared/settings.base.json");
|
|
7718
|
-
writeJsonAtomic(
|
|
8157
|
+
writeJsonAtomic(join59(repo, "path-map.json"), { projects: {} });
|
|
7719
8158
|
item("created path-map.json");
|
|
7720
|
-
|
|
8159
|
+
writeFileSync8(join59(repo, ".gitattributes"), GITATTRIBUTES);
|
|
7721
8160
|
item("created .gitattributes");
|
|
7722
8161
|
if (snapshot) {
|
|
7723
8162
|
snapshotIntoShared({ projects: {} });
|
|
@@ -7783,11 +8222,11 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
7783
8222
|
// src/init.prompt.ts
|
|
7784
8223
|
init_config();
|
|
7785
8224
|
init_utils();
|
|
7786
|
-
import { existsSync as
|
|
7787
|
-
import { join as
|
|
8225
|
+
import { existsSync as existsSync51, readdirSync as readdirSync18, statSync as statSync13 } from "node:fs";
|
|
8226
|
+
import { join as join60 } from "node:path";
|
|
7788
8227
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
7789
8228
|
function nonEmptyExists(path) {
|
|
7790
|
-
if (!
|
|
8229
|
+
if (!existsSync51(path)) return false;
|
|
7791
8230
|
try {
|
|
7792
8231
|
if (statSync13(path).isDirectory()) return readdirSync18(path).length > 0;
|
|
7793
8232
|
return true;
|
|
@@ -7796,8 +8235,8 @@ function nonEmptyExists(path) {
|
|
|
7796
8235
|
}
|
|
7797
8236
|
}
|
|
7798
8237
|
function hasExistingClaudeConfig(claudeHome2) {
|
|
7799
|
-
if (
|
|
7800
|
-
return SHARED_LINKS.some((name) => nonEmptyExists(
|
|
8238
|
+
if (existsSync51(join60(claudeHome2, "settings.json"))) return true;
|
|
8239
|
+
return SHARED_LINKS.some((name) => nonEmptyExists(join60(claudeHome2, name)));
|
|
7801
8240
|
}
|
|
7802
8241
|
async function confirmSnapshotDefault(claudeHome2) {
|
|
7803
8242
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
@@ -8084,24 +8523,27 @@ function parsePushArgs(argv) {
|
|
|
8084
8523
|
// src/nomad.dispatch.sync.ts
|
|
8085
8524
|
function parseSyncArgs(argv) {
|
|
8086
8525
|
let dryRun = false;
|
|
8526
|
+
let verbose = false;
|
|
8087
8527
|
let i = 3;
|
|
8088
8528
|
while (i < argv.length) {
|
|
8089
8529
|
const token = argv[i];
|
|
8090
8530
|
if (token === "--dry-run") {
|
|
8091
8531
|
if (dryRun) return null;
|
|
8092
8532
|
dryRun = true;
|
|
8533
|
+
} else if (token === "--verbose" || token === "--all" || token === "-v") {
|
|
8534
|
+
verbose = true;
|
|
8093
8535
|
} else {
|
|
8094
8536
|
return null;
|
|
8095
8537
|
}
|
|
8096
8538
|
i++;
|
|
8097
8539
|
}
|
|
8098
|
-
return { dryRun };
|
|
8540
|
+
return { dryRun, verbose };
|
|
8099
8541
|
}
|
|
8100
8542
|
|
|
8101
8543
|
// package.json
|
|
8102
8544
|
var package_default = {
|
|
8103
8545
|
name: "claude-nomad",
|
|
8104
|
-
version: "0.
|
|
8546
|
+
version: "0.61.0",
|
|
8105
8547
|
type: "module",
|
|
8106
8548
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
8107
8549
|
keywords: [
|
|
@@ -8236,10 +8678,15 @@ var DEFAULT_HELP = [
|
|
|
8236
8678
|
cont("Mutually exclusive with --redact-all/--allow. Cannot combine with --dry-run."),
|
|
8237
8679
|
"",
|
|
8238
8680
|
row(" sync", "Pull (retain-merge), then push, under one lock. One-shot; hides ordering."),
|
|
8681
|
+
cont("Compact by default: prints only the Sync summary."),
|
|
8239
8682
|
row(" --dry-run", "Stack the pull preview then the push preview; mutates nothing."),
|
|
8240
8683
|
cont("Fails fast on a pull-half error; a wedged repo hints at nomad pull --force-remote"),
|
|
8241
8684
|
cont("(sync itself takes no --force-remote). Mid-push leak recovery behaves exactly"),
|
|
8242
8685
|
cont("like nomad push."),
|
|
8686
|
+
row(
|
|
8687
|
+
" --verbose, --all, -v",
|
|
8688
|
+
"Show the full merged tree; default output is the Sync summary only."
|
|
8689
|
+
),
|
|
8243
8690
|
"",
|
|
8244
8691
|
row(" diff", "Offline preview of what `pull` would change against local repo state."),
|
|
8245
8692
|
cont("No git pull, no lock acquired."),
|
|
@@ -8337,15 +8784,15 @@ var DEFAULT_HELP = [
|
|
|
8337
8784
|
init_config();
|
|
8338
8785
|
init_utils();
|
|
8339
8786
|
init_utils_json();
|
|
8340
|
-
import { existsSync as
|
|
8341
|
-
import { join as
|
|
8787
|
+
import { existsSync as existsSync52, readFileSync as readFileSync19, readdirSync as readdirSync19 } from "node:fs";
|
|
8788
|
+
import { join as join61 } from "node:path";
|
|
8342
8789
|
function resumeCmd(sessionId) {
|
|
8343
8790
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
8344
8791
|
fail(`invalid session id: ${sessionId}`);
|
|
8345
8792
|
process.exit(1);
|
|
8346
8793
|
}
|
|
8347
|
-
const projectsRoot =
|
|
8348
|
-
if (!
|
|
8794
|
+
const projectsRoot = join61(claudeHome(), "projects");
|
|
8795
|
+
if (!existsSync52(projectsRoot)) {
|
|
8349
8796
|
fail(`${projectsRoot} does not exist`);
|
|
8350
8797
|
process.exit(1);
|
|
8351
8798
|
}
|
|
@@ -8359,8 +8806,8 @@ function resumeCmd(sessionId) {
|
|
|
8359
8806
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
8360
8807
|
process.exit(1);
|
|
8361
8808
|
}
|
|
8362
|
-
const mapPath =
|
|
8363
|
-
if (!
|
|
8809
|
+
const mapPath = join61(repoHome(), "path-map.json");
|
|
8810
|
+
if (!existsSync52(mapPath)) {
|
|
8364
8811
|
fail("path-map.json missing");
|
|
8365
8812
|
process.exit(1);
|
|
8366
8813
|
}
|
|
@@ -8383,13 +8830,13 @@ function resumeCmd(sessionId) {
|
|
|
8383
8830
|
}
|
|
8384
8831
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
8385
8832
|
for (const dir of readdirSync19(projectsRoot)) {
|
|
8386
|
-
const candidate =
|
|
8387
|
-
if (
|
|
8833
|
+
const candidate = join61(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
8834
|
+
if (existsSync52(candidate)) return candidate;
|
|
8388
8835
|
}
|
|
8389
8836
|
return null;
|
|
8390
8837
|
}
|
|
8391
8838
|
function extractRecordedCwd(jsonlPath) {
|
|
8392
|
-
for (const line of
|
|
8839
|
+
for (const line of readFileSync19(jsonlPath, "utf8").split("\n")) {
|
|
8393
8840
|
if (!line.trim()) continue;
|
|
8394
8841
|
try {
|
|
8395
8842
|
const obj = JSON.parse(line);
|
|
@@ -8465,10 +8912,10 @@ try {
|
|
|
8465
8912
|
case "sync": {
|
|
8466
8913
|
const syncArgs = parseSyncArgs(process.argv);
|
|
8467
8914
|
if (syncArgs === null) {
|
|
8468
|
-
console.error("usage: nomad sync [--dry-run]");
|
|
8915
|
+
console.error("usage: nomad sync [--dry-run] [--verbose|--all|-v]");
|
|
8469
8916
|
process.exit(1);
|
|
8470
8917
|
}
|
|
8471
|
-
await cmdSync({ dryRun: syncArgs.dryRun });
|
|
8918
|
+
await cmdSync({ dryRun: syncArgs.dryRun, verbose: syncArgs.verbose });
|
|
8472
8919
|
break;
|
|
8473
8920
|
}
|
|
8474
8921
|
case "init": {
|