@rightkit/release 0.2.164 → 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 +18 -9
- package/disk-policy.mjs +36 -6
- package/package.json +1 -1
- package/release.mjs +2 -1
- package/rightkit-versions.json +4 -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");
|
|
@@ -246,6 +249,12 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
|
|
|
246
249
|
const exitCode = schedule(rest[0], { stdout, cli: at === -1 ? undefined : rest[at + 1], platform, env });
|
|
247
250
|
return { exitCode };
|
|
248
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
|
+
}
|
|
249
258
|
const opts = parse(rest);
|
|
250
259
|
const policy = readDiskPolicy(opts.policy);
|
|
251
260
|
const key = platformKey(platform);
|
|
@@ -262,9 +271,9 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
|
|
|
262
271
|
if (command === "sweep") {
|
|
263
272
|
if (!opts.dryRun) ensureScratchRoots({ policy, platform, env });
|
|
264
273
|
const p = plan();
|
|
265
|
-
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 }; }
|
|
266
275
|
const applied = applySweep({ plan: p, policy, platform, deleteMode: opts.deleteMode, ...deps.apply });
|
|
267
|
-
out({ ...p, applied }, renderSweep(p, applied));
|
|
276
|
+
out({ ...p, applied }, renderSweep(p, applied, { all: opts.all }));
|
|
268
277
|
return { exitCode: applied.results.some((r) => !r.ok) ? 1 : 0, plan: p, applied };
|
|
269
278
|
}
|
|
270
279
|
if (command === "purge") {
|
|
@@ -294,7 +303,7 @@ export async function runDiskCommand(args = process.argv.slice(2), { env = proce
|
|
|
294
303
|
if (warn) stdout(`WARNING: ${after.warnings.join("; ")}`);
|
|
295
304
|
return { exitCode: !self.ok ? 2 : warn ? DISK_EXIT_WARNING : 0, before, after, applied, purged, selfCheck: self };
|
|
296
305
|
}
|
|
297
|
-
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]");
|
|
298
307
|
}
|
|
299
308
|
|
|
300
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",
|
|
@@ -204,7 +204,8 @@
|
|
|
204
204
|
"0.2.159",
|
|
205
205
|
"0.2.160",
|
|
206
206
|
"0.2.161",
|
|
207
|
-
"0.2.162"
|
|
207
|
+
"0.2.162",
|
|
208
|
+
"0.2.163"
|
|
208
209
|
],
|
|
209
210
|
"@rightkit/qa": [
|
|
210
211
|
"0.1.0",
|
|
@@ -516,7 +517,7 @@
|
|
|
516
517
|
"@rightkit/qa": "0.4.8",
|
|
517
518
|
"@rightkit/qa-chrome": "0.1.6",
|
|
518
519
|
"@rightkit/react-shell": "0.6.4",
|
|
519
|
-
"@rightkit/release": "0.2.
|
|
520
|
+
"@rightkit/release": "0.2.164",
|
|
520
521
|
"@rightkit/settings-ui": "0.4.4",
|
|
521
522
|
"@rightkit/shell": "0.1.1",
|
|
522
523
|
"@rightkit/tauri": "0.2.1",
|