@rightkit/release 0.2.156 → 0.2.159

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.
@@ -78,6 +78,8 @@ if (first === "--version" || first === "-v") {
78
78
  );
79
79
  } else if (first === "cache") {
80
80
  run("cache-command.mjs", args.slice(1));
81
+ } else if (first === "disk") {
82
+ run("disk-command.mjs", args.slice(1));
81
83
  } else if (first === "install-app") {
82
84
  run("install-app.mjs", args.slice(1));
83
85
  } else if (first === "dmg") {
@@ -242,6 +244,11 @@ Commands:
242
244
  cache prune [--dry-run|--apply] [--json] Plan or apply safe shared-target pruning
243
245
  cache migrate --config <file> [--dry-run|--apply] [--json]
244
246
  Move a same-volume legacy target without copying
247
+ disk check [--json] Per-writer sizes vs caps and free-space floor (exit 3 on warning)
248
+ disk sweep [--dry-run] [--days N] [--delete] [--json]
249
+ Move stale idle heavy data to the heavy volume (delete only with --delete)
250
+ disk run check, dry-run sweep, move sweep, warn (the scheduled job)
251
+ disk schedule install|uninstall|status Daily launchd job (Mac) / hidden Task Scheduler entry (Windows)
245
252
  deps --check|--audit|--update Shared dependency lane
246
253
  hardening <artifact...> Run the Right Suite hardening scan
247
254
  lsclean <AppName.app> Clear macOS LaunchServices duplicates
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, realpathSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import { spawnSync } from "./background-process.mjs";
7
+ import {
8
+ DISK_EXIT_WARNING, applySweep, checkDisk, formatBytes, listOpenPaths, logEvent, planSweep, platformKey, purgeHoldings, readDiskPolicy,
9
+ } from "./disk-policy.mjs";
10
+
11
+ export const LAUNCHD_LABEL = "dev.rightkit.disk-hygiene";
12
+ export const WINDOWS_TASK = "RightKit Disk Hygiene";
13
+
14
+ export function renderCheck(result) {
15
+ const lines = [`${result.floor.label}: ${formatBytes(result.floor.freeBytes)} free of ${formatBytes(result.floor.totalBytes)} (floor ${formatBytes(result.floor.thresholdBytes)})${result.floor.warn ? " WARN" : ""}`];
16
+ for (const w of result.writers) {
17
+ const size = w.error ? `error: ${w.error}` : w.matches === 0 ? "absent" : `${formatBytes(w.bytes)} / cap ${formatBytes(w.capBytes)}`;
18
+ lines.push(` ${w.id.padEnd(28)} ${w.action.padEnd(6)} ${size}${w.over ? " OVER CAP" : ""}`);
19
+ }
20
+ for (const w of result.warnings) lines.push(`WARNING: ${w}`);
21
+ return lines.join("\n");
22
+ }
23
+
24
+ export function renderSweep(plan, applied) {
25
+ const lines = [`${applied ? "Swept" : "Dry run: would move"} ${plan.candidates.length} item(s), ${formatBytes(plan.totalBytes)} (stale > ${plan.staleDays} days)`];
26
+ const rows = applied ? applied.results : plan.candidates;
27
+ const byWriter = new Map();
28
+ for (const r of rows) byWriter.set(r.writer, [...(byWriter.get(r.writer) ?? []), r]);
29
+ for (const [writer, items] of byWriter) {
30
+ const total = items.reduce((s, r) => s + r.bytes, 0);
31
+ lines.push(` ${writer.padEnd(26)} ${String(items.length).padStart(4)} item(s) ${formatBytes(total).padStart(9)}`);
32
+ for (const r of [...items].sort((a, b) => b.bytes - a.bytes).slice(0, 5)) lines.push(` ${formatBytes(r.bytes).padStart(9)} ${r.path}${r.destination ? ` -> ${r.destination}` : ""}${r.ok === false ? ` FAILED: ${r.error}` : ""}`);
33
+ if (items.length > 5) lines.push(` ... ${items.length - 5} more`);
34
+ }
35
+ const counts = {};
36
+ for (const s of plan.skipped) counts[s.reason] = (counts[s.reason] ?? 0) + 1;
37
+ if (plan.skipped.length) lines.push(` skipped: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ")}`);
38
+ return lines.join("\n");
39
+ }
40
+
41
+ function parse(rest) {
42
+ const known = new Set(["--json", "--dry-run", "--delete", "--days", "--policy"]);
43
+ const opts = { json: false, dryRun: false, deleteMode: false, days: undefined, policy: undefined };
44
+ for (let i = 0; i < rest.length; i += 1) {
45
+ const a = rest[i];
46
+ if (!known.has(a)) throw new Error(`disk: unknown option ${a}`);
47
+ if (a === "--json") opts.json = true;
48
+ else if (a === "--dry-run") opts.dryRun = true;
49
+ else if (a === "--delete") opts.deleteMode = true;
50
+ 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; }
51
+ }
52
+ if (opts.days !== undefined && !(opts.days >= 0)) throw new Error("disk: --days must be a non-negative number");
53
+ if (opts.dryRun && opts.deleteMode) throw new Error("disk sweep accepts either --dry-run or --delete");
54
+ return opts;
55
+ }
56
+
57
+ // A path inside any git checkout (a `.git` entry on an ancestor) is development code, never an installed package.
58
+ export function insideGitCheckout(file, exists = existsSync) {
59
+ let dir = path.dirname(file);
60
+ for (;;) {
61
+ if (exists(path.join(dir, ".git"))) return true;
62
+ const up = path.dirname(dir);
63
+ if (up === dir) return false;
64
+ dir = up;
65
+ }
66
+ }
67
+
68
+ /** Installed @rightkit/release CLI for the Windows task: explicit --cli / RIGHT_RELEASE_INSTALLED_CLI, else the global
69
+ * node_modules (`npm root -g`, `pnpm root -g`). Only a candidate inside a git checkout is refused; the installed package may itself run install. */
70
+ export function resolveInstalledCli({ explicit, env = process.env, globalRoots = defaultGlobalRoots, exists = existsSync } = {}) {
71
+ const rel = path.join("@rightkit", "release", "cli", "right-release.mjs");
72
+ const candidates = explicit || env.RIGHT_RELEASE_INSTALLED_CLI
73
+ ? [explicit || env.RIGHT_RELEASE_INSTALLED_CLI]
74
+ : globalRoots({ env }).map((root) => path.join(root, rel));
75
+ for (const candidate of candidates) {
76
+ if (!exists(candidate)) continue;
77
+ let real = candidate;
78
+ try { real = realpathSync(candidate); } catch { /* keep lexical */ }
79
+ if (insideGitCheckout(real, exists)) continue;
80
+ return candidate;
81
+ }
82
+ throw new Error("disk schedule install needs the installed @rightkit/release (not a checkout): install it globally or pass --cli <path to the installed cli/right-release.mjs>");
83
+ }
84
+
85
+ function defaultGlobalRoots() {
86
+ const roots = [];
87
+ for (const tool of ["npm", "pnpm"]) {
88
+ const r = spawnSync(tool, ["root", "-g"], { encoding: "utf8", shell: process.platform === "win32" });
89
+ const line = String(r.stdout ?? "").trim().split(/\r?\n/).pop();
90
+ if (r.status === 0 && line) roots.push(line);
91
+ }
92
+ return roots;
93
+ }
94
+
95
+ 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 } = {}) {
96
+ if (platform === "darwin") {
97
+ const logDir = "/Volumes/D/claude/tmp/swept";
98
+ const file = path.join(home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
99
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
100
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
101
+ <plist version="1.0"><dict>
102
+ <key>Label</key><string>${LAUNCHD_LABEL}</string>
103
+ <key>ProgramArguments</key><array><string>${nodePath}</string><string>${cli}</string><string>disk</string><string>run</string></array>
104
+ <key>StartCalendarInterval</key><dict><key>Hour</key><integer>${hour}</integer><key>Minute</key><integer>${minute}</integer></dict>
105
+ <key>StandardOutPath</key><string>${logDir}/launchd.out.log</string>
106
+ <key>StandardErrorPath</key><string>${logDir}/launchd.err.log</string>
107
+ <key>ProcessType</key><string>Background</string><key>LowPriorityIO</key><true/><key>Nice</key><integer>10</integer>
108
+ </dict></plist>
109
+ `;
110
+ return { platform: "mac", file, plist };
111
+ }
112
+ const script = `$a = New-ScheduledTaskAction -Execute '${nodePath}' -Argument '"${cli}" disk run'
113
+ $t = New-ScheduledTaskTrigger -Daily -At ${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}
114
+ $s = New-ScheduledTaskSettingsSet -Hidden -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 2)
115
+ $p = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\\$env:USERNAME" -LogonType Interactive -RunLevel Limited
116
+ Register-ScheduledTask -TaskName '${WINDOWS_TASK}' -Action $a -Trigger $t -Settings $s -Principal $p -Force | Out-Null`;
117
+ return { platform: "win", script };
118
+ }
119
+
120
+ function schedule(sub, { stdout, cli }) {
121
+ const platform = process.platform;
122
+ const spec = scheduleSpec(platform === "darwin" || sub !== "install" ? { platform } : { platform, cli: resolveInstalledCli({ explicit: cli }) });
123
+ if (platform === "darwin") {
124
+ const uid = String(process.getuid());
125
+ if (sub === "install") {
126
+ mkdirSync(path.dirname(spec.file), { recursive: true });
127
+ mkdirSync("/Volumes/D/claude/tmp/swept", { recursive: true });
128
+ writeFileSync(spec.file, spec.plist);
129
+ spawnSync("launchctl", ["bootout", `gui/${uid}/${LAUNCHD_LABEL}`], { encoding: "utf8" });
130
+ const r = spawnSync("launchctl", ["bootstrap", `gui/${uid}`, spec.file], { encoding: "utf8" });
131
+ if (r.status !== 0) throw new Error(`launchctl bootstrap failed: ${r.stderr}`);
132
+ stdout(`installed ${spec.file} (daily 03:30)`);
133
+ } else if (sub === "uninstall") {
134
+ spawnSync("launchctl", ["bootout", `gui/${uid}/${LAUNCHD_LABEL}`], { encoding: "utf8" });
135
+ if (existsSync(spec.file)) unlinkSync(spec.file);
136
+ stdout("uninstalled");
137
+ } else if (sub === "status") {
138
+ const r = spawnSync("launchctl", ["print", `gui/${uid}/${LAUNCHD_LABEL}`], { encoding: "utf8" });
139
+ stdout(r.status === 0 ? `installed (${spec.file})` : "not installed");
140
+ } else throw new Error("disk schedule install|uninstall|status");
141
+ return;
142
+ }
143
+ if (sub === "install") {
144
+ const r = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", spec.script], { encoding: "utf8" });
145
+ if (r.status !== 0) throw new Error(`Register-ScheduledTask failed: ${r.stderr || r.stdout}`);
146
+ stdout(`installed scheduled task "${WINDOWS_TASK}" (daily 03:30, hidden)`);
147
+ } else if (sub === "uninstall") {
148
+ spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `Unregister-ScheduledTask -TaskName '${WINDOWS_TASK}' -Confirm:$false`], { encoding: "utf8" });
149
+ stdout("uninstalled");
150
+ } else if (sub === "status") {
151
+ const r = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `Get-ScheduledTask -TaskName '${WINDOWS_TASK}' | Select-Object -ExpandProperty State`], { encoding: "utf8" });
152
+ stdout(r.status === 0 ? `installed (${String(r.stdout).trim()})` : "not installed");
153
+ } else throw new Error("disk schedule install|uninstall|status");
154
+ }
155
+
156
+ export async function runDiskCommand(args = process.argv.slice(2), { env = process.env, stdout = console.log, platform = process.platform, deps = {} } = {}) {
157
+ const [command, ...rest] = args;
158
+ if (command === "schedule") {
159
+ const at = rest.indexOf("--cli");
160
+ schedule(rest[0], { stdout, cli: at === -1 ? undefined : rest[at + 1] });
161
+ return { exitCode: 0 };
162
+ }
163
+ const opts = parse(rest);
164
+ const policy = readDiskPolicy(opts.policy);
165
+ const key = platformKey(platform);
166
+ const logFile = path.join(policy.heavyVolume[key], "disk-hygiene.jsonl");
167
+ const out = (value, text) => stdout(opts.json ? JSON.stringify(value) : text);
168
+ const check = () => checkDisk({ policy, platform, env, ...deps.check });
169
+ const plan = () => planSweep({ policy, platform, env, staleDays: opts.days, openPaths: platform === "win32" ? null : (deps.openPaths ?? listOpenPaths({ platform })), ...deps.plan });
170
+
171
+ if (command === "check") {
172
+ const result = check();
173
+ out(result, renderCheck(result));
174
+ return { exitCode: result.warn ? DISK_EXIT_WARNING : 0, result };
175
+ }
176
+ if (command === "sweep") {
177
+ const p = plan();
178
+ if (opts.dryRun) { out(p, renderSweep(p)); return { exitCode: 0, plan: p }; }
179
+ const applied = applySweep({ plan: p, policy, platform, deleteMode: opts.deleteMode, ...deps.apply });
180
+ out({ ...p, applied }, renderSweep(p, applied));
181
+ return { exitCode: applied.results.some((r) => !r.ok) ? 1 : 0, plan: p, applied };
182
+ }
183
+ if (command === "purge") {
184
+ const purged = purgeHoldings({ policy, platform, env, days: opts.days, dryRun: opts.dryRun, ...deps.purge });
185
+ out(purged, `${opts.dryRun ? "Dry run: would purge" : "Purged"} ${purged.results.length} holding dir(s)${purged.results.map((r) => `\n ${formatBytes(r.bytes).padStart(9)} ${r.path}${r.ok ? "" : ` FAILED: ${r.error}`}`).join("")}`);
186
+ return { exitCode: purged.results.some((r) => !r.ok) ? 1 : 0, purged };
187
+ }
188
+ if (command === "run") {
189
+ const before = check();
190
+ stdout(renderCheck(before));
191
+ const p = plan();
192
+ stdout(renderSweep(p));
193
+ const applied = applySweep({ plan: p, policy, platform, ...deps.apply });
194
+ stdout(`moved ${formatBytes(applied.movedBytes)} (${applied.results.filter((r) => r.ok).length}/${applied.results.length})`);
195
+ const purged = purgeHoldings({ policy, platform, env, ...deps.purge });
196
+ stdout(`purged ${purged.results.filter((r) => r.ok).length} holding dir(s), ${formatBytes(purged.purgedBytes)}`);
197
+ const after = check();
198
+ const warn = after.warn;
199
+ logEvent(logFile, { event: warn ? "disk.floor.warn" : "disk.run.ok", warnings: after.warnings, freeBytes: after.floor.freeBytes, movedBytes: applied.movedBytes });
200
+ if (warn) stdout(`WARNING: ${after.warnings.join("; ")}`);
201
+ return { exitCode: warn ? DISK_EXIT_WARNING : 0, before, after, applied, purged };
202
+ }
203
+ throw new Error("disk usage: check|sweep [--dry-run|--delete] [--days N]|purge [--dry-run] [--days N]|run|schedule install|uninstall|status [--json]");
204
+ }
205
+
206
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
207
+ runDiskCommand().then((r) => process.exit(r.exitCode)).catch((e) => { console.error(e.message); process.exit(2); });
208
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "schema": 1,
3
+ "staleDays": 7,
4
+ "floors": {
5
+ "mac": {"path": "/System/Volumes/Data", "label": "Mac internal", "warnFreeBytes": 42949672960},
6
+ "win": {"path": "D:\\", "label": "Dell D:", "warnFreeBytes": 429496729600, "warnFreePercent": 10}
7
+ },
8
+ "holdingDays": 3,
9
+ "holding": {
10
+ "mac": [{"prefix": "/", "path": "~/.rightkit-swept"}],
11
+ "win": [{"prefix": "C:\\", "path": "${LOCALAPPDATA}\\Orthic Labs\\.rightkit-swept"}]
12
+ },
13
+ "heavyVolume": {
14
+ "mac": "/Volumes/D/claude/tmp/swept",
15
+ "win": "D:\\claude\\tmp\\swept"
16
+ },
17
+ "writers": [
18
+ {"id": "claude-session-dirs", "platform": "mac", "path": "/private/tmp/claude-501/*/*", "capBytes": 2147483648, "action": "sweep", "unit": "self"},
19
+ {"id": "rightkit-qa-tmp", "platform": "mac", "path": "/private/tmp/rightkit-qa", "capBytes": 2147483648, "action": "sweep", "unit": "children"},
20
+ {"id": "cache-e2e", "platform": "mac", "path": "~/Library/Caches/e2e_*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
21
+ {"id": "cache-parakeet", "platform": "mac", "path": "~/Library/Caches/parakeet_*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
22
+ {"id": "cache-swiftright-factory", "platform": "mac", "path": "~/Library/Caches/SwiftRightFactory", "capBytes": 10737418240, "action": "sweep", "unit": "children"},
23
+ {"id": "cache-cutright-engine", "platform": "mac", "path": "~/Library/Caches/cutright-engine", "capBytes": 2147483648, "action": "report"},
24
+ {"id": "cache-heardright-engine", "platform": "mac", "path": "~/Library/Caches/heardright-engine", "capBytes": 2147483648, "action": "report"},
25
+ {"id": "cache-pnpm", "platform": "mac", "path": "~/Library/Caches/pnpm", "capBytes": 3221225472, "action": "report"},
26
+ {"id": "cache-python", "platform": "mac", "path": "~/Library/Caches/python", "capBytes": 3221225472, "action": "report"},
27
+ {"id": "xcode-deriveddata", "platform": "mac", "path": "~/Library/Developer/Xcode/DerivedData", "capBytes": 10737418240, "action": "sweep", "unit": "children"},
28
+ {"id": "coresimulator", "platform": "mac", "path": "~/Library/Developer/CoreSimulator", "capBytes": 26843545600, "action": "report"},
29
+ {"id": "documents-scratch", "platform": "mac", "path": "~/Documents", "capBytes": 21474836480, "action": "report"},
30
+ {"id": "shared-models", "platform": "mac", "path": "/Volumes/D/Orthic Labs/shared/models", "capBytes": 107374182400, "action": "report"},
31
+ {"id": "stale-release-tarballs", "platform": "mac", "path": "/Volumes/D/claude/rightkit/packages/release/rightkit-release-*.tgz", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
32
+ {"id": "win-temp", "platform": "win", "path": "${TEMP}", "capBytes": 10737418240, "action": "sweep", "unit": "children"},
33
+ {"id": "win-pnpm", "platform": "win", "path": "${LOCALAPPDATA}\\pnpm", "capBytes": 21474836480, "action": "report"},
34
+ {"id": "win-sccache", "platform": "win", "path": "D:\\.rightkit-managed\\rightkit-build-control\\global", "capBytes": 34359738368, "action": "report"},
35
+ {"id": "win-claude-workspace", "platform": "win", "path": "D:\\claude", "capBytes": 805306368000, "action": "report"},
36
+ {"id": "win-claude-tmp", "platform": "win", "path": "D:\\claude\\tmp", "capBytes": 107374182400, "action": "report"},
37
+ {"id": "win-shared-models", "platform": "win", "path": "${LOCALAPPDATA}\\Orthic Labs\\shared\\models", "capBytes": 107374182400, "action": "report"},
38
+ {"id": "stale-release-tarballs", "platform": "win", "path": "D:\\claude\\rightkit\\packages\\release\\rightkit-release-*.tgz", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
39
+ {"id": "win-remote-inputs", "platform": "win", "path": "D:\\.rightkit-managed\\remote-inputs", "capBytes": 21474836480, "action": "report"},
40
+ {"id": "leftover-delete-me", "platform": "win", "path": "D:\\claude\\*-DELETE-ME", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
41
+ {"id": "leftover-node-modules-medium-rightkit", "platform": "win", "path": "D:\\claude\\rightkit\\node_modules.medium-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
42
+ {"id": "leftover-node-modules-medium-packages", "platform": "win", "path": "D:\\claude\\rightkit\\packages\\*\\node_modules.medium-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
43
+ {"id": "leftover-legion-snap", "platform": "win", "path": "D:\\claude\\legion-snap-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
44
+ {"id": "leftover-legion-final", "platform": "win", "path": "D:\\claude\\legion-final-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
45
+ {"id": "leftover-delete-me", "platform": "mac", "path": "/Volumes/D/claude/*-DELETE-ME", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
46
+ {"id": "leftover-node-modules-medium-rightkit", "platform": "mac", "path": "/Volumes/D/claude/rightkit/node_modules.medium-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
47
+ {"id": "leftover-node-modules-medium-packages", "platform": "mac", "path": "/Volumes/D/claude/rightkit/packages/*/node_modules.medium-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
48
+ {"id": "leftover-legion-snap", "platform": "mac", "path": "/Volumes/D/claude/legion-snap-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
49
+ {"id": "leftover-legion-final", "platform": "mac", "path": "/Volumes/D/claude/legion-final-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
50
+ {"id": "leftover-pulse-bak", "platform": "win", "path": "${LOCALAPPDATA}\\Programs\\Pulse.bak-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"}
51
+ ],
52
+ "protectedSegments": ["Application Support", "Keychains", "Keychain", "HeardRightNext", "Credentials", ".ssh", ".rightkit-managed", ".rightkit-swept"]
53
+ }
@@ -0,0 +1,382 @@
1
+ import { closeSync, cpSync, existsSync, lstatSync, mkdirSync, opendirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, statfsSync, appendFileSync, writeFileSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { spawnSync } from "./background-process.mjs";
6
+
7
+ export const DISK_EXIT_WARNING = 3;
8
+ export const DEFAULT_POLICY_FILE = path.join(path.dirname(fileURLToPath(import.meta.url)), "disk-policy.json");
9
+
10
+ export function readDiskPolicy(file = DEFAULT_POLICY_FILE) {
11
+ const policy = JSON.parse(readFileSync(file, "utf8"));
12
+ if (policy.schema !== 1 || !Array.isArray(policy.writers)) throw new Error("disk policy: unsupported schema");
13
+ return policy;
14
+ }
15
+
16
+ export function platformKey(platform = process.platform) {
17
+ return platform === "win32" || platform === "win" ? "win" : "mac";
18
+ }
19
+
20
+ export function expandPolicyPath(value, { env = process.env, home = os.homedir() } = {}) {
21
+ let out = value.replace(/^~(?=$|[\\/])/, home);
22
+ out = out.replace(/\$\{(\w+)\}/g, (_, name) => {
23
+ if (!env[name]) throw new Error(`disk policy: ${name} is not set`);
24
+ return env[name];
25
+ });
26
+ return out;
27
+ }
28
+
29
+ // Expand a policy path whose segments may contain `*` wildcards into existing paths.
30
+ export function expandGlob(pattern, sep = path.sep) {
31
+ const parts = pattern.split(/[\\/]/);
32
+ let bases = [parts[0] === "" ? sep : `${parts[0]}${sep}`];
33
+ for (const part of parts.slice(1)) {
34
+ if (part === "") continue;
35
+ const next = [];
36
+ for (const base of bases) {
37
+ if (!part.includes("*")) {
38
+ const candidate = path.join(base, part);
39
+ if (existsSync(candidate)) next.push(candidate);
40
+ continue;
41
+ }
42
+ const rx = new RegExp(`^${part.split("*").map((s) => s.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`);
43
+ let names = [];
44
+ try { names = readdirSync(base); } catch { continue; }
45
+ for (const name of names) if (rx.test(name)) next.push(path.join(base, name));
46
+ }
47
+ bases = next;
48
+ }
49
+ return bases;
50
+ }
51
+
52
+ export function walkSize(root) {
53
+ let total = 0;
54
+ const stack = [root];
55
+ while (stack.length) {
56
+ const dir = stack.pop();
57
+ let st;
58
+ try { st = lstatSync(dir); } catch { continue; }
59
+ total += st.blocks !== undefined ? st.blocks * 512 : st.size;
60
+ if (!st.isDirectory()) continue;
61
+ let handle;
62
+ try { handle = opendirSync(dir); } catch { continue; }
63
+ try {
64
+ for (let e = handle.readSync(); e; e = handle.readSync()) stack.push(path.join(dir, e.name));
65
+ } finally { try { handle.closeSync(); } catch { /* ignore */ } }
66
+ }
67
+ return total;
68
+ }
69
+
70
+ export function pathSize(target, { platform = process.platform } = {}) {
71
+ if (platform === "darwin") {
72
+ const r = spawnSync("du", ["-sk", target], { encoding: "utf8" });
73
+ const kb = Number.parseInt(String(r.stdout).split(/\s+/)[0], 10);
74
+ if (Number.isFinite(kb)) return kb * 1024;
75
+ }
76
+ return walkSize(target);
77
+ }
78
+
79
+ export function freeSpace(volumePath) {
80
+ const s = statfsSync(volumePath);
81
+ return { freeBytes: Number(s.bavail) * Number(s.bsize), totalBytes: Number(s.blocks) * Number(s.bsize) };
82
+ }
83
+
84
+ export function floorThreshold(floor, totalBytes) {
85
+ const pct = floor.warnFreePercent ? Math.floor((totalBytes * floor.warnFreePercent) / 100) : 0;
86
+ return Math.max(floor.warnFreeBytes ?? 0, pct);
87
+ }
88
+
89
+ export function evaluateFloor(floor, space) {
90
+ const thresholdBytes = floorThreshold(floor, space.totalBytes);
91
+ return { label: floor.label, path: floor.path, ...space, thresholdBytes, warn: space.freeBytes < thresholdBytes };
92
+ }
93
+
94
+ // Literal directory prefix of a policy path: everything before the first wildcard segment
95
+ // (for a wildcard-free path, its parent). A sweep unit must resolve inside it.
96
+ export function literalRoot(expanded) {
97
+ const parts = expanded.split(/[\\/]/);
98
+ const firstWild = parts.findIndex((p) => p.includes("*"));
99
+ const keep = firstWild === -1 ? parts.slice(0, -1) : parts.slice(0, firstWild);
100
+ const joined = keep.join(path.sep);
101
+ return joined === "" || /^[A-Za-z]:$/.test(joined) ? `${joined}${path.sep}` : joined;
102
+ }
103
+
104
+ export function isInsideRoot(candidate, root) {
105
+ try {
106
+ const realRoot = realpathSync(root);
107
+ const realCandidate = realpathSync(candidate);
108
+ const prefix = realRoot.endsWith(path.sep) ? realRoot : realRoot + path.sep;
109
+ return realCandidate !== realRoot && realCandidate.startsWith(prefix);
110
+ } catch { return false; }
111
+ }
112
+
113
+ export function writerMatches(writer, ctx) {
114
+ const pattern = expandPolicyPath(writer.path, ctx);
115
+ return expandGlob(pattern);
116
+ }
117
+
118
+ export function checkDisk({ policy, platform = process.platform, env = process.env, home = os.homedir(), size = (p) => pathSize(p, { platform }), space = freeSpace } = {}) {
119
+ const key = platformKey(platform);
120
+ const floorPolicy = policy.floors[key];
121
+ const floor = evaluateFloor(floorPolicy, space(floorPolicy.path));
122
+ const writers = [];
123
+ for (const writer of policy.writers.filter((w) => w.platform === key)) {
124
+ let matches;
125
+ try { matches = writerMatches(writer, { env, home }); } catch (error) {
126
+ writers.push({ id: writer.id, action: writer.action, error: error.message, bytes: 0, capBytes: writer.capBytes, over: false, matches: 0 });
127
+ continue;
128
+ }
129
+ const bytes = matches.reduce((sum, m) => sum + size(m), 0);
130
+ writers.push({ id: writer.id, action: writer.action, path: writer.path, matches: matches.length, bytes, capBytes: writer.capBytes, over: bytes > writer.capBytes });
131
+ }
132
+ const warnings = [];
133
+ if (floor.warn) warnings.push(`${floor.label} free ${formatBytes(floor.freeBytes)} is below the ${formatBytes(floor.thresholdBytes)} floor`);
134
+ for (const w of writers) if (w.over) warnings.push(`${w.id} holds ${formatBytes(w.bytes)} over its ${formatBytes(w.capBytes)} cap`);
135
+ return { schema: 1, platform: key, floor, writers, warnings, warn: warnings.length > 0 };
136
+ }
137
+
138
+ export function formatBytes(n) {
139
+ if (n >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(1)} GB`;
140
+ if (n >= 1024 ** 2) return `${(n / 1024 ** 2).toFixed(1)} MB`;
141
+ return `${Math.round(n / 1024)} KB`;
142
+ }
143
+
144
+ // True when anything under `root` was modified after `cutoffMs` (early exit).
145
+ export function hasFreshContent(root, cutoffMs) {
146
+ const stack = [root];
147
+ while (stack.length) {
148
+ const entry = stack.pop();
149
+ let st;
150
+ try { st = lstatSync(entry); } catch { continue; }
151
+ if (st.mtimeMs > cutoffMs) return true;
152
+ if (!st.isDirectory()) continue;
153
+ let handle;
154
+ try { handle = opendirSync(entry); } catch { continue; }
155
+ try {
156
+ for (let e = handle.readSync(); e; e = handle.readSync()) stack.push(path.join(entry, e.name));
157
+ } finally { try { handle.closeSync(); } catch { /* ignore */ } }
158
+ }
159
+ return false;
160
+ }
161
+
162
+ export function isProtectedPath(candidate, policy) {
163
+ const segments = candidate.split(/[\\/]/);
164
+ return (policy.protectedSegments ?? []).some((p) => segments.includes(p));
165
+ }
166
+
167
+ // Open-file listing for the whole machine on macOS; paths a live process holds.
168
+ export function listOpenPaths({ platform = process.platform } = {}) {
169
+ if (platform === "win32") return null;
170
+ const r = spawnSync("lsof", ["-n", "-P", "-w", "-F", "n"], { encoding: "utf8", maxBuffer: 1 << 28 });
171
+ if (r.error || (r.status !== 0 && !r.stdout)) return undefined;
172
+ return String(r.stdout).split("\n").filter((l) => l.startsWith("n/")).map((l) => l.slice(1));
173
+ }
174
+
175
+ export function inUseByOpenPaths(candidate, openPaths) {
176
+ if (openPaths === undefined) return true; // cannot prove idle: treat as in use
177
+ if (openPaths === null) return false;
178
+ const prefix = candidate.endsWith(path.sep) ? candidate : candidate + path.sep;
179
+ return openPaths.some((p) => p === candidate || p.startsWith(prefix));
180
+ }
181
+
182
+ // Windows has no lsof; a locked directory cannot be renamed. Probe by renaming away and back.
183
+ export function windowsLockProbe(candidate) {
184
+ const probe = `${candidate}.rk-probe`;
185
+ try { renameSync(candidate, probe); } catch { return true; }
186
+ try { renameSync(probe, candidate); } catch { return true; }
187
+ return false;
188
+ }
189
+
190
+ export function planSweep({ policy, platform = process.platform, env = process.env, home = os.homedir(), now = Date.now(), staleDays = policy.staleDays, openPaths, inUse, size = (p) => pathSize(p, { platform }) } = {}) {
191
+ const key = platformKey(platform);
192
+ const cutoff = now - staleDays * 86400_000;
193
+ const candidates = [];
194
+ const skipped = [];
195
+ const seen = new Set();
196
+ const lockCheck = inUse ?? (key === "win" ? windowsLockProbe : (p) => inUseByOpenPaths(p, openPaths));
197
+ for (const writer of policy.writers.filter((w) => w.platform === key && w.action === "sweep")) {
198
+ let roots; let literal;
199
+ try { roots = writerMatches(writer, { env, home }); literal = literalRoot(expandPolicyPath(writer.path, { env, home })); } catch { continue; }
200
+ const units = [];
201
+ for (const r of roots) {
202
+ if (writer.unit !== "children") { units.push({ unit: r, root: literal }); continue; }
203
+ let rst;
204
+ try { rst = lstatSync(r); } catch { continue; }
205
+ if (rst.isSymbolicLink()) { skipped.push({ writer: writer.id, path: r, reason: "symlink-root" }); continue; }
206
+ let names = [];
207
+ try { names = readdirSync(r); } catch { continue; }
208
+ for (const n of names) units.push({ unit: path.join(r, n), root: r });
209
+ }
210
+ for (const { unit, root } of units) {
211
+ if (seen.has(unit)) continue;
212
+ seen.add(unit);
213
+ const base = { writer: writer.id, path: unit };
214
+ let st;
215
+ try { st = lstatSync(unit); } catch { continue; }
216
+ if (st.isSymbolicLink()) { skipped.push({ ...base, reason: "symlink" }); continue; }
217
+ if (!isInsideRoot(unit, root)) { skipped.push({ ...base, reason: "outside-root" }); continue; }
218
+ if (isProtectedPath(unit, policy)) { skipped.push({ ...base, reason: "protected" }); continue; }
219
+ if (hasFreshContent(unit, cutoff)) { skipped.push({ ...base, reason: "fresh" }); continue; }
220
+ if (lockCheck(unit)) { skipped.push({ ...base, reason: "in-use" }); continue; }
221
+ candidates.push({ ...base, root, bytes: size(unit), mtime: new Date(st.mtimeMs).toISOString() });
222
+ }
223
+ }
224
+ return { schema: 1, platform: key, staleDays, candidates, skipped, totalBytes: candidates.reduce((s, c) => s + c.bytes, 0) };
225
+ }
226
+
227
+ export const HOLDING_DIR = ".rightkit-swept";
228
+ export const HOLDING_MARKER = ".rightkit-release-holding";
229
+ const DATE_DIR = /^\d{4}-\d{2}-\d{2}$/;
230
+
231
+ // Device id of a path, or of its nearest existing ancestor (null when none can be read).
232
+ export function deviceOf(target) {
233
+ let cur = target;
234
+ for (;;) {
235
+ try { return statSync(cur).dev; } catch {
236
+ const up = path.dirname(cur);
237
+ if (up === cur) return null;
238
+ cur = up;
239
+ }
240
+ }
241
+ }
242
+
243
+ // Highest ancestor of `target` still on the same device: the mount point (or drive root) holding it.
244
+ export function volumeRoot(target) {
245
+ const dev = deviceOf(target);
246
+ let cur = target;
247
+ for (;;) {
248
+ const up = path.dirname(cur);
249
+ if (up === cur || deviceOf(up) !== dev) return cur;
250
+ cur = up;
251
+ }
252
+ }
253
+
254
+ const isUnder = (candidate, prefix, win) => {
255
+ const norm = (v) => (win ? v.toLowerCase() : v);
256
+ const c = norm(candidate); const p = norm(prefix);
257
+ const withSep = p.endsWith(path.sep) || p.endsWith("/") || p.endsWith("\\") ? p : p + path.sep;
258
+ return c === p || c.startsWith(withSep);
259
+ };
260
+
261
+ // Candidate holding bases for an item, best first: policy per-volume rules (longest prefix), then the item's own volume root.
262
+ export function holdingBases(candidate, { policy, platform = process.platform, env = process.env, home = os.homedir() } = {}) {
263
+ const key = platformKey(platform);
264
+ const out = [];
265
+ const rules = (policy.holding?.[key] ?? []).flatMap((r) => {
266
+ try { return [{ prefix: expandPolicyPath(r.prefix, { env, home }), path: expandPolicyPath(r.path, { env, home }) }]; } catch { return []; }
267
+ }).filter((r) => isUnder(candidate, r.prefix, key === "win")).sort((a, b) => b.prefix.length - a.prefix.length);
268
+ for (const r of rules) out.push(r.path);
269
+ out.push(path.join(volumeRoot(path.dirname(candidate)), HOLDING_DIR));
270
+ return out;
271
+ }
272
+
273
+ // Create (and mark) today's holding dir on the item's own volume; null when no holding dir on that volume is possible.
274
+ export function pickHolding(candidate, { policy, platform = process.platform, env = process.env, home = os.homedir(), date = new Date(), bases } = {}) {
275
+ const dev = deviceOf(path.dirname(candidate));
276
+ for (const base of bases ?? holdingBases(candidate, { policy, platform, env, home })) {
277
+ try {
278
+ if (deviceOf(base) !== dev) continue; // checked before creating anything: a base on another volume would free nothing
279
+ mkdirSync(base, { recursive: true });
280
+ if (deviceOf(base) !== dev || lstatSync(base).isSymbolicLink()) continue;
281
+ const marker = path.join(base, HOLDING_MARKER);
282
+ if (!existsSync(marker)) writeFileSync(marker, "created by right-release disk; date-named children are purged after the retention window\n");
283
+ const dir = path.join(base, date.toISOString().slice(0, 10));
284
+ mkdirSync(dir, { recursive: true });
285
+ return dir;
286
+ } catch { /* try the next base */ }
287
+ }
288
+ return null;
289
+ }
290
+
291
+ export function moveEntry(source, destination) {
292
+ mkdirSync(path.dirname(destination), { recursive: true });
293
+ try { renameSync(source, destination); return "rename"; } catch (error) {
294
+ if (error.code !== "EXDEV") throw error;
295
+ }
296
+ const before = walkSize(source);
297
+ cpSync(source, destination, { recursive: true, preserveTimestamps: true, verbatimSymlinks: true, errorOnExist: true, force: false });
298
+ if (walkSize(destination) < before) throw new Error(`copy verification failed for ${source}; source kept`);
299
+ rmSync(source, { recursive: true, force: true }); // completes the move after verified copy
300
+ return "copy-verify-remove";
301
+ }
302
+
303
+ export function logEvent(logFile, event) {
304
+ try {
305
+ mkdirSync(path.dirname(logFile), { recursive: true });
306
+ appendFileSync(logFile, `${JSON.stringify({ ts: new Date().toISOString(), ...event })}\n`);
307
+ } catch { /* logging must never fail the sweep */ }
308
+ }
309
+
310
+ export function applySweep({ plan, policy, platform = process.platform, deleteMode = false, date = new Date(), env = process.env, home = os.homedir(), move = moveEntry, pick = pickHolding, remove = (p) => rmSync(p, { recursive: true, force: true }) }) {
311
+ const logFile = path.join(policy.heavyVolume[platformKey(platform)], "disk-hygiene.jsonl");
312
+ const results = [];
313
+ for (const c of plan.candidates) {
314
+ try {
315
+ // Re-verify at apply time: still the planned unit, not a symlink, still inside its root.
316
+ if (!c.root || lstatSync(c.path).isSymbolicLink() || !isInsideRoot(c.path, c.root)) throw new Error("refused: path is not a sweepable unit inside its target root");
317
+ if (deleteMode) { remove(c.path); results.push({ ...c, action: "delete", ok: true }); logEvent(logFile, { event: "disk.sweep.delete", ...c }); continue; }
318
+ // An item stays on its own volume: moving it elsewhere frees nothing on the volume that is short of space.
319
+ const holding = pick(c.path, { policy, platform, env, home, date });
320
+ if (!holding) {
321
+ remove(c.path);
322
+ results.push({ ...c, action: "delete", how: "no-holding-on-volume", ok: true });
323
+ logEvent(logFile, { event: "disk.sweep.delete", reason: "no holding dir possible on the item's volume", ...c });
324
+ continue;
325
+ }
326
+ let destination = path.join(holding, c.writer, path.basename(c.path));
327
+ if (existsSync(destination)) destination = `${destination}.${date.getTime()}`;
328
+ const how = move(c.path, destination);
329
+ results.push({ ...c, action: "move", destination, how, ok: true });
330
+ logEvent(logFile, { event: "disk.sweep.move", ...c, destination, how });
331
+ } catch (error) {
332
+ results.push({ ...c, action: deleteMode ? "delete" : "move", ok: false, error: error.message });
333
+ logEvent(logFile, { event: "disk.sweep.error", ...c, error: error.message });
334
+ }
335
+ }
336
+ return { logFile, results, movedBytes: results.filter((r) => r.ok).reduce((s, r) => s + r.bytes, 0) };
337
+ }
338
+
339
+ // Holding bases that may exist: policy rule paths, each drive root (Windows) or /Volumes/* and the legacy heavy dir (Mac).
340
+ export function knownHoldingBases({ policy, platform = process.platform, env = process.env, home = os.homedir() } = {}) {
341
+ const key = platformKey(platform);
342
+ const bases = new Set();
343
+ for (const r of policy.holding?.[key] ?? []) { try { bases.add(expandPolicyPath(r.path, { env, home })); } catch { /* unset variable */ } }
344
+ if (key === "win") for (const letter of "ABCDEFGHIJKLMNOPQRSTUVWXYZ") { const root = `${letter}:\\`; if (existsSync(root)) bases.add(path.join(root, HOLDING_DIR)); }
345
+ else { bases.add(path.join(path.sep, HOLDING_DIR)); try { for (const v of readdirSync("/Volumes")) bases.add(path.join("/Volumes", v, HOLDING_DIR)); } catch { /* no /Volumes */ } }
346
+ return [...bases];
347
+ }
348
+
349
+ /** Remove date-named holding dirs older than `days`. Only dirs this tool created: the base must carry the holding marker
350
+ * (or, for the legacy heavy dir, the tool's own log), the child must be a real directory named YYYY-MM-DD, never a symlink. */
351
+ export function purgeHoldings({ policy, platform = process.platform, env = process.env, home = os.homedir(), now = Date.now(), days = policy.holdingDays ?? 3, dryRun = false, bases, remove = (p) => rmSync(p, { recursive: true, force: true }), size = walkSize } = {}) {
352
+ const key = platformKey(platform);
353
+ const legacy = policy.heavyVolume?.[key];
354
+ const list = bases ?? [...knownHoldingBases({ policy, platform, env, home }), ...(legacy ? [legacy] : [])];
355
+ const logFile = legacy ? path.join(legacy, "disk-hygiene.jsonl") : null;
356
+ const cutoff = now - days * 86400_000;
357
+ const results = [];
358
+ for (const base of new Set(list)) {
359
+ let st;
360
+ try { st = lstatSync(base); } catch { continue; }
361
+ if (!st.isDirectory() || st.isSymbolicLink()) continue;
362
+ const owned = existsSync(path.join(base, HOLDING_MARKER)) || (base === legacy && existsSync(path.join(base, "disk-hygiene.jsonl")));
363
+ if (!owned) continue;
364
+ let names = [];
365
+ try { names = readdirSync(base); } catch { continue; }
366
+ for (const name of names) {
367
+ if (!DATE_DIR.test(name)) continue;
368
+ const dir = path.join(base, name);
369
+ let dst;
370
+ try { dst = lstatSync(dir); } catch { continue; }
371
+ if (!dst.isDirectory() || dst.isSymbolicLink()) continue;
372
+ const stamp = Date.parse(`${name}T00:00:00Z`);
373
+ if (!Number.isFinite(stamp) || stamp + 86400_000 > cutoff) continue; // the whole day must be older than the window
374
+ const bytes = size(dir);
375
+ try {
376
+ if (!dryRun) { remove(dir); if (logFile) logEvent(logFile, { event: "disk.purge", path: dir, bytes }); }
377
+ results.push({ path: dir, bytes, ok: true, dryRun });
378
+ } catch (error) { results.push({ path: dir, bytes, ok: false, error: error.message }); }
379
+ }
380
+ }
381
+ return { results, purgedBytes: results.filter((r) => r.ok && !r.dryRun).reduce((a, r) => a + r.bytes, 0) };
382
+ }
package/local-publish.mjs CHANGED
@@ -30,18 +30,20 @@ const refuse = (message) => { throw new LocalPublishError(message); };
30
30
 
31
31
  export function parseLocalPublishArgs(kind, argv, cwd = process.cwd()) {
32
32
  const flag = kind === "npm" ? "--package" : "--crate";
33
- let unit; let workspace; let local = false;
33
+ let unit; let workspace; let local = false; let allowIgnored = false;
34
34
  for (let i = 0; i < argv.length; i += 1) {
35
35
  const arg = argv[i];
36
36
  if (arg === "--local") local = true;
37
37
  else if (arg === flag) unit = argv[++i];
38
38
  else if (arg === "--workspace") workspace = argv[++i];
39
- else refuse(`unknown publish ${kind} --local option: ${arg} (accepted: ${flag} <name>, --workspace <dir>, --local)`);
39
+ // Same meaning as publish.yml's allow_ignored: this unit may have ignored tests; the count is recorded.
40
+ else if (arg === "--allow-ignored") allowIgnored = true;
41
+ else refuse(`unknown publish ${kind} --local option: ${arg} (accepted: ${flag} <name>, --workspace <dir>, --allow-ignored, --local)`);
40
42
  }
41
43
  if (!local) refuse("--local is required");
42
44
  if (kind === "npm" && !/^@rightkit\/[a-z0-9-]+$/.test(unit ?? "")) refuse("publish npm --local requires --package @rightkit/<name>");
43
45
  if (kind === "cargo" && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(unit ?? "")) refuse("publish cargo --local requires --crate <name>");
44
- return { kind, unit, workspaceRoot: path.resolve(workspace ?? cwd) };
46
+ return { kind, unit, allowIgnored, workspaceRoot: path.resolve(workspace ?? cwd) };
45
47
  }
46
48
 
47
49
  function defaultGit(args, cwd) {
@@ -107,7 +109,7 @@ const tail = (text, lines = 40) => String(text ?? "").split(/\r?\n/).slice(-line
107
109
 
108
110
  export async function runLocalPublish(kind, argv, deps = {}) {
109
111
  const log = deps.log ?? ((line) => process.stdout.write(`${line}\n`));
110
- const { unit, workspaceRoot } = parseLocalPublishArgs(kind, argv, deps.cwd);
112
+ const { unit, allowIgnored, workspaceRoot } = parseLocalPublishArgs(kind, argv, deps.cwd);
111
113
  const platform = deps.platform ?? process.platform;
112
114
  const git = deps.git ?? defaultGit;
113
115
  const readJson = deps.readJson ?? defaultReadJson;
@@ -170,7 +172,7 @@ export async function runLocalPublish(kind, argv, deps = {}) {
170
172
  }
171
173
  steps.push(row);
172
174
  }
173
- const verdict = judgeUnit({ name: unit, steps: steps.map((s) => ({ ...s, unit: plan.id })), allowIgnored: [], allowMissingAssets: isAssetGated ? [unit] : [] });
175
+ const verdict = judgeUnit({ name: unit, steps: steps.map((s) => ({ ...s, unit: plan.id })), allowIgnored: allowIgnored ? [unit] : [], allowMissingAssets: isAssetGated ? [unit] : [] });
174
176
  if (verdict.status === "fail" || steps.some((s) => s.failed > 0)) refuse(`${plan.id} not qualified: ${verdict.problems.join("; ") || "failed tests reported"}`);
175
177
  if (!(verdict.executed > 0)) refuse(`${plan.id} executed no test`);
176
178
  log(`[right-release] ${plan.id}: ${verdict.executed} passed, 0 failed, ${verdict.skipped} skipped, ${verdict.ignored} ignored${isAssetGated ? " (asset-gated: RIGHTKIT_REQUIRE_ASSETS=0, skips recorded)" : ""}`);
@@ -212,7 +214,7 @@ export async function runLocalPublish(kind, argv, deps = {}) {
212
214
  runs: [{
213
215
  host: { os: "darwin", arch: deps.arch ?? process.arch }, commit: head, features: plan.featureLabels, artifactSha256,
214
216
  command: commands.join(" && "), operator: LOCAL_OPERATOR, timestamp,
215
- passed: verdict.executed, failed: 0, skipped: verdict.skipped, ignored: verdict.ignored,
217
+ passed: verdict.executed, failed: 0, skipped: verdict.skipped, ignored: verdict.ignored, allowIgnored,
216
218
  requireAssets: env.RIGHTKIT_REQUIRE_ASSETS, assetGated: isAssetGated,
217
219
  steps: steps.map(({ command, ok, executed, failed, ignored, skipped, binaries }) => ({ command, ok, ...(executed === undefined ? {} : { executed, failed, ignored, skipped, binaries }) })),
218
220
  }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.2.156",
3
+ "version": "0.2.159",
4
4
  "description": "Portable release CLI/SDK with fail-closed public GitHub Releases and private R2 + RightApps distribution.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",
package/publish-cargo.mjs CHANGED
@@ -42,6 +42,17 @@ export function parseCargoPublishArgs(argv, cwd = process.cwd()) {
42
42
  };
43
43
  }
44
44
 
45
+ // A committed ci/runtime-budget-deferred.json names crates whose runtime-budget refusal is deferred by an
46
+ // explicit decision (who, when, why). The refusal is still printed; a listed crate publishes with the
47
+ // deferral logged. Returns the logged reason, or null when the crate is not listed.
48
+ export function runtimeBudgetDeferral(crateDir, crate) {
49
+ const file = path.join(crateDir, "..", "..", "ci", "runtime-budget-deferred.json");
50
+ let data;
51
+ try { data = JSON.parse(fs.readFileSync(file, "utf8")); } catch { return null; }
52
+ if (!data?.crates?.includes?.(crate) || !data.approvedBy || !data.approved || !data.reason) return null;
53
+ return `${data.reason} (${data.approvedBy}, ${data.approved}; ci/runtime-budget-deferred.json)`;
54
+ }
55
+
45
56
  export function buildCargoPublishPlan({ crate, crateDir, dryRun, allowDirty = false }) {
46
57
  const dryRunArgs = ["publish", "--dry-run", "-p", crate];
47
58
  const packageArgs = ["package", "--list", "-p", crate];
@@ -123,6 +134,11 @@ export async function runCargoPublish(options, execute = (_command, args, settin
123
134
  if (step.internal === "runtime-budget") {
124
135
  const check = runtimeBudgetCheck({ crate: options.crate, crateDir, version: cargoVersion, evidence: gate.evidence ?? undefined });
125
136
  if (check) process.stdout.write(`${formatPreflight([check])}\n`);
137
+ const deferral = check?.status === PREFLIGHT_FAIL ? runtimeBudgetDeferral(crateDir, options.crate) : null;
138
+ if (deferral) {
139
+ process.stdout.write(`[right-release] runtime budget DEFERRED for ${options.crate}: ${deferral}\n`);
140
+ continue;
141
+ }
126
142
  if (check?.status === PREFLIGHT_FAIL && !options.dryRun) {
127
143
  const error = new Error(`runtime budget refused: ${check.detail}. Fix: ${check.fix}`);
128
144
  error.code = RUNTIME_BUDGET_CODE;
@@ -13,23 +13,28 @@
13
13
  "@rightkit/updates": "0.2.3"
14
14
  },
15
15
  "stagedNpm": {
16
- "@rightkit/ax": "0.2.4",
16
+ "@rightkit/app-update": "0.2.5",
17
+ "@rightkit/ax": "0.3.2",
18
+ "@rightkit/ci": "0.2.44",
17
19
  "@rightkit/doc-render": "0.1.6",
18
20
  "@rightkit/hooks": "0.1.2",
19
21
  "@rightkit/i18n": "0.1.0",
20
22
  "@rightkit/legal": "0.3.1",
21
23
  "@rightkit/legal-ui": "0.2.2",
22
- "@rightkit/license": "0.1.8",
24
+ "@rightkit/license": "0.2.0",
23
25
  "@rightkit/logs": "0.1.4",
24
26
  "@rightkit/media-ui": "0.3.4",
25
27
  "@rightkit/onboarding": "0.3.3",
28
+ "@rightkit/outdated": "0.2.0",
26
29
  "@rightkit/pdf-ui": "0.1.6",
27
- "@rightkit/platform-ui": "0.2.7",
28
- "@rightkit/qa": "0.4.7",
29
- "@rightkit/qa-chrome": "0.1.5",
30
- "@rightkit/release": "0.2.156",
31
- "@rightkit/settings-ui": "0.4.3",
32
- "@rightkit/tauri": "0.2.0",
30
+ "@rightkit/platform-ui": "0.2.8",
31
+ "@rightkit/qa": "0.4.8",
32
+ "@rightkit/qa-chrome": "0.1.6",
33
+ "@rightkit/react-shell": "0.6.4",
34
+ "@rightkit/release": "0.2.159",
35
+ "@rightkit/settings-ui": "0.4.4",
36
+ "@rightkit/tauri": "0.2.1",
37
+ "@rightkit/tauri-shell": "0.1.1",
33
38
  "@rightkit/theme": "0.2.3"
34
39
  },
35
40
  "legacyNpm": {
@@ -37,7 +42,8 @@
37
42
  "0.2.0",
38
43
  "0.2.1",
39
44
  "0.2.2",
40
- "0.2.3"
45
+ "0.2.3",
46
+ "0.2.4"
41
47
  ],
42
48
  "@rightkit/git": [
43
49
  "0.2.1",
@@ -97,7 +103,8 @@
97
103
  ],
98
104
  "@rightkit/license": [
99
105
  "0.1.6",
100
- "0.1.7"
106
+ "0.1.7",
107
+ "0.1.8"
101
108
  ],
102
109
  "@rightkit/release": [
103
110
  "0.2.22",
@@ -191,7 +198,9 @@
191
198
  "0.2.149",
192
199
  "0.2.150",
193
200
  "0.2.151",
194
- "0.2.155"
201
+ "0.2.155",
202
+ "0.2.156",
203
+ "0.2.158"
195
204
  ],
196
205
  "@rightkit/qa": [
197
206
  "0.1.0",
@@ -209,7 +218,8 @@
209
218
  "0.4.3",
210
219
  "0.4.4",
211
220
  "0.4.5",
212
- "0.4.6"
221
+ "0.4.6",
222
+ "0.4.7"
213
223
  ],
214
224
  "@rightkit/app-shell": [
215
225
  "0.1.0",
@@ -259,7 +269,8 @@
259
269
  "0.3.4",
260
270
  "0.4.0",
261
271
  "0.4.1",
262
- "0.4.2"
272
+ "0.4.2",
273
+ "0.4.3"
263
274
  ],
264
275
  "@rightkit/onboarding": [
265
276
  "0.1.0",
@@ -288,7 +299,8 @@
288
299
  "0.2.3",
289
300
  "0.2.4",
290
301
  "0.2.5",
291
- "0.2.6"
302
+ "0.2.6",
303
+ "0.2.7"
292
304
  ],
293
305
  "@rightkit/pdf-ui": [
294
306
  "0.1.0",
@@ -303,16 +315,21 @@
303
315
  "0.1.1",
304
316
  "0.1.2",
305
317
  "0.1.3",
306
- "0.1.4"
318
+ "0.1.4",
319
+ "0.1.5"
307
320
  ],
308
321
  "@rightkit/updates": [
309
322
  "0.2.4"
310
323
  ],
311
324
  "@rightkit/tauri": [
312
- "0.1.1"
325
+ "0.1.1",
326
+ "0.2.0"
313
327
  ],
314
328
  "@rightkit/shell": [
315
329
  "0.1.0"
330
+ ],
331
+ "@rightkit/ci": [
332
+ "0.2.43"
316
333
  ]
317
334
  },
318
335
  "cargo": {
@@ -334,40 +351,58 @@
334
351
  },
335
352
  "stagedCargo": {
336
353
  "rightkit-agent-tools": "0.1.2",
337
- "rightkit-asr": "0.1.0",
338
- "rightkit-audio": "0.2.1",
354
+ "rightkit-agents": "0.1.0",
355
+ "rightkit-app-settings": "0.2.0",
356
+ "rightkit-asr": "0.2.1",
357
+ "rightkit-audio": "0.2.3",
339
358
  "rightkit-automation": "0.3.0",
340
- "rightkit-browser": "0.2.4",
341
- "rightkit-cache": "0.1.0",
342
- "rightkit-contracts": "0.1.2",
343
- "rightkit-crash": "0.2.1",
344
- "rightkit-db": "0.3.2",
345
- "rightkit-embed": "0.3.2",
346
- "rightkit-fs": "0.3.3",
347
- "rightkit-fsindex": "0.1.1",
359
+ "rightkit-browser": "0.3.1",
360
+ "rightkit-cache": "0.2.0",
361
+ "rightkit-channel-memory": "0.1.0",
362
+ "rightkit-child-process": "0.4.0",
363
+ "rightkit-compose": "0.1.0",
364
+ "rightkit-contracts": "0.1.5",
365
+ "rightkit-crash": "0.3.0",
366
+ "rightkit-credentials": "0.2.0",
367
+ "rightkit-daemon": "0.2.2",
368
+ "rightkit-db": "0.4.0",
369
+ "rightkit-device": "0.1.3",
370
+ "rightkit-embed": "0.4.0",
371
+ "rightkit-ffmpeg": "0.2.6",
372
+ "rightkit-file-watch": "0.4.0",
373
+ "rightkit-fs": "0.4.0",
374
+ "rightkit-fsindex": "0.2.1",
348
375
  "rightkit-hotkeys": "0.1.0",
349
- "rightkit-http": "0.2.5",
350
- "rightkit-license": "0.1.3",
351
- "rightkit-llm": "0.2.0",
352
- "rightkit-logs": "0.2.1",
376
+ "rightkit-http": "0.3.0",
377
+ "rightkit-job-queue": "0.3.1",
378
+ "rightkit-license": "0.2.0",
379
+ "rightkit-llm": "0.2.1",
380
+ "rightkit-logs": "0.3.0",
353
381
  "rightkit-mcp": "0.5.3",
354
- "rightkit-models": "0.2.8",
355
- "rightkit-oauth": "0.3.1",
356
- "rightkit-ocr": "0.2.0",
382
+ "rightkit-models": "0.3.1",
383
+ "rightkit-oauth": "0.5.0",
384
+ "rightkit-ocr": "0.2.2",
385
+ "rightkit-onnx": "0.2.3",
357
386
  "rightkit-overlay": "0.1.1",
358
387
  "rightkit-ownership": "0.2.1",
359
- "rightkit-pdf": "0.2.0",
360
- "rightkit-playback": "0.1.2",
361
- "rightkit-qa": "0.2.15",
362
- "rightkit-review": "0.1.0",
363
- "rightkit-sandbox": "0.3.4",
364
- "rightkit-search": "0.2.0",
388
+ "rightkit-pdf": "0.3.0",
389
+ "rightkit-playback": "0.2.1",
390
+ "rightkit-qa": "0.3.0",
391
+ "rightkit-qa-driver": "0.3.1",
392
+ "rightkit-review": "0.2.0",
393
+ "rightkit-sandbox": "0.4.0",
394
+ "rightkit-search": "0.2.1",
365
395
  "rightkit-send": "0.1.0",
366
- "rightkit-speech-client": "0.3.3",
367
- "rightkit-sync": "0.1.0",
368
- "rightkit-transcript": "0.2.0",
369
- "rightkit-tts": "0.1.1",
370
- "rightkit-vad": "0.0.3"
396
+ "rightkit-sidecar": "0.2.0",
397
+ "rightkit-speech-client": "0.3.5",
398
+ "rightkit-sync": "0.1.1",
399
+ "rightkit-tauri-shell": "0.3.0",
400
+ "rightkit-transcript": "0.3.0",
401
+ "rightkit-tts": "0.2.1",
402
+ "rightkit-tts-qwen": "0.2.6",
403
+ "rightkit-tts-qwen-sys": "0.1.2",
404
+ "rightkit-vad": "0.1.1",
405
+ "rightkit-workflow": "0.1.0"
371
406
  },
372
407
  "swift": {
373
408
  "rightkit-swift": {
@@ -384,70 +419,101 @@
384
419
  },
385
420
  "registry": {
386
421
  "cargo": {
387
- "rightkit-asr": "0.1.0",
388
- "rightkit-audio": "0.2.1",
389
- "rightkit-browser": "0.2.4",
390
- "rightkit-cache": "0.1.0",
391
- "rightkit-control": "0.1.11",
392
- "rightkit-crash": "0.2.1",
393
- "rightkit-db": "0.3.2",
394
- "rightkit-embed": "0.3.2",
395
- "rightkit-framed-sidecar": "0.1.0",
396
- "rightkit-fs": "0.3.3",
397
- "rightkit-fsindex": "0.1.1",
398
- "rightkit-http": "0.2.5",
399
- "rightkit-jobs": "0.2.7",
400
- "rightkit-license": "0.1.3",
401
- "rightkit-llm": "0.1.0",
402
- "rightkit-logs": "0.2.1",
422
+ "rightkit-agent-tools": "0.1.2",
423
+ "rightkit-agents": "0.1.0",
424
+ "rightkit-app-settings": "0.2.0",
425
+ "rightkit-asr": "0.2.1",
426
+ "rightkit-audio": "0.2.3",
427
+ "rightkit-automation": "0.3.0",
428
+ "rightkit-browser": "0.3.1",
429
+ "rightkit-cache": "0.2.0",
430
+ "rightkit-channel-memory": "0.1.0",
431
+ "rightkit-child-process": "0.4.0",
432
+ "rightkit-compose": "0.1.0",
433
+ "rightkit-contracts": "0.1.5",
434
+ "rightkit-control": "0.3.0",
435
+ "rightkit-crash": "0.3.0",
436
+ "rightkit-credentials": "0.2.0",
437
+ "rightkit-daemon": "0.2.2",
438
+ "rightkit-db": "0.4.0",
439
+ "rightkit-device": "0.1.3",
440
+ "rightkit-embed": "0.4.0",
441
+ "rightkit-ffmpeg": "0.2.6",
442
+ "rightkit-file-watch": "0.4.0",
443
+ "rightkit-framed-sidecar": "0.2.0",
444
+ "rightkit-fs": "0.4.0",
445
+ "rightkit-fsindex": "0.2.1",
446
+ "rightkit-hotkeys": "0.1.0",
447
+ "rightkit-http": "0.3.0",
448
+ "rightkit-job-queue": "0.3.1",
449
+ "rightkit-jobs": "0.3.0",
450
+ "rightkit-license": "0.2.0",
451
+ "rightkit-llm": "0.2.1",
452
+ "rightkit-logs": "0.3.0",
403
453
  "rightkit-mcp": "0.5.3",
404
- "rightkit-media": "0.2.5",
405
- "rightkit-models": "0.2.8",
406
- "rightkit-oauth": "0.3.1",
407
- "rightkit-ocr": "0.2.0",
408
- "rightkit-ort": "0.2.2",
454
+ "rightkit-media": "0.2.6",
455
+ "rightkit-models": "0.3.1",
456
+ "rightkit-oauth": "0.5.0",
457
+ "rightkit-ocr": "0.2.2",
458
+ "rightkit-onnx": "0.2.3",
459
+ "rightkit-ort": "0.2.3",
460
+ "rightkit-overlay": "0.1.1",
409
461
  "rightkit-ownership": "0.2.1",
410
- "rightkit-pdf": "0.2.0",
411
- "rightkit-playback": "0.1.1",
412
- "rightkit-process": "0.3.3",
413
- "rightkit-qa": "0.2.14",
414
- "rightkit-qts": "0.2.5",
462
+ "rightkit-pdf": "0.3.0",
463
+ "rightkit-playback": "0.2.1",
464
+ "rightkit-process": "0.4.0",
465
+ "rightkit-qa": "0.3.0",
466
+ "rightkit-qa-driver": "0.3.1",
467
+ "rightkit-qts": "0.2.6",
415
468
  "rightkit-qts-ggml": "0.1.2",
416
469
  "rightkit-qts-ggml-sys": "0.1.2",
417
- "rightkit-sandbox": "0.3.4",
418
- "rightkit-search": "0.2.0",
419
- "rightkit-secrets": "0.1.3",
470
+ "rightkit-review": "0.2.0",
471
+ "rightkit-sandbox": "0.4.0",
472
+ "rightkit-search": "0.2.1",
473
+ "rightkit-secrets": "0.2.0",
474
+ "rightkit-send": "0.1.0",
420
475
  "rightkit-service": "0.1.4",
421
- "rightkit-settings": "0.1.1",
422
- "rightkit-shell": "0.2.0",
423
- "rightkit-speech-client": "0.3.3",
424
- "rightkit-tauri": "0.1.2",
425
- "rightkit-transcript": "0.2.0",
426
- "rightkit-tts": "0.1.1",
427
- "rightkit-vad": "0.0.3",
428
- "rightkit-watch": "0.3.1"
476
+ "rightkit-settings": "0.2.0",
477
+ "rightkit-shell": "0.3.0",
478
+ "rightkit-sidecar": "0.2.0",
479
+ "rightkit-speech-client": "0.3.5",
480
+ "rightkit-sync": "0.1.1",
481
+ "rightkit-tauri": "0.1.3",
482
+ "rightkit-tauri-shell": "0.3.0",
483
+ "rightkit-transcript": "0.3.0",
484
+ "rightkit-tts": "0.2.1",
485
+ "rightkit-tts-qwen": "0.2.6",
486
+ "rightkit-tts-qwen-sys": "0.1.2",
487
+ "rightkit-vad": "0.1.1",
488
+ "rightkit-watch": "0.4.0",
489
+ "rightkit-workflow": "0.1.0"
429
490
  },
430
491
  "npm": {
431
492
  "@rightkit/app-shell": "0.6.2",
432
- "@rightkit/ax": "0.2.4",
493
+ "@rightkit/app-update": "0.2.5",
494
+ "@rightkit/ax": "0.3.2",
495
+ "@rightkit/ci": "0.2.44",
433
496
  "@rightkit/doc-render": "0.1.6",
434
497
  "@rightkit/git": "0.2.42",
435
498
  "@rightkit/hooks": "0.1.2",
436
499
  "@rightkit/i18n": "0.1.0",
437
500
  "@rightkit/legal": "0.3.1",
438
501
  "@rightkit/legal-ui": "0.2.2",
439
- "@rightkit/license": "0.1.8",
502
+ "@rightkit/license": "0.2.0",
440
503
  "@rightkit/logs": "0.1.4",
441
504
  "@rightkit/media-ui": "0.3.4",
442
- "@rightkit/onboarding": "0.3.2",
505
+ "@rightkit/onboarding": "0.3.3",
506
+ "@rightkit/outdated": "0.2.0",
443
507
  "@rightkit/pdf-ui": "0.1.6",
444
- "@rightkit/platform-ui": "0.2.7",
445
- "@rightkit/qa": "0.4.7",
446
- "@rightkit/qa-chrome": "0.1.5",
447
- "@rightkit/release": "0.2.155",
448
- "@rightkit/settings-ui": "0.4.3",
508
+ "@rightkit/platform-ui": "0.2.8",
509
+ "@rightkit/qa": "0.4.8",
510
+ "@rightkit/qa-chrome": "0.1.6",
511
+ "@rightkit/react-shell": "0.6.4",
512
+ "@rightkit/release": "0.2.158",
513
+ "@rightkit/settings-ui": "0.4.4",
449
514
  "@rightkit/shell": "0.1.1",
450
- "@rightkit/tauri": "0.2.0",
515
+ "@rightkit/tauri": "0.2.1",
516
+ "@rightkit/tauri-shell": "0.1.1",
451
517
  "@rightkit/theme": "0.2.3",
452
518
  "@rightkit/updates": "0.2.5"
453
519
  }
@@ -459,7 +525,8 @@
459
525
  "rightkit-browser": [
460
526
  "0.2.0",
461
527
  "0.2.1",
462
- "0.2.2"
528
+ "0.2.2",
529
+ "0.3.0"
463
530
  ],
464
531
  "rightkit-models": [
465
532
  "0.2.1",
@@ -468,12 +535,14 @@
468
535
  "0.2.4",
469
536
  "0.2.5",
470
537
  "0.2.6",
471
- "0.2.7"
538
+ "0.2.7",
539
+ "0.3.0"
472
540
  ],
473
541
  "rightkit-audio": [
474
542
  "0.1.0",
475
543
  "0.1.1",
476
- "0.2.0"
544
+ "0.2.0",
545
+ "0.2.2"
477
546
  ],
478
547
  "rightkit-db": [
479
548
  "0.2.1",
@@ -484,7 +553,8 @@
484
553
  "0.2.1",
485
554
  "0.2.2",
486
555
  "0.3.0",
487
- "0.3.1"
556
+ "0.3.1",
557
+ "0.3.2"
488
558
  ],
489
559
  "rightkit-jobs": [
490
560
  "0.2.1",
@@ -512,7 +582,8 @@
512
582
  "rightkit-oauth": [
513
583
  "0.2.0",
514
584
  "0.2.1",
515
- "0.3.0"
585
+ "0.3.0",
586
+ "0.4.0"
516
587
  ],
517
588
  "rightkit-ort": [
518
589
  "0.1.4",
@@ -521,7 +592,8 @@
521
592
  ],
522
593
  "rightkit-playback": [
523
594
  "0.1.0",
524
- "0.1.1"
595
+ "0.1.1",
596
+ "0.2.0"
525
597
  ],
526
598
  "rightkit-process": [
527
599
  "0.2.1",
@@ -536,7 +608,8 @@
536
608
  "0.2.10",
537
609
  "0.2.11",
538
610
  "0.2.12",
539
- "0.2.14"
611
+ "0.2.14",
612
+ "0.2.16"
540
613
  ],
541
614
  "rightkit-sandbox": [
542
615
  "0.1.0",
@@ -548,7 +621,8 @@
548
621
  "0.3.0",
549
622
  "0.3.1",
550
623
  "0.3.2",
551
- "0.3.3"
624
+ "0.3.3",
625
+ "0.3.5"
552
626
  ],
553
627
  "rightkit-shell": [
554
628
  "0.1.0",
@@ -620,27 +694,33 @@
620
694
  "rightkit-speech-client": [
621
695
  "0.3.0",
622
696
  "0.3.1",
623
- "0.3.2"
697
+ "0.3.2",
698
+ "0.3.4"
624
699
  ],
625
700
  "rightkit-crash": [
626
701
  "0.1.0",
627
- "0.2.0"
702
+ "0.2.0",
703
+ "0.2.2"
628
704
  ],
629
705
  "rightkit-qts-ggml-sys": [
630
706
  "0.1.0",
631
707
  "0.1.1"
632
708
  ],
633
709
  "rightkit-fsindex": [
634
- "0.1.0"
710
+ "0.1.0",
711
+ "0.2.0"
635
712
  ],
636
713
  "rightkit-asr": [
637
- "0.0.2"
714
+ "0.0.2",
715
+ "0.2.0"
638
716
  ],
639
717
  "rightkit-logs": [
640
- "0.2.0"
718
+ "0.2.0",
719
+ "0.2.2"
641
720
  ],
642
721
  "rightkit-ocr": [
643
- "0.1.2"
722
+ "0.1.2",
723
+ "0.2.1"
644
724
  ],
645
725
  "rightkit-settings": [
646
726
  "0.1.0"
@@ -649,13 +729,26 @@
649
729
  "0.1.0"
650
730
  ],
651
731
  "rightkit-vad": [
652
- "0.0.2"
732
+ "0.0.2",
733
+ "0.1.0"
653
734
  ],
654
735
  "rightkit-tts": [
655
- "0.1.0"
736
+ "0.1.0",
737
+ "0.2.0"
656
738
  ],
657
739
  "rightkit-llm": [
658
- "0.1.0"
740
+ "0.1.0",
741
+ "0.2.0"
742
+ ],
743
+ "rightkit-daemon": [
744
+ "0.2.0",
745
+ "0.2.1"
746
+ ],
747
+ "rightkit-job-queue": [
748
+ "0.3.0"
749
+ ],
750
+ "rightkit-qa-driver": [
751
+ "0.3.0"
659
752
  ]
660
753
  }
661
754
  }
@@ -1,4 +1,33 @@
1
1
  {
2
2
  "schema": 1,
3
- "heldResource": {}
3
+ "heldResource": {
4
+ "rightkit-asr": {
5
+ "ceilings": null,
6
+ "requiredScale": null
7
+ },
8
+ "rightkit-audio": {
9
+ "ceilings": null,
10
+ "requiredScale": null
11
+ },
12
+ "rightkit-browser": {
13
+ "ceilings": null,
14
+ "requiredScale": null
15
+ },
16
+ "rightkit-embed": {
17
+ "ceilings": null,
18
+ "requiredScale": null
19
+ },
20
+ "rightkit-ocr": {
21
+ "ceilings": null,
22
+ "requiredScale": null
23
+ },
24
+ "rightkit-sandbox": {
25
+ "ceilings": null,
26
+ "requiredScale": null
27
+ },
28
+ "rightkit-vad": {
29
+ "ceilings": null,
30
+ "requiredScale": null
31
+ }
32
+ }
4
33
  }