@rightkit/release 0.2.163 → 0.2.165
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/disk-command.mjs +21 -9
- package/disk-policy.mjs +36 -6
- package/package.json +1 -1
- package/release.mjs +2 -1
- package/rightkit-versions.json +7 -3
package/disk-command.mjs
CHANGED
|
@@ -6,7 +6,7 @@ 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,
|
|
9
|
+
DISK_EXIT_WARNING, applySweep, checkDisk, ensureScratchRoots, formatBytes, listOpenPaths, logEvent, planSweep, platformKey, purgeHoldings, readDiskPolicy, setWorkspaceSweepAllowed,
|
|
10
10
|
} from "./disk-policy.mjs";
|
|
11
11
|
import { evaluateBinding, installStableRoot, readScheduleBinding, selfCheck } from "./disk-stable-root.mjs";
|
|
12
12
|
|
|
@@ -27,7 +27,7 @@ export function renderCheck(result) {
|
|
|
27
27
|
return lines.join("\n");
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export function renderSweep(plan, applied) {
|
|
30
|
+
export function renderSweep(plan, applied, { all = false } = {}) {
|
|
31
31
|
const lines = [`${applied ? "Swept" : "Dry run: would move"} ${plan.candidates.length} item(s), ${formatBytes(plan.totalBytes)} (stale > ${plan.staleDays} days)`];
|
|
32
32
|
const rows = applied ? applied.results : plan.candidates;
|
|
33
33
|
const byWriter = new Map();
|
|
@@ -35,24 +35,27 @@ export function renderSweep(plan, applied) {
|
|
|
35
35
|
for (const [writer, items] of byWriter) {
|
|
36
36
|
const total = items.reduce((s, r) => s + r.bytes, 0);
|
|
37
37
|
lines.push(` ${writer.padEnd(26)} ${String(items.length).padStart(4)} item(s) ${formatBytes(total).padStart(9)}`);
|
|
38
|
-
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}` : ""}`);
|
|
39
|
-
if (items.length > 5) lines.push(` ... ${items.length - 5} more`);
|
|
38
|
+
for (const r of [...items].sort((a, b) => b.bytes - a.bytes).slice(0, all ? Infinity : 5)) lines.push(` ${formatBytes(r.bytes).padStart(9)} ${r.path}${r.destination ? ` -> ${r.destination}` : ""}${r.ok === false ? ` FAILED: ${r.error}` : ""}`);
|
|
39
|
+
if (!all && items.length > 5) lines.push(` ... ${items.length - 5} more`);
|
|
40
40
|
}
|
|
41
41
|
const counts = {};
|
|
42
42
|
for (const s of plan.skipped) counts[s.reason] = (counts[s.reason] ?? 0) + 1;
|
|
43
|
+
const reportOnly = plan.skipped.filter((s) => s.reason === "report-only");
|
|
44
|
+
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)`);
|
|
43
45
|
if (plan.skipped.length) lines.push(` skipped: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ")}`);
|
|
44
46
|
return lines.join("\n");
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
function parse(rest) {
|
|
48
|
-
const known = new Set(["--json", "--dry-run", "--delete", "--days", "--policy"]);
|
|
49
|
-
const opts = { json: false, dryRun: false, deleteMode: false, days: undefined, policy: undefined };
|
|
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 };
|
|
50
52
|
for (let i = 0; i < rest.length; i += 1) {
|
|
51
53
|
const a = rest[i];
|
|
52
54
|
if (!known.has(a)) throw new Error(`disk: unknown option ${a}`);
|
|
53
55
|
if (a === "--json") opts.json = true;
|
|
54
56
|
else if (a === "--dry-run") opts.dryRun = true;
|
|
55
57
|
else if (a === "--delete") opts.deleteMode = true;
|
|
58
|
+
else if (a === "--all") opts.all = true;
|
|
56
59
|
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; }
|
|
57
60
|
}
|
|
58
61
|
if (opts.days !== undefined && !(opts.days >= 0)) throw new Error("disk: --days must be a non-negative number");
|
|
@@ -62,6 +65,9 @@ function parse(rest) {
|
|
|
62
65
|
|
|
63
66
|
// A path inside any git checkout (a `.git` entry on an ancestor) is development code, never an installed package.
|
|
64
67
|
export function insideGitCheckout(file, exists = existsSync) {
|
|
68
|
+
// A pnpm store is an install location even when it sits inside a workspace that is itself a git checkout (the Mac keeps
|
|
69
|
+
// .pnpm-store under /Volumes/D/claude).
|
|
70
|
+
if (file.split(/[\\/]+/).some((seg) => seg === ".pnpm-store" || seg === ".pnpm")) return false;
|
|
65
71
|
let dir = path.dirname(file);
|
|
66
72
|
for (;;) {
|
|
67
73
|
if (exists(path.join(dir, ".git"))) return true;
|
|
@@ -243,6 +249,12 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
|
|
|
243
249
|
const exitCode = schedule(rest[0], { stdout, cli: at === -1 ? undefined : rest[at + 1], platform, env });
|
|
244
250
|
return { exitCode };
|
|
245
251
|
}
|
|
252
|
+
if (command === "allow-workspace-sweep") {
|
|
253
|
+
const off = rest.includes("--off");
|
|
254
|
+
const file = setWorkspaceSweepAllowed(!off, { platform, env, ...(deps.flagHome ? { home: deps.flagHome } : {}) });
|
|
255
|
+
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`);
|
|
256
|
+
return { exitCode: 0 };
|
|
257
|
+
}
|
|
246
258
|
const opts = parse(rest);
|
|
247
259
|
const policy = readDiskPolicy(opts.policy);
|
|
248
260
|
const key = platformKey(platform);
|
|
@@ -259,9 +271,9 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
|
|
|
259
271
|
if (command === "sweep") {
|
|
260
272
|
if (!opts.dryRun) ensureScratchRoots({ policy, platform, env });
|
|
261
273
|
const p = plan();
|
|
262
|
-
if (opts.dryRun) { out(p, renderSweep(p)); return { exitCode: 0, plan: p }; }
|
|
274
|
+
if (opts.dryRun) { out(p, renderSweep(p, undefined, { all: opts.all })); return { exitCode: 0, plan: p }; }
|
|
263
275
|
const applied = applySweep({ plan: p, policy, platform, deleteMode: opts.deleteMode, ...deps.apply });
|
|
264
|
-
out({ ...p, applied }, renderSweep(p, applied));
|
|
276
|
+
out({ ...p, applied }, renderSweep(p, applied, { all: opts.all }));
|
|
265
277
|
return { exitCode: applied.results.some((r) => !r.ok) ? 1 : 0, plan: p, applied };
|
|
266
278
|
}
|
|
267
279
|
if (command === "purge") {
|
|
@@ -291,7 +303,7 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
|
|
|
291
303
|
if (warn) stdout(`WARNING: ${after.warnings.join("; ")}`);
|
|
292
304
|
return { exitCode: !self.ok ? 2 : warn ? DISK_EXIT_WARNING : 0, before, after, applied, purged, selfCheck: self };
|
|
293
305
|
}
|
|
294
|
-
throw new Error("disk usage: check|sweep [--dry-run|--delete] [--days N]|purge [--dry-run] [--days N]|run|schedule install|uninstall|status [--json]");
|
|
306
|
+
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 [--json]");
|
|
295
307
|
}
|
|
296
308
|
|
|
297
309
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
package/disk-policy.mjs
CHANGED
|
@@ -160,9 +160,33 @@ export function ensureScratchRoots({ policy, platform = process.platform, env =
|
|
|
160
160
|
|
|
161
161
|
/** Top-level entries of each workspace root that are neither tracked by the workspace repo, nested git checkouts,
|
|
162
162
|
* nor on the allowlist. Symlinks are listed (`symlink: true`) but sweep never touches them. */
|
|
163
|
+
// Names that are always known at a workspace root, whatever the policy file says: agent instructions and agent state.
|
|
164
|
+
export const WORKSPACE_ALWAYS_ALLOWED = ["CLAUDE.md", "AGENTS.md", "CLAUDE.local.md", ".claude", ".codex", ".agent", ".agents", ".membrane", "cockpit", "contributions"];
|
|
165
|
+
const PROJECT_MARKERS = [".git", "package.json", "Cargo.toml"];
|
|
166
|
+
|
|
167
|
+
/** Host-local opt-in for moving workspace-unknown entries. Without it the target is report-only on every host. */
|
|
168
|
+
export function workspaceSweepFlagPath({ platform = process.platform, env = process.env, home = os.homedir() } = {}) {
|
|
169
|
+
if (platform === "win32" || platform === "win") {
|
|
170
|
+
if (!env.LOCALAPPDATA) return null;
|
|
171
|
+
return path.win32.join(env.LOCALAPPDATA, "Orthic Labs", "rightkit-release", "workspace-sweep-allowed");
|
|
172
|
+
}
|
|
173
|
+
return path.posix.join(home, "Library", "Application Support", "Orthic Labs", "rightkit-release", "workspace-sweep-allowed");
|
|
174
|
+
}
|
|
175
|
+
export function workspaceSweepAllowed(options = {}) {
|
|
176
|
+
const file = workspaceSweepFlagPath(options);
|
|
177
|
+
return Boolean(file) && existsSync(file);
|
|
178
|
+
}
|
|
179
|
+
export function setWorkspaceSweepAllowed(on, options = {}) {
|
|
180
|
+
const file = workspaceSweepFlagPath(options);
|
|
181
|
+
if (!file) throw new Error("disk: cannot place the host-local flag (LOCALAPPDATA is not set)");
|
|
182
|
+
if (on) { mkdirSync(path.dirname(file), { recursive: true }); writeFileSync(file, `workspace-unknown sweep allowed on this host ${new Date().toISOString()}\n`); }
|
|
183
|
+
else renameSync(file, `${file}.revoked`);
|
|
184
|
+
return file;
|
|
185
|
+
}
|
|
186
|
+
|
|
163
187
|
export function scanWorkspaceRoots({ policy, platform = process.platform, env = process.env, home = os.homedir(), tracked = (r) => workspaceTracked(r, { platform }) } = {}) {
|
|
164
188
|
const key = platformKey(platform);
|
|
165
|
-
const allow = new Set((policy.workspaceAllowlist ?? []).map((n) => fold(n, key)));
|
|
189
|
+
const allow = new Set([...WORKSPACE_ALWAYS_ALLOWED, ...(policy.workspaceAllowlist ?? [])].map((n) => fold(n, key)));
|
|
166
190
|
const unknown = [];
|
|
167
191
|
const skipped = [];
|
|
168
192
|
for (const entry of policy.workspaceRoots?.[key] ?? []) {
|
|
@@ -177,10 +201,12 @@ export function scanWorkspaceRoots({ policy, platform = process.platform, env =
|
|
|
177
201
|
for (const name of names) {
|
|
178
202
|
const folded = fold(name, key);
|
|
179
203
|
if (allow.has(folded) || extra.has(folded) || trackedNames.has(folded)) continue;
|
|
204
|
+
if (/\.md$/i.test(name)) continue; // any markdown file at the root is documentation
|
|
180
205
|
const full = path.join(root, name);
|
|
181
206
|
let st;
|
|
182
207
|
try { st = lstatSync(full); } catch { continue; }
|
|
183
|
-
|
|
208
|
+
// a nested git checkout or any project directory (own package.json / Cargo.toml) is known
|
|
209
|
+
if (!st.isSymbolicLink() && st.isDirectory() && PROJECT_MARKERS.some((m) => existsSync(path.join(full, m)))) continue;
|
|
184
210
|
unknown.push({ root, name, path: full, symlink: st.isSymbolicLink(), mtimeMs: st.mtimeMs });
|
|
185
211
|
}
|
|
186
212
|
}
|
|
@@ -206,7 +232,7 @@ export function checkDisk({ policy, platform = process.platform, env = process.e
|
|
|
206
232
|
for (const w of writers) if (w.over) warnings.push(`${w.id} holds ${formatBytes(w.bytes)} over its ${formatBytes(w.capBytes)} cap`);
|
|
207
233
|
const scanned = scan({ policy, platform, env, home });
|
|
208
234
|
const unknown = scanned.unknown.map((u) => ({ root: u.root, name: u.name, path: u.path, symlink: u.symlink, bytes: u.symlink ? 0 : size(u.path), ageDays: Math.max(0, Math.floor((now - u.mtimeMs) / 86400_000)) }));
|
|
209
|
-
for (const u of unknown) warnings.push(`unknown top-level entry ${u.path} (${u.symlink ? "symlink" : formatBytes(u.bytes)}, ${u.ageDays}d old): track it
|
|
235
|
+
for (const u of unknown) warnings.push(`unknown top-level entry ${u.path} (${u.symlink ? "symlink" : formatBytes(u.bytes)}, ${u.ageDays}d old): track it or add it to workspaceAllowlist (sweep only moves it on a host that ran \`disk allow-workspace-sweep\`)`);
|
|
210
236
|
for (const s of scanned.skipped) warnings.push(`workspace root ${s.path}: tracked paths cannot be read from git, so unknown entries are not evaluated or swept`);
|
|
211
237
|
return { schema: 1, platform: key, floor, writers, unknown, warnings, warn: warnings.length > 0 };
|
|
212
238
|
}
|
|
@@ -272,7 +298,7 @@ export function gitTracksPath(target) {
|
|
|
272
298
|
return String(listed.stdout).length > 0;
|
|
273
299
|
}
|
|
274
300
|
|
|
275
|
-
export function planSweep({ policy, platform = process.platform, env = process.env, home = os.homedir(), now = Date.now(), staleDays, openPaths, inUse, tracked = gitTracksPath, size = (p) => pathSize(p, { platform }), scan = scanWorkspaceRoots } = {}) {
|
|
301
|
+
export function planSweep({ policy, platform = process.platform, env = process.env, home = os.homedir(), now = Date.now(), staleDays, openPaths, inUse, tracked = gitTracksPath, size = (p) => pathSize(p, { platform }), scan = scanWorkspaceRoots, workspaceSweep = workspaceSweepAllowed({ platform, env, home }) } = {}) {
|
|
276
302
|
const key = platformKey(platform);
|
|
277
303
|
const effectiveDays = staleDays ?? policy.staleDays;
|
|
278
304
|
const candidates = [];
|
|
@@ -326,7 +352,11 @@ export function planSweep({ policy, platform = process.platform, env = process.e
|
|
|
326
352
|
if (lockCheck(unit)) { skipped.push({ ...base, reason: "in-use" }); return; }
|
|
327
353
|
candidates.push({ ...base, root, bytes: size(unit), mtime: new Date(st.mtimeMs).toISOString() });
|
|
328
354
|
};
|
|
329
|
-
|
|
355
|
+
// Report-only unless this host opted in (`disk allow-workspace-sweep`): unknown entries are warned about, never moved.
|
|
356
|
+
for (const u of scanned.unknown) {
|
|
357
|
+
if (!workspaceSweep) { skipped.push({ writer: "workspace-unknown", path: u.path, reason: "report-only" }); continue; }
|
|
358
|
+
consider("workspace-unknown", u.path, u.root, unknownCutoff);
|
|
359
|
+
}
|
|
330
360
|
// Scratch roots: every child older than 3 days and idle (the same window).
|
|
331
361
|
for (const root of scratchRoots(policy, { platform, env, home })) {
|
|
332
362
|
let rst;
|
|
@@ -429,7 +459,7 @@ export function applySweep({ plan, policy, platform = process.platform, deleteMo
|
|
|
429
459
|
try {
|
|
430
460
|
// Re-verify at apply time: still the planned unit, not a symlink, still inside its root.
|
|
431
461
|
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");
|
|
432
|
-
if (c.writer === "workspace-unknown" && existsSync(path.join(c.path,
|
|
462
|
+
if (c.writer === "workspace-unknown" && PROJECT_MARKERS.some((m) => existsSync(path.join(c.path, m)))) throw new Error("refused: a nested git checkout or project directory is never swept");
|
|
433
463
|
if (deleteMode) { remove(c.path); results.push({ ...c, action: "delete", ok: true }); logEvent(logFile, { event: "disk.sweep.delete", ...c }); continue; }
|
|
434
464
|
// An item stays on its own volume: moving it elsewhere frees nothing on the volume that is short of space.
|
|
435
465
|
const holding = pick(c.path, { policy, platform, env, home, date });
|
package/package.json
CHANGED
package/release.mjs
CHANGED
|
@@ -51,7 +51,8 @@ const SIGN_MACOS = path.resolve(TOOL_ROOT, "sign-macos.mjs");
|
|
|
51
51
|
const NOTARIZE_MACOS = path.resolve(TOOL_ROOT, "notarize-macos.mjs");
|
|
52
52
|
const SIGN_UPDATER = path.resolve(TOOL_ROOT, "sign-updater.mjs");
|
|
53
53
|
const RIGHTKIT_VERSIONS = JSON.parse(readFileSync(path.resolve(TOOL_ROOT, "rightkit-versions.json"), "utf8"));
|
|
54
|
-
|
|
54
|
+
// The running package's own version: the bundled feed lags by one release (it is written after the publish).
|
|
55
|
+
const VERSION = JSON.parse(readFileSync(path.resolve(TOOL_ROOT, "package.json"), "utf8")).version;
|
|
55
56
|
const TIERS = new Set(["patch", "update"]);
|
|
56
57
|
const RIGHTKIT_ALLOWED = buildAllowedVersions(
|
|
57
58
|
RIGHTKIT_VERSIONS.npm,
|
package/rightkit-versions.json
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@rightkit/qa": "0.4.8",
|
|
32
32
|
"@rightkit/qa-chrome": "0.1.6",
|
|
33
33
|
"@rightkit/react-shell": "0.6.4",
|
|
34
|
-
"@rightkit/release": "0.2.
|
|
34
|
+
"@rightkit/release": "0.2.164",
|
|
35
35
|
"@rightkit/settings-ui": "0.4.4",
|
|
36
36
|
"@rightkit/tauri": "0.2.1",
|
|
37
37
|
"@rightkit/tauri-shell": "0.1.1",
|
|
@@ -203,7 +203,9 @@
|
|
|
203
203
|
"0.2.158",
|
|
204
204
|
"0.2.159",
|
|
205
205
|
"0.2.160",
|
|
206
|
-
"0.2.161"
|
|
206
|
+
"0.2.161",
|
|
207
|
+
"0.2.162",
|
|
208
|
+
"0.2.163"
|
|
207
209
|
],
|
|
208
210
|
"@rightkit/qa": [
|
|
209
211
|
"0.1.0",
|
|
@@ -393,6 +395,7 @@
|
|
|
393
395
|
"rightkit-playback": "0.2.1",
|
|
394
396
|
"rightkit-qa": "0.4.0",
|
|
395
397
|
"rightkit-qa-driver": "0.4.0",
|
|
398
|
+
"rightkit-record": "0.3.0",
|
|
396
399
|
"rightkit-review": "0.2.0",
|
|
397
400
|
"rightkit-sandbox": "0.4.0",
|
|
398
401
|
"rightkit-search": "0.2.1",
|
|
@@ -471,6 +474,7 @@
|
|
|
471
474
|
"rightkit-qts": "0.2.6",
|
|
472
475
|
"rightkit-qts-ggml": "0.1.2",
|
|
473
476
|
"rightkit-qts-ggml-sys": "0.1.2",
|
|
477
|
+
"rightkit-record": "0.3.0",
|
|
474
478
|
"rightkit-review": "0.2.0",
|
|
475
479
|
"rightkit-sandbox": "0.4.0",
|
|
476
480
|
"rightkit-search": "0.2.1",
|
|
@@ -513,7 +517,7 @@
|
|
|
513
517
|
"@rightkit/qa": "0.4.8",
|
|
514
518
|
"@rightkit/qa-chrome": "0.1.6",
|
|
515
519
|
"@rightkit/react-shell": "0.6.4",
|
|
516
|
-
"@rightkit/release": "0.2.
|
|
520
|
+
"@rightkit/release": "0.2.164",
|
|
517
521
|
"@rightkit/settings-ui": "0.4.4",
|
|
518
522
|
"@rightkit/shell": "0.1.1",
|
|
519
523
|
"@rightkit/tauri": "0.2.1",
|