Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions claude-code/bundle/plugin-cache-gc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#!/usr/bin/env node

// dist/src/hooks/plugin-cache-gc.js
import { fileURLToPath, pathToFileURL } from "node:url";
import { dirname as dirname2 } from "node:path";

// dist/src/utils/debug.js
import { appendFileSync } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";
var DEBUG = process.env.HIVEMIND_DEBUG === "1";
var LOG = join(homedir(), ".deeplake", "hook-debug.log");
function log(tag, msg) {
if (!DEBUG)
return;
appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
`);
}

// dist/src/utils/plugin-cache.js
import { cpSync, existsSync, readdirSync, readFileSync, renameSync, rmSync, statSync } from "node:fs";
import { basename, dirname, join as join2, resolve, sep } from "node:path";
import { homedir as homedir2 } from "node:os";
var SEMVER_RE = /^\d+\.\d+\.\d+$/;
var KEEP_RE = /\.keep-(\d+)$/;
function isSemver(name) {
return SEMVER_RE.test(name);
}
function compareSemverDesc(a, b) {
const pa = a.split(".").map(Number);
const pb = b.split(".").map(Number);
for (let i = 0; i < 3; i++) {
if (pa[i] !== pb[i])
return pb[i] - pa[i];
}
return 0;
}
function resolveVersionedPluginDir(bundleDir) {
const pluginDir = dirname(bundleDir);
const versionsRoot = dirname(pluginDir);
const version = basename(pluginDir);
if (!isSemver(version))
return null;
if (basename(versionsRoot) !== "hivemind")
return null;
const expectedPrefix = resolve(homedir2(), ".claude", "plugins", "cache") + sep;
if (!resolve(versionsRoot).startsWith(expectedPrefix))
return null;
return { pluginDir, versionsRoot, version };
}
function isPidAlive(pid) {
try {
process.kill(pid, 0);
return true;
} catch (e) {
return e?.code === "EPERM";
}
}
function readCurrentVersionFromManifest(manifestPath) {
try {
const raw = readFileSync(manifestPath, "utf-8");
const parsed = JSON.parse(raw);
const entries = parsed?.plugins?.["hivemind@hivemind"];
if (!Array.isArray(entries))
return null;
for (const e of entries) {
if (typeof e?.version === "string" && isSemver(e.version))
return e.version;
}
return null;
} catch {
return null;
}
}
function planGc(versionsRoot, currentVersion, keepCount, isAlive = isPidAlive) {
const entries = safeReaddir(versionsRoot);
const versions = entries.filter(isSemver);
const snapshots = entries.filter((e) => KEEP_RE.test(e));
const sorted = [...versions].sort(compareSemverDesc);
const keep = /* @__PURE__ */ new Set();
if (currentVersion && versions.includes(currentVersion))
keep.add(currentVersion);
for (const v of sorted) {
if (keep.size >= keepCount)
break;
keep.add(v);
}
const deleteVersions = [];
if (currentVersion && versions.includes(currentVersion)) {
for (const v of versions) {
if (!keep.has(v))
deleteVersions.push(v);
}
}
const deleteSnapshots = [];
for (const s of snapshots) {
const m = s.match(KEEP_RE);
if (!m)
continue;
const pid = Number(m[1]);
if (!Number.isFinite(pid) || !isAlive(pid))
deleteSnapshots.push(s);
}
return { keep: [...keep], deleteVersions, deleteSnapshots };
}
function executeGc(versionsRoot, plan) {
const errors = [];
const deletedVersions = [];
const deletedSnapshots = [];
for (const v of plan.deleteVersions) {
const target = join2(versionsRoot, v);
try {
rmSync(target, { recursive: true, force: true });
deletedVersions.push(v);
} catch (e) {
errors.push(`${v}: ${e.message}`);
}
}
for (const s of plan.deleteSnapshots) {
const target = join2(versionsRoot, s);
try {
rmSync(target, { recursive: true, force: true });
deletedSnapshots.push(s);
} catch (e) {
errors.push(`${s}: ${e.message}`);
}
}
return { kept: plan.keep, deletedVersions, deletedSnapshots, errors };
}
function safeReaddir(dir) {
try {
return readdirSync(dir).filter((name) => {
try {
return statSync(join2(dir, name)).isDirectory();
} catch {
return false;
}
});
} catch {
return [];
}
}
var DEFAULT_MANIFEST_PATH = join2(homedir2(), ".claude", "plugins", "installed_plugins.json");
var DEFAULT_KEEP_COUNT = 3;

// dist/src/hooks/plugin-cache-gc.js
var defaultLog = (msg) => log("plugin-cache-gc", msg);
function runGc(bundleDir, opts = {}) {
const log2 = opts.log ?? defaultLog;
if (process.env.HIVEMIND_WIKI_WORKER === "1")
return;
const resolved = resolveVersionedPluginDir(bundleDir);
if (!resolved) {
log2("not a versioned install, skipping");
return;
}
const manifestPath = opts.manifestPath ?? DEFAULT_MANIFEST_PATH;
const keepCount = opts.keepCount ?? DEFAULT_KEEP_COUNT;
const currentVersion = readCurrentVersionFromManifest(manifestPath);
const plan = planGc(resolved.versionsRoot, currentVersion, keepCount);
if (plan.deleteVersions.length === 0 && plan.deleteSnapshots.length === 0) {
log2(`nothing to gc (kept: ${plan.keep.join(", ")})`);
return;
}
const result = executeGc(resolved.versionsRoot, plan);
log2(`gc kept=${result.kept.join(",")} deletedVersions=${result.deletedVersions.join(",")} deletedSnapshots=${result.deletedSnapshots.join(",")} errors=${result.errors.length}`);
}
var __bundleDir = dirname2(fileURLToPath(import.meta.url));
var __entryUrl = process.argv[1] ? pathToFileURL(process.argv[1]).href : "";
if (import.meta.url === __entryUrl) {
try {
runGc(__bundleDir);
} catch (e) {
defaultLog(`fatal: ${e.message}`);
}
}
export {
runGc
};
84 changes: 75 additions & 9 deletions claude-code/bundle/session-start-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

// dist/src/hooks/session-start-setup.js
import { fileURLToPath } from "node:url";
import { dirname as dirname2, join as join7 } from "node:path";
import { dirname as dirname3, join as join8 } from "node:path";
import { execSync as execSync2 } from "node:child_process";
import { homedir as homedir4 } from "node:os";
import { homedir as homedir5 } from "node:os";

// dist/src/commands/auth.js
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from "node:fs";
Expand Down Expand Up @@ -109,7 +109,7 @@ var MAX_CONCURRENCY = 5;
var QUERY_TIMEOUT_MS = Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
var INDEX_MARKER_TTL_MS = Number(process.env.HIVEMIND_INDEX_MARKER_TTL_MS ?? 6 * 60 * 6e4);
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
return new Promise((resolve2) => setTimeout(resolve2, ms));
}
function isTimeoutError(error) {
const name = error instanceof Error ? error.name.toLowerCase() : "";
Expand Down Expand Up @@ -142,7 +142,7 @@ var Semaphore = class {
this.active++;
return;
}
await new Promise((resolve) => this.waiting.push(resolve));
await new Promise((resolve2) => this.waiting.push(resolve2));
}
release() {
this.active--;
Expand Down Expand Up @@ -403,13 +403,13 @@ var DeeplakeApi = class {

// dist/src/utils/stdin.js
function readStdin() {
return new Promise((resolve, reject) => {
return new Promise((resolve2, reject) => {
let data = "";
process.stdin.setEncoding("utf-8");
process.stdin.on("data", (chunk) => data += chunk);
process.stdin.on("end", () => {
try {
resolve(JSON.parse(data));
resolve2(JSON.parse(data));
} catch (err) {
reject(new Error(`Failed to parse hook input: ${err}`));
}
Expand Down Expand Up @@ -482,10 +482,71 @@ function makeWikiLogger(hooksDir, filename = "deeplake-wiki.log") {
};
}

// dist/src/utils/plugin-cache.js
import { cpSync, existsSync as existsSync4, readdirSync, readFileSync as readFileSync5, renameSync, rmSync, statSync } from "node:fs";
import { basename, dirname as dirname2, join as join7, resolve, sep } from "node:path";
import { homedir as homedir4 } from "node:os";
var SEMVER_RE = /^\d+\.\d+\.\d+$/;
function isSemver(name) {
return SEMVER_RE.test(name);
}
function resolveVersionedPluginDir(bundleDir) {
const pluginDir = dirname2(bundleDir);
const versionsRoot = dirname2(pluginDir);
const version = basename(pluginDir);
if (!isSemver(version))
return null;
if (basename(versionsRoot) !== "hivemind")
return null;
const expectedPrefix = resolve(homedir4(), ".claude", "plugins", "cache") + sep;
if (!resolve(versionsRoot).startsWith(expectedPrefix))
return null;
return { pluginDir, versionsRoot, version };
}
function snapshotPath(pluginDir, pid) {
return `${pluginDir}.keep-${pid}`;
}
function snapshotPluginDir(pluginDir, pid = process.pid) {
if (!existsSync4(pluginDir))
return null;
const snapshot = snapshotPath(pluginDir, pid);
try {
rmSync(snapshot, { recursive: true, force: true });
cpSync(pluginDir, snapshot, { recursive: true, dereference: false });
return { pluginDir, snapshot };
} catch {
return null;
}
}
function restoreOrCleanup(handle) {
if (!handle)
return "noop";
const { pluginDir, snapshot } = handle;
try {
if (!existsSync4(pluginDir)) {
if (existsSync4(snapshot)) {
renameSync(snapshot, pluginDir);
return "restored";
}
return "noop";
}
rmSync(snapshot, { recursive: true, force: true });
return "cleaned";
} catch (e) {
try {
process.stderr.write(`[plugin-cache] restoreOrCleanup failed for ${pluginDir}: ${e?.message}
`);
} catch {
}
return "restore-failed";
}
}
var DEFAULT_MANIFEST_PATH = join7(homedir4(), ".claude", "plugins", "installed_plugins.json");

// dist/src/hooks/session-start-setup.js
var log3 = (msg) => log("session-setup", msg);
var __bundleDir = dirname2(fileURLToPath(import.meta.url));
var { log: wikiLog } = makeWikiLogger(join7(homedir4(), ".claude", "hooks"));
var __bundleDir = dirname3(fileURLToPath(import.meta.url));
var { log: wikiLog } = makeWikiLogger(join8(homedir5(), ".claude", "hooks"));
async function main() {
if (process.env.HIVEMIND_WIKI_WORKER === "1")
return;
Expand Down Expand Up @@ -526,14 +587,19 @@ async function main() {
if (latest && isNewer(latest, current)) {
if (autoupdate) {
log3(`autoupdate: updating ${current} \u2192 ${latest}`);
const resolved = resolveVersionedPluginDir(__bundleDir);
const handle = resolved ? snapshotPluginDir(resolved.pluginDir) : null;
try {
const scopes = ["user", "project", "local", "managed"];
const cmd = scopes.map((s) => `claude plugin update hivemind@hivemind --scope ${s} 2>/dev/null`).join("; ");
const cmd = scopes.map((s) => `claude plugin update hivemind@hivemind --scope ${s} 2>/dev/null || true`).join("; ");
execSync2(cmd, { stdio: "ignore", timeout: 6e4 });
const outcome = restoreOrCleanup(handle);
log3(`autoupdate snapshot outcome: ${outcome}`);
process.stderr.write(`\u2705 Hivemind auto-updated: ${current} \u2192 ${latest}. Run /reload-plugins to apply.
`);
log3(`autoupdate succeeded: ${current} \u2192 ${latest}`);
} catch (e) {
restoreOrCleanup(handle);
process.stderr.write(`\u2B06\uFE0F Hivemind update available: ${current} \u2192 ${latest}. Auto-update failed \u2014 run /hivemind:update to upgrade manually.
`);
log3(`autoupdate failed: ${e.message}`);
Expand Down
Loading
Loading