akm-cli 0.8.2 → 0.8.3
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/CHANGELOG.md +20 -0
- package/dist/assets/profiles/quick.json +2 -1
- package/dist/commands/improve.js +18 -1
- package/dist/core/file-lock.js +22 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [0.8.3] - 2026-06-08
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`improve.lock` leaked on signal death (cron timeout).** The improve
|
|
12
|
+
SIGTERM/SIGINT/SIGHUP handler calls `process.exit()`, which skips `finally`
|
|
13
|
+
blocks — so the `finally` that releases `improve.lock` never ran, and every
|
|
14
|
+
timed-out cron run leaked the lock sentinel. (It wasn't a permanent deadlock
|
|
15
|
+
only because the next run reclaims a dead-PID lock, a path that PID reuse can
|
|
16
|
+
defeat.) The lock is now released from a `process.on("exit", …)` handler
|
|
17
|
+
registered at acquire time (exit handlers DO run on `process.exit()`), via a
|
|
18
|
+
new ownership-checked `releaseLockIfOwned(path, pid)` so a backstop release can
|
|
19
|
+
never delete a different run's lock. This generalizes to the budget watchdog
|
|
20
|
+
and any future exit path.
|
|
21
|
+
- **`quick` profile was not quick.** It was documented "Reflect-only" but did
|
|
22
|
+
not disable the session-`extract` process (which is default-ON), so a `quick`
|
|
23
|
+
run processed the entire unindexed-session backlog (~40 min) — guaranteeing a
|
|
24
|
+
5-minute cron timeout → SIGTERM → the lock leak above, every run. `quick` now
|
|
25
|
+
explicitly sets `processes.extract.enabled: false`.
|
|
26
|
+
|
|
7
27
|
## [0.8.2] - 2026-06-05
|
|
8
28
|
|
|
9
29
|
### Added
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "Reflect-only pass — no distill, consolidate, memoryInference, or graphExtraction.",
|
|
2
|
+
"description": "Reflect-only pass — no extract, distill, consolidate, memoryInference, or graphExtraction.",
|
|
3
3
|
"processes": {
|
|
4
4
|
"reflect": {
|
|
5
5
|
"enabled": true,
|
|
6
6
|
"allowedTypes": ["agent", "command", "knowledge", "lesson", "memory", "skill", "wiki", "workflow"]
|
|
7
7
|
},
|
|
8
|
+
"extract": { "enabled": false },
|
|
8
9
|
"distill": { "enabled": false },
|
|
9
10
|
"consolidate": { "enabled": false },
|
|
10
11
|
"memoryInference": { "enabled": false },
|
package/dist/commands/improve.js
CHANGED
|
@@ -8,7 +8,7 @@ import { daysToMs, isAssetType } from "../core/common";
|
|
|
8
8
|
import { getDefaultLlmConfig, loadConfig } from "../core/config";
|
|
9
9
|
import { ConfigError, NotFoundError, rethrowIfTestIsolationError, UsageError } from "../core/errors";
|
|
10
10
|
import { appendEvent, readEvents } from "../core/events";
|
|
11
|
-
import { probeLock, releaseLock, tryAcquireLockSync } from "../core/file-lock";
|
|
11
|
+
import { probeLock, releaseLock, releaseLockIfOwned, tryAcquireLockSync } from "../core/file-lock";
|
|
12
12
|
import { parseFrontmatter } from "../core/frontmatter";
|
|
13
13
|
import { detectAndWriteContradictions } from "../core/memory-contradiction-detect";
|
|
14
14
|
import { analyzeMemoryCleanup, applyMemoryCleanup, } from "../core/memory-improve";
|
|
@@ -497,6 +497,17 @@ export async function akmImprove(options = {}) {
|
|
|
497
497
|
}
|
|
498
498
|
lockAcquired = false;
|
|
499
499
|
};
|
|
500
|
+
// Signal-safe lock release (0.8.3 hotfix). The SIGTERM/SIGINT/SIGHUP handler
|
|
501
|
+
// in improve-cli.ts calls `process.exit()`, which does NOT run the `finally`
|
|
502
|
+
// below that owns lock release — so a cron-timeout SIGTERM leaked
|
|
503
|
+
// `improve.lock` every run. `process.exit()` DOES fire `'exit'` listeners,
|
|
504
|
+
// so we release the lock from one. `releaseLockIfOwned` only unlinks a lock
|
|
505
|
+
// still owned by this PID, so it is safe even if a later run re-acquired it.
|
|
506
|
+
// The listener is removed in the `finally` so the normal path stays single-release
|
|
507
|
+
// and repeated in-process `akmImprove` calls (tests) do not accumulate listeners.
|
|
508
|
+
const releaseLockOnExit = () => {
|
|
509
|
+
releaseLockIfOwned(resolvedLockPath, process.pid);
|
|
510
|
+
};
|
|
500
511
|
const preEnsureCleanupWarnings = [];
|
|
501
512
|
let plannedRefs;
|
|
502
513
|
let memorySummary;
|
|
@@ -510,6 +521,9 @@ export async function akmImprove(options = {}) {
|
|
|
510
521
|
if (!options.dryRun) {
|
|
511
522
|
acquireLock();
|
|
512
523
|
lockAcquired = true;
|
|
524
|
+
// Backstop release on process.exit() (signal handler / budget watchdog),
|
|
525
|
+
// which skips the finally below. Removed in that finally on the normal path.
|
|
526
|
+
process.on("exit", releaseLockOnExit);
|
|
513
527
|
// Phase 4 triage pre-pass (§7, §13): drain the standing pending backlog
|
|
514
528
|
// BEFORE ensureIndex so improve generates fresh proposals against a cleared
|
|
515
529
|
// queue (no `duplicate_pending` collisions) and ensureIndex absorbs triage's
|
|
@@ -922,6 +936,9 @@ export async function akmImprove(options = {}) {
|
|
|
922
936
|
catch {
|
|
923
937
|
// ignore
|
|
924
938
|
}
|
|
939
|
+
// The normal path released the lock above; drop the process.exit backstop so
|
|
940
|
+
// it does not fire later (or accumulate across repeated in-process calls).
|
|
941
|
+
process.removeListener("exit", releaseLockOnExit);
|
|
925
942
|
// I1: close the long-lived state.db connection opened at the top of the run.
|
|
926
943
|
try {
|
|
927
944
|
eventsDb?.close();
|
package/dist/core/file-lock.js
CHANGED
|
@@ -79,6 +79,28 @@ export function releaseLock(lockPath) {
|
|
|
79
79
|
// Sentinel already gone — fine.
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Release a lock ONLY if it is still owned by `ownerPid`. Safe to call from a
|
|
84
|
+
* `process.exit()` / `'exit'` handler as a backstop: `process.exit()` skips
|
|
85
|
+
* `finally` blocks — so the normal lock-release never runs on signal death
|
|
86
|
+
* (SIGTERM/SIGINT) — but it DOES fire `'exit'` listeners synchronously. Checking
|
|
87
|
+
* ownership first means that if the lock was already released and re-acquired by
|
|
88
|
+
* a different process, this leaves that process's lock intact (no cross-run
|
|
89
|
+
* deletion / PID-reuse footgun). Synchronous so it is valid inside an exit handler.
|
|
90
|
+
*/
|
|
91
|
+
export function releaseLockIfOwned(lockPath, ownerPid) {
|
|
92
|
+
let rawContent;
|
|
93
|
+
try {
|
|
94
|
+
rawContent = fs.readFileSync(lockPath, "utf8");
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Absent or unreadable — nothing of ours to release.
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (extractHolderPid(rawContent) === ownerPid) {
|
|
101
|
+
releaseLock(lockPath);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
82
104
|
/**
|
|
83
105
|
* Extract a PID from a sentinel body. Accepts the two shapes used across
|
|
84
106
|
* the codebase: a bare numeric string (config-io, vault, lockfile) and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Knowledge Management) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
|
|
6
6
|
"keywords": [
|