claude-nomad 0.50.1 → 0.50.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitleaks.toml +13 -1
- package/CHANGELOG.md +43 -0
- package/README.md +2 -0
- package/dist/nomad.mjs +313 -216
- package/package.json +1 -1
package/.gitleaks.toml
CHANGED
|
@@ -6,14 +6,26 @@
|
|
|
6
6
|
[extend]
|
|
7
7
|
useDefault = true
|
|
8
8
|
|
|
9
|
+
# Path-scoped: this tool-output noise (gitleaks fingerprints, coverage
|
|
10
|
+
# line-keys, npm audit JSON id hashes, Sonar issue keys) accumulates in Claude
|
|
11
|
+
# Code session transcripts when a conversation runs those tools. Two of these
|
|
12
|
+
# regexes are structurally generic enough that, left global, they could suppress
|
|
13
|
+
# a real credential that happened to sit in a `path:word:digit` or hex-`id`
|
|
14
|
+
# context anywhere in the repo. Scoping to `shared/projects/<logical>/.../*.jsonl`
|
|
15
|
+
# with `condition = "AND"` (matching every other block below) confines the
|
|
16
|
+
# suppression to synced transcripts: a real secret in a source file still fires.
|
|
9
17
|
[[allowlists]]
|
|
10
|
-
description = "claude-nomad: structurally-distinguishable tool-output noise"
|
|
18
|
+
description = "claude-nomad: structurally-distinguishable tool-output noise in synced session transcripts"
|
|
11
19
|
regexes = [
|
|
12
20
|
'''AY[A-Za-z0-9_-]{20,}''',
|
|
13
21
|
'''[\w./-]+\.[A-Za-z0-9]+:[\w-]+:\d+''',
|
|
14
22
|
'''"id"\s*:\s*"[a-f0-9]{40,64}"''',
|
|
15
23
|
'''key=[a-f0-9]{8,} [\w./-]+\.\w+:\d+''',
|
|
16
24
|
]
|
|
25
|
+
paths = [
|
|
26
|
+
'''^shared/projects/[^/]+/.*\.jsonl$''',
|
|
27
|
+
]
|
|
28
|
+
condition = "AND"
|
|
17
29
|
|
|
18
30
|
# Path-scoped: the documented test-fixture github-pat literal AND the three
|
|
19
31
|
# entropy-variant placeholders (zero-suffix, alphabet, repeat-A) accumulate
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.50.3](https://github.com/funkadelic/claude-nomad/compare/v0.50.2...v0.50.3) (2026-06-17)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
* **gitleaks:** scope tool-output noise allowlist to session transcripts ([#305](https://github.com/funkadelic/claude-nomad/issues/305)) ([ca76680](https://github.com/funkadelic/claude-nomad/commit/ca766803a425d21747f959606d6436989ce249e6))
|
|
9
|
+
* **push:** make --redact-all all-or-nothing ([#302](https://github.com/funkadelic/claude-nomad/issues/302)) ([f2cffeb](https://github.com/funkadelic/claude-nomad/commit/f2cffeb9204b68fb046ee55c9768e065272fb431))
|
|
10
|
+
* **push:** warn when drop-session/redact target is already in pushed history ([#304](https://github.com/funkadelic/claude-nomad/issues/304)) ([739676f](https://github.com/funkadelic/claude-nomad/commit/739676ffbf1345fc7ff124ac7eafb9802ba09be4))
|
|
11
|
+
* **redact:** warn when a finding match is not located in the file ([#310](https://github.com/funkadelic/claude-nomad/issues/310)) ([db1442b](https://github.com/funkadelic/claude-nomad/commit/db1442b311db04944b655ece91a56ef23a39abe1))
|
|
12
|
+
* **remap:** make session-transcript mirror copy atomic ([4fc518f](https://github.com/funkadelic/claude-nomad/commit/4fc518fd80ce7fffb56ba2a6d135e6f6b795bc91))
|
|
13
|
+
* **utils:** guard deepMerge against prototype pollution ([#299](https://github.com/funkadelic/claude-nomad/issues/299)) ([8e57539](https://github.com/funkadelic/claude-nomad/commit/8e575393a320386dd7329aab8071fd84557ec4e9))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
* **config:** centralize path-map.json shape validation ([#306](https://github.com/funkadelic/claude-nomad/issues/306)) ([88edda3](https://github.com/funkadelic/claude-nomad/commit/88edda3f45954d4dc89d65e2ec55a7d9d5b6b3f8))
|
|
19
|
+
* **dispatch:** share argv token-parser primitives ([#307](https://github.com/funkadelic/claude-nomad/issues/307)) ([9f62b51](https://github.com/funkadelic/claude-nomad/commit/9f62b5156bbc3b7e03f8a81fe0f844cbeb1da3fa))
|
|
20
|
+
* **doctor:** rename repository check module to git-state ([#309](https://github.com/funkadelic/claude-nomad/issues/309)) ([b6a0d5c](https://github.com/funkadelic/claude-nomad/commit/b6a0d5c8461c2f63af7d77464db9558271d46f46))
|
|
21
|
+
* **utils:** dedup backup helpers and document lock/exit conventions ([#308](https://github.com/funkadelic/claude-nomad/issues/308)) ([4c2d59d](https://github.com/funkadelic/claude-nomad/commit/4c2d59d97c875851733175e4b4346e5714c48a32))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Documentation
|
|
25
|
+
|
|
26
|
+
* **recovery:** reflect session-scoped allowlist and redact no-match warning ([#311](https://github.com/funkadelic/claude-nomad/issues/311)) ([7faf564](https://github.com/funkadelic/claude-nomad/commit/7faf5647d7dfd30822950d687fd8be15b5c49910))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Testing
|
|
30
|
+
|
|
31
|
+
* **push:** add full-pipeline cmdPush E2E against real git and gitleaks ([#303](https://github.com/funkadelic/claude-nomad/issues/303)) ([3c01262](https://github.com/funkadelic/claude-nomad/commit/3c012629e7b99dcf4c5e679d93acb353ee6c3409))
|
|
32
|
+
|
|
33
|
+
## [0.50.2](https://github.com/funkadelic/claude-nomad/compare/v0.50.1...v0.50.2) (2026-06-15)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
* **push:** skip gsd-dropped hooks and agents from shared push ([#295](https://github.com/funkadelic/claude-nomad/issues/295)) ([b8f6665](https://github.com/funkadelic/claude-nomad/commit/b8f66658dd47dac40bfafd9899206a481d04a87a))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Dependencies
|
|
42
|
+
|
|
43
|
+
* bump SonarSource/sonarqube-scan-action from 8.1.0 to 8.2.0 ([#297](https://github.com/funkadelic/claude-nomad/issues/297)) ([fdb681c](https://github.com/funkadelic/claude-nomad/commit/fdb681c4d346b9bd82b34fe6d9dee2d202186473))
|
|
44
|
+
* bump the dev-dependencies group with 5 updates ([#298](https://github.com/funkadelic/claude-nomad/issues/298)) ([0909a02](https://github.com/funkadelic/claude-nomad/commit/0909a025b698162c7a695354465046973ee93ab4))
|
|
45
|
+
|
|
3
46
|
## [0.50.1](https://github.com/funkadelic/claude-nomad/compare/v0.50.0...v0.50.1) (2026-06-12)
|
|
4
47
|
|
|
5
48
|
|
package/README.md
CHANGED
|
@@ -100,6 +100,8 @@ When `nomad push` detects a potential secret, it drops into an interactive menu
|
|
|
100
100
|
a recovery hint (non-TTY/CI). Three non-interactive recovery paths are available without the menu:
|
|
101
101
|
|
|
102
102
|
- `nomad push --redact-all` -- scrub every finding from the local transcript in place, then push.
|
|
103
|
+
All-or-nothing: if any finding cannot be redacted (an active session, or one that does not map to
|
|
104
|
+
a synced transcript), nothing is changed and the push stops so you can handle those sessions.
|
|
103
105
|
- `nomad push --allow <rule>` -- record findings matching one gitleaks rule id as false positives
|
|
104
106
|
(appends their fingerprints to `.gitleaksignore`), then re-scan and push.
|
|
105
107
|
- `nomad push --allow-all` -- record every current finding as a false positive, then re-scan and
|
package/dist/nomad.mjs
CHANGED
|
@@ -5,11 +5,20 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
|
|
8
|
+
var __esm = (fn, res, err) => function __init() {
|
|
9
|
+
if (err) throw err[0];
|
|
10
|
+
try {
|
|
11
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
throw err = [e], e;
|
|
14
|
+
}
|
|
10
15
|
};
|
|
11
16
|
var __commonJS = (cb, mod) => function __require() {
|
|
12
|
-
|
|
17
|
+
try {
|
|
18
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
19
|
+
} catch (e) {
|
|
20
|
+
throw mod = 0, e;
|
|
21
|
+
}
|
|
13
22
|
};
|
|
14
23
|
var __export = (target, all) => {
|
|
15
24
|
for (var name in all)
|
|
@@ -442,16 +451,41 @@ function readJson(path) {
|
|
|
442
451
|
return data;
|
|
443
452
|
}
|
|
444
453
|
function readPathMap(mapPath) {
|
|
454
|
+
let parsed;
|
|
445
455
|
try {
|
|
446
|
-
|
|
456
|
+
parsed = readJson(mapPath);
|
|
447
457
|
} catch (err) {
|
|
448
458
|
const verb = err instanceof SyntaxError ? "parse" : "read";
|
|
449
459
|
throw new NomadFatal(`could not ${verb} path-map.json: ${err.message}`);
|
|
450
460
|
}
|
|
461
|
+
const shapeError = validatePathMapShape(parsed);
|
|
462
|
+
if (shapeError !== null) throw new NomadFatal(shapeError);
|
|
463
|
+
return parsed;
|
|
464
|
+
}
|
|
465
|
+
function validatePathMapShape(raw) {
|
|
466
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
467
|
+
return "path-map.json invalid schema: top-level value must be an object";
|
|
468
|
+
}
|
|
469
|
+
const projects = raw.projects;
|
|
470
|
+
if (projects === null || typeof projects !== "object" || Array.isArray(projects)) {
|
|
471
|
+
return 'path-map.json invalid schema: "projects" must be an object';
|
|
472
|
+
}
|
|
473
|
+
for (const [name, hosts] of Object.entries(projects)) {
|
|
474
|
+
if (hosts === null || typeof hosts !== "object" || Array.isArray(hosts)) {
|
|
475
|
+
return `path-map.json invalid schema: project "${name}" hosts must be an object`;
|
|
476
|
+
}
|
|
477
|
+
for (const [host, value] of Object.entries(hosts)) {
|
|
478
|
+
if (typeof value !== "string") {
|
|
479
|
+
return `path-map.json invalid schema: project "${name}" host "${host}" path must be a string`;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return null;
|
|
451
484
|
}
|
|
452
485
|
function deepMerge(target, source) {
|
|
453
486
|
const out = { ...target };
|
|
454
487
|
for (const [key, value] of Object.entries(source)) {
|
|
488
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
455
489
|
const existing = out[key];
|
|
456
490
|
const bothObjects = value !== null && typeof value === "object" && !Array.isArray(value) && existing !== null && typeof existing === "object" && !Array.isArray(existing);
|
|
457
491
|
out[key] = bothObjects ? deepMerge(existing, value) : value;
|
|
@@ -493,7 +527,7 @@ import {
|
|
|
493
527
|
symlinkSync,
|
|
494
528
|
writeFileSync
|
|
495
529
|
} from "node:fs";
|
|
496
|
-
import { dirname, join as join2, relative } from "node:path";
|
|
530
|
+
import { dirname, join as join2, relative, sep } from "node:path";
|
|
497
531
|
function writeJsonAtomic(path, data) {
|
|
498
532
|
const mode = existsSync(path) ? statSync(path).mode & 511 : 384;
|
|
499
533
|
const tmp = `${path}.tmp.${process.pid}`;
|
|
@@ -533,33 +567,22 @@ function ensureSymlink(linkPath, target) {
|
|
|
533
567
|
symlinkSync(target, linkPath);
|
|
534
568
|
log(`linked ${linkPath} -> ${target}`);
|
|
535
569
|
}
|
|
536
|
-
function
|
|
570
|
+
function backupUnder(absPath, anchor, destRoot) {
|
|
537
571
|
if (!existsSync(absPath)) return;
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const backupRoot = join2(backupBase(), ts);
|
|
542
|
-
const dst = join2(backupRoot, rel);
|
|
572
|
+
const rel = relative(anchor, absPath);
|
|
573
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`)) return;
|
|
574
|
+
const dst = join2(destRoot, rel);
|
|
543
575
|
mkdirSync(dirname(dst), { recursive: true });
|
|
544
576
|
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
545
577
|
}
|
|
578
|
+
function backupBeforeWrite(absPath, ts) {
|
|
579
|
+
backupUnder(absPath, claudeHome(), join2(backupBase(), ts));
|
|
580
|
+
}
|
|
546
581
|
function backupRepoWrite(absPath, ts, repoHome2) {
|
|
547
|
-
|
|
548
|
-
const rel = relative(repoHome2, absPath);
|
|
549
|
-
if (rel.startsWith("..") || rel === "") return;
|
|
550
|
-
const backupRoot = join2(backupBase(), ts, "repo");
|
|
551
|
-
const dst = join2(backupRoot, rel);
|
|
552
|
-
mkdirSync(dirname(dst), { recursive: true });
|
|
553
|
-
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
582
|
+
backupUnder(absPath, repoHome2, join2(backupBase(), ts, "repo"));
|
|
554
583
|
}
|
|
555
584
|
function backupExtrasWrite(absPath, ts, projectRoot) {
|
|
556
|
-
|
|
557
|
-
const rel = relative(projectRoot, absPath);
|
|
558
|
-
if (rel.startsWith("..") || rel === "") return;
|
|
559
|
-
const backupRoot = join2(backupBase(), ts, "extras");
|
|
560
|
-
const dst = join2(backupRoot, encodePath(projectRoot), rel);
|
|
561
|
-
mkdirSync(dirname(dst), { recursive: true });
|
|
562
|
-
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
585
|
+
backupUnder(absPath, projectRoot, join2(backupBase(), ts, "extras", encodePath(projectRoot)));
|
|
563
586
|
}
|
|
564
587
|
var init_utils_fs = __esm({
|
|
565
588
|
"src/utils.fs.ts"() {
|
|
@@ -1310,7 +1333,7 @@ init_config();
|
|
|
1310
1333
|
init_utils();
|
|
1311
1334
|
init_utils_json();
|
|
1312
1335
|
import { cpSync as cpSync3, existsSync as existsSync5, lstatSync as lstatSync4, realpathSync, renameSync as renameSync2, rmSync as rmSync3 } from "node:fs";
|
|
1313
|
-
import { join as join6, sep } from "node:path";
|
|
1336
|
+
import { join as join6, sep as sep2 } from "node:path";
|
|
1314
1337
|
function ejectChecklist() {
|
|
1315
1338
|
return [
|
|
1316
1339
|
"Manual steps remaining to finish leaving claude-nomad on this host:",
|
|
@@ -1352,7 +1375,7 @@ function resolveSharedRoot(repoHome2) {
|
|
|
1352
1375
|
}
|
|
1353
1376
|
}
|
|
1354
1377
|
function isManagedTarget(target, sharedRoot) {
|
|
1355
|
-
return target.startsWith(sharedRoot +
|
|
1378
|
+
return target.startsWith(sharedRoot + sep2);
|
|
1356
1379
|
}
|
|
1357
1380
|
function materializeOne(name, linkPath, sharedRoot) {
|
|
1358
1381
|
const target = realpathSync(linkPath);
|
|
@@ -1832,35 +1855,12 @@ function reportPathMap(section2) {
|
|
|
1832
1855
|
}
|
|
1833
1856
|
const map = readJsonSafe(mapPath, mapPath, section2);
|
|
1834
1857
|
if (map === null) return;
|
|
1835
|
-
const
|
|
1836
|
-
if (
|
|
1837
|
-
addItem(
|
|
1838
|
-
section2,
|
|
1839
|
-
`${red(failGlyph)} path-map.json invalid schema: "projects" must be an object`
|
|
1840
|
-
);
|
|
1858
|
+
const shapeError = validatePathMapShape(map);
|
|
1859
|
+
if (shapeError !== null) {
|
|
1860
|
+
addItem(section2, `${red(failGlyph)} ${shapeError}`);
|
|
1841
1861
|
process.exitCode = 1;
|
|
1842
1862
|
return;
|
|
1843
1863
|
}
|
|
1844
|
-
for (const [name, hosts] of Object.entries(projects)) {
|
|
1845
|
-
if (hosts === null || typeof hosts !== "object" || Array.isArray(hosts)) {
|
|
1846
|
-
addItem(
|
|
1847
|
-
section2,
|
|
1848
|
-
`${red(failGlyph)} path-map.json invalid schema: project "${name}" hosts must be an object`
|
|
1849
|
-
);
|
|
1850
|
-
process.exitCode = 1;
|
|
1851
|
-
return;
|
|
1852
|
-
}
|
|
1853
|
-
for (const [hostName, mappedPath] of Object.entries(hosts)) {
|
|
1854
|
-
if (typeof mappedPath !== "string") {
|
|
1855
|
-
addItem(
|
|
1856
|
-
section2,
|
|
1857
|
-
`${red(failGlyph)} path-map.json invalid schema: project "${name}" host "${hostName}" path must be a string`
|
|
1858
|
-
);
|
|
1859
|
-
process.exitCode = 1;
|
|
1860
|
-
return;
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
1864
|
reportMappedProjects(section2, map);
|
|
1865
1865
|
reportUnmappedProjects(section2, map);
|
|
1866
1866
|
reportPathCollisions(section2, map);
|
|
@@ -1874,7 +1874,7 @@ function reportNeverSync(section2) {
|
|
|
1874
1874
|
);
|
|
1875
1875
|
}
|
|
1876
1876
|
|
|
1877
|
-
// src/commands.doctor.checks.
|
|
1877
|
+
// src/commands.doctor.checks.git-state.ts
|
|
1878
1878
|
init_color();
|
|
1879
1879
|
init_config();
|
|
1880
1880
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
@@ -2194,21 +2194,27 @@ init_config();
|
|
|
2194
2194
|
init_utils();
|
|
2195
2195
|
init_utils_fs();
|
|
2196
2196
|
init_utils_json();
|
|
2197
|
-
import { cpSync as cpSync4, existsSync as existsSync15, mkdirSync as mkdirSync3, readdirSync as readdirSync6, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
|
|
2198
|
-
import { join as join19, relative as relative3, sep as
|
|
2199
|
-
|
|
2197
|
+
import { cpSync as cpSync4, existsSync as existsSync15, mkdirSync as mkdirSync3, readdirSync as readdirSync6, renameSync as renameSync3, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
|
|
2198
|
+
import { join as join19, relative as relative3, sep as sep3 } from "node:path";
|
|
2199
|
+
var TMP_SUFFIX = ".nomad-tmp";
|
|
2200
|
+
function atomicMirror(src, dst, options) {
|
|
2201
|
+
const tmp = `${dst}${TMP_SUFFIX}`;
|
|
2202
|
+
rmSync6(tmp, { recursive: true, force: true });
|
|
2203
|
+
cpSync4(src, tmp, options);
|
|
2200
2204
|
rmSync6(dst, { recursive: true, force: true });
|
|
2201
|
-
|
|
2205
|
+
renameSync3(tmp, dst);
|
|
2206
|
+
}
|
|
2207
|
+
function copyDir(src, dst) {
|
|
2208
|
+
atomicMirror(src, dst, { recursive: true, force: true });
|
|
2202
2209
|
}
|
|
2203
2210
|
function copyDirJsonlOnly(src, dst) {
|
|
2204
|
-
|
|
2205
|
-
cpSync4(src, dst, {
|
|
2211
|
+
atomicMirror(src, dst, {
|
|
2206
2212
|
recursive: true,
|
|
2207
2213
|
force: true,
|
|
2208
2214
|
filter: (srcPath) => {
|
|
2209
2215
|
const rel = relative3(src, srcPath);
|
|
2210
2216
|
if (rel === "") return true;
|
|
2211
|
-
if (rel.split(
|
|
2217
|
+
if (rel.split(sep3).length > 1) return true;
|
|
2212
2218
|
if (statSync4(srcPath).isDirectory()) return true;
|
|
2213
2219
|
if (srcPath.endsWith(".jsonl")) return true;
|
|
2214
2220
|
item(`skip ${rel}: extension not in allowlist`);
|
|
@@ -2234,7 +2240,7 @@ function remapPull(ts, opts = {}) {
|
|
|
2234
2240
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
2235
2241
|
return { unmapped: 0, pulled, wouldPull };
|
|
2236
2242
|
}
|
|
2237
|
-
const map =
|
|
2243
|
+
const map = readPathMap(mapPath);
|
|
2238
2244
|
const localProjects = join19(claude, "projects");
|
|
2239
2245
|
if (!dryRun) mkdirSync3(localProjects, { recursive: true });
|
|
2240
2246
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
@@ -2298,13 +2304,14 @@ function remapPush(ts, opts = {}) {
|
|
|
2298
2304
|
log("no path-map.json; skipping session export");
|
|
2299
2305
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
2300
2306
|
}
|
|
2301
|
-
const map =
|
|
2307
|
+
const map = readPathMap(mapPath);
|
|
2302
2308
|
const localProjects = join19(claude, "projects");
|
|
2303
2309
|
const repoProjects = join19(repo, "shared", "projects");
|
|
2304
2310
|
const reverse = buildReverseMap(map);
|
|
2305
2311
|
if (!existsSync15(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
2306
2312
|
if (!dryRun) mkdirSync3(repoProjects, { recursive: true });
|
|
2307
2313
|
for (const dir of readdirSync6(localProjects)) {
|
|
2314
|
+
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
2308
2315
|
const logical = reverse.get(dir);
|
|
2309
2316
|
if (!logical) {
|
|
2310
2317
|
unmapped++;
|
|
@@ -3042,13 +3049,13 @@ import { createInterface } from "node:readline/promises";
|
|
|
3042
3049
|
// src/commands.push.recovery.actions.ts
|
|
3043
3050
|
init_config();
|
|
3044
3051
|
import { readFileSync as readFileSync12 } from "node:fs";
|
|
3045
|
-
import { isAbsolute, resolve as resolve3, sep as
|
|
3052
|
+
import { isAbsolute, resolve as resolve3, sep as sep5 } from "node:path";
|
|
3046
3053
|
|
|
3047
3054
|
// src/commands.push.recovery.redact.ts
|
|
3048
3055
|
init_config();
|
|
3049
3056
|
init_config_sharedDirs_guard();
|
|
3050
3057
|
import { cpSync as cpSync5, existsSync as existsSync24, mkdirSync as mkdirSync6, statSync as statSync7 } from "node:fs";
|
|
3051
|
-
import { dirname as dirname6, join as join29, sep as
|
|
3058
|
+
import { dirname as dirname6, join as join29, sep as sep4 } from "node:path";
|
|
3052
3059
|
|
|
3053
3060
|
// src/commands.redact.ts
|
|
3054
3061
|
init_config();
|
|
@@ -3059,6 +3066,7 @@ import { dirname as dirname5, join as join28 } from "node:path";
|
|
|
3059
3066
|
import { existsSync as existsSync22, lstatSync as lstatSync7, readFileSync as readFileSync10, readdirSync as readdirSync9, statSync as statSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3060
3067
|
import { join as join26 } from "node:path";
|
|
3061
3068
|
init_utils_fs();
|
|
3069
|
+
init_utils();
|
|
3062
3070
|
function collectFiles(dir, out) {
|
|
3063
3071
|
if (!existsSync22(dir)) return;
|
|
3064
3072
|
const st = lstatSync7(dir);
|
|
@@ -3101,12 +3109,67 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
3101
3109
|
if (!dryRun && total > 0) {
|
|
3102
3110
|
for (const { path: filePath, findings } of dirty) {
|
|
3103
3111
|
backupBeforeWrite(filePath, ts);
|
|
3104
|
-
|
|
3112
|
+
const before = readFileSync10(filePath, "utf8");
|
|
3113
|
+
const after = applyRedactions(before, findings);
|
|
3114
|
+
if (after === before) {
|
|
3115
|
+
log(
|
|
3116
|
+
`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.`
|
|
3117
|
+
);
|
|
3118
|
+
}
|
|
3119
|
+
writeFileSync3(filePath, after, "utf8");
|
|
3105
3120
|
}
|
|
3106
3121
|
}
|
|
3107
3122
|
return { total, dirty };
|
|
3108
3123
|
}
|
|
3109
3124
|
|
|
3125
|
+
// src/commands.pushed-history.ts
|
|
3126
|
+
init_utils();
|
|
3127
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
3128
|
+
function pushedRef(repo) {
|
|
3129
|
+
try {
|
|
3130
|
+
const ref = execFileSync8("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
|
|
3131
|
+
cwd: repo,
|
|
3132
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3133
|
+
}).toString().trim();
|
|
3134
|
+
return ref.length > 0 ? ref : null;
|
|
3135
|
+
} catch {
|
|
3136
|
+
return null;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
function sessionInPushedHistory(id, repo) {
|
|
3140
|
+
const ref = pushedRef(repo);
|
|
3141
|
+
if (ref === null) return false;
|
|
3142
|
+
try {
|
|
3143
|
+
const out = execFileSync8(
|
|
3144
|
+
"git",
|
|
3145
|
+
[
|
|
3146
|
+
"log",
|
|
3147
|
+
ref,
|
|
3148
|
+
"--oneline",
|
|
3149
|
+
"-1",
|
|
3150
|
+
"--",
|
|
3151
|
+
`shared/projects/*/${id}.jsonl`,
|
|
3152
|
+
`shared/projects/*/${id}/*`
|
|
3153
|
+
],
|
|
3154
|
+
{ cwd: repo, stdio: ["ignore", "pipe", "ignore"] }
|
|
3155
|
+
).toString().trim();
|
|
3156
|
+
return out.length > 0;
|
|
3157
|
+
} catch {
|
|
3158
|
+
return false;
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
function warnIfSessionPushed(id, repo) {
|
|
3162
|
+
if (!sessionInPushedHistory(id, repo)) return;
|
|
3163
|
+
log(
|
|
3164
|
+
`warning: session ${id} is already in pushed history (origin).
|
|
3165
|
+
This command only changes your local copy and the next push; it does NOT
|
|
3166
|
+
remove the secret from commits already on the remote.
|
|
3167
|
+
To fully remediate a real secret: rotate the credential, then rewrite
|
|
3168
|
+
history (e.g. with git filter-repo) and force-push, coordinating with
|
|
3169
|
+
anyone else who has cloned the repo.`
|
|
3170
|
+
);
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3110
3173
|
// src/commands.redact.ts
|
|
3111
3174
|
init_push_gitleaks_scan();
|
|
3112
3175
|
init_utils_fs();
|
|
@@ -3308,6 +3371,7 @@ ${lines}`);
|
|
|
3308
3371
|
return;
|
|
3309
3372
|
}
|
|
3310
3373
|
log(`redacted ${totalCount} finding(s) in ${localPath} (backup: ${ts})`);
|
|
3374
|
+
warnIfSessionPushed(id, repo);
|
|
3311
3375
|
} catch (err) {
|
|
3312
3376
|
if (!(err instanceof NomadFatal)) {
|
|
3313
3377
|
throw err;
|
|
@@ -3382,12 +3446,28 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
3382
3446
|
assertSafeLogical(logical);
|
|
3383
3447
|
const abs = hostMap[HOST];
|
|
3384
3448
|
if (abs === void 0) continue;
|
|
3385
|
-
if (localPath.startsWith(join29(claude, "projects", encodePath(abs)) +
|
|
3449
|
+
if (localPath.startsWith(join29(claude, "projects", encodePath(abs)) + sep4)) {
|
|
3386
3450
|
return join29(repo, "shared", "projects", logical);
|
|
3387
3451
|
}
|
|
3388
3452
|
}
|
|
3389
3453
|
return null;
|
|
3390
3454
|
}
|
|
3455
|
+
function preflightRedactable(f, map, nowMs) {
|
|
3456
|
+
const sid = sessionIdFromFinding(f);
|
|
3457
|
+
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
3458
|
+
const localPath = resolveLiveTranscript(sid);
|
|
3459
|
+
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
3460
|
+
const sessionDir = join29(dirname6(localPath), sid);
|
|
3461
|
+
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3462
|
+
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
3463
|
+
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
3464
|
+
return `session ${sid}: looks active (modified within the last 5 minutes)`;
|
|
3465
|
+
}
|
|
3466
|
+
if (resolveStagedDir(localPath, map, claudeHome(), repoHome()) === null) {
|
|
3467
|
+
return `session ${sid}: not mapped to a staged copy`;
|
|
3468
|
+
}
|
|
3469
|
+
return null;
|
|
3470
|
+
}
|
|
3391
3471
|
function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
3392
3472
|
const refuse = (msg) => {
|
|
3393
3473
|
log(msg);
|
|
@@ -3491,7 +3571,7 @@ function makeDefaultReadLine(repo) {
|
|
|
3491
3571
|
try {
|
|
3492
3572
|
const repoRoot = resolve3(repo);
|
|
3493
3573
|
const target = resolve3(repoRoot, file);
|
|
3494
|
-
if (isAbsolute(file) || target !== repoRoot && !target.startsWith(repoRoot +
|
|
3574
|
+
if (isAbsolute(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
|
|
3495
3575
|
return null;
|
|
3496
3576
|
}
|
|
3497
3577
|
const content = readFileSync12(target, "utf8");
|
|
@@ -3558,6 +3638,22 @@ function dispatchActions(findings, actions, opts) {
|
|
|
3558
3638
|
}
|
|
3559
3639
|
}
|
|
3560
3640
|
function redactAllFindings(findings, ts, map, nowMs, scan = scanFile) {
|
|
3641
|
+
const refusals = [];
|
|
3642
|
+
const preflighted = /* @__PURE__ */ new Set();
|
|
3643
|
+
for (const f of findings) {
|
|
3644
|
+
const dedupeKey = sessionIdFromFinding(f) ?? findingKey(f);
|
|
3645
|
+
if (preflighted.has(dedupeKey)) continue;
|
|
3646
|
+
preflighted.add(dedupeKey);
|
|
3647
|
+
const reason = preflightRedactable(f, map, nowMs);
|
|
3648
|
+
if (reason !== null) refusals.push(reason);
|
|
3649
|
+
}
|
|
3650
|
+
if (refusals.length > 0) {
|
|
3651
|
+
throw new NomadFatal(
|
|
3652
|
+
`--redact-all cannot redact every finding, so no changes were made:
|
|
3653
|
+
` + refusals.map((r) => ` - ${r}`).join("\n") + `
|
|
3654
|
+
Re-run without --redact-all to triage these interactively (Drop session / Skip), or end any active session and retry.`
|
|
3655
|
+
);
|
|
3656
|
+
}
|
|
3561
3657
|
const redactedSids = /* @__PURE__ */ new Set();
|
|
3562
3658
|
for (const f of findings) {
|
|
3563
3659
|
const sid = sessionIdFromFinding(f);
|
|
@@ -3764,7 +3860,7 @@ function withSpinner(label, fn, deps) {
|
|
|
3764
3860
|
|
|
3765
3861
|
// src/commands.doctor.gitleaks-version.ts
|
|
3766
3862
|
init_color();
|
|
3767
|
-
import { execFileSync as
|
|
3863
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
3768
3864
|
import { existsSync as existsSync26 } from "node:fs";
|
|
3769
3865
|
import { join as join32 } from "node:path";
|
|
3770
3866
|
init_config();
|
|
@@ -3787,7 +3883,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
3787
3883
|
return null;
|
|
3788
3884
|
}
|
|
3789
3885
|
}
|
|
3790
|
-
function reportGitleaksVersionCheck(section2, run =
|
|
3886
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync9, tomlExists = existsSync26) {
|
|
3791
3887
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
3792
3888
|
if (raw === null) return;
|
|
3793
3889
|
const local = majorMinorOf(raw);
|
|
@@ -3807,7 +3903,7 @@ function reportGitleaksVersionCheck(section2, run = execFileSync8, tomlExists =
|
|
|
3807
3903
|
|
|
3808
3904
|
// src/commands.doctor.checks.deps.ts
|
|
3809
3905
|
init_color();
|
|
3810
|
-
import { execFileSync as
|
|
3906
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
3811
3907
|
var VERSION_TOKEN = /(\d{1,9}\.\d{1,9}\.\d{1,9})/;
|
|
3812
3908
|
var PROBE_TIMEOUT_MS = 3e3;
|
|
3813
3909
|
var FETCHER_BASE = "HTTP fetcher";
|
|
@@ -3844,7 +3940,7 @@ function reportFetcherRow(section2, run) {
|
|
|
3844
3940
|
);
|
|
3845
3941
|
}
|
|
3846
3942
|
}
|
|
3847
|
-
function reportOptionalDeps(section2, run =
|
|
3943
|
+
function reportOptionalDeps(section2, run = execFileSync10) {
|
|
3848
3944
|
const gh = probeOptionalDep("gh", run);
|
|
3849
3945
|
if (gh.status === "present") {
|
|
3850
3946
|
addItem(section2, `${green(okGlyph)} gh: ${gh.version ?? "present"}`);
|
|
@@ -3859,11 +3955,11 @@ function reportOptionalDeps(section2, run = execFileSync9) {
|
|
|
3859
3955
|
|
|
3860
3956
|
// src/commands.doctor.actions-drift.ts
|
|
3861
3957
|
init_color();
|
|
3862
|
-
import { execFileSync as
|
|
3958
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
3863
3959
|
init_config();
|
|
3864
3960
|
|
|
3865
3961
|
// src/gh-actions.ts
|
|
3866
|
-
import { execFileSync as
|
|
3962
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
3867
3963
|
var GH_TIMEOUT_MS = 5e3;
|
|
3868
3964
|
function parseGitHubRemote(remoteUrl) {
|
|
3869
3965
|
const normalized = remoteUrl.trim().replace(/\/$/, "");
|
|
@@ -3871,7 +3967,7 @@ function parseGitHubRemote(remoteUrl) {
|
|
|
3871
3967
|
if (m === null) return null;
|
|
3872
3968
|
return { owner: m[1], repo: m[2] };
|
|
3873
3969
|
}
|
|
3874
|
-
function ghAuthStatus(run =
|
|
3970
|
+
function ghAuthStatus(run = execFileSync11) {
|
|
3875
3971
|
try {
|
|
3876
3972
|
run("gh", ["auth", "status"], {
|
|
3877
3973
|
stdio: ["ignore", "ignore", "ignore"],
|
|
@@ -3885,7 +3981,7 @@ function ghAuthStatus(run = execFileSync10) {
|
|
|
3885
3981
|
return "gh-probe-error";
|
|
3886
3982
|
}
|
|
3887
3983
|
}
|
|
3888
|
-
function isRepoPrivate(ref, run =
|
|
3984
|
+
function isRepoPrivate(ref, run = execFileSync11) {
|
|
3889
3985
|
const out = run("gh", ["repo", "view", `${ref.owner}/${ref.repo}`, "--json", "isPrivate"], {
|
|
3890
3986
|
stdio: ["ignore", "pipe", "ignore"],
|
|
3891
3987
|
timeout: GH_TIMEOUT_MS
|
|
@@ -3893,7 +3989,7 @@ function isRepoPrivate(ref, run = execFileSync10) {
|
|
|
3893
3989
|
const parsed = JSON.parse(out);
|
|
3894
3990
|
return parsed.isPrivate === true;
|
|
3895
3991
|
}
|
|
3896
|
-
function isActionsEnabled(ref, run =
|
|
3992
|
+
function isActionsEnabled(ref, run = execFileSync11) {
|
|
3897
3993
|
const out = run(
|
|
3898
3994
|
"gh",
|
|
3899
3995
|
["api", `repos/${ref.owner}/${ref.repo}/actions/permissions`, "--jq", ".enabled"],
|
|
@@ -3901,7 +3997,7 @@ function isActionsEnabled(ref, run = execFileSync10) {
|
|
|
3901
3997
|
).toString().trim();
|
|
3902
3998
|
return out === "true";
|
|
3903
3999
|
}
|
|
3904
|
-
function disableActions(ref, run =
|
|
4000
|
+
function disableActions(ref, run = execFileSync11) {
|
|
3905
4001
|
run(
|
|
3906
4002
|
"gh",
|
|
3907
4003
|
[
|
|
@@ -3915,7 +4011,7 @@ function disableActions(ref, run = execFileSync10) {
|
|
|
3915
4011
|
{ stdio: ["ignore", "ignore", "pipe"], timeout: GH_TIMEOUT_MS }
|
|
3916
4012
|
);
|
|
3917
4013
|
}
|
|
3918
|
-
function readOriginRemote(cwd, run =
|
|
4014
|
+
function readOriginRemote(cwd, run = execFileSync11) {
|
|
3919
4015
|
return run("git", ["remote", "get-url", "origin"], {
|
|
3920
4016
|
cwd,
|
|
3921
4017
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -3923,7 +4019,7 @@ function readOriginRemote(cwd, run = execFileSync10) {
|
|
|
3923
4019
|
}
|
|
3924
4020
|
|
|
3925
4021
|
// src/commands.doctor.actions-drift.ts
|
|
3926
|
-
function reportActionsDrift(section2, run =
|
|
4022
|
+
function reportActionsDrift(section2, run = execFileSync12) {
|
|
3927
4023
|
let remote;
|
|
3928
4024
|
try {
|
|
3929
4025
|
remote = readOriginRemote(repoHome(), run);
|
|
@@ -4052,15 +4148,15 @@ function cmdDoctor(opts = {}) {
|
|
|
4052
4148
|
|
|
4053
4149
|
// src/commands.drop-session.ts
|
|
4054
4150
|
init_config();
|
|
4055
|
-
import { execFileSync as
|
|
4151
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
4056
4152
|
import { existsSync as existsSync29, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
|
|
4057
4153
|
import { join as join35, relative as relative4 } from "node:path";
|
|
4058
4154
|
|
|
4059
4155
|
// src/commands.drop-session.git.ts
|
|
4060
|
-
import { execFileSync as
|
|
4156
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
4061
4157
|
function expandStagedDir(dirRel, repo) {
|
|
4062
4158
|
try {
|
|
4063
|
-
const out =
|
|
4159
|
+
const out = execFileSync13("git", ["ls-files", "-z", "--", dirRel], {
|
|
4064
4160
|
cwd: repo,
|
|
4065
4161
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4066
4162
|
});
|
|
@@ -4071,7 +4167,7 @@ function expandStagedDir(dirRel, repo) {
|
|
|
4071
4167
|
}
|
|
4072
4168
|
function isTrackedInHead(rel, repo) {
|
|
4073
4169
|
try {
|
|
4074
|
-
|
|
4170
|
+
execFileSync13("git", ["cat-file", "-e", `HEAD:${rel}`], {
|
|
4075
4171
|
cwd: repo,
|
|
4076
4172
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4077
4173
|
});
|
|
@@ -4082,7 +4178,7 @@ function isTrackedInHead(rel, repo) {
|
|
|
4082
4178
|
}
|
|
4083
4179
|
function isInIndex(rel, repo) {
|
|
4084
4180
|
try {
|
|
4085
|
-
const out =
|
|
4181
|
+
const out = execFileSync13("git", ["ls-files", "--", rel], {
|
|
4086
4182
|
cwd: repo,
|
|
4087
4183
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4088
4184
|
});
|
|
@@ -4153,6 +4249,7 @@ function cmdDropSession(id) {
|
|
|
4153
4249
|
}
|
|
4154
4250
|
for (const rel of matches) unstageOne(rel, repo);
|
|
4155
4251
|
reportScrubHint(id, matches);
|
|
4252
|
+
warnIfSessionPushed(id, repo);
|
|
4156
4253
|
} catch (err) {
|
|
4157
4254
|
if (!(err instanceof NomadFatal)) {
|
|
4158
4255
|
throw err;
|
|
@@ -4187,12 +4284,12 @@ function unstageOne(rel, repo) {
|
|
|
4187
4284
|
}
|
|
4188
4285
|
try {
|
|
4189
4286
|
if (isTrackedInHead(rel, repo)) {
|
|
4190
|
-
|
|
4287
|
+
execFileSync14("git", ["restore", "--staged", "--worktree", "--", rel], {
|
|
4191
4288
|
cwd: repo,
|
|
4192
4289
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4193
4290
|
});
|
|
4194
4291
|
} else {
|
|
4195
|
-
|
|
4292
|
+
execFileSync14("git", ["rm", "--cached", "-f", "--", rel], {
|
|
4196
4293
|
cwd: repo,
|
|
4197
4294
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4198
4295
|
});
|
|
@@ -4307,7 +4404,7 @@ import { join as join39 } from "node:path";
|
|
|
4307
4404
|
|
|
4308
4405
|
// src/extras-sync.diff.ts
|
|
4309
4406
|
init_utils();
|
|
4310
|
-
import { execFileSync as
|
|
4407
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
4311
4408
|
function labelDiffLine(line) {
|
|
4312
4409
|
const tab = line.indexOf(" ");
|
|
4313
4410
|
if (tab === -1) return line;
|
|
@@ -4322,7 +4419,7 @@ function parseDiffOutput(stdout) {
|
|
|
4322
4419
|
}
|
|
4323
4420
|
function listDivergingFiles(a, b) {
|
|
4324
4421
|
try {
|
|
4325
|
-
const stdout =
|
|
4422
|
+
const stdout = execFileSync15("git", ["diff", "--no-index", "--name-status", a, b], {
|
|
4326
4423
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4327
4424
|
}).toString();
|
|
4328
4425
|
return parseDiffOutput(stdout);
|
|
@@ -4502,10 +4599,10 @@ init_utils_json();
|
|
|
4502
4599
|
// src/extras-sync.remap.ts
|
|
4503
4600
|
init_config();
|
|
4504
4601
|
import { existsSync as existsSync31, mkdirSync as mkdirSync7, readdirSync as readdirSync12, realpathSync as realpathSync4, rmSync as rmSync11 } from "node:fs";
|
|
4505
|
-
import { dirname as dirname7, join as join38, sep as
|
|
4602
|
+
import { dirname as dirname7, join as join38, sep as sep7 } from "node:path";
|
|
4506
4603
|
|
|
4507
4604
|
// src/extras-sync.planning-diff.ts
|
|
4508
|
-
import { join as join37, normalize as normalize2, sep as
|
|
4605
|
+
import { join as join37, normalize as normalize2, sep as sep6 } from "node:path";
|
|
4509
4606
|
init_utils();
|
|
4510
4607
|
function processRecord(fields, i, changed, deleted) {
|
|
4511
4608
|
const status = fields[i];
|
|
@@ -4558,7 +4655,7 @@ function planningDeleteTargets(opts) {
|
|
|
4558
4655
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
4559
4656
|
const prefix = logicalPrefix + ".planning/";
|
|
4560
4657
|
const planningRoot = join37(localRoot, ".planning");
|
|
4561
|
-
const planningRootBoundary = planningRoot +
|
|
4658
|
+
const planningRootBoundary = planningRoot + sep6;
|
|
4562
4659
|
const targets = [];
|
|
4563
4660
|
for (const repoPath of deleted) {
|
|
4564
4661
|
if (!repoPath.startsWith(prefix)) {
|
|
@@ -4600,7 +4697,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
4600
4697
|
}
|
|
4601
4698
|
function pruneEmptyAncestors(target, planningRoot) {
|
|
4602
4699
|
let dir = dirname7(target);
|
|
4603
|
-
while (dir !== planningRoot && dir.startsWith(planningRoot +
|
|
4700
|
+
while (dir !== planningRoot && dir.startsWith(planningRoot + sep7)) {
|
|
4604
4701
|
try {
|
|
4605
4702
|
if (readdirSync12(dir).length > 0) break;
|
|
4606
4703
|
rmSync11(dir, { recursive: true, force: true });
|
|
@@ -4618,7 +4715,7 @@ function tryRealpath(dir) {
|
|
|
4618
4715
|
}
|
|
4619
4716
|
}
|
|
4620
4717
|
function isInsidePlanningRoot(parentReal, rootReal) {
|
|
4621
|
-
return parentReal === rootReal || parentReal.startsWith(rootReal +
|
|
4718
|
+
return parentReal === rootReal || parentReal.startsWith(rootReal + sep7);
|
|
4622
4719
|
}
|
|
4623
4720
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
4624
4721
|
if (existsSync31(repoCounterpart)) return;
|
|
@@ -4660,7 +4757,7 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
4660
4757
|
backupExtrasWrite(join38(t.localRoot, t.dirname), ts, t.localRoot);
|
|
4661
4758
|
const planningRoot = join38(t.localRoot, ".planning");
|
|
4662
4759
|
for (const target of targets) {
|
|
4663
|
-
const relToLocal = target.slice(t.localRoot.length +
|
|
4760
|
+
const relToLocal = target.slice(t.localRoot.length + sep7.length);
|
|
4664
4761
|
deletePlanningTarget(target, planningRoot, join38(repoExtras, t.logical, relToLocal));
|
|
4665
4762
|
}
|
|
4666
4763
|
}
|
|
@@ -5265,9 +5362,9 @@ init_config();
|
|
|
5265
5362
|
init_commands_pull_wedge();
|
|
5266
5363
|
init_utils();
|
|
5267
5364
|
init_utils_fs();
|
|
5268
|
-
import { execFileSync as
|
|
5365
|
+
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
5269
5366
|
function gitCapture(args, cwd) {
|
|
5270
|
-
return
|
|
5367
|
+
return execFileSync16("git", args, {
|
|
5271
5368
|
cwd,
|
|
5272
5369
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5273
5370
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -5521,6 +5618,19 @@ function isNeverSync(path) {
|
|
|
5521
5618
|
}
|
|
5522
5619
|
return false;
|
|
5523
5620
|
}
|
|
5621
|
+
var GSD_HOOKS_SUPPORT_FILES = /* @__PURE__ */ new Set(["managed-hooks-registry.cjs", "package.json"]);
|
|
5622
|
+
var GSD_HOOKS_SUPPORT_DIR = "lib";
|
|
5623
|
+
function isGsdDropped(path) {
|
|
5624
|
+
const segments = path.split("/");
|
|
5625
|
+
if (segments[0] !== "shared" || segments.length < 3) return false;
|
|
5626
|
+
const dirName = segments[1];
|
|
5627
|
+
if (!GSD_DROPPED_NAMES.includes(dirName)) return false;
|
|
5628
|
+
const childName = segments[2];
|
|
5629
|
+
if (childName.startsWith(GSD_PREFIX)) return true;
|
|
5630
|
+
if (dirName !== "hooks") return false;
|
|
5631
|
+
if (segments.length === 3) return GSD_HOOKS_SUPPORT_FILES.has(childName);
|
|
5632
|
+
return childName === GSD_HOOKS_SUPPORT_DIR;
|
|
5633
|
+
}
|
|
5524
5634
|
function parsePorcelainZ2(statusPorcelain) {
|
|
5525
5635
|
const records = statusPorcelain.split("\0");
|
|
5526
5636
|
const paths = [];
|
|
@@ -5554,6 +5664,7 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
5554
5664
|
for (const path of parsePorcelainZ2(statusPorcelain)) {
|
|
5555
5665
|
if (isNeverSync(path)) {
|
|
5556
5666
|
neverSyncHits.push(path);
|
|
5667
|
+
} else if (isGsdDropped(path)) {
|
|
5557
5668
|
} else if (!isAllowed(path, allowed)) {
|
|
5558
5669
|
violations.push(path);
|
|
5559
5670
|
}
|
|
@@ -5570,7 +5681,7 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
5570
5681
|
|
|
5571
5682
|
// src/push-global-config.ts
|
|
5572
5683
|
init_config();
|
|
5573
|
-
import { execFileSync as
|
|
5684
|
+
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
5574
5685
|
var STATUS_LABELS = {
|
|
5575
5686
|
A: "add",
|
|
5576
5687
|
M: "modify",
|
|
@@ -5610,7 +5721,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
|
|
|
5610
5721
|
}
|
|
5611
5722
|
function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
|
|
5612
5723
|
const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
|
|
5613
|
-
const raw =
|
|
5724
|
+
const raw = execFileSync17("git", args, {
|
|
5614
5725
|
cwd: repoHome2,
|
|
5615
5726
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5616
5727
|
}).toString();
|
|
@@ -5745,6 +5856,16 @@ function guardGitlinks(repo) {
|
|
|
5745
5856
|
}
|
|
5746
5857
|
async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo) {
|
|
5747
5858
|
gitOrFatal(["add", "-A"], "git add", repo);
|
|
5859
|
+
const staged = parsePorcelainZ2(gitStatusPorcelainZ(repo));
|
|
5860
|
+
const toDrop = staged.filter((p) => isGsdDropped(p));
|
|
5861
|
+
if (toDrop.length > 0) {
|
|
5862
|
+
gitOrFatal(["restore", "--staged", "--", ...toDrop], "git restore --staged", repo);
|
|
5863
|
+
}
|
|
5864
|
+
if (staged.length === toDrop.length) {
|
|
5865
|
+
log("nothing to commit");
|
|
5866
|
+
renderNoScanTree(st);
|
|
5867
|
+
return;
|
|
5868
|
+
}
|
|
5748
5869
|
st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: true });
|
|
5749
5870
|
let verdict = withSpinner("Scanning for secrets", () => scanPushVerdict(repo));
|
|
5750
5871
|
if (verdict.leak) {
|
|
@@ -5829,16 +5950,16 @@ async function cmdPush(opts = {}) {
|
|
|
5829
5950
|
}
|
|
5830
5951
|
|
|
5831
5952
|
// src/commands.update.ts
|
|
5832
|
-
import { execFileSync as
|
|
5953
|
+
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
5833
5954
|
init_utils();
|
|
5834
|
-
function readInstalledVersion(run =
|
|
5955
|
+
function readInstalledVersion(run = execFileSync18) {
|
|
5835
5956
|
try {
|
|
5836
5957
|
return run("nomad", ["--version"], { encoding: "utf8" }).toString().trim() || null;
|
|
5837
5958
|
} catch {
|
|
5838
5959
|
return null;
|
|
5839
5960
|
}
|
|
5840
5961
|
}
|
|
5841
|
-
function cmdUpdate(run =
|
|
5962
|
+
function cmdUpdate(run = execFileSync18) {
|
|
5842
5963
|
console.log("Updating claude-nomad CLI via npm...");
|
|
5843
5964
|
try {
|
|
5844
5965
|
run("npm", ["update", "-g", "claude-nomad"], { stdio: "inherit" });
|
|
@@ -5892,14 +6013,14 @@ import { join as join48 } from "node:path";
|
|
|
5892
6013
|
|
|
5893
6014
|
// src/init.gh-onboard.ts
|
|
5894
6015
|
init_config();
|
|
5895
|
-
import { execFileSync as
|
|
6016
|
+
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
5896
6017
|
init_utils();
|
|
5897
6018
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
5898
6019
|
function isValidRepoName(name) {
|
|
5899
6020
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
5900
6021
|
}
|
|
5901
6022
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
5902
|
-
function ensureOriginRepo(repoName, run =
|
|
6023
|
+
function ensureOriginRepo(repoName, run = execFileSync19) {
|
|
5903
6024
|
if (!isValidRepoName(repoName)) {
|
|
5904
6025
|
die(
|
|
5905
6026
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -6112,86 +6233,20 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
6112
6233
|
}
|
|
6113
6234
|
}
|
|
6114
6235
|
|
|
6115
|
-
// src/nomad.dispatch.ts
|
|
6236
|
+
// src/nomad.dispatch.helpers.ts
|
|
6237
|
+
var REJECT = { ok: false, advance: 0 };
|
|
6238
|
+
function applyBool(seen, set) {
|
|
6239
|
+
if (seen) return REJECT;
|
|
6240
|
+
set();
|
|
6241
|
+
return { ok: true, advance: 1 };
|
|
6242
|
+
}
|
|
6116
6243
|
function extractFlagValue(argv, i) {
|
|
6117
6244
|
const val = argv[i + 1];
|
|
6118
6245
|
if (val === void 0 || val.startsWith("--")) return null;
|
|
6119
6246
|
return val;
|
|
6120
6247
|
}
|
|
6121
|
-
function applyInitToken(argv, i, st) {
|
|
6122
|
-
const token = argv[i];
|
|
6123
|
-
if (token === "--snapshot") {
|
|
6124
|
-
if (st.sawSnapshot) return { ok: false, advance: 0 };
|
|
6125
|
-
st.sawSnapshot = true;
|
|
6126
|
-
st.snapshot = true;
|
|
6127
|
-
return { ok: true, advance: 1 };
|
|
6128
|
-
}
|
|
6129
|
-
if (token === "--keep-actions") {
|
|
6130
|
-
if (st.sawKeepActions) return { ok: false, advance: 0 };
|
|
6131
|
-
st.sawKeepActions = true;
|
|
6132
|
-
st.keepActions = true;
|
|
6133
|
-
return { ok: true, advance: 1 };
|
|
6134
|
-
}
|
|
6135
|
-
if (token === "--repo") {
|
|
6136
|
-
if (st.sawRepo) return { ok: false, advance: 0 };
|
|
6137
|
-
st.sawRepo = true;
|
|
6138
|
-
const val = extractFlagValue(argv, i);
|
|
6139
|
-
if (val === null) return { ok: false, advance: 0 };
|
|
6140
|
-
st.repoName = val;
|
|
6141
|
-
return { ok: true, advance: 2 };
|
|
6142
|
-
}
|
|
6143
|
-
return { ok: false, advance: 0 };
|
|
6144
|
-
}
|
|
6145
|
-
function parseInitArgs(argv) {
|
|
6146
|
-
const st = {
|
|
6147
|
-
snapshot: false,
|
|
6148
|
-
keepActions: false,
|
|
6149
|
-
repoName: void 0,
|
|
6150
|
-
sawSnapshot: false,
|
|
6151
|
-
sawKeepActions: false,
|
|
6152
|
-
sawRepo: false
|
|
6153
|
-
};
|
|
6154
|
-
let i = 3;
|
|
6155
|
-
while (i < argv.length) {
|
|
6156
|
-
const { ok: ok2, advance } = applyInitToken(argv, i, st);
|
|
6157
|
-
if (!ok2) return null;
|
|
6158
|
-
i += advance;
|
|
6159
|
-
}
|
|
6160
|
-
return { snapshot: st.snapshot, keepActions: st.keepActions, repoName: st.repoName };
|
|
6161
|
-
}
|
|
6162
|
-
function parseRedactArgs(argv) {
|
|
6163
|
-
const id = argv[3];
|
|
6164
|
-
if (typeof id !== "string" || !/^\w[\w-]{0,127}$/.test(id)) {
|
|
6165
|
-
return null;
|
|
6166
|
-
}
|
|
6167
|
-
let rule;
|
|
6168
|
-
let dryRun = false;
|
|
6169
|
-
let sawRule = false;
|
|
6170
|
-
let sawDryRun = false;
|
|
6171
|
-
let i = 4;
|
|
6172
|
-
while (i < argv.length) {
|
|
6173
|
-
const token = argv[i];
|
|
6174
|
-
if (token === "--dry-run") {
|
|
6175
|
-
if (sawDryRun) return null;
|
|
6176
|
-
sawDryRun = true;
|
|
6177
|
-
dryRun = true;
|
|
6178
|
-
i++;
|
|
6179
|
-
} else if (token === "--rule") {
|
|
6180
|
-
if (sawRule) return null;
|
|
6181
|
-
sawRule = true;
|
|
6182
|
-
const val = argv[i + 1];
|
|
6183
|
-
if (val === void 0 || val.startsWith("--")) return null;
|
|
6184
|
-
rule = val;
|
|
6185
|
-
i += 2;
|
|
6186
|
-
} else {
|
|
6187
|
-
return null;
|
|
6188
|
-
}
|
|
6189
|
-
}
|
|
6190
|
-
return { id, rule, dryRun };
|
|
6191
|
-
}
|
|
6192
6248
|
|
|
6193
6249
|
// src/nomad.dispatch.clean.ts
|
|
6194
|
-
var REJECT = { ok: false, advance: 0 };
|
|
6195
6250
|
function applyOlderThan(argv, i, st) {
|
|
6196
6251
|
if (st.olderThan !== void 0) return REJECT;
|
|
6197
6252
|
const val = extractFlagValue(argv, i);
|
|
@@ -6206,11 +6261,6 @@ function applyKeep(argv, i, st) {
|
|
|
6206
6261
|
st.keep = Number(val);
|
|
6207
6262
|
return { ok: true, advance: 2 };
|
|
6208
6263
|
}
|
|
6209
|
-
function applyBool(seen, set) {
|
|
6210
|
-
if (seen) return REJECT;
|
|
6211
|
-
set();
|
|
6212
|
-
return { ok: true, advance: 1 };
|
|
6213
|
-
}
|
|
6214
6264
|
function applyCleanToken(argv, i, st) {
|
|
6215
6265
|
switch (argv[i]) {
|
|
6216
6266
|
case "--backups":
|
|
@@ -6260,6 +6310,79 @@ function parseEjectArgs(argv) {
|
|
|
6260
6310
|
return { dryRun };
|
|
6261
6311
|
}
|
|
6262
6312
|
|
|
6313
|
+
// src/nomad.dispatch.ts
|
|
6314
|
+
function applyInitToken(argv, i, st) {
|
|
6315
|
+
const token = argv[i];
|
|
6316
|
+
if (token === "--snapshot") {
|
|
6317
|
+
if (st.sawSnapshot) return REJECT;
|
|
6318
|
+
st.sawSnapshot = true;
|
|
6319
|
+
st.snapshot = true;
|
|
6320
|
+
return { ok: true, advance: 1 };
|
|
6321
|
+
}
|
|
6322
|
+
if (token === "--keep-actions") {
|
|
6323
|
+
if (st.sawKeepActions) return REJECT;
|
|
6324
|
+
st.sawKeepActions = true;
|
|
6325
|
+
st.keepActions = true;
|
|
6326
|
+
return { ok: true, advance: 1 };
|
|
6327
|
+
}
|
|
6328
|
+
if (token === "--repo") {
|
|
6329
|
+
if (st.sawRepo) return REJECT;
|
|
6330
|
+
st.sawRepo = true;
|
|
6331
|
+
const val = extractFlagValue(argv, i);
|
|
6332
|
+
if (val === null) return REJECT;
|
|
6333
|
+
st.repoName = val;
|
|
6334
|
+
return { ok: true, advance: 2 };
|
|
6335
|
+
}
|
|
6336
|
+
return REJECT;
|
|
6337
|
+
}
|
|
6338
|
+
function parseInitArgs(argv) {
|
|
6339
|
+
const st = {
|
|
6340
|
+
snapshot: false,
|
|
6341
|
+
keepActions: false,
|
|
6342
|
+
repoName: void 0,
|
|
6343
|
+
sawSnapshot: false,
|
|
6344
|
+
sawKeepActions: false,
|
|
6345
|
+
sawRepo: false
|
|
6346
|
+
};
|
|
6347
|
+
let i = 3;
|
|
6348
|
+
while (i < argv.length) {
|
|
6349
|
+
const { ok: ok2, advance } = applyInitToken(argv, i, st);
|
|
6350
|
+
if (!ok2) return null;
|
|
6351
|
+
i += advance;
|
|
6352
|
+
}
|
|
6353
|
+
return { snapshot: st.snapshot, keepActions: st.keepActions, repoName: st.repoName };
|
|
6354
|
+
}
|
|
6355
|
+
function parseRedactArgs(argv) {
|
|
6356
|
+
const id = argv[3];
|
|
6357
|
+
if (typeof id !== "string" || !/^\w[\w-]{0,127}$/.test(id)) {
|
|
6358
|
+
return null;
|
|
6359
|
+
}
|
|
6360
|
+
let rule;
|
|
6361
|
+
let dryRun = false;
|
|
6362
|
+
let sawRule = false;
|
|
6363
|
+
let sawDryRun = false;
|
|
6364
|
+
let i = 4;
|
|
6365
|
+
while (i < argv.length) {
|
|
6366
|
+
const token = argv[i];
|
|
6367
|
+
if (token === "--dry-run") {
|
|
6368
|
+
if (sawDryRun) return null;
|
|
6369
|
+
sawDryRun = true;
|
|
6370
|
+
dryRun = true;
|
|
6371
|
+
i++;
|
|
6372
|
+
} else if (token === "--rule") {
|
|
6373
|
+
if (sawRule) return null;
|
|
6374
|
+
sawRule = true;
|
|
6375
|
+
const val = extractFlagValue(argv, i);
|
|
6376
|
+
if (val === null) return null;
|
|
6377
|
+
rule = val;
|
|
6378
|
+
i += 2;
|
|
6379
|
+
} else {
|
|
6380
|
+
return null;
|
|
6381
|
+
}
|
|
6382
|
+
}
|
|
6383
|
+
return { id, rule, dryRun };
|
|
6384
|
+
}
|
|
6385
|
+
|
|
6263
6386
|
// src/nomad.dispatch.allow.ts
|
|
6264
6387
|
function parseAllowArgs(argv) {
|
|
6265
6388
|
const positionals = argv.slice(3);
|
|
@@ -6293,32 +6416,26 @@ function parsePullArgs(argv) {
|
|
|
6293
6416
|
}
|
|
6294
6417
|
|
|
6295
6418
|
// src/nomad.dispatch.push.ts
|
|
6296
|
-
var REJECT2 = { ok: false, advance: 0 };
|
|
6297
|
-
function applyBool2(seen, set) {
|
|
6298
|
-
if (seen) return REJECT2;
|
|
6299
|
-
set();
|
|
6300
|
-
return { ok: true, advance: 1 };
|
|
6301
|
-
}
|
|
6302
6419
|
var RULE_ID_RE = /^\w[\w-]*$/;
|
|
6303
6420
|
function applyAllow2(argv, i, st) {
|
|
6304
|
-
if (st.allowRule !== void 0) return
|
|
6421
|
+
if (st.allowRule !== void 0) return REJECT;
|
|
6305
6422
|
const val = extractFlagValue(argv, i);
|
|
6306
|
-
if (val === null || !RULE_ID_RE.test(val)) return
|
|
6423
|
+
if (val === null || !RULE_ID_RE.test(val)) return REJECT;
|
|
6307
6424
|
st.allowRule = val;
|
|
6308
6425
|
return { ok: true, advance: 2 };
|
|
6309
6426
|
}
|
|
6310
6427
|
function applyPushToken(argv, i, st) {
|
|
6311
6428
|
switch (argv[i]) {
|
|
6312
6429
|
case "--dry-run":
|
|
6313
|
-
return
|
|
6430
|
+
return applyBool(st.dryRun, () => st.dryRun = true);
|
|
6314
6431
|
case "--redact-all":
|
|
6315
|
-
return
|
|
6432
|
+
return applyBool(st.redactAll, () => st.redactAll = true);
|
|
6316
6433
|
case "--allow-all":
|
|
6317
|
-
return
|
|
6434
|
+
return applyBool(st.allowAll, () => st.allowAll = true);
|
|
6318
6435
|
case "--allow":
|
|
6319
6436
|
return applyAllow2(argv, i, st);
|
|
6320
6437
|
default:
|
|
6321
|
-
return
|
|
6438
|
+
return REJECT;
|
|
6322
6439
|
}
|
|
6323
6440
|
}
|
|
6324
6441
|
function parsePushArgs(argv) {
|
|
@@ -6350,7 +6467,7 @@ function parsePushArgs(argv) {
|
|
|
6350
6467
|
// package.json
|
|
6351
6468
|
var package_default = {
|
|
6352
6469
|
name: "claude-nomad",
|
|
6353
|
-
version: "0.50.
|
|
6470
|
+
version: "0.50.3",
|
|
6354
6471
|
type: "module",
|
|
6355
6472
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
6356
6473
|
keywords: [
|
|
@@ -6579,7 +6696,7 @@ function resumeCmd(sessionId) {
|
|
|
6579
6696
|
process.exit(1);
|
|
6580
6697
|
}
|
|
6581
6698
|
const map = readJson(mapPath);
|
|
6582
|
-
const schemaError =
|
|
6699
|
+
const schemaError = validatePathMapShape(map);
|
|
6583
6700
|
if (schemaError !== null) {
|
|
6584
6701
|
fail(schemaError);
|
|
6585
6702
|
process.exit(1);
|
|
@@ -6614,26 +6731,6 @@ function extractRecordedCwd(jsonlPath) {
|
|
|
6614
6731
|
}
|
|
6615
6732
|
return null;
|
|
6616
6733
|
}
|
|
6617
|
-
function validatePathMap(raw) {
|
|
6618
|
-
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
6619
|
-
return "path-map.json invalid schema: top-level value must be an object";
|
|
6620
|
-
}
|
|
6621
|
-
const projects = raw.projects;
|
|
6622
|
-
if (projects === null || typeof projects !== "object" || Array.isArray(projects)) {
|
|
6623
|
-
return 'path-map.json invalid schema: "projects" must be an object';
|
|
6624
|
-
}
|
|
6625
|
-
for (const [name, hosts] of Object.entries(projects)) {
|
|
6626
|
-
if (hosts === null || typeof hosts !== "object" || Array.isArray(hosts)) {
|
|
6627
|
-
return `path-map.json invalid schema: project "${name}" hosts must be an object`;
|
|
6628
|
-
}
|
|
6629
|
-
for (const [host, value] of Object.entries(hosts)) {
|
|
6630
|
-
if (typeof value !== "string") {
|
|
6631
|
-
return `path-map.json invalid schema: project "${name}" host "${host}" path must be a string`;
|
|
6632
|
-
}
|
|
6633
|
-
}
|
|
6634
|
-
}
|
|
6635
|
-
return null;
|
|
6636
|
-
}
|
|
6637
6734
|
function lookupLocalPath(map, recordedCwd) {
|
|
6638
6735
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
6639
6736
|
const isUnderMappedPath = Object.values(hosts).some(
|
package/package.json
CHANGED