akm-cli
Version:
akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur
66 lines (65 loc) • 3.27 kB
JavaScript
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
/**
* Opt-in, non-blocking rebuild lock for `akm index` (#956).
*
* #872 removed the blocking index-rebuild lease: the index is a regenerable
* cache, so a concurrent rebuild only wastes work rather than corrupts
* anything, and a live-but-wedged holder passed a PID-liveness check forever
* — only an age-based clock could ever free it, which is exactly the hazard
* #872 deleted. This module does not reinstate that lock. It adds a
* PID-liveness-only sentinel an explicit `akm index` run acquires and
* releases on exit purely so a *scheduled or opportunistic* run
* (`--skip-if-locked`) can step aside instead of piling up behind a rebuild
* already in progress. A human-typed `akm index` with no flag is never
* gated: it warns and proceeds exactly as it did before this lock existed.
*
* Built on the shared PID-liveness mechanics in `core/run-lock.ts` (the same
* ones `akm improve`'s whole-run lock uses) — see that module's doc for the
* no-stale-age-window rationale.
*/
import { releaseLock } from "../core/file-lock.js";
import { getIndexRebuildLockPath } from "../core/paths.js";
import { formatLockHolderPid, tryAcquireRunLock } from "../core/run-lock.js";
import { warn, warnVerbose } from "../core/warn.js";
export function indexRebuildLockPath() {
return getIndexRebuildLockPath();
}
/**
* Acquire the rebuild lock for the duration of one `akm index` run.
*
* - Free: always returns `"acquired"`.
* - Held, `skipIfLocked`: warns once (naming the holder) and returns
* `"skipped"` — the caller must not run `akmIndex()` at all.
* - Held, no flag: warns once and returns `"contended"` — the caller runs
* `akmIndex()` unlocked, exactly as every `akm index` did before #956.
*
* A dead holder's lease is reclaimed silently (verbose-only log line, never
* a user-facing warning) — the operator did nothing wrong and nothing here
* requires their attention.
*/
export function tryAcquireIndexRebuildLock(skipIfLocked) {
const lockPath = indexRebuildLockPath();
const acquire = () => tryAcquireRunLock(lockPath, {
label: "index rebuild",
onReclaimed: (info) => {
warnVerbose(`[index] reclaimed a rebuild lock left by pid ${info.holderPid ?? "unknown"} ` +
`(${info.reason}); that process is no longer running.`);
},
});
const result = acquire();
if (result.state === "acquired")
return result;
if (skipIfLocked) {
warn(`[index] another index run holds the lock (PID ${formatLockHolderPid(result.holder)}, started ${result.holder.startedAt}); ` +
"skipping (--skip-if-locked)");
return { state: "skipped", holder: result.holder };
}
warn(`[index] another index run is active (pid ${formatLockHolderPid(result.holder)}, started ${result.holder.startedAt}); ` +
"this run will contend with it — pass --skip-if-locked for scheduled runs");
return { state: "contended", holder: result.holder };
}
export function releaseIndexRebuildLock(ownership) {
releaseLock(ownership);
}