@indigoai-us/hq-cloud 6.18.31 → 6.18.32
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/dist/bin/sync-runner-watch-loop.test.js +30 -0
- package/dist/bin/sync-runner-watch-loop.test.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +3 -1
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +79 -1
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/cli/rescue-core.d.ts +5 -2
- package/dist/cli/rescue-core.d.ts.map +1 -1
- package/dist/cli/rescue-core.js +152 -37
- package/dist/cli/rescue-core.js.map +1 -1
- package/dist/cli/rescue-settings-reconcile.test.js +91 -13
- package/dist/cli/rescue-settings-reconcile.test.js.map +1 -1
- package/dist/cli/rescue.d.ts +2 -0
- package/dist/cli/rescue.d.ts.map +1 -1
- package/dist/cli/rescue.js +35 -0
- package/dist/cli/rescue.js.map +1 -1
- package/dist/sync/child-process-sync-worker-errors.test.d.ts +2 -0
- package/dist/sync/child-process-sync-worker-errors.test.d.ts.map +1 -0
- package/dist/sync/child-process-sync-worker-errors.test.js +121 -0
- package/dist/sync/child-process-sync-worker-errors.test.js.map +1 -0
- package/dist/sync/child-process-sync-worker.d.ts.map +1 -1
- package/dist/sync/child-process-sync-worker.js +25 -2
- package/dist/sync/child-process-sync-worker.js.map +1 -1
- package/dist/telemetry-events.js +1 -1
- package/dist/telemetry-events.js.map +1 -1
- package/dist/telemetry-events.test.js +2 -1
- package/dist/telemetry-events.test.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/rescue-core.js
CHANGED
|
@@ -733,7 +733,7 @@ export function runRescue(argv, opts = {}) {
|
|
|
733
733
|
tmpdir = d;
|
|
734
734
|
}, () => {
|
|
735
735
|
retainTmpdir = true;
|
|
736
|
-
}, opts.gitReachabilityProbeTimeoutMs ?? GIT_REACHABILITY_PROBE_TIMEOUT_MS, opts.coreSourceStampWriter, opts.restoreOnRsyncFailure === true, opts.backupCapacityStatfsSync ?? fs.statfsSync, opts.backupCapacityCloneProbe ?? probeCopyOnWriteClone, opts.backupCapacityStatSync ?? fs.statSync);
|
|
736
|
+
}, opts.gitReachabilityProbeTimeoutMs ?? GIT_REACHABILITY_PROBE_TIMEOUT_MS, opts.coreSourceStampWriter, opts.restoreOnRsyncFailure === true, opts.setupPathSettingsLocal === true, opts.backupCapacityStatfsSync ?? fs.statfsSync, opts.backupCapacityCloneProbe ?? probeCopyOnWriteClone, opts.backupCapacityStatSync ?? fs.statSync);
|
|
737
737
|
}
|
|
738
738
|
catch (e) {
|
|
739
739
|
if (e instanceof ExitError)
|
|
@@ -1536,7 +1536,7 @@ function recoveryProof(unresolved, cfg, hqRoot, srcDir, srcSha, rsyncLauncher, r
|
|
|
1536
1536
|
}
|
|
1537
1537
|
return { eligible: !hasSkippedSnapshotEntries(newest.entry.manifest), failures };
|
|
1538
1538
|
}
|
|
1539
|
-
function doRescue(cfg, env, out, err, setTmp, retainTmp, gitReachabilityProbeTimeoutMs = GIT_REACHABILITY_PROBE_TIMEOUT_MS, stampWriter = writeCoreSourceStamp, restoreOnRsyncFailure = false, backupCapacityStatfsSync = fs.statfsSync, backupCapacityCloneProbe = probeCopyOnWriteClone, backupCapacityStatSync = fs.statSync) {
|
|
1539
|
+
function doRescue(cfg, env, out, err, setTmp, retainTmp, gitReachabilityProbeTimeoutMs = GIT_REACHABILITY_PROBE_TIMEOUT_MS, stampWriter = writeCoreSourceStamp, restoreOnRsyncFailure = false, setupPathSettingsLocal = false, backupCapacityStatfsSync = fs.statfsSync, backupCapacityCloneProbe = probeCopyOnWriteClone, backupCapacityStatSync = fs.statSync) {
|
|
1540
1540
|
const hqRoot = resolveHqRoot(cfg, err);
|
|
1541
1541
|
const RUN_TS = utcStamp(new Date(), "dash");
|
|
1542
1542
|
// Both the release overlay and a restorable snapshot require rsync. A shim
|
|
@@ -2280,8 +2280,8 @@ function doRescue(cfg, env, out, err, setTmp, retainTmp, gitReachabilityProbeTim
|
|
|
2280
2280
|
// Runs AFTER the overlay (which re-laid the release template, wiring in any
|
|
2281
2281
|
// newly-released hook registrations) and AFTER the preserve-subpath restore
|
|
2282
2282
|
// (which brought back the user's .claude/settings.local.json). Re-composes the
|
|
2283
|
-
// machine env.PATH
|
|
2284
|
-
// (vs the last-sync floor) into settings.local.json so the effective merged
|
|
2283
|
+
// machine env.PATH into the flag-selected settings file and relocates genuine
|
|
2284
|
+
// user drift (vs the last-sync floor) into settings.local.json so the effective merged
|
|
2285
2285
|
// settings are preserved. No-op in narrow mode that excludes .claude.
|
|
2286
2286
|
if (claudeSettingsInScope && !isSkippedSnapshotPath(ctx, claudeSettingsRel)) {
|
|
2287
2287
|
reconcileClaudeSettings({
|
|
@@ -2291,6 +2291,7 @@ function doRescue(cfg, env, out, err, setTmp, retainTmp, gitReachabilityProbeTim
|
|
|
2291
2291
|
historyFloor,
|
|
2292
2292
|
baselineMode,
|
|
2293
2293
|
preserveSubpaths: cfg.preserveSubpaths,
|
|
2294
|
+
setupPathSettingsLocal,
|
|
2294
2295
|
}, preRescueSettingsJson, out, err);
|
|
2295
2296
|
}
|
|
2296
2297
|
else if (claudeSettingsInScope) {
|
|
@@ -3020,7 +3021,7 @@ function jsonParseOrNull(text) {
|
|
|
3020
3021
|
* - scalar / type-mismatch: drift = ours's value when not deep-equal.
|
|
3021
3022
|
*
|
|
3022
3023
|
* `excludePath` names dotted key paths that are NEVER drift (env.PATH — a
|
|
3023
|
-
* machine-local value
|
|
3024
|
+
* machine-local value composed into the flag-selected settings file, not relocated).
|
|
3024
3025
|
* Returns `undefined` when there is no drift at this node.
|
|
3025
3026
|
*/
|
|
3026
3027
|
export function settingsDrift(ours, base, excludePath = ["env.PATH"], pathPrefix = "") {
|
|
@@ -3130,6 +3131,8 @@ export function applyArrayRemovals(theirs, ours, base) {
|
|
|
3130
3131
|
* falls back to the overlaid one; if neither runs, carries `basePath` verbatim.
|
|
3131
3132
|
*/
|
|
3132
3133
|
function composeSettingsPath(srcDir, hqRoot, basePath, env) {
|
|
3134
|
+
const baseEntries = mergeUsablePathEntries(basePath, env);
|
|
3135
|
+
const cleanBase = baseEntries.join(path.delimiter);
|
|
3133
3136
|
const candidates = [
|
|
3134
3137
|
path.join(srcDir, "core", "scripts", "compose-settings-path.sh"),
|
|
3135
3138
|
path.join(hqRoot, "core", "scripts", "compose-settings-path.sh"),
|
|
@@ -3137,12 +3140,76 @@ function composeSettingsPath(srcDir, hqRoot, basePath, env) {
|
|
|
3137
3140
|
for (const script of candidates) {
|
|
3138
3141
|
if (!isFileFollow(script))
|
|
3139
3142
|
continue;
|
|
3140
|
-
const r = run("bash", [script,
|
|
3143
|
+
const r = run("bash", [script, cleanBase], { env });
|
|
3141
3144
|
const composed = r.stdout.replace(/\n+$/, "");
|
|
3142
|
-
if (r.status === 0 && composed)
|
|
3143
|
-
|
|
3145
|
+
if (r.status === 0 && composed) {
|
|
3146
|
+
// Composer versions bundled in older releases may prepend toolchain
|
|
3147
|
+
// paths. Preserve the user's established order and append only clean,
|
|
3148
|
+
// newly discovered paths from the composer.
|
|
3149
|
+
return mergeUsablePathEntries(cleanBase, composed, env).join(path.delimiter);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
return cleanBase;
|
|
3153
|
+
}
|
|
3154
|
+
function mergeUsablePathEntries(...pathsAndEnv) {
|
|
3155
|
+
const env = pathsAndEnv.find((value) => typeof value !== "string") ?? process.env;
|
|
3156
|
+
const values = pathsAndEnv.filter((value) => typeof value === "string");
|
|
3157
|
+
const home = env.HOME ?? env.USERPROFILE;
|
|
3158
|
+
const homePath = home ? path.resolve(home) : undefined;
|
|
3159
|
+
const temporaryRoots = [
|
|
3160
|
+
os.tmpdir(),
|
|
3161
|
+
env.TMPDIR,
|
|
3162
|
+
env.TMP,
|
|
3163
|
+
env.TEMP,
|
|
3164
|
+
"/tmp",
|
|
3165
|
+
"/var/tmp",
|
|
3166
|
+
"/private/tmp",
|
|
3167
|
+
"/private/var/folders",
|
|
3168
|
+
]
|
|
3169
|
+
.filter((value) => Boolean(value))
|
|
3170
|
+
.map((root) => path.resolve(root))
|
|
3171
|
+
.filter((root) => {
|
|
3172
|
+
// A malformed temp setting such as TMPDIR=/ (or /home when HOME=/home/u)
|
|
3173
|
+
// must not classify every usable PATH entry as temporary.
|
|
3174
|
+
if (root === path.parse(root).root)
|
|
3175
|
+
return false;
|
|
3176
|
+
if (homePath) {
|
|
3177
|
+
const homeRelative = path.relative(root, homePath);
|
|
3178
|
+
if (homeRelative === "" ||
|
|
3179
|
+
(!homeRelative.startsWith(`..${path.sep}`) && homeRelative !== "..")) {
|
|
3180
|
+
return false;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
return true;
|
|
3184
|
+
});
|
|
3185
|
+
const result = [];
|
|
3186
|
+
const seen = new Set();
|
|
3187
|
+
for (const value of values) {
|
|
3188
|
+
for (const rawEntry of value.split(path.delimiter)) {
|
|
3189
|
+
const entry = rawEntry.trim();
|
|
3190
|
+
if (!entry)
|
|
3191
|
+
continue;
|
|
3192
|
+
const normalized = path.normalize(entry);
|
|
3193
|
+
const parts = normalized.split(/[\\/]+/);
|
|
3194
|
+
if (parts.some((part) => {
|
|
3195
|
+
const lower = part.toLowerCase();
|
|
3196
|
+
return lower === "_npx";
|
|
3197
|
+
}))
|
|
3198
|
+
continue;
|
|
3199
|
+
const absolute = path.resolve(entry);
|
|
3200
|
+
if (temporaryRoots.some((root) => {
|
|
3201
|
+
const relative = path.relative(path.resolve(root), absolute);
|
|
3202
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== "..");
|
|
3203
|
+
}))
|
|
3204
|
+
continue;
|
|
3205
|
+
const key = process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
3206
|
+
if (seen.has(key))
|
|
3207
|
+
continue;
|
|
3208
|
+
seen.add(key);
|
|
3209
|
+
result.push(entry);
|
|
3210
|
+
}
|
|
3144
3211
|
}
|
|
3145
|
-
return
|
|
3212
|
+
return result;
|
|
3146
3213
|
}
|
|
3147
3214
|
/** True when `.claude/settings.json` is covered by an EXPLICIT preserve subpath
|
|
3148
3215
|
* (the caller asked rescue to keep it, so the reconcile must not rewrite it).
|
|
@@ -3163,7 +3230,7 @@ function settingsExplicitlyPreserved(preserveSubpaths) {
|
|
|
3163
3230
|
* At entry the overlay has already re-laid the release template (theirs) over
|
|
3164
3231
|
* settings.json, and the user's settings.local.json has been restored from the
|
|
3165
3232
|
* preserve shuttle. This function:
|
|
3166
|
-
* 1.
|
|
3233
|
+
* 1. composes env.PATH into the flag-selected settings file (machine-specific), and
|
|
3167
3234
|
* 2. extracts ours's drift versus the last-sync floor (base) — excluding
|
|
3168
3235
|
* env.PATH — and merges it into settings.local.json (deliverable 2),
|
|
3169
3236
|
* so the EFFECTIVE merged settings (Project ⊕ Local, per Claude Code's rules)
|
|
@@ -3175,7 +3242,7 @@ function settingsExplicitlyPreserved(preserveSubpaths) {
|
|
|
3175
3242
|
* rescue's posture elsewhere.
|
|
3176
3243
|
*/
|
|
3177
3244
|
export function reconcileClaudeSettings(ctx, preRescueSettingsJson, out, err) {
|
|
3178
|
-
const { hqRoot, srcDir, env, historyFloor, baselineMode, preserveSubpaths } = ctx;
|
|
3245
|
+
const { hqRoot, srcDir, env, historyFloor, baselineMode, preserveSubpaths, setupPathSettingsLocal = false, } = ctx;
|
|
3179
3246
|
const settingsPath = path.join(hqRoot, ".claude", "settings.json");
|
|
3180
3247
|
const localPath = path.join(hqRoot, ".claude", "settings.local.json");
|
|
3181
3248
|
// The caller explicitly asked to keep settings.json (via `--preserve-subpath
|
|
@@ -3238,18 +3305,34 @@ export function reconcileClaudeSettings(ctx, preRescueSettingsJson, out, err) {
|
|
|
3238
3305
|
const oursEnvPath = oursIsValid && isPlainObject(ours.env) && typeof ours.env.PATH === "string"
|
|
3239
3306
|
? ours.env.PATH
|
|
3240
3307
|
: undefined;
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3308
|
+
const existingLocalForPath = jsonParseOrNull((() => {
|
|
3309
|
+
try {
|
|
3310
|
+
return isFileFollow(localPath) ? fs.readFileSync(localPath, "utf-8") : null;
|
|
3311
|
+
}
|
|
3312
|
+
catch {
|
|
3313
|
+
return null;
|
|
3314
|
+
}
|
|
3315
|
+
})());
|
|
3316
|
+
const localEnvPath = isPlainObject(existingLocalForPath) &&
|
|
3317
|
+
isPlainObject(existingLocalForPath.env) &&
|
|
3318
|
+
typeof existingLocalForPath.env.PATH === "string"
|
|
3319
|
+
? existingLocalForPath.env.PATH
|
|
3320
|
+
: undefined;
|
|
3321
|
+
// Keep user-local entries first, followed by the prior project PATH and the
|
|
3322
|
+
// current process PATH. Re-compose only after removing npx/temp paths.
|
|
3323
|
+
const basePath = [localEnvPath, oursEnvPath, envValue(env, "PATH")]
|
|
3324
|
+
.filter((value) => typeof value === "string")
|
|
3325
|
+
.join(path.delimiter);
|
|
3244
3326
|
const composedPath = composeSettingsPath(srcDir, hqRoot, basePath, env);
|
|
3245
|
-
|
|
3327
|
+
const writePathLocally = setupPathSettingsLocal && Boolean(composedPath);
|
|
3328
|
+
const releaseBaseline = structuredClone(theirs);
|
|
3329
|
+
if (composedPath && !setupPathSettingsLocal) {
|
|
3246
3330
|
const envObj = isPlainObject(theirs.env) ? theirs.env : (theirs.env = {});
|
|
3247
3331
|
envObj.PATH = composedPath;
|
|
3248
3332
|
}
|
|
3249
|
-
//
|
|
3250
|
-
//
|
|
3251
|
-
let
|
|
3252
|
-
let relocationFailed = false;
|
|
3333
|
+
// Relocate genuine user drift into settings.local, and apply user array
|
|
3334
|
+
// removals to the release baseline.
|
|
3335
|
+
let drift;
|
|
3253
3336
|
if (oursIsValid) {
|
|
3254
3337
|
// realBase = settings.json at the last-sync floor (only trustworthy in
|
|
3255
3338
|
// history_floor mode). Removals require it; additions fall back to theirs.
|
|
@@ -3272,21 +3355,46 @@ export function reconcileClaudeSettings(ctx, preRescueSettingsJson, out, err) {
|
|
|
3272
3355
|
const additionsBase = isPlainObject(realBase)
|
|
3273
3356
|
? realBase
|
|
3274
3357
|
: theirs;
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
}
|
|
3358
|
+
drift = settingsDrift(ours, additionsBase);
|
|
3359
|
+
}
|
|
3360
|
+
let driftKeys = 0;
|
|
3361
|
+
let relocationFailed = false;
|
|
3362
|
+
const hasDrift = isPlainObject(drift) && Object.keys(drift).length > 0;
|
|
3363
|
+
if (hasDrift || writePathLocally) {
|
|
3364
|
+
let existingLocalRaw = null;
|
|
3365
|
+
let existingLocalReadable = true;
|
|
3366
|
+
if (isFileFollow(localPath)) {
|
|
3367
|
+
try {
|
|
3368
|
+
existingLocalRaw = fs.readFileSync(localPath, "utf-8");
|
|
3369
|
+
}
|
|
3370
|
+
catch {
|
|
3371
|
+
existingLocalReadable = false;
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
const existingLocal = jsonParseOrNull(existingLocalRaw);
|
|
3375
|
+
const localShapeInvalid = writePathLocally &&
|
|
3376
|
+
(!existingLocalReadable ||
|
|
3377
|
+
(existingLocalRaw !== null && !isPlainObject(existingLocal)) ||
|
|
3378
|
+
(isPlainObject(existingLocal) &&
|
|
3379
|
+
existingLocal.env !== undefined &&
|
|
3380
|
+
existingLocal.env !== null &&
|
|
3381
|
+
!isPlainObject(existingLocal.env)));
|
|
3382
|
+
if (localShapeInvalid) {
|
|
3383
|
+
relocationFailed = true;
|
|
3384
|
+
err(" WARN: could not write .claude/settings.local.json during settings reconcile\n");
|
|
3385
|
+
}
|
|
3386
|
+
else {
|
|
3287
3387
|
const localObj = isPlainObject(existingLocal) ? existingLocal : {};
|
|
3288
|
-
|
|
3289
|
-
|
|
3388
|
+
if (writePathLocally && composedPath) {
|
|
3389
|
+
const localEnv = isPlainObject(localObj.env)
|
|
3390
|
+
? localObj.env
|
|
3391
|
+
: (localObj.env = {});
|
|
3392
|
+
localEnv.PATH = composedPath;
|
|
3393
|
+
}
|
|
3394
|
+
if (hasDrift && isPlainObject(drift)) {
|
|
3395
|
+
deepMergeSettings(localObj, drift);
|
|
3396
|
+
driftKeys = Object.keys(drift).length;
|
|
3397
|
+
}
|
|
3290
3398
|
try {
|
|
3291
3399
|
fs.mkdirSync(path.dirname(localPath), { recursive: true });
|
|
3292
3400
|
fs.writeFileSync(localPath, JSON.stringify(localObj, null, 2) + "\n");
|
|
@@ -3314,20 +3422,27 @@ export function reconcileClaudeSettings(ctx, preRescueSettingsJson, out, err) {
|
|
|
3314
3422
|
err(" WARN: kept your original .claude/settings.json (settings update not applied — settings.local.json was unwritable)\n");
|
|
3315
3423
|
return;
|
|
3316
3424
|
}
|
|
3317
|
-
//
|
|
3425
|
+
// In local-flag mode, leave the release blob byte-for-byte intact unless
|
|
3426
|
+
// preserving an intentional user array removal changed its content.
|
|
3427
|
+
const releasedSettingsUnchanged = setupPathSettingsLocal && jsonDeepEqual(theirs, releaseBaseline);
|
|
3318
3428
|
try {
|
|
3319
|
-
|
|
3429
|
+
if (!releasedSettingsUnchanged) {
|
|
3430
|
+
fs.writeFileSync(settingsPath, JSON.stringify(theirs, null, 2) + "\n");
|
|
3431
|
+
}
|
|
3320
3432
|
}
|
|
3321
3433
|
catch {
|
|
3322
3434
|
err(" WARN: could not write re-composed .claude/settings.json during reconcile\n");
|
|
3323
3435
|
return;
|
|
3324
3436
|
}
|
|
3437
|
+
const pathTarget = setupPathSettingsLocal
|
|
3438
|
+
? ".claude/settings.local.json"
|
|
3439
|
+
: ".claude/settings.json";
|
|
3325
3440
|
if (driftKeys > 0) {
|
|
3326
|
-
out(`==> Reconciled .claude/settings.json to release baseline (env.PATH
|
|
3441
|
+
out(`==> Reconciled .claude/settings.json to release baseline (env.PATH composed into ${pathTarget}; ` +
|
|
3327
3442
|
`relocated ${driftKeys} drift key(s) -> .claude/settings.local.json)\n`);
|
|
3328
3443
|
}
|
|
3329
3444
|
else {
|
|
3330
|
-
out(
|
|
3445
|
+
out(`==> Reconciled .claude/settings.json to release baseline (env.PATH composed into ${pathTarget}; no local drift to relocate)\n`);
|
|
3331
3446
|
}
|
|
3332
3447
|
}
|
|
3333
3448
|
/**
|