@rightkit/release 0.6.6 → 0.6.8

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.
Files changed (46) hide show
  1. package/README.md +132 -10
  2. package/admission-files.mjs +3 -2
  3. package/advisory-db.json +4 -0
  4. package/advisory-db.mjs +124 -0
  5. package/advisory-exceptions.mjs +13 -10
  6. package/app-check.mjs +257 -38
  7. package/bin/darwin/rightkit-swap +0 -0
  8. package/bin/rightkit-agent-hook.mjs +9 -0
  9. package/bin/rightkit-gate.mjs +524 -345
  10. package/build-release.mjs +13 -6
  11. package/cli/right-release.mjs +29 -6
  12. package/compile-inputs.mjs +66 -0
  13. package/consumer-contracts.json +1044 -0
  14. package/consumer-contracts.mjs +627 -0
  15. package/consumer-input-contracts.mjs +422 -0
  16. package/consumer-policy.mjs +588 -0
  17. package/consumer-source-contracts.mjs +469 -0
  18. package/deny-default.toml +42 -0
  19. package/deterministic-scans.mjs +240 -29
  20. package/disk-command.mjs +270 -30
  21. package/disk-policy.json +50 -10
  22. package/disk-policy.mjs +482 -35
  23. package/disk-report.mjs +129 -0
  24. package/door-policy.mjs +228 -0
  25. package/door.mjs +23 -0
  26. package/gate-executor.mjs +30 -0
  27. package/gate-stages.mjs +110 -0
  28. package/github-updater-release.mjs +48 -8
  29. package/init-command.mjs +41 -0
  30. package/legion-semantic-contracts.mjs +428 -0
  31. package/ownership-class-rules.mjs +277 -0
  32. package/ownership-key-authority.mjs +3 -1
  33. package/ownership-mandatory-set.mjs +3 -2
  34. package/package.json +11 -3
  35. package/publish-cargo.mjs +5 -2
  36. package/publish-update.mjs +14 -11
  37. package/release-config.mjs +51 -6
  38. package/rightkit-versions.json +172 -167
  39. package/shell-contract-bridge.json +251 -0
  40. package/shell-contract.mjs +1069 -0
  41. package/sign-updater.mjs +24 -4
  42. package/source-closure-rules.mjs +204 -0
  43. package/stage-sidecar-command.mjs +2 -1
  44. package/stage-sidecar.mjs +437 -114
  45. package/suite-api.generated.mjs +96 -6
  46. package/suite-trust-roots.mjs +198 -0
package/disk-command.mjs CHANGED
@@ -6,19 +6,74 @@ import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { spawnSync } from "./background-process.mjs";
7
7
  import { compareVersions } from "./rightkit-sync.mjs";
8
8
  import {
9
- DISK_EXIT_WARNING, applySweep, checkDisk, ensureScratchRoots, formatBytes, listOpenPaths, logEvent, planSweep, platformKey, purgeHoldings, readDiskPolicy, setWorkspaceSweepAllowed,
9
+ DISK_EXIT_WARNING, applySweep, checkDisk, checkFloors, ensureScratchRoots, formatBytes, holdingBasesOnVolume, inUseByOpenPaths, lastEvent, listOpenPaths, logEvent, planSweep, platformKey, purgeHoldings, readDiskPolicy, readRecentEvents, setWorkspaceSweepAllowed,
10
+ createRunRoot, releaseRunRoot,
10
11
  } from "./disk-policy.mjs";
12
+ import { buildDiskReport, measureScratchUnits, reportDue } from "./disk-report.mjs";
11
13
  import { evaluateBinding, installStableRoot, readScheduleBinding, selfCheck } from "./disk-stable-root.mjs";
12
14
 
13
15
  export const LAUNCHD_LABEL = "dev.rightkit.disk-hygiene";
14
16
  export const WINDOWS_TASK = "RightKit Disk Hygiene";
15
17
 
18
+ // One trigger on both hosts, every hour, running `disk run --auto`. The first run after the full-pass time is the full pass;
19
+ // every other run is the light one (see fullPassDue), so no second trigger or launchd calendar key is needed.
20
+ export const RUN_INTERVAL_SECONDS = 3600;
21
+ export const FULL_PASS_HOUR = 3;
22
+ export const FULL_PASS_MINUTE = 30;
23
+
24
+ /** True when no full pass has finished since the most recent local `hour:minute` at or before `now` (a missed fire runs full
25
+ * at the next trigger, and after one full pass the remaining runs of that day are light). No full pass on record is due. */
26
+ export function fullPassDue({ now, lastFullMs, hour = FULL_PASS_HOUR, minute = FULL_PASS_MINUTE }) {
27
+ if (lastFullMs === null || lastFullMs === undefined || !Number.isFinite(lastFullMs)) return true;
28
+ const slot = new Date(now);
29
+ slot.setHours(hour, minute, 0, 0);
30
+ if (slot.getTime() > now) slot.setDate(slot.getDate() - 1);
31
+ return lastFullMs < slot.getTime();
32
+ }
33
+
34
+ /** The floor every run event reports: the agent volume's (readable or not: an unmounted D: logs null free bytes, never the internal
35
+ * drive's), else the first readable floor, else the first. Its `id` is logged next to the number as `floorId`. */
36
+ export function leadFloor(floors) {
37
+ return floors.find((f) => f.agentVolume) ?? floors.find((f) => !f.absent) ?? floors[0];
38
+ }
39
+
40
+ const floorKey = (f) => f.id ?? f.label;
41
+
42
+ /** Why an emergency purge must not run yet, or null when it may: `uncalibrated` (no `disk.calibrated` event), `calibration-stale` (the
43
+ * log holds one, but none lists every agentVolume floor of this policy with the threshold it has now; a floor that cannot be read has
44
+ * no threshold to compare), `log-unreadable` (any read error but a missing log: nothing proves a calibration, so it counts as none).
45
+ * The calibration is searched backwards through the whole log, so a busy day of sweep lines cannot bury it. */
46
+ export function calibrationGap({ logFile, floors }) {
47
+ const agent = floors.filter((f) => f.agentVolume);
48
+ const covers = (e) => Array.isArray(e.floors) && agent.every((f) => Number.isFinite(f.thresholdBytes) && e.floors.some((r) => r && r.id === floorKey(f) && r.thresholdBytes === f.thresholdBytes));
49
+ try {
50
+ if (lastEvent(logFile, ["disk.calibrated"], { accept: covers })) return null;
51
+ return lastEvent(logFile, ["disk.calibrated"]) ? "calibration-stale" : "uncalibrated";
52
+ } catch { return "log-unreadable"; }
53
+ }
54
+
55
+ function renderFloor(f) {
56
+ if (f.error) return `${f.label}: free space cannot be read (${f.error}) WARN`;
57
+ return `${f.label}: ${formatBytes(f.freeBytes)} free of ${formatBytes(f.totalBytes)} (floor ${formatBytes(f.thresholdBytes)})${f.warn ? " WARN" : ""}${f.hardWarn ? ` BELOW THE ${formatBytes(f.hardFreeBytes)} BUILD REFUSAL LEVEL` : ""}`;
58
+ }
59
+
16
60
  export function renderCheck(result) {
17
- const lines = [`${result.floor.label}: ${formatBytes(result.floor.freeBytes)} free of ${formatBytes(result.floor.totalBytes)} (floor ${formatBytes(result.floor.thresholdBytes)})${result.floor.warn ? " WARN" : ""}`];
61
+ const lines = (result.floors ?? [result.floor]).map(renderFloor);
18
62
  for (const w of result.writers) {
19
- const size = w.error ? `error: ${w.error}` : w.matches === 0 ? "absent" : `${formatBytes(w.bytes)} / cap ${formatBytes(w.capBytes)}`;
63
+ const size = w.error ? `error: ${w.error}` : w.matches === 0 ? "absent" : `${formatBytes(w.bytes)} / cap ${formatBytes(w.capBytes)}${w.capSource === "rustright" ? " (RustRight budget)" : ""}`;
20
64
  lines.push(` ${w.id.padEnd(28)} ${w.action.padEnd(6)} ${size}${w.over ? " OVER CAP" : ""}`);
21
65
  }
66
+ for (const u of result.unmanaged ?? []) {
67
+ lines.push(` unmanaged remainder on ${u.floor} ${formatBytes(u.unmanagedBytes)} (estimate: used ${formatBytes(u.usedBytes)} - writers ${formatBytes(u.writerBytes)})`);
68
+ if (u.uncoveredTopLevel.length) {
69
+ lines.push(`Uncovered top-level on ${u.floor} (${u.uncoveredTopLevel.length}):`);
70
+ for (const e of u.uncoveredTopLevel) lines.push(` ${(e.symlink ? "symlink" : formatBytes(e.bytes)).padStart(9)} ${e.path}`);
71
+ }
72
+ }
73
+ if (result.strays?.length) {
74
+ lines.push(`Stray cargo targets (${result.strays.length}):`);
75
+ for (const t of result.strays) lines.push(` ${formatBytes(t.bytes).padStart(9)} ${String(t.ageDays).padStart(4)}d ${t.path}`);
76
+ }
22
77
  if (result.unknown?.length) {
23
78
  lines.push(`Unknown top-level entries (${result.unknown.length}):`);
24
79
  for (const u of result.unknown) lines.push(` ${(u.symlink ? "symlink" : formatBytes(u.bytes)).padStart(9)} ${String(u.ageDays).padStart(4)}d ${u.path}`);
@@ -43,12 +98,14 @@ export function renderSweep(plan, applied, { all = false } = {}) {
43
98
  const reportOnly = plan.skipped.filter((s) => s.reason === "report-only");
44
99
  if (reportOnly.length) lines.push(` workspace-unknown is report-only on this host: ${reportOnly.length} unknown entr${reportOnly.length === 1 ? "y" : "ies"} will not be moved (opt in with: right-release disk allow-workspace-sweep)`);
45
100
  if (plan.skipped.length) lines.push(` skipped: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ")}`);
101
+ // Items that were kept, leased or touched between the plan and the move are left where they are.
102
+ if (applied?.skipped?.length) lines.push(` left in place at apply time: ${applied.skipped.map((r) => `${path.basename(r.path)} (${r.reason})`).join(", ")}`);
46
103
  return lines.join("\n");
47
104
  }
48
105
 
49
- function parse(rest) {
50
- const known = new Set(["--json", "--dry-run", "--delete", "--all", "--days", "--policy"]);
51
- const opts = { json: false, dryRun: false, deleteMode: false, all: false, days: undefined, policy: undefined };
106
+ function parse(rest, command) {
107
+ const known = new Set(["--json", "--dry-run", "--delete", "--all", "--days", "--policy", "--auto", "--light", "--calibrate"]);
108
+ const opts = { json: false, dryRun: false, deleteMode: false, all: false, auto: false, light: false, calibrate: false, days: undefined, policy: undefined };
52
109
  for (let i = 0; i < rest.length; i += 1) {
53
110
  const a = rest[i];
54
111
  if (!known.has(a)) throw new Error(`disk: unknown option ${a}`);
@@ -56,10 +113,15 @@ function parse(rest) {
56
113
  else if (a === "--dry-run") opts.dryRun = true;
57
114
  else if (a === "--delete") opts.deleteMode = true;
58
115
  else if (a === "--all") opts.all = true;
116
+ else if (a === "--auto") opts.auto = true;
117
+ else if (a === "--light") opts.light = true;
118
+ else if (a === "--calibrate") opts.calibrate = true;
59
119
  else { const v = rest[++i]; if (!v) throw new Error(`disk: ${a} needs a value`); if (a === "--days") opts.days = Number(v); else opts.policy = v; }
60
120
  }
61
121
  if (opts.days !== undefined && !(opts.days >= 0)) throw new Error("disk: --days must be a non-negative number");
62
122
  if (opts.dryRun && opts.deleteMode) throw new Error("disk sweep accepts either --dry-run or --delete");
123
+ if (opts.auto && opts.light) throw new Error("disk run accepts either --auto or --light");
124
+ if (opts.calibrate && command !== "check") throw new Error("disk: --calibrate is only valid with check");
63
125
  return opts;
64
126
  }
65
127
 
@@ -148,7 +210,7 @@ function defaultGlobalRoots() {
148
210
  return roots;
149
211
  }
150
212
 
151
- export function scheduleSpec({ platform = process.platform, nodePath = process.execPath, cli = path.join(path.dirname(fileURLToPath(import.meta.url)), "cli", "right-release.mjs"), home = os.homedir(), hour = 3, minute = 30 } = {}) {
213
+ export function scheduleSpec({ platform = process.platform, nodePath = process.execPath, cli = path.join(path.dirname(fileURLToPath(import.meta.url)), "cli", "right-release.mjs"), home = os.homedir() } = {}) {
152
214
  if (platform === "darwin") {
153
215
  // launchd cannot open log files on an external volume for a background job (spawn fails with EX_CONFIG);
154
216
  // `disk run` writes its own structured log to disk-hygiene.jsonl on the heavy volume.
@@ -157,8 +219,8 @@ export function scheduleSpec({ platform = process.platform, nodePath = process.e
157
219
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
158
220
  <plist version="1.0"><dict>
159
221
  <key>Label</key><string>${LAUNCHD_LABEL}</string>
160
- <key>ProgramArguments</key><array><string>${nodePath}</string><string>${cli}</string><string>disk</string><string>run</string></array>
161
- <key>StartCalendarInterval</key><dict><key>Hour</key><integer>${hour}</integer><key>Minute</key><integer>${minute}</integer></dict>
222
+ <key>ProgramArguments</key><array><string>${nodePath}</string><string>${cli}</string><string>disk</string><string>run</string><string>--auto</string></array>
223
+ <key>StartInterval</key><integer>${RUN_INTERVAL_SECONDS}</integer>
162
224
  <key>StandardOutPath</key><string>/dev/null</string>
163
225
  <key>StandardErrorPath</key><string>/dev/null</string>
164
226
  <key>ProcessType</key><string>Background</string><key>LowPriorityIO</key><true/><key>Nice</key><integer>10</integer>
@@ -166,8 +228,8 @@ export function scheduleSpec({ platform = process.platform, nodePath = process.e
166
228
  `;
167
229
  return { platform: "mac", file, plist };
168
230
  }
169
- const script = `$a = New-ScheduledTaskAction -Execute '${nodePath}' -Argument '"${cli}" disk run'
170
- $t = New-ScheduledTaskTrigger -Daily -At ${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}
231
+ const script = `$a = New-ScheduledTaskAction -Execute '${nodePath}' -Argument '"${cli}" disk run --auto'
232
+ $t = New-ScheduledTaskTrigger -Once -At 00:05 -RepetitionInterval (New-TimeSpan -Hours ${RUN_INTERVAL_SECONDS / 3600}) -RepetitionDuration (New-TimeSpan -Days 3650)
171
233
  $s = New-ScheduledTaskSettingsSet -Hidden -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 2)
172
234
  $p = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\\$env:USERNAME" -LogonType Interactive -RunLevel Limited
173
235
  Register-ScheduledTask -TaskName '${WINDOWS_TASK}' -Action $a -Trigger $t -Settings $s -Principal $p -Force | Out-Null`;
@@ -186,11 +248,35 @@ export function sourceCli({ explicit, env = process.env, self = path.join(here,
186
248
  return resolve({ explicit, env });
187
249
  }
188
250
 
189
- function scheduleStatus({ platform, stdout, env = process.env, home = os.homedir() }) {
251
+ const CALIBRATE_NOTICE = "before the first emergency purge can act: run `right-release disk check --json` and compare freeBytes with each floor, then `right-release disk check --calibrate`; until a calibration is recorded an emergency purges nothing";
252
+
253
+ const AGE_LIMIT_HOURS = 26;
254
+
255
+ const humanAge = (ms) => (ms < 3600_000 ? `${Math.max(0, Math.round(ms / 60_000))} min` : `${(ms / 3600_000).toFixed(1)} h`);
256
+
257
+ /** What the log says about the hourly job: the newest completed full pass (disk.run.ok or disk.floor.warn; a legacy line without
258
+ * `pass` is a full pass) and the newest light pass. A problem when there is no full pass on record or it is older than `maxHours`.
259
+ * A light pass alone never satisfies the requirement: it proves the job fires, not that the daily work ran. Pure. */
260
+ export function evaluateRunAge({ events, now, maxHours = AGE_LIMIT_HOURS }) {
261
+ const newest = (accept) => events.filter(accept).sort((a, b) => Date.parse(a.ts) - Date.parse(b.ts)).at(-1) ?? null;
262
+ const lastFull = newest((e) => (e.event === "disk.run.ok" || e.event === "disk.floor.warn") && e.pass !== "light" && Number.isFinite(Date.parse(e.ts)));
263
+ const lastLight = newest((e) => e.event === "disk.run.light" && Number.isFinite(Date.parse(e.ts)));
264
+ const problems = [];
265
+ if (!lastFull) problems.push("no completed full run recorded");
266
+ else {
267
+ const hours = (now - Date.parse(lastFull.ts)) / 3600_000;
268
+ if (hours > maxHours) problems.push(`last completed full run was ${hours.toFixed(1)} h ago (limit ${maxHours} h)`);
269
+ }
270
+ return { ok: problems.length === 0, lastFull, lastLight, problems };
271
+ }
272
+
273
+ function scheduleStatus({ platform, stdout, env = process.env, home: defaultHome = os.homedir(), schedule: injected = {} }) {
274
+ const home = injected.home ?? defaultHome;
190
275
  let binding = null;
191
276
  let loaded = false;
192
- try { binding = readScheduleBinding({ platform, home }); } catch (error) { stdout(`status: cannot read the schedule (${error.message})`); return 1; }
193
- if (platform === "darwin") {
277
+ try { binding = "binding" in injected ? injected.binding : readScheduleBinding({ platform, home }); } catch (error) { stdout(`status: cannot read the schedule (${error.message})`); return 1; }
278
+ if ("loaded" in injected) loaded = injected.loaded;
279
+ else if (platform === "darwin") {
194
280
  const r = spawnSync("launchctl", ["print", `gui/${process.getuid()}/${LAUNCHD_LABEL}`], { encoding: "utf8" });
195
281
  loaded = r.status === 0;
196
282
  } else loaded = Boolean(binding);
@@ -198,14 +284,38 @@ function scheduleStatus({ platform, stdout, env = process.env, home = os.homedir
198
284
  const verdict = evaluateBinding(binding, { platform, env, home });
199
285
  stdout(`${loaded ? "installed" : "plist present but not loaded"} (${binding.source})`);
200
286
  stdout(` target: ${binding.cli ?? "(none)"}`);
201
- if (verdict.ok) { stdout(" binding ok: stable root, target present"); return loaded ? 0 : 1; }
202
- for (const p of verdict.problems) stdout(` PROBLEM: ${p}`);
203
- return 1;
287
+ if (!verdict.ok) {
288
+ for (const p of verdict.problems) stdout(` PROBLEM: ${p}`);
289
+ return 1;
290
+ }
291
+ stdout(" binding ok: stable root, target present");
292
+ const now = injected.now ?? Date.now();
293
+ const logFile = injected.logFile ?? path.join(readDiskPolicy().heavyVolume[platformKey(platform)], "disk-hygiene.jsonl");
294
+ // The newest full and light lines, each found by a backwards scan that grows until it hits one (a busy day can bury them under
295
+ // thousands of sweep lines). A log that cannot be read is said so: "no completed full run" would be a guess.
296
+ let events;
297
+ try {
298
+ events = injected.readEvents ? injected.readEvents(logFile) : [lastEvent(logFile, ["disk.run.ok", "disk.floor.warn"], { accept: (e) => e.pass !== "light" }), lastEvent(logFile, ["disk.run.light"])].filter(Boolean);
299
+ } catch (error) {
300
+ stdout(` PROBLEM: the run log ${logFile} cannot be read (${error.code ?? error.message}: ${error.message}); the last run is unknown`);
301
+ return 1;
302
+ }
303
+ const age = evaluateRunAge({ events, now });
304
+ if (age.lastFull) stdout(` last full run: ${humanAge(now - Date.parse(age.lastFull.ts))} ago (${age.lastFull.ts}, ${age.lastFull.event})`);
305
+ if (age.lastLight) stdout(` last light run: ${humanAge(now - Date.parse(age.lastLight.ts))} ago`);
306
+ else stdout(" last light run: none recorded");
307
+ for (const p of age.problems) stdout(` PROBLEM: ${p}`);
308
+ // Informational: the emergency purge is simply off until a calibration is recorded, which is not a fault of the job.
309
+ let calibrated = null;
310
+ try { calibrated = lastEvent(logFile, ["disk.calibrated"]); } catch { /* an unreadable log counts as no calibration */ }
311
+ const calibratedAt = calibrated ? Date.parse(calibrated.ts) : NaN;
312
+ stdout(Number.isFinite(calibratedAt) ? ` calibration: recorded ${humanAge(now - calibratedAt)} ago` : " calibration: NOT RECORDED (emergency purge disabled)");
313
+ return loaded && age.ok ? 0 : 1;
204
314
  }
205
315
 
206
- function schedule(sub, { stdout, cli, platform = process.platform, env = process.env }) {
316
+ function schedule(sub, { stdout, cli, platform = process.platform, env = process.env, injected }) {
207
317
  if (!["install", "uninstall", "status"].includes(sub)) throw new Error("disk schedule install|uninstall|status");
208
- if (sub === "status") return scheduleStatus({ platform, stdout, env });
318
+ if (sub === "status") return scheduleStatus({ platform, stdout, env, schedule: injected });
209
319
  const spec = scheduleSpec({ platform });
210
320
  if (platform === "darwin") {
211
321
  const uid = String(process.getuid());
@@ -214,13 +324,15 @@ function schedule(sub, { stdout, cli, platform = process.platform, env = process
214
324
  stdout(`${installed.unchanged ? "kept" : "installed"} @rightkit/release ${installed.version ?? "?"} in ${installed.stable}`);
215
325
  const bound = scheduleSpec({ platform, cli: installed.cli });
216
326
  mkdirSync(path.dirname(bound.file), { recursive: true });
217
- mkdirSync("/Volumes/D/claude/tmp/swept", { recursive: true });
218
- ensureScratchRoots({ policy: readDiskPolicy(), platform, env });
327
+ const policy = readDiskPolicy();
328
+ mkdirSync(policy.heavyVolume[platformKey(platform)], { recursive: true });
329
+ ensureScratchRoots({ policy, platform, env });
219
330
  writeFileSync(bound.file, bound.plist);
220
331
  spawnSync("launchctl", ["bootout", `gui/${uid}/${LAUNCHD_LABEL}`], { encoding: "utf8" });
221
332
  const r = spawnSync("launchctl", ["bootstrap", `gui/${uid}`, bound.file], { encoding: "utf8" });
222
333
  if (r.status !== 0) throw new Error(`launchctl bootstrap failed: ${r.stderr}`);
223
- stdout(`installed ${bound.file} (daily 03:30), bound to ${installed.cli}`);
334
+ stdout(`installed ${bound.file} (hourly: full pass after 03:30, light passes otherwise), bound to ${installed.cli}`);
335
+ stdout(CALIBRATE_NOTICE);
224
336
  } else {
225
337
  spawnSync("launchctl", ["bootout", `gui/${uid}/${LAUNCHD_LABEL}`], { encoding: "utf8" });
226
338
  if (existsSync(spec.file)) unlinkSync(spec.file);
@@ -235,7 +347,8 @@ function schedule(sub, { stdout, cli, platform = process.platform, env = process
235
347
  const bound = scheduleSpec({ platform, cli: installed.cli });
236
348
  const r = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", bound.script], { encoding: "utf8" });
237
349
  if (r.status !== 0) throw new Error(`Register-ScheduledTask failed: ${r.stderr || r.stdout}`);
238
- stdout(`installed scheduled task "${WINDOWS_TASK}" (daily 03:30, hidden), bound to ${installed.cli}`);
350
+ stdout(`installed scheduled task "${WINDOWS_TASK}" (hourly, hidden: full pass after 03:30, light passes otherwise), bound to ${installed.cli}`);
351
+ stdout(CALIBRATE_NOTICE);
239
352
  } else {
240
353
  spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `Unregister-ScheduledTask -TaskName '${WINDOWS_TASK}' -Confirm:$false`], { encoding: "utf8" });
241
354
  stdout("uninstalled");
@@ -533,13 +646,47 @@ function probeCommand(rest, { env, stdout, platform, deps }) {
533
646
  return { exitCode: report.internalWritable || leftovers.length || problems.length ? 1 : 0, report, problems };
534
647
  }
535
648
 
649
+ function runRootCommand(rest, { env, stdout, platform, deps }) {
650
+ const stderr = deps.stderr ?? console.error;
651
+ const opts = { name: undefined, days: undefined, release: false, json: false, policy: undefined };
652
+ for (let i = 0; i < rest.length; i += 1) {
653
+ const a = rest[i];
654
+ if (a === "--release") opts.release = true;
655
+ else if (a === "--json") opts.json = true;
656
+ else if (a === "--days" || a === "--policy") {
657
+ const v = rest[++i];
658
+ if (!v) throw new Error(`disk run-root: ${a} needs a value`);
659
+ if (a === "--days") opts.days = Number(v); else opts.policy = v;
660
+ } else if (a.startsWith("--")) throw new Error(`disk run-root: unknown option ${a}`);
661
+ else if (opts.name === undefined) opts.name = a;
662
+ else throw new Error("disk run-root: one run root name only");
663
+ }
664
+ if (opts.name === undefined) throw new Error("disk run-root <name> [--days N] | --release <name>");
665
+ if (opts.release && opts.days !== undefined) throw new Error("disk run-root: --release takes no --days");
666
+ const policy = readDiskPolicy(opts.policy);
667
+ const common = { policy, platform, env, name: opts.name, ...(deps.runRoot ?? {}) };
668
+ try {
669
+ if (opts.release) {
670
+ const r = releaseRunRoot(common);
671
+ stdout(opts.json ? JSON.stringify(r) : r.released ? `released ${r.path}: the lease is gone, the directory ages out by the scratch rules` : `${r.path} had no lease`);
672
+ return { exitCode: 0, result: r };
673
+ }
674
+ const r = createRunRoot({ ...common, days: opts.days });
675
+ stdout(opts.json ? JSON.stringify({ path: r.path, ...r.lease }) : `${r.renewed ? "renewed" : "leased"} run root ${r.path} until ${r.lease.leaseUntil}; the sweep skips it until then (end it early: right-release disk run-root --release ${opts.name})`);
676
+ return { exitCode: 0, result: r };
677
+ } catch (error) {
678
+ stderr(error.message);
679
+ return { exitCode: 1, error: error.message };
680
+ }
681
+ }
682
+
536
683
  export async function runDiskCommand(args = process.argv.slice(2), { env = process.env, stdout = console.log, platform = process.platform, deps = {} } = {}) {
537
684
  const [command, ...rest] = args;
538
685
  if (command === "agent-env") return agentEnvCommand(rest, { env, stdout, platform, deps });
539
686
  if (command === "probe") return probeCommand(rest, { env, stdout, platform, deps });
540
687
  if (command === "schedule") {
541
688
  const at = rest.indexOf("--cli");
542
- const exitCode = schedule(rest[0], { stdout, cli: at === -1 ? undefined : rest[at + 1], platform, env });
689
+ const exitCode = schedule(rest[0], { stdout, cli: at === -1 ? undefined : rest[at + 1], platform, env, injected: deps.schedule });
543
690
  return { exitCode };
544
691
  }
545
692
  if (command === "allow-workspace-sweep") {
@@ -548,7 +695,8 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
548
695
  stdout(off ? `workspace-unknown sweep revoked on this host (${file}.revoked)` : `workspace-unknown sweep allowed on this host (${file}); review \`disk sweep --dry-run --all\` first, revoke with --off`);
549
696
  return { exitCode: 0 };
550
697
  }
551
- const opts = parse(rest);
698
+ if (command === "run-root") return runRootCommand(rest, { env, stdout, platform, deps });
699
+ const opts = parse(rest, command);
552
700
  const policy = readDiskPolicy(opts.policy);
553
701
  const key = platformKey(platform);
554
702
  const logFile = path.join(policy.heavyVolume[key], "disk-hygiene.jsonl");
@@ -556,6 +704,30 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
556
704
  const check = () => checkDisk({ policy, platform, env, ...deps.check });
557
705
  const plan = () => planSweep({ policy, platform, env, staleDays: opts.days, openPaths: platform === "win32" ? null : (deps.openPaths ?? listOpenPaths({ platform })), ...deps.plan });
558
706
 
707
+ if (command === "check" && opts.calibrate) {
708
+ const nowMs = deps.now ?? Date.now();
709
+ const floors = checkFloors({ policy, platform, ...(deps.check?.space ? { space: deps.check.space } : {}) });
710
+ const below = floors.filter((f) => f.agentVolume && f.warn);
711
+ if (below.length) {
712
+ // Recording a calibration while the volume is already under its floor would make a chronically low volume an accident.
713
+ const lines = below.map((f) => (f.error ? `${f.label}: free space cannot be read (${f.error})` : `${f.label}: ${formatBytes(f.freeBytes)} free, ${formatBytes(f.thresholdBytes - f.freeBytes)} below its floor of ${formatBytes(f.thresholdBytes)}`));
714
+ lines.push("Nothing was recorded: lower the floor in disk-policy.json or free space first, so that running this volume low is a decision, never an accident.");
715
+ out({ calibrated: false, below: below.map((f) => ({ id: floorKey(f), freeBytes: f.freeBytes ?? null, thresholdBytes: f.thresholdBytes ?? null })) }, lines.join("\n"));
716
+ return { exitCode: 1, calibrated: false, floors };
717
+ }
718
+ const ts = new Date(nowMs).toISOString();
719
+ const recorded = floors.map((f) => ({ id: floorKey(f), label: f.label, path: f.path, thresholdBytes: f.thresholdBytes ?? null, freeBytes: f.freeBytes ?? null, agentVolume: Boolean(f.agentVolume) }));
720
+ logEvent(logFile, { ts, event: "disk.calibrated", floors: recorded });
721
+ // logEvent never throws; read the line back so "recorded" is only ever said about a line that is in the log.
722
+ let found = false;
723
+ try { found = Boolean(lastEvent(logFile, ["disk.calibrated"], { accept: (e) => e.ts === ts })); } catch { /* not found */ }
724
+ if (!found) {
725
+ out({ calibrated: false, error: `cannot write ${logFile}` }, `Nothing was recorded: ${logFile} cannot be written`);
726
+ return { exitCode: 1, calibrated: false, floors };
727
+ }
728
+ out({ calibrated: true, logFile, floors: recorded }, [`Calibration recorded in ${logFile}:`, ...recorded.map((f) => ` ${f.label}: ${f.freeBytes === null ? "free space unreadable" : `${formatBytes(f.freeBytes)} free`}, floor ${f.thresholdBytes === null ? "unknown" : formatBytes(f.thresholdBytes)}${f.agentVolume ? " (agent volume)" : ""}`), "Emergency purge is enabled while these floors stay as they are; changing a floor needs a new calibration."].join("\n"));
729
+ return { exitCode: 0, calibrated: true, floors: recorded };
730
+ }
559
731
  if (command === "check") {
560
732
  const result = check();
561
733
  out(result, renderCheck(result));
@@ -582,6 +754,62 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
582
754
  logEvent(logFile, { event: "disk.run.selfcheck.fail", problems: self.problems, version: self.version });
583
755
  return { exitCode: 2, selfCheck: self };
584
756
  }
757
+ const nowMs = deps.now ?? Date.now();
758
+ const log = (event) => logEvent(logFile, { ts: new Date(nowMs).toISOString(), ...event });
759
+ // The hourly job passes --auto: the first run after the full-pass time is the full pass, every other run is light.
760
+ // A bare `disk run` is always the full pass.
761
+ // A log that does not exist means the job never ran (full pass due). A log that exists but cannot be read (EIO, a volume that went away)
762
+ // proves nothing about the last full pass: the run stays light instead of repeating the full pass every hour.
763
+ let lastFull = null;
764
+ let logReadError = null;
765
+ if (opts.auto) {
766
+ try { lastFull = lastEvent(logFile, ["disk.run.ok", "disk.floor.warn"], { accept: (e) => e.pass !== "light" }); } catch (error) { logReadError = error; }
767
+ }
768
+ const light = opts.light || (opts.auto && (logReadError !== null || !fullPassDue({ now: nowMs, lastFullMs: lastFull ? Date.parse(lastFull.ts) : null })));
769
+ // Only the agent volume running below its soft floor is an emergency: the undo window is spent
770
+ // (days 0, so what this pass just moved is purged too). Warnings from other floors or writers are not.
771
+ const emergencyFloor = (floors) => floors.find((f) => f.agentVolume && f.warn && !f.absent);
772
+ // Purging without an undo window on numbers nobody has looked at is how a mis-set floor would empty every holding folder each
773
+ // hour, so it waits for a recorded calibration (`disk check --calibrate`). Without one the run is an ordinary run: null.
774
+ const emergencyPurge = (squeezed, floors) => {
775
+ const gap = calibrationGap({ logFile, floors });
776
+ if (gap) {
777
+ logEvent(logFile, { event: "disk.emergency.skipped", reason: gap, floorId: floorKey(squeezed), freeBytes: squeezed.freeBytes, thresholdBytes: squeezed.thresholdBytes });
778
+ stdout(`EMERGENCY SKIPPED (${gap}): ${squeezed.label} is below its floor but no calibration is recorded; run \`right-release disk check --json\`, compare freeBytes with the floor, then \`right-release disk check --calibrate\``);
779
+ return null;
780
+ }
781
+ // Only the holding bases on the squeezed volume: emptying another volume's undo window would free nothing here.
782
+ const bases = holdingBasesOnVolume(squeezed.path, { policy, platform, env, ...(deps.deviceOf ? { deviceOf: deps.deviceOf } : {}) });
783
+ const purged = purgeHoldings({ policy, platform, env, days: 0, bases, ...deps.purge });
784
+ stdout(`purged ${purged.results.filter((r) => r.ok).length} holding dir(s), ${formatBytes(purged.purgedBytes)}`);
785
+ stdout("EMERGENCY: the agent volume is below its soft floor, holding folders were purged without their undo window");
786
+ logEvent(logFile, { event: "disk.run.emergency", floor: squeezed.id ?? squeezed.label, freeBytes: squeezed.freeBytes, purgedBytes: purged.purgedBytes });
787
+ return purged;
788
+ };
789
+
790
+ if (light) {
791
+ // Stat-only: free space of the floors, the cheap temp-like writers and the scratch roots. No du of any writer, no workspace
792
+ // scan, no stray walk, and lsof only once a candidate exists. It purges nothing but in the emergency.
793
+ ensureScratchRoots({ policy, platform, env });
794
+ const floorOptions = { policy, platform, ...(deps.check?.space ? { space: deps.check.space } : {}) };
795
+ const floorsBefore = checkFloors(floorOptions);
796
+ const squeezed = emergencyFloor(floorsBefore);
797
+ let lockCheck = {};
798
+ if (platform !== "win32" && !deps.plan?.inUse) {
799
+ if (deps.openPaths !== undefined) lockCheck = { openPaths: deps.openPaths };
800
+ else { let open; let read = false; lockCheck = { inUse: (p) => { if (!read) { open = (deps.listOpenPaths ?? listOpenPaths)({ platform }); read = true; } return inUseByOpenPaths(p, open); } }; }
801
+ }
802
+ const p = planSweep({ policy, platform, env, staleDays: opts.days, light: true, ...lockCheck, ...deps.plan });
803
+ const applied = applySweep({ plan: p, policy, platform, ...deps.apply });
804
+ const purged = squeezed ? emergencyPurge(squeezed, floorsBefore) : null;
805
+ const floors = checkFloors(floorOptions);
806
+ const lead = leadFloor(floors);
807
+ if (logReadError) stdout(`WARNING: the run log ${logFile} cannot be read (${logReadError.message}), so this run stays light`);
808
+ log({ event: "disk.run.light", pass: "light", freeBytes: lead.freeBytes ?? null, floorId: lead.id ?? lead.label, ...(logReadError ? { logReadError: logReadError.message } : {}), floors: floors.map((f) => ({ id: f.id ?? f.label, freeBytes: f.freeBytes ?? null, warn: f.warn })), movedBytes: applied.movedBytes, emergency: Boolean(purged), kept: p.skipped.filter((s) => s.reason === "kept").length, leased: p.skipped.filter((s) => s.reason === "leased").length });
809
+ stdout(`light pass: moved ${formatBytes(applied.movedBytes)} (${applied.results.filter((r) => r.ok).length}/${applied.results.length}), ${lead.absent ? `${lead.label} is not readable` : `${lead.label} ${formatBytes(lead.freeBytes)} free`}`);
810
+ return { exitCode: 0, pass: "light", plan: p, applied, purged, floors, selfCheck: self };
811
+ }
812
+
585
813
  ensureScratchRoots({ policy, platform, env });
586
814
  const before = check();
587
815
  stdout(renderCheck(before));
@@ -589,15 +817,27 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
589
817
  stdout(renderSweep(p));
590
818
  const applied = applySweep({ plan: p, policy, platform, ...deps.apply });
591
819
  stdout(`moved ${formatBytes(applied.movedBytes)} (${applied.results.filter((r) => r.ok).length}/${applied.results.length})`);
592
- const purged = purgeHoldings({ policy, platform, env, ...deps.purge });
593
- stdout(`purged ${purged.results.filter((r) => r.ok).length} holding dir(s), ${formatBytes(purged.purgedBytes)}`);
820
+ const squeezed = emergencyFloor(before.floors ?? [before.floor]);
821
+ let purged = squeezed ? emergencyPurge(squeezed, before.floors ?? [before.floor]) : null;
822
+ if (!purged) {
823
+ purged = purgeHoldings({ policy, platform, env, ...deps.purge });
824
+ stdout(`purged ${purged.results.filter((r) => r.ok).length} holding dir(s), ${formatBytes(purged.purgedBytes)}`);
825
+ }
594
826
  const after = check();
595
827
  const warn = after.warn;
596
- logEvent(logFile, { event: warn ? "disk.floor.warn" : "disk.run.ok", warnings: after.warnings, freeBytes: after.floor.freeBytes, movedBytes: applied.movedBytes });
828
+ const lead = leadFloor(after.floors ?? [after.floor]);
829
+ log({ event: warn ? "disk.floor.warn" : "disk.run.ok", pass: "full", warnings: after.warnings, freeBytes: lead.freeBytes ?? null, floorId: lead.id ?? lead.label, movedBytes: applied.movedBytes, kept: p.skipped.filter((s) => s.reason === "kept").length, leased: p.skipped.filter((s) => s.reason === "leased").length });
830
+ // One report a UTC day: its per-unit sizes need a du per scratch run, which only the daily pass may afford.
831
+ let previousReport = null;
832
+ try { previousReport = lastEvent(logFile, ["disk.report"]); } catch { /* unreadable log: this report has no delta */ }
833
+ if (reportDue({ lastReportTs: previousReport?.ts, now: nowMs })) {
834
+ const scratchUnits = measureScratchUnits({ policy, platform, env, ...(deps.check?.size ? { size: deps.check.size } : {}) });
835
+ log(buildDiskReport({ check: after, previous: previousReport, now: nowMs, policy, scratchUnits, moved: applied.movedBytes, purged: purged.purgedBytes }));
836
+ }
597
837
  if (warn) stdout(`WARNING: ${after.warnings.join("; ")}`);
598
- return { exitCode: !self.ok ? 2 : warn ? DISK_EXIT_WARNING : 0, before, after, applied, purged, selfCheck: self };
838
+ return { exitCode: warn ? DISK_EXIT_WARNING : 0, pass: "full", before, after, applied, purged, selfCheck: self };
599
839
  }
600
- throw new Error("disk usage: check|sweep [--dry-run|--delete] [--all]|allow-workspace-sweep [--off] [--days N]|purge [--dry-run] [--days N]|run|schedule install|uninstall|status|agent-env [--root dir] [--format sh|ps1|cmd|json] [--check] [--include-agent-homes]|probe [--json]");
840
+ throw new Error("disk usage: check [--calibrate]|sweep [--dry-run|--delete] [--all]|allow-workspace-sweep [--off] [--days N]|purge [--dry-run] [--days N]|run [--auto|--light]|run-root <name> [--days N] [--release]|schedule install|uninstall|status|agent-env [--root dir] [--format sh|ps1|cmd|json] [--check] [--include-agent-homes]|probe [--json]");
601
841
  }
602
842
 
603
843
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
package/disk-policy.json CHANGED
@@ -2,10 +2,13 @@
2
2
  "schema": 1,
3
3
  "staleDays": 7,
4
4
  "floors": {
5
- "mac": {"path": "/System/Volumes/Data", "label": "Mac internal", "warnFreeBytes": 42949672960},
6
- "win": {"path": "D:\\", "label": "Dell D:", "warnFreeBytes": 429496729600, "warnFreePercent": 10}
5
+ "mac": [
6
+ {"id": "mac-internal", "path": "/System/Volumes/Data", "label": "Mac internal", "warnFreeBytes": 42949672960, "holdingSource": true},
7
+ {"id": "mac-d", "path": "/Volumes/D", "label": "Mac D:", "warnFreeBytes": 268435456000, "hardFreeBytes": 200000000000, "agentVolume": true}
8
+ ],
9
+ "win": {"id": "win-d", "path": "D:\\", "label": "Dell D:", "warnFreeBytes": 429496729600, "warnFreePercent": 10, "agentVolume": true}
7
10
  },
8
- "holdingDays": 3,
11
+ "holdingDays": 1,
9
12
  "holding": {
10
13
  "mac": [{"prefix": "/", "path": "/Volumes/D/.rightkit-swept", "mount": "/Volumes/D"}, {"prefix": "/", "path": "~/.rightkit-swept"}],
11
14
  "win": [{"prefix": "C:\\", "path": "${LOCALAPPDATA}\\Orthic Labs\\.rightkit-swept"}]
@@ -15,18 +18,25 @@
15
18
  "win": "D:\\claude\\tmp\\swept"
16
19
  },
17
20
  "unknownStaleDays": 3,
21
+ "scratchStaleDays": 1,
22
+ "strayTargets": {
23
+ "mac": [{"path": "/Volumes/D/claude", "depth": 4}, {"path": "/Volumes/D/scratch", "depth": 4}],
24
+ "win": [{"path": "D:\\claude", "depth": 4}, {"path": "D:\\scratch", "depth": 4}]
25
+ },
26
+ "strayTargetDays": 1,
18
27
  "workspaceRoots": {
19
28
  "mac": [{"path": "/Volumes/D/claude"}],
20
29
  "win": [{"path": "D:\\claude"}]
21
30
  },
22
31
  "workspaceAllowlist": [".DS_Store", ".git", "node_modules", "tmp", "reports", "tasks", ".cache", "bin", "repos", "packages", "tests", "schemas", "assets", "migration", "archive", "catalog.db", "catalog.db-shm", "catalog.db-wal", "apple-skill-sources", "foundation-runs", "SEO", ".audit", ".commandcode", ".fastembed_cache", ".legion", ".model-bench", ".pnpm-store", ".pytest_cache", ".research", ".right-release", ".rightkit-managed", ".tether", ".venv-tools", ".vite"],
32
+ "scratchPrefixCaps": {"legion": 42949672960, "rk": 32212254720, "pulse": 21474836480, "hr": 21474836480},
23
33
  "scratchRoots": {
24
34
  "mac": ["/Volumes/D/scratch"],
25
35
  "win": ["D:\\scratch"]
26
36
  },
27
37
  "writers": [
28
- {"id": "claude-session-dirs", "platform": "mac", "path": "/private/tmp/claude-501/*/*", "capBytes": 2147483648, "action": "sweep", "unit": "self"},
29
- {"id": "rightkit-qa-tmp", "platform": "mac", "path": "/private/tmp/rightkit-qa", "capBytes": 2147483648, "action": "sweep", "unit": "children"},
38
+ {"id": "claude-session-dirs", "platform": "mac", "path": "/private/tmp/claude-501/*/*", "capBytes": 2147483648, "action": "sweep", "unit": "self", "light": true},
39
+ {"id": "rightkit-qa-tmp", "platform": "mac", "path": "/private/tmp/rightkit-qa", "capBytes": 2147483648, "action": "sweep", "unit": "children", "light": true},
30
40
  {"id": "cache-coreml-e5rt-tmp", "platform": "mac", "path": "~/Library/Caches/*/com.apple.e5rt.e5bundlecache/*/*/*.tmp.*", "capBytes": 4294967296, "action": "sweep", "unit": "self", "staleDays": 1},
31
41
  {"id": "cache-coreml-e5rt", "platform": "mac", "path": "~/Library/Caches/*/com.apple.e5rt.e5bundlecache/*/*", "capBytes": 4294967296, "action": "sweep", "unit": "children", "staleDays": 2},
32
42
  {"id": "cache-e2e", "platform": "mac", "path": "~/Library/Caches/e2e_*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
@@ -42,9 +52,14 @@
42
52
  {"id": "documents-scratch", "platform": "mac", "path": "~/Documents", "capBytes": 21474836480, "action": "report"},
43
53
  {"id": "shared-models", "platform": "mac", "path": "~/Library/Application Support/Orthic Labs/shared/models", "capBytes": 107374182400, "action": "report"},
44
54
  {"id": "stale-release-tarballs", "platform": "mac", "path": "/Volumes/D/claude/rightkit/packages/release/rightkit-release-*.tgz", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
45
- {"id": "win-temp", "platform": "win", "path": "${TEMP}", "capBytes": 10737418240, "action": "sweep", "unit": "children"},
55
+ {"id": "agent-tmp", "platform": "mac", "path": "/Volumes/D/agent-cache/tmp", "capBytes": 21474836480, "action": "sweep", "unit": "children", "staleDays": 1, "category": "agent-cache", "light": true},
56
+ {"id": "agent-tmp", "platform": "win", "path": "D:\\agent-cache\\tmp", "capBytes": 21474836480, "action": "sweep", "unit": "children", "staleDays": 1, "category": "agent-cache", "light": true},
57
+ {"id": "build-home-tmp", "platform": "mac", "path": "/Volumes/D/.build-home/*/tmp", "capBytes": 10737418240, "action": "sweep", "unit": "children", "staleDays": 1, "light": true},
58
+ {"id": "build-home-tmp", "platform": "win", "path": "D:\\.build-home\\*\\tmp", "capBytes": 10737418240, "action": "sweep", "unit": "children", "staleDays": 1, "light": true},
59
+ {"id": "xcode-deriveddata-d", "platform": "mac", "path": "/Volumes/D/.xcode/DerivedData", "capBytes": 42949672960, "action": "sweep", "unit": "children"},
60
+ {"id": "win-temp", "platform": "win", "path": "${TEMP}", "capBytes": 10737418240, "action": "sweep", "unit": "children", "light": true},
46
61
  {"id": "win-pnpm", "platform": "win", "path": "${LOCALAPPDATA}\\pnpm", "capBytes": 21474836480, "action": "report"},
47
- {"id": "win-sccache", "platform": "win", "path": "D:\\.rightkit-managed\\rightkit-build-control\\global", "capBytes": 34359738368, "action": "report"},
62
+ {"id": "win-sccache", "platform": "win", "path": "D:\\.rightkit-managed\\rightkit-build-control\\global\\compiler-cache", "capBytes": 55000000000, "action": "report", "budget": ["sccache"]},
48
63
  {"id": "win-claude-workspace", "platform": "win", "path": "D:\\claude", "capBytes": 805306368000, "action": "report"},
49
64
  {"id": "win-claude-tmp", "platform": "win", "path": "D:\\claude\\tmp", "capBytes": 107374182400, "action": "report"},
50
65
  {"id": "win-shared-models", "platform": "win", "path": "${LOCALAPPDATA}\\Orthic Labs\\shared\\models", "capBytes": 107374182400, "action": "report"},
@@ -63,9 +78,34 @@
63
78
  {"id": "leftover-pulse-bak", "platform": "win", "path": "${LOCALAPPDATA}\\Programs\\Pulse.bak-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
64
79
  {"id": "qa-evidence-runs", "platform": "mac", "path": "/Volumes/D/claude/*/.cache/rightkit-qa/*", "capBytes": 5368709120, "action": "sweep", "unit": "self", "staleDays": 3, "skipTracked": true},
65
80
  {"id": "qa-evidence-runs", "platform": "win", "path": "D:\\claude\\*\\.cache\\rightkit-qa\\*", "capBytes": 5368709120, "action": "sweep", "unit": "self", "staleDays": 3, "skipTracked": true},
66
- {"id": "build-control-targets", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/rightkit-build-control", "capBytes": 429496729600, "action": "report"},
67
- {"id": "scratch-total", "platform": "mac", "path": "/Volumes/D/scratch", "capBytes": 322122547200, "action": "report"},
68
- {"id": "claude-tmp", "platform": "mac", "path": "/Volumes/D/claude/tmp", "capBytes": 53687091200, "action": "report"}
81
+ {"id": "build-control-targets", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/rightkit-build-control", "capBytes": 250000000000, "action": "report", "budget": ["total"]},
82
+ {"id": "scratch-total", "platform": "mac", "path": "/Volumes/D/scratch", "capBytes": 322122547200, "action": "report", "category": "scratch"},
83
+ {"id": "claude-tmp", "platform": "mac", "path": "/Volumes/D/claude/tmp", "capBytes": 53687091200, "action": "report"},
84
+ {"id": "agent-cache", "platform": "mac", "path": "/Volumes/D/agent-cache", "capBytes": 64424509440, "action": "report"},
85
+ {"id": "agent-cache", "platform": "win", "path": "D:\\agent-cache", "capBytes": 64424509440, "action": "report"},
86
+ {"id": "dev-cache", "platform": "mac", "path": "/Volumes/D/.dev-cache", "capBytes": 85899345920, "action": "report"},
87
+ {"id": "dev-cache", "platform": "win", "path": "D:\\.dev-cache", "capBytes": 85899345920, "action": "report"},
88
+ {"id": "agent-home", "platform": "mac", "path": "/Volumes/D/agent-home", "capBytes": 21474836480, "action": "report"},
89
+ {"id": "agent-home", "platform": "win", "path": "D:\\agent-home", "capBytes": 21474836480, "action": "report"},
90
+ {"id": "build-home", "platform": "mac", "path": "/Volumes/D/.build-home", "capBytes": 21474836480, "action": "report"},
91
+ {"id": "build-home", "platform": "win", "path": "D:\\.build-home", "capBytes": 21474836480, "action": "report"},
92
+ {"id": "managed-runs", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/runs", "capBytes": 107374182400, "action": "report"},
93
+ {"id": "managed-runs", "platform": "win", "path": "D:\\.rightkit-managed\\runs", "capBytes": 107374182400, "action": "report"},
94
+ {"id": "managed-artifacts", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/artifacts", "capBytes": 42949672960, "action": "report"},
95
+ {"id": "managed-artifacts", "platform": "win", "path": "D:\\.rightkit-managed\\artifacts", "capBytes": 42949672960, "action": "report"},
96
+ {"id": "remote-workspaces", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/remote-workspaces", "capBytes": 64424509440, "action": "report"},
97
+ {"id": "remote-workspaces", "platform": "win", "path": "D:\\.rightkit-managed\\remote-workspaces", "capBytes": 64424509440, "action": "report"},
98
+ {"id": "holding-volume", "platform": "mac", "path": "/Volumes/D/.rightkit-swept", "capBytes": 107374182400, "action": "report", "category": "holding"},
99
+ {"id": "holding-volume", "platform": "win", "path": "D:\\.rightkit-swept", "capBytes": 107374182400, "action": "report", "category": "holding"},
100
+ {"id": "build-control-sccache", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/rightkit-build-control/global/compiler-cache", "capBytes": 55000000000, "action": "report", "budget": ["sccache"]},
101
+ {"id": "tart-home", "platform": "mac", "path": "/Volumes/D/tart-home", "capBytes": 161061273600, "action": "report"},
102
+ {"id": "xcode-home", "platform": "mac", "path": "/Volumes/D/.xcode", "capBytes": 64424509440, "action": "report"},
103
+ {"id": "remote-inputs", "platform": "mac", "path": "/Volumes/D/.rightkit-managed/remote-inputs", "capBytes": 21474836480, "action": "report"},
104
+ {"id": "claude-workspace", "platform": "mac", "path": "/Volumes/D/claude", "capBytes": 805306368000, "action": "report"},
105
+ {"id": "legacy-rightkit-publish", "platform": "mac", "path": "/Volumes/D/rightkit-publish", "capBytes": 1073741824, "action": "report"},
106
+ {"id": "scratch-total", "platform": "win", "path": "D:\\scratch", "capBytes": 322122547200, "action": "report", "category": "scratch"},
107
+ {"id": "build-control-targets", "platform": "win", "path": "D:\\.rightkit-managed\\rightkit-build-control", "capBytes": 250000000000, "action": "report", "budget": ["total"]},
108
+ {"id": "win-build-control-local", "platform": "win", "path": "${LOCALAPPDATA}\\RightKit\\build-control", "capBytes": 5368709120, "action": "report"}
69
109
  ],
70
110
  "protectedSegments": ["Application Support", "Keychains", "Keychain", "HeardRightNext", "Credentials", ".ssh", ".rightkit-managed", ".rightkit-swept"]
71
111
  }