@indigoai-us/hq-cli 5.345.146 → 5.345.148
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/README.md +14 -0
- package/assets/bot-workers/setup/worker.yaml +11 -0
- package/dist/command-catalog.generated.d.ts +326 -4
- package/dist/command-catalog.generated.js +403 -4
- package/dist/commands/agent-inbox.js +2 -1
- package/dist/commands/auth.js +4 -2
- package/dist/commands/bot.js +1 -1
- package/dist/commands/cloud.js +31 -1
- package/dist/commands/core-refresh-vault-access-native.d.ts +17 -0
- package/dist/commands/core-refresh-vault-access-native.js +214 -19
- package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +9 -0
- package/dist/commands/core-work-mesh-bind-trusted-native.js +26 -19
- package/dist/commands/core.js +6 -0
- package/dist/commands/daemon.d.ts +7 -0
- package/dist/commands/daemon.js +37 -3
- package/dist/commands/dm.js +9 -2
- package/dist/commands/doctor.d.ts +11 -1
- package/dist/commands/doctor.js +56 -1
- package/dist/commands/files.js +10 -6
- package/dist/commands/install-global.d.ts +7 -0
- package/dist/commands/install-global.js +129 -35
- package/dist/commands/lanes-shared.d.ts +3 -1
- package/dist/commands/lanes.d.ts +4 -4
- package/dist/commands/lanes.js +380 -25
- package/dist/commands/login.js +4 -3
- package/dist/commands/pack-install.d.ts +2 -2
- package/dist/commands/pack-install.js +133 -35
- package/dist/commands/sync-manifest.d.ts +2 -9
- package/dist/commands/sync-manifest.js +4 -15
- package/dist/hook-engine-path.d.ts +4 -0
- package/dist/hook-engine-path.js +36 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2784 -2360
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/index.js +6 -0
- package/dist/lib/agent-kit/inbox-redact.d.ts +44 -0
- package/dist/lib/agent-kit/inbox-redact.js +361 -0
- package/dist/lib/agent-kit/inbox-state.js +11 -2
- package/dist/lib/agent-kit/mcp/tools.js +2 -1
- package/dist/lib/agent-kit/run/inbox.js +36 -7
- package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -0
- package/dist/lib/anywhere-runtime-flag-key.js +9 -0
- package/dist/lib/bot/daemon.js +3 -0
- package/dist/lib/bot/runtime/index.js +3 -1
- package/dist/lib/company-skill-wrapper.js +6 -4
- package/dist/lib/daemon/install.d.ts +47 -1
- package/dist/lib/daemon/install.js +181 -15
- package/dist/lib/daemon/lock.d.ts +32 -0
- package/dist/lib/daemon/lock.js +55 -2
- package/dist/lib/doctor/__testing__/run-doctor.js +4 -0
- package/dist/lib/doctor/checks/anywhere.d.ts +3 -0
- package/dist/lib/doctor/checks/anywhere.js +23 -8
- package/dist/lib/doctor/checks/cli-path.d.ts +11 -0
- package/dist/lib/doctor/checks/cli-path.js +45 -0
- package/dist/lib/doctor/checks/hook-engine.d.ts +4 -0
- package/dist/lib/doctor/checks/hook-engine.js +16 -0
- package/dist/lib/doctor/checks/release-links.d.ts +23 -0
- package/dist/lib/doctor/checks/release-links.js +152 -0
- package/dist/lib/doctor/checks/sync-health.d.ts +7 -0
- package/dist/lib/doctor/checks/sync-health.js +38 -2
- package/dist/lib/doctor/checks/temp-homes.d.ts +19 -0
- package/dist/lib/doctor/checks/temp-homes.js +54 -0
- package/dist/lib/doctor/fix/apply.d.ts +90 -1
- package/dist/lib/doctor/fix/apply.js +176 -10
- package/dist/lib/doctor/fix/json-output.d.ts +4 -1
- package/dist/lib/doctor/fix/json-output.js +6 -0
- package/dist/lib/doctor/fix/remediation.d.ts +7 -1
- package/dist/lib/doctor/fix/remediation.js +23 -1
- package/dist/lib/doctor/registry.js +13 -0
- package/dist/lib/doctor/temp-home-sweeper.d.ts +149 -0
- package/dist/lib/doctor/temp-home-sweeper.js +418 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/flag-registry.js +9 -1
- package/dist/lib/flag-snapshot-file.js +8 -3
- package/dist/lib/hook-engine/resolve.d.ts +31 -0
- package/dist/lib/hook-engine/resolve.js +134 -0
- package/dist/lib/lanes/admission-host.d.ts +1 -0
- package/dist/lib/lanes/admission-host.js +8 -1
- package/dist/lib/lanes/admission.d.ts +4 -0
- package/dist/lib/lanes/admission.js +6 -2
- package/dist/lib/lanes/envelope-repair-dispatch.d.ts +5 -0
- package/dist/lib/lanes/envelope-repair-dispatch.js +12 -1
- package/dist/lib/lanes/index.d.ts +2 -2
- package/dist/lib/lanes/index.js +1 -1
- package/dist/lib/lanes/keep-alive.js +4 -1
- package/dist/lib/lanes/local-host-identity.d.ts +40 -0
- package/dist/lib/lanes/local-host-identity.js +314 -0
- package/dist/lib/lanes/reaper.d.ts +1 -1
- package/dist/lib/lanes/reaper.js +7 -0
- package/dist/lib/lanes/reconcile.d.ts +4 -0
- package/dist/lib/lanes/reconcile.js +16 -8
- package/dist/lib/lanes/spawn.d.ts +20 -0
- package/dist/lib/lanes/spawn.js +48 -2
- package/dist/lib/lanes/status-fields.js +6 -1
- package/dist/lib/lanes/store.js +8 -1
- package/dist/lib/lanes/throttle.d.ts +49 -6
- package/dist/lib/lanes/throttle.js +154 -17
- package/dist/lib/lanes/types.d.ts +22 -1
- package/dist/lib/lanes/types.js +27 -3
- package/dist/lib/lanes/watcher.js +27 -2
- package/dist/lib/lanes/worker-continuity.d.ts +2 -0
- package/dist/lib/lanes/worker-continuity.js +18 -11
- package/dist/lib/manifest-disabled.d.ts +13 -0
- package/dist/lib/manifest-disabled.js +19 -0
- package/dist/lib/service-manager/launchd.d.ts +1 -0
- package/dist/lib/service-manager/launchd.js +1 -1
- package/dist/lib/windows/user-path.d.ts +48 -0
- package/dist/lib/windows/user-path.js +114 -0
- package/dist/lib/work-context/auto-bind.d.ts +1 -1
- package/dist/lib/work-context/auto-bind.js +22 -3
- package/dist/lib/work-context/lane-bind.d.ts +31 -0
- package/dist/lib/work-context/lane-bind.js +101 -0
- package/dist/lib/work-context/organize.js +1 -0
- package/dist/lib/work-context/prd-sync.js +7 -0
- package/dist/lib/work-context/project.d.ts +36 -10
- package/dist/lib/work-context/project.js +130 -41
- package/dist/lib/work-context/reconcile.d.ts +6 -0
- package/dist/lib/work-context/reconcile.js +25 -5
- package/dist/lib/work-context/state.d.ts +6 -0
- package/dist/lib/work-context/state.js +29 -1
- package/dist/lib/workers-registry/index.d.ts +6 -0
- package/dist/lib/workers-registry/index.js +8 -0
- package/dist/utils/copy-tree.d.ts +61 -0
- package/dist/utils/copy-tree.js +249 -0
- package/dist/utils/environmental-error.js +7 -3
- package/dist/utils/headless.d.ts +2 -0
- package/dist/utils/headless.js +8 -0
- package/dist/utils/login-browser.d.ts +4 -0
- package/dist/utils/login-browser.js +122 -0
- package/dist/utils/npm-invocation.d.ts +27 -0
- package/dist/utils/npm-invocation.js +54 -0
- package/dist/utils/safe-link.d.ts +50 -0
- package/dist/utils/safe-link.js +300 -0
- package/dist/utils/team-upgrade.js +1 -7
- package/package.json +17 -5
- package/scripts/hook-engine-manifest.mjs +22 -0
- package/scripts/install-hook-engine.mjs +10 -0
|
@@ -56,7 +56,11 @@ export const HOOK_CONFIG_DIRS = [".claude", ".codex", ".grok"];
|
|
|
56
56
|
export const REFRESH_SYNC_TIMEOUT_MS = 90_000;
|
|
57
57
|
/** A newly unlocked safe repair can be picked up for at most this many passes. */
|
|
58
58
|
export const MAX_FIX_PASSES = 3;
|
|
59
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Two 90s network repairs leave 120s for startup and check overhead under the
|
|
61
|
+
* hook's 300s ceiling. Journal pulls and manifest uploads share this budget;
|
|
62
|
+
* pulls go first and uploads take any slot left.
|
|
63
|
+
*/
|
|
60
64
|
export const MAX_REFRESH_SYNC_PLANS_PER_RUN = 2;
|
|
61
65
|
function rotateRefreshSyncPlans(plans, afterSlug) {
|
|
62
66
|
const ordered = [...plans].sort((a, b) => a.target < b.target ? -1 : a.target > b.target ? 1 : 0);
|
|
@@ -85,6 +89,7 @@ export async function applyFixes(options) {
|
|
|
85
89
|
restoreCommand: null,
|
|
86
90
|
backups: [],
|
|
87
91
|
applied: [],
|
|
92
|
+
unresolved: [],
|
|
88
93
|
...over,
|
|
89
94
|
});
|
|
90
95
|
// 1. Dirty-tree refusal (AC8). Never write over uncommitted security-layer
|
|
@@ -99,7 +104,9 @@ export async function applyFixes(options) {
|
|
|
99
104
|
// Journal refreshes never write hook files. Allow them by class, not
|
|
100
105
|
// only by empty relpath, so a personal-vault pull and a company pull
|
|
101
106
|
// still run when SessionStart `--fix --yes` cannot pass `--force`.
|
|
102
|
-
const allowed = plans.filter((plan) => plan.fixClass === "refresh-sync" ||
|
|
107
|
+
const allowed = plans.filter((plan) => plan.fixClass === "refresh-sync" ||
|
|
108
|
+
plan.fixClass === "refresh-manifest" ||
|
|
109
|
+
plan.relpath.length === 0);
|
|
103
110
|
if (allowed.length === plans.length)
|
|
104
111
|
return plans;
|
|
105
112
|
if (allowed.length === 0)
|
|
@@ -109,12 +116,14 @@ export async function applyFixes(options) {
|
|
|
109
116
|
`re-run with --force to repair hook files.\n`);
|
|
110
117
|
return allowed;
|
|
111
118
|
};
|
|
112
|
-
const refuseDirtyTree = () => {
|
|
119
|
+
const refuseDirtyTree = (results) => {
|
|
113
120
|
writeErr(`hq doctor --fix: refusing to run — uncommitted changes under ` +
|
|
114
121
|
`${HOOK_CONFIG_DIRS.join(", ")}:\n` +
|
|
115
122
|
dirty.map((line) => ` ${line}`).join("\n") +
|
|
116
123
|
`\nCommit or stash them, or re-run with --force.\n`);
|
|
117
|
-
|
|
124
|
+
const unresolved = unresolvedSyncChecks(results, []);
|
|
125
|
+
writeUnresolvedSyncChecks(unresolved, write);
|
|
126
|
+
return empty({ exitCode: 1, refused: "dirty-tree", unresolved });
|
|
118
127
|
};
|
|
119
128
|
// Resolve the customer-visible manual-only output gate once per fix run.
|
|
120
129
|
// Fail open to the pre-change output if the identity is absent or the
|
|
@@ -129,6 +138,7 @@ export async function applyFixes(options) {
|
|
|
129
138
|
}
|
|
130
139
|
const collect = options.runChecks ?? defaultRunChecks;
|
|
131
140
|
const refreshSync = options.refreshSync ?? defaultRefreshSync;
|
|
141
|
+
const refreshManifest = options.refreshManifest ?? defaultRefreshManifest;
|
|
132
142
|
const rewriteBotNodePath = options.rewriteBotNodePath ?? defaultRewriteBotNodePath;
|
|
133
143
|
const repairBotHqBinary = options.repairBotHqBinary ?? defaultRepairBotHqBinary;
|
|
134
144
|
// 2. Collect findings, then plan each newly unlocked allowlisted repair.
|
|
@@ -137,6 +147,7 @@ export async function applyFixes(options) {
|
|
|
137
147
|
let findings = await collect(hqRoot);
|
|
138
148
|
let lastAttemptedRefreshSyncSlug = readRefreshSyncProgress(hqRoot)?.lastAttemptedSlug ?? null;
|
|
139
149
|
let refreshSyncAppliedThisRun = 0;
|
|
150
|
+
let refreshManifestAppliedThisRun = 0;
|
|
140
151
|
const appliedKeys = new Set();
|
|
141
152
|
const planned = [];
|
|
142
153
|
const backups = [];
|
|
@@ -155,7 +166,7 @@ export async function applyFixes(options) {
|
|
|
155
166
|
const rem = deriveRemediation(result);
|
|
156
167
|
if (!rem?.autoFixable)
|
|
157
168
|
continue;
|
|
158
|
-
const plan = planFix(hqRoot, result, rem, refreshSync, rewriteBotNodePath, repairBotHqBinary);
|
|
169
|
+
const plan = planFix(hqRoot, result, rem, refreshSync, rewriteBotNodePath, repairBotHqBinary, refreshManifest);
|
|
159
170
|
if (!plan)
|
|
160
171
|
continue;
|
|
161
172
|
const key = JSON.stringify([plan.checkId, plan.target]);
|
|
@@ -171,16 +182,25 @@ export async function applyFixes(options) {
|
|
|
171
182
|
seenRefreshTargets.add(plan.target);
|
|
172
183
|
return true;
|
|
173
184
|
}).slice(0, Math.max(0, MAX_REFRESH_SYNC_PLANS_PER_RUN - refreshSyncAppliedThisRun));
|
|
185
|
+
// Manifest uploads take whatever network budget the pulls left.
|
|
186
|
+
const manifestPlans = plans
|
|
187
|
+
.filter((plan) => plan.fixClass === "refresh-manifest")
|
|
188
|
+
.sort((a, b) => (a.target < b.target ? -1 : a.target > b.target ? 1 : 0))
|
|
189
|
+
.slice(0, Math.max(0, MAX_REFRESH_SYNC_PLANS_PER_RUN -
|
|
190
|
+
refreshSyncAppliedThisRun -
|
|
191
|
+
refreshManifestAppliedThisRun -
|
|
192
|
+
refreshPlans.length));
|
|
174
193
|
return [
|
|
175
|
-
...plans.filter((plan) => plan.fixClass !== "refresh-sync"),
|
|
194
|
+
...plans.filter((plan) => plan.fixClass !== "refresh-sync" && plan.fixClass !== "refresh-manifest"),
|
|
176
195
|
...refreshPlans,
|
|
196
|
+
...manifestPlans,
|
|
177
197
|
];
|
|
178
198
|
};
|
|
179
199
|
for (let pass = 1; pass <= MAX_FIX_PASSES; pass += 1) {
|
|
180
200
|
const plannedThisPass = refuseDirtyHookWrites(buildPlans(findings));
|
|
181
201
|
if (plannedThisPass === "refuse") {
|
|
182
202
|
if (planned.length === 0)
|
|
183
|
-
return refuseDirtyTree();
|
|
203
|
+
return refuseDirtyTree(findings);
|
|
184
204
|
write(`Pass ${pass}/${MAX_FIX_PASSES}: remaining repairs write hook files; ` +
|
|
185
205
|
`stopped because the tree is dirty.\n`);
|
|
186
206
|
refused = "dirty-tree";
|
|
@@ -202,7 +222,9 @@ export async function applyFixes(options) {
|
|
|
202
222
|
write("Aborted — no changes written.\n");
|
|
203
223
|
if (showManualOnlyRemediations)
|
|
204
224
|
writeManualOnlyRemediations(findings, write);
|
|
205
|
-
|
|
225
|
+
const unresolved = unresolvedSyncChecks(findings, []);
|
|
226
|
+
writeUnresolvedSyncChecks(unresolved, write);
|
|
227
|
+
return empty({ refused, fixableCount, unresolved });
|
|
206
228
|
}
|
|
207
229
|
write(`Pass ${pass} declined — no further changes written.\n`);
|
|
208
230
|
break;
|
|
@@ -233,6 +255,8 @@ export async function applyFixes(options) {
|
|
|
233
255
|
lastAttemptedRefreshSyncSlug = plan.target;
|
|
234
256
|
refreshSyncAppliedThisRun += 1;
|
|
235
257
|
}
|
|
258
|
+
if (plan.fixClass === "refresh-manifest")
|
|
259
|
+
refreshManifestAppliedThisRun += 1;
|
|
236
260
|
appliedKeys.add(JSON.stringify([plan.checkId, plan.target]));
|
|
237
261
|
planned.push({ plan, pass });
|
|
238
262
|
await plan.apply();
|
|
@@ -244,7 +268,9 @@ export async function applyFixes(options) {
|
|
|
244
268
|
write("hq doctor --fix: nothing to repair — no auto-fixable findings.\n");
|
|
245
269
|
if (showManualOnlyRemediations)
|
|
246
270
|
writeManualOnlyRemediations(findings, write);
|
|
247
|
-
|
|
271
|
+
const unresolved = unresolvedSyncChecks(findings, []);
|
|
272
|
+
writeUnresolvedSyncChecks(unresolved, write);
|
|
273
|
+
return empty({ refused, fixableCount, unresolved });
|
|
248
274
|
}
|
|
249
275
|
// Show each repair's final post-fix status, including work from earlier passes.
|
|
250
276
|
const applied = planned.map(({ plan, pass }) => {
|
|
@@ -283,6 +309,8 @@ export async function applyFixes(options) {
|
|
|
283
309
|
}
|
|
284
310
|
if (showManualOnlyRemediations)
|
|
285
311
|
writeManualOnlyRemediations(findings, write);
|
|
312
|
+
const unresolved = unresolvedSyncChecks(findings, planned.map(({ plan }) => plan));
|
|
313
|
+
writeUnresolvedSyncChecks(unresolved, write);
|
|
286
314
|
return {
|
|
287
315
|
exitCode: 0,
|
|
288
316
|
wrote: true,
|
|
@@ -292,11 +320,81 @@ export async function applyFixes(options) {
|
|
|
292
320
|
restoreCommand,
|
|
293
321
|
backups,
|
|
294
322
|
applied,
|
|
323
|
+
unresolved,
|
|
295
324
|
};
|
|
296
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* The sync checks still FAIL or WARN in `findings`, each with why `--fix` left
|
|
328
|
+
* it and what to do next. Reported on every run, independent of the
|
|
329
|
+
* manual-only flag: the SessionStart health hook files a bug for any sync row
|
|
330
|
+
* left after `--fix`, so the user must see the same rows and a next step.
|
|
331
|
+
*/
|
|
332
|
+
export function unresolvedSyncChecks(findings, attempted) {
|
|
333
|
+
return findings.flatMap((result) => {
|
|
334
|
+
if (!result.checkId.startsWith("sync."))
|
|
335
|
+
return [];
|
|
336
|
+
const remediation = deriveRemediation(result);
|
|
337
|
+
if (!remediation)
|
|
338
|
+
return [];
|
|
339
|
+
const tried = attempted.find((plan) => plan.checkId === result.checkId);
|
|
340
|
+
const command = remediation.command.trim();
|
|
341
|
+
if (tried) {
|
|
342
|
+
const failure = tried.failureReason?.();
|
|
343
|
+
const outcome = failure
|
|
344
|
+
? `--fix tried to ${tried.summary}, but ${failure}, so the check still shows ${result.status}.`
|
|
345
|
+
: `--fix tried to ${tried.summary}, but the check still shows ${result.status}.`;
|
|
346
|
+
return [
|
|
347
|
+
{
|
|
348
|
+
checkId: result.checkId,
|
|
349
|
+
status: result.status,
|
|
350
|
+
reason: "repair-did-not-clear",
|
|
351
|
+
nextStep: command
|
|
352
|
+
? `${outcome} Run \`${command}\` yourself and read its error.`
|
|
353
|
+
: `${outcome} Run \`hq doctor --only sync\` for details.`,
|
|
354
|
+
},
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
if (remediation.autoFixable) {
|
|
358
|
+
return [
|
|
359
|
+
{
|
|
360
|
+
checkId: result.checkId,
|
|
361
|
+
status: result.status,
|
|
362
|
+
reason: "not-attempted",
|
|
363
|
+
nextStep: `--fix did not get to this one in this run. Run \`hq doctor --fix\` again, or run \`${command}\` now.`,
|
|
364
|
+
},
|
|
365
|
+
];
|
|
366
|
+
}
|
|
367
|
+
// The action is the check's remediation (or a classifier override). When
|
|
368
|
+
// it is only the finding message, give a generic step: messages can hold
|
|
369
|
+
// paths, and the JSON surface carries none.
|
|
370
|
+
const nextStep = remediation.action && remediation.action !== result.message
|
|
371
|
+
? remediation.action
|
|
372
|
+
: "Run `hq doctor --only sync` to see the details, then fix what it describes.";
|
|
373
|
+
return [
|
|
374
|
+
{
|
|
375
|
+
checkId: result.checkId,
|
|
376
|
+
status: result.status,
|
|
377
|
+
reason: "manual-only",
|
|
378
|
+
nextStep: `--fix cannot repair this automatically. ${nextStep}`,
|
|
379
|
+
},
|
|
380
|
+
];
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/** Print the sync checks `--fix` left unresolved, or nothing when there are none. */
|
|
384
|
+
function writeUnresolvedSyncChecks(unresolved, write) {
|
|
385
|
+
if (unresolved.length === 0)
|
|
386
|
+
return;
|
|
387
|
+
write("\nSync checks still not resolved after --fix:\n");
|
|
388
|
+
for (const entry of unresolved) {
|
|
389
|
+
write(` ${entry.status} ${entry.checkId}\n Next step: ${entry.nextStep}\n`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
297
392
|
/** Print the available user action for findings that `--fix` deliberately skips. */
|
|
298
393
|
function writeManualOnlyRemediations(findings, write) {
|
|
299
394
|
const manual = findings.flatMap((result) => {
|
|
395
|
+
// Sync rows are always listed by writeUnresolvedSyncChecks.
|
|
396
|
+
if (result.checkId.startsWith("sync."))
|
|
397
|
+
return [];
|
|
300
398
|
const remediation = deriveRemediation(result);
|
|
301
399
|
return remediation && !remediation.autoFixable
|
|
302
400
|
? [{ result, remediation }]
|
|
@@ -403,6 +501,49 @@ export function defaultRefreshSync(target) {
|
|
|
403
501
|
message: `pulled '${target.slug}'`,
|
|
404
502
|
});
|
|
405
503
|
}
|
|
504
|
+
/**
|
|
505
|
+
* Default `refresh-manifest` repair: a bounded `hq sync manifest --scope <s>`
|
|
506
|
+
* against the tree `--fix` is repairing. Exit 0 also covers throttled,
|
|
507
|
+
* disabled, and skipped passes, so the post-fix re-check decides whether the
|
|
508
|
+
* row cleared. A timeout or non-zero exit is reported, not thrown.
|
|
509
|
+
*/
|
|
510
|
+
export function defaultRefreshManifest(target) {
|
|
511
|
+
const hqBin = process.argv[1];
|
|
512
|
+
const args = ["sync", "manifest", "--scope", target.scope, "--hq-root", target.hqRoot];
|
|
513
|
+
const result = spawnSync(hqBin ? process.execPath : "hq", hqBin ? [hqBin, ...args] : args, {
|
|
514
|
+
encoding: "utf8",
|
|
515
|
+
timeout: REFRESH_SYNC_TIMEOUT_MS,
|
|
516
|
+
cwd: target.hqRoot,
|
|
517
|
+
});
|
|
518
|
+
return Promise.resolve(manifestUploadResult(target.scope, result));
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Map one `hq sync manifest` spawn result to a {@link RefreshManifestResult}.
|
|
522
|
+
* A spawn killed by the timeout has a null status, so it is reported as a
|
|
523
|
+
* timeout rather than as an exit code.
|
|
524
|
+
*/
|
|
525
|
+
export function manifestUploadResult(scope, result) {
|
|
526
|
+
const errorCode = result.error?.code;
|
|
527
|
+
if (errorCode === "ETIMEDOUT" || (result.status === null && result.signal)) {
|
|
528
|
+
const reason = `the upload timed out after ${REFRESH_SYNC_TIMEOUT_MS / 1000}s`;
|
|
529
|
+
return { ok: false, reason, message: `manifest upload for '${scope}' timed out` };
|
|
530
|
+
}
|
|
531
|
+
if (result.error) {
|
|
532
|
+
return {
|
|
533
|
+
ok: false,
|
|
534
|
+
reason: `the upload could not start${errorCode ? ` (${errorCode})` : ""}`,
|
|
535
|
+
message: `could not upload the manifest for '${scope}': ${result.error.message}`,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
if (result.status !== 0) {
|
|
539
|
+
return {
|
|
540
|
+
ok: false,
|
|
541
|
+
reason: `the upload exited with code ${result.status}`,
|
|
542
|
+
message: `manifest upload for '${scope}' exited ${result.status}`,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
return { ok: true, message: `uploaded the manifest for '${scope}'` };
|
|
546
|
+
}
|
|
406
547
|
// --- Dirty-tree probe ----------------------------------------------------------
|
|
407
548
|
/**
|
|
408
549
|
* The uncommitted changes under {@link HOOK_CONFIG_DIRS}, one porcelain line
|
|
@@ -422,7 +563,7 @@ export function uncommittedHookConfigChanges(hqRoot) {
|
|
|
422
563
|
}
|
|
423
564
|
// --- Fix planning --------------------------------------------------------------
|
|
424
565
|
/** Build the concrete plan for one auto-fixable finding, or null if unplannable. */
|
|
425
|
-
function planFix(hqRoot, result, rem, refreshSync, rewriteBotNodePath = defaultRewriteBotNodePath, repairBotHqBinary = defaultRepairBotHqBinary) {
|
|
566
|
+
function planFix(hqRoot, result, rem, refreshSync, rewriteBotNodePath = defaultRewriteBotNodePath, repairBotHqBinary = defaultRepairBotHqBinary, refreshManifest = defaultRefreshManifest) {
|
|
426
567
|
switch (rem.fixClass) {
|
|
427
568
|
case "bot-node-path":
|
|
428
569
|
return planBotNodePath(result, rem, rewriteBotNodePath);
|
|
@@ -436,6 +577,8 @@ function planFix(hqRoot, result, rem, refreshSync, rewriteBotNodePath = defaultR
|
|
|
436
577
|
return planRemoveStrayGateEntries(hqRoot, result, rem);
|
|
437
578
|
case "refresh-sync":
|
|
438
579
|
return planRefreshSync(hqRoot, result, rem, refreshSync);
|
|
580
|
+
case "refresh-manifest":
|
|
581
|
+
return planRefreshManifest(hqRoot, result, rem, refreshManifest);
|
|
439
582
|
default:
|
|
440
583
|
return null;
|
|
441
584
|
}
|
|
@@ -555,6 +698,29 @@ function planRefreshSync(hqRoot, result, rem, refreshSync) {
|
|
|
555
698
|
postStatus: (results) => results.find((entry) => entry.checkId === result.checkId)?.status ?? null,
|
|
556
699
|
};
|
|
557
700
|
}
|
|
701
|
+
/** Re-upload one stale sync manifest scope. Writes nothing under the HQ tree. */
|
|
702
|
+
function planRefreshManifest(hqRoot, result, rem, refreshManifest) {
|
|
703
|
+
const scope = rem.fixTarget;
|
|
704
|
+
if (!scope)
|
|
705
|
+
return null;
|
|
706
|
+
let failureReason;
|
|
707
|
+
return {
|
|
708
|
+
checkId: result.checkId,
|
|
709
|
+
fixClass: "refresh-manifest",
|
|
710
|
+
target: scope,
|
|
711
|
+
relpath: "",
|
|
712
|
+
preview: ` sync manifest (${scope === "personal" ? "personal vault" : `company ${scope}`})\n` +
|
|
713
|
+
` ~ hq sync manifest --scope ${scope}\n` +
|
|
714
|
+
` upload a fresh file manifest for the sync audit (no local files change)`,
|
|
715
|
+
summary: `upload a fresh sync manifest for ${scope}`,
|
|
716
|
+
apply: async () => {
|
|
717
|
+
const outcome = await refreshManifest({ scope, hqRoot });
|
|
718
|
+
failureReason = outcome.ok ? undefined : outcome.reason;
|
|
719
|
+
},
|
|
720
|
+
postStatus: (results) => results.find((entry) => entry.checkId === result.checkId)?.status ?? null,
|
|
721
|
+
failureReason: () => failureReason,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
558
724
|
/** Restore the execute bit on a hook script. */
|
|
559
725
|
function planExecutableBit(hqRoot, result, rem) {
|
|
560
726
|
const abs = rem.fixTarget;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppliedFix, AppliedFixBackup, ApplyFixesResult } from "./apply.js";
|
|
1
|
+
import type { AppliedFix, AppliedFixBackup, ApplyFixesResult, UnresolvedSyncCheck } from "./apply.js";
|
|
2
2
|
export interface DoctorFixJsonResult {
|
|
3
3
|
schemaVersion: number;
|
|
4
4
|
exitCode: number;
|
|
@@ -15,6 +15,8 @@ export interface DoctorFixJsonResult {
|
|
|
15
15
|
outcome: NonNullable<AppliedFix["refreshSyncOutcome"]>;
|
|
16
16
|
postStatus: AppliedFix["postStatus"];
|
|
17
17
|
}[];
|
|
18
|
+
/** Sync checks still FAIL or WARN after the run, with the next step. */
|
|
19
|
+
unresolved: Pick<UnresolvedSyncCheck, "checkId" | "status" | "reason" | "nextStep">[];
|
|
18
20
|
}
|
|
19
21
|
/** Build the intentionally narrow JSON surface for `hq doctor --fix --json`. */
|
|
20
22
|
export declare function buildDoctorFixJson(input: {
|
|
@@ -22,6 +24,7 @@ export declare function buildDoctorFixJson(input: {
|
|
|
22
24
|
refused: ApplyFixesResult["refused"] | "outside-hq-tree";
|
|
23
25
|
applied: readonly AppliedFix[];
|
|
24
26
|
backups?: readonly AppliedFixBackup[];
|
|
27
|
+
unresolved?: readonly UnresolvedSyncCheck[];
|
|
25
28
|
}): DoctorFixJsonResult;
|
|
26
29
|
export declare function renderDoctorFixJson(input: Parameters<typeof buildDoctorFixJson>[0]): string;
|
|
27
30
|
//# sourceMappingURL=json-output.d.ts.map
|
|
@@ -25,6 +25,12 @@ export function buildDoctorFixJson(input) {
|
|
|
25
25
|
},
|
|
26
26
|
]
|
|
27
27
|
: []),
|
|
28
|
+
unresolved: (input.unresolved ?? []).map(({ checkId, status, reason, nextStep }) => ({
|
|
29
|
+
checkId,
|
|
30
|
+
status,
|
|
31
|
+
reason,
|
|
32
|
+
nextStep,
|
|
33
|
+
})),
|
|
28
34
|
};
|
|
29
35
|
}
|
|
30
36
|
export function renderDoctorFixJson(input) {
|
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
* keep`, no hook-file rewrite. Leftover shards
|
|
30
30
|
* without a local tree stay unfixed (the check reports
|
|
31
31
|
* NA).
|
|
32
|
+
* - `refresh-manifest` — re-run `hq sync manifest --scope <slug|personal>`
|
|
33
|
+
* for a manifest scope whose last upload is past the
|
|
34
|
+
* stale threshold. Upload-only metadata (file paths
|
|
35
|
+
* and hashes); it never writes the HQ tree. A company
|
|
36
|
+
* scope whose slug did not resolve locally stays
|
|
37
|
+
* manual-only.
|
|
32
38
|
*
|
|
33
39
|
* EVERYTHING else — content drift between platform copies, a missing script, a
|
|
34
40
|
* missing Codex counterpart, an unquoted `$CLAUDE_PROJECT_DIR`, a stale
|
|
@@ -40,7 +46,7 @@
|
|
|
40
46
|
*/
|
|
41
47
|
import type { CheckResult } from "../types.js";
|
|
42
48
|
/** The allowlisted safe repair classes `--fix` is permitted to apply. */
|
|
43
|
-
export type FixClass = "executable-bit" | "gate-profile" | "remove-stray-gate-entries" | "refresh-sync" | "bot-node-path" | "bot-hq-binary";
|
|
49
|
+
export type FixClass = "executable-bit" | "gate-profile" | "remove-stray-gate-entries" | "refresh-sync" | "refresh-manifest" | "bot-node-path" | "bot-hq-binary";
|
|
44
50
|
/**
|
|
45
51
|
* A finding's structured remediation. `autoFixable`, `action`, and `command`
|
|
46
52
|
* are the `--json` surface (AC1); `fixTarget`/`fixClass` are the internal handle
|
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
* keep`, no hook-file rewrite. Leftover shards
|
|
30
30
|
* without a local tree stay unfixed (the check reports
|
|
31
31
|
* NA).
|
|
32
|
+
* - `refresh-manifest` — re-run `hq sync manifest --scope <slug|personal>`
|
|
33
|
+
* for a manifest scope whose last upload is past the
|
|
34
|
+
* stale threshold. Upload-only metadata (file paths
|
|
35
|
+
* and hashes); it never writes the HQ tree. A company
|
|
36
|
+
* scope whose slug did not resolve locally stays
|
|
37
|
+
* manual-only.
|
|
32
38
|
*
|
|
33
39
|
* EVERYTHING else — content drift between platform copies, a missing script, a
|
|
34
40
|
* missing Codex counterpart, an unquoted `$CLAUDE_PROJECT_DIR`, a stale
|
|
@@ -38,7 +44,7 @@
|
|
|
38
44
|
* content-drift finding is manual-only, because that boundary is load-bearing:
|
|
39
45
|
* deciding which of two diverged copies is correct needs human judgement.
|
|
40
46
|
*/
|
|
41
|
-
import { isCompanyJournalSlug, isPersonalJournalSlug, journalSlugFromCheckId, } from "../checks/sync-health.js";
|
|
47
|
+
import { isCompanyJournalSlug, isSafeCompanySlug, isPersonalJournalSlug, journalSlugFromCheckId, } from "../checks/sync-health.js";
|
|
42
48
|
/** Matches an exec-bit remediation command, quoted or not: `chmod +x <path>`. */
|
|
43
49
|
const CHMOD_EXEC = /^chmod\s+\+x\s+(.+)$/;
|
|
44
50
|
/**
|
|
@@ -171,6 +177,22 @@ export function deriveRemediation(result) {
|
|
|
171
177
|
command: `hq sync pull --company ${journalSlug} --on-conflict keep`,
|
|
172
178
|
};
|
|
173
179
|
}
|
|
180
|
+
// refresh-manifest — a stale manifest upload. The check sets `target` to the
|
|
181
|
+
// `--scope` value only when it resolved one it can name safely.
|
|
182
|
+
if (checkId.startsWith("sync.manifest.") &&
|
|
183
|
+
result.status === "WARN" &&
|
|
184
|
+
result.reasonCode === "stale-threshold" &&
|
|
185
|
+
target &&
|
|
186
|
+
(target === "personal" || isSafeCompanySlug(target))) {
|
|
187
|
+
const scopeLabel = target === "personal" ? "the personal vault" : `company '${target}'`;
|
|
188
|
+
return {
|
|
189
|
+
autoFixable: true,
|
|
190
|
+
fixClass: "refresh-manifest",
|
|
191
|
+
fixTarget: target,
|
|
192
|
+
action: `Upload a fresh sync manifest for ${scopeLabel}.`,
|
|
193
|
+
command: `hq sync manifest --scope ${target}`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
174
196
|
// Manual-only: content drift, missing scripts/counterparts, unquoted
|
|
175
197
|
// expansions, stale allowed-divergence entries, invalid settings, etc. `--fix`
|
|
176
198
|
// never touches these.
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* later stories add per-item results to this family without touching the engine.
|
|
15
15
|
*/
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
|
+
import { hookEngineFamily } from './checks/hook-engine.js';
|
|
17
18
|
import * as path from "node:path";
|
|
18
19
|
import { checkCodexWiring } from "./checks/codex-wiring.js";
|
|
19
20
|
import { checkGrokWiring } from "./checks/grok-wiring.js";
|
|
@@ -26,6 +27,9 @@ import { botsFamily } from "./checks/bots.js";
|
|
|
26
27
|
import { anywhereFamily } from "./checks/anywhere.js";
|
|
27
28
|
import { scaffoldCliFloorFamily } from "./checks/scaffold-cli-floor.js";
|
|
28
29
|
import { strandedLanesFamily } from "./checks/stranded-lanes.js";
|
|
30
|
+
import { releaseLinksFamily } from "./checks/release-links.js";
|
|
31
|
+
import { cliPathFamily } from "./checks/cli-path.js";
|
|
32
|
+
import { tempHomesFamily } from "./checks/temp-homes.js";
|
|
29
33
|
import { fixtureCoverageFamily } from "./fixtures/discover.js";
|
|
30
34
|
import { checkClaudeWiring } from "./checks/claude-wiring.js";
|
|
31
35
|
/**
|
|
@@ -239,6 +243,7 @@ function hasCommandHook(settings, event) {
|
|
|
239
243
|
export function createDefaultRegistry() {
|
|
240
244
|
const registry = new DoctorRegistry();
|
|
241
245
|
registry.register(hooksFamily);
|
|
246
|
+
registry.register(hookEngineFamily);
|
|
242
247
|
registry.register(scaffoldCliFloorFamily);
|
|
243
248
|
// Fixture coverage (US-007): reports UNTESTED for every registered hook with
|
|
244
249
|
// no fixture and prints the tested/total coverage line. A separate family so
|
|
@@ -270,6 +275,14 @@ export function createDefaultRegistry() {
|
|
|
270
275
|
// Failed keep-alive lead lanes cannot recover themselves; make them visible
|
|
271
276
|
// in the standard local doctor report with the senior recovery command.
|
|
272
277
|
registry.register(strandedLanesFamily);
|
|
278
|
+
// Release symlinks left pointing at a deleted update staging folder.
|
|
279
|
+
registry.register(releaseLinksFamily);
|
|
280
|
+
// Windows: the npm hq.cmd shim folder must be on PATH for a new terminal to
|
|
281
|
+
// find hq. Registry read only; prints the fix, never edits PATH.
|
|
282
|
+
registry.register(cliPathFamily);
|
|
283
|
+
// Idle tmp.* throwaway homes holding a Rust toolchain download. Dry-run
|
|
284
|
+
// scan only; `hq doctor --sweep-temp-homes` is the delete path.
|
|
285
|
+
registry.register(tempHomesFamily);
|
|
273
286
|
return registry;
|
|
274
287
|
}
|
|
275
288
|
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sweeper for abandoned throwaway `$HOME` folders.
|
|
3
|
+
*
|
|
4
|
+
* Commands run as `HOME=$(mktemp -d) <cmd>` leave a `tmp.XXXXXXXX` folder in
|
|
5
|
+
* the temp dir. When the command touched Rust or pnpm tooling, that folder
|
|
6
|
+
* holds a full `.rustup` / `.cargo` download (1-2 GB) and nothing deletes it.
|
|
7
|
+
* On 2026-10-18 about 45 GB refilled within hours of a cleanup this way.
|
|
8
|
+
* `core/scripts/with-temp-home.sh` makes `hq-temp-home.XXXXXXXX` folders and
|
|
9
|
+
* removes them on exit, but a wrapper killed with SIGKILL leaves its folder.
|
|
10
|
+
*
|
|
11
|
+
* The sweep is deliberately narrow. A folder is removed only when ALL hold:
|
|
12
|
+
*
|
|
13
|
+
* - it is a direct child of a temp root (TMPDIR, os.tmpdir(), /tmp,
|
|
14
|
+
* /private/tmp, /var/folders/{a}/{b}/T) and its name starts with `tmp.` or
|
|
15
|
+
* `hq-temp-home.`;
|
|
16
|
+
* - `lstat` says it is a real directory (never a symlink) owned by this user;
|
|
17
|
+
* - a `tmp.*` folder holds a `.rustup` or `.cargo` directory (again by
|
|
18
|
+
* `lstat`). An `hq-temp-home.*` folder needs no marker: the name shows the
|
|
19
|
+
* wrapper made it;
|
|
20
|
+
* - nothing inside it, the folder itself included, was modified in the last
|
|
21
|
+
* 90 minutes;
|
|
22
|
+
* - no live process uses it: the wrapper's `.hq-temp-home.lock` does not name
|
|
23
|
+
* a live pid, and no process of this user has its cwd or an open file
|
|
24
|
+
* there (on Linux, or its `HOME` there either).
|
|
25
|
+
*
|
|
26
|
+
* The walk uses `lstat` throughout and never descends into a symlink, so a
|
|
27
|
+
* link inside a temp home that points elsewhere is neither measured nor
|
|
28
|
+
* followed. `fs.rmSync` unlinks symlinks rather than following them. A folder
|
|
29
|
+
* that cannot be fully read is kept, because an incomplete scan cannot prove
|
|
30
|
+
* the folder is idle. When the in-use probe fails, every candidate is kept.
|
|
31
|
+
*
|
|
32
|
+
* `shallow` mode is for plain `hq doctor`, which must stay fast. It checks
|
|
33
|
+
* only the folder and its direct children for recent changes, skips the size
|
|
34
|
+
* walk and the process probe, and reports no byte counts.
|
|
35
|
+
*/
|
|
36
|
+
import * as fs from "node:fs";
|
|
37
|
+
/** Folders younger than this (by newest mtime anywhere inside) are kept. */
|
|
38
|
+
export declare const TEMP_HOME_MIN_IDLE_MS: number;
|
|
39
|
+
/** Names that mark a `tmp.*` temp home as holding a toolchain download. */
|
|
40
|
+
export declare const TEMP_HOME_TOOLCHAIN_MARKERS: readonly [".rustup", ".cargo"];
|
|
41
|
+
/** Folders made by `HOME=$(mktemp -d)`; swept only with a toolchain marker. */
|
|
42
|
+
export declare const TEMP_HOME_MKTEMP_PREFIX = "tmp.";
|
|
43
|
+
/** Folders made by `core/scripts/with-temp-home.sh`. */
|
|
44
|
+
export declare const TEMP_HOME_WRAPPER_PREFIX = "hq-temp-home.";
|
|
45
|
+
/** Lock file the wrapper writes into its temp home; holds the wrapper pid. */
|
|
46
|
+
export declare const TEMP_HOME_LOCK_FILE = ".hq-temp-home.lock";
|
|
47
|
+
export type TempHomeAction = "removed" | "would-remove" | "kept" | "failed";
|
|
48
|
+
export type TempHomeKeptReason = "symlink" | "not-directory" | "not-owned" | "no-toolchain-marker" | "recent" | "in-use" | "in-use-unknown" | "unreadable" | "changed-before-delete";
|
|
49
|
+
export interface TempHomeEntry {
|
|
50
|
+
path: string;
|
|
51
|
+
action: TempHomeAction;
|
|
52
|
+
/** On-disk bytes. Present when the folder was fully measured. */
|
|
53
|
+
bytes?: number;
|
|
54
|
+
/** Why the folder was kept. Present only for `kept`. */
|
|
55
|
+
reason?: TempHomeKeptReason;
|
|
56
|
+
/** Error message. Present for `failed`, and for some `kept` entries. */
|
|
57
|
+
error?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface TempHomeSweepResult {
|
|
60
|
+
dryRun: boolean;
|
|
61
|
+
/** True when the sweep ran in shallow mode (no sizes, no process probe). */
|
|
62
|
+
shallow: boolean;
|
|
63
|
+
/** Real paths of the temp roots that were scanned, deduplicated. */
|
|
64
|
+
roots: string[];
|
|
65
|
+
/** Every matching child that was examined, in scan order. */
|
|
66
|
+
entries: TempHomeEntry[];
|
|
67
|
+
/** Bytes removed, or bytes that would be removed in a dry run. 0 when shallow. */
|
|
68
|
+
bytesFreed: number;
|
|
69
|
+
/** `bytesFreed` in GB (10^9 bytes), rounded to two decimals. */
|
|
70
|
+
gbFreed: number;
|
|
71
|
+
}
|
|
72
|
+
/** File system calls the sweeper uses; a seam for tests. */
|
|
73
|
+
export interface TempHomeSweepFs {
|
|
74
|
+
lstatSync(target: string): fs.Stats;
|
|
75
|
+
readdirSync(target: string): string[];
|
|
76
|
+
realpathSync(target: string): string;
|
|
77
|
+
readFileSync(target: string, encoding: "utf8"): string;
|
|
78
|
+
rmSync(target: string, options: {
|
|
79
|
+
recursive: true;
|
|
80
|
+
force: false;
|
|
81
|
+
}): void;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Returns the subset of `homes` that a live process uses: its cwd or an open
|
|
85
|
+
* file is inside the home (and on Linux, its `HOME` is the home). Throws when
|
|
86
|
+
* it cannot tell; the sweep then keeps every candidate.
|
|
87
|
+
*/
|
|
88
|
+
export type TempHomeInUseProbe = (homes: readonly string[]) => Set<string>;
|
|
89
|
+
export interface TempHomeSweepOptions {
|
|
90
|
+
/** Temp roots to scan. Defaults to {@link defaultTempRoots}. */
|
|
91
|
+
roots?: readonly string[];
|
|
92
|
+
/** Report what would be removed without deleting anything. */
|
|
93
|
+
dryRun?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Cheap scan for plain `hq doctor`: no size walk, no process probe, only
|
|
96
|
+
* the folder and its direct children checked for recent changes. Implies
|
|
97
|
+
* `dryRun`.
|
|
98
|
+
*/
|
|
99
|
+
shallow?: boolean;
|
|
100
|
+
/** Current time in ms. Defaults to `Date.now()`. */
|
|
101
|
+
nowMs?: number;
|
|
102
|
+
/** Minimum idle time in ms. Defaults to {@link TEMP_HOME_MIN_IDLE_MS}. */
|
|
103
|
+
minIdleMs?: number;
|
|
104
|
+
/** Owner uid to require. Defaults to `process.getuid()` when it exists. */
|
|
105
|
+
uid?: number;
|
|
106
|
+
fs?: TempHomeSweepFs;
|
|
107
|
+
/** Live-process probe. Defaults to {@link defaultInUseProbe}. */
|
|
108
|
+
inUse?: TempHomeInUseProbe;
|
|
109
|
+
/** Whether a pid is alive. Defaults to `process.kill(pid, 0)`. */
|
|
110
|
+
isPidAlive?: (pid: number) => boolean;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The temp roots a throwaway home can land in: `$TMPDIR`, `os.tmpdir()`,
|
|
114
|
+
* `/tmp`, `/private/tmp`, and every per-user macOS temp dir
|
|
115
|
+
* `/var/folders/{a}/{b}/T`. Missing roots are dropped later by the sweep.
|
|
116
|
+
*/
|
|
117
|
+
export declare function defaultTempRoots(env?: NodeJS.ProcessEnv, fsImpl?: Pick<TempHomeSweepFs, "readdirSync">): string[];
|
|
118
|
+
/** `process.kill(pid, 0)`: alive unless the pid does not exist (ESRCH). */
|
|
119
|
+
export declare function defaultIsPidAlive(pid: number): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* True when `value` (a cwd or open-file path, or `HOME`) is `home` or a path
|
|
122
|
+
* inside it. Matched by folder name as a path segment, because the same temp
|
|
123
|
+
* dir is reachable as `/var/folders/...` and `/private/var/folders/...`; the
|
|
124
|
+
* random `mktemp` suffix makes the name unique. Over-matching only keeps more.
|
|
125
|
+
*/
|
|
126
|
+
export declare function pathUsesHome(value: string, home: string): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Default in-use probe. Only processes of `uid` are checked; other users
|
|
129
|
+
* cannot own these folders' contents in a way the sweep would delete.
|
|
130
|
+
*
|
|
131
|
+
* - Linux: `/proc/{pid}/cwd`, every `/proc/{pid}/fd/*` link, and `HOME` from
|
|
132
|
+
* `/proc/{pid}/environ`. Environment values are matched in memory and never
|
|
133
|
+
* logged.
|
|
134
|
+
* - macOS: `lsof -u uid` for every file each process has open, its cwd and
|
|
135
|
+
* its executable included. macOS does not show other processes'
|
|
136
|
+
* environments (`ps -E` leaves them out), so `HOME` itself is not checked
|
|
137
|
+
* there. A process that has HOME set to a temp home but holds nothing open
|
|
138
|
+
* inside it is not seen; the wrapper lock and the 90-minute idle check
|
|
139
|
+
* cover that case.
|
|
140
|
+
*/
|
|
141
|
+
export declare function defaultInUseProbe(uid: number | undefined): TempHomeInUseProbe;
|
|
142
|
+
/**
|
|
143
|
+
* Find and (unless `dryRun`) remove idle, unused temp homes. See the module
|
|
144
|
+
* comment for the exact rules.
|
|
145
|
+
*/
|
|
146
|
+
export declare function sweepTempHomes(options?: TempHomeSweepOptions): TempHomeSweepResult;
|
|
147
|
+
/** Bytes to GB (10^9 bytes), rounded to two decimals. */
|
|
148
|
+
export declare function toGb(bytes: number): number;
|
|
149
|
+
//# sourceMappingURL=temp-home-sweeper.d.ts.map
|