akm-cli 0.9.0-beta.3 → 0.9.0-beta.4
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 +161 -0
- package/dist/commands/improve/consolidate.js +15 -2
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -341,6 +341,167 @@ proposal and log storage, `--format html` output, and per-stage LLM telemetry.
|
|
|
341
341
|
`migrate-storage` change is pinned by a sha256 + file-mode fixture-stash
|
|
342
342
|
differential test.
|
|
343
343
|
|
|
344
|
+
## [0.8.14] - 2026-06-11
|
|
345
|
+
|
|
346
|
+
### Fixed
|
|
347
|
+
|
|
348
|
+
- **`akm extract` minContentChars default lowered from 500 to 10.** The 500-char
|
|
349
|
+
threshold used inputCount (raw session size) but analysis showed 209 of 218
|
|
350
|
+
candidate-producing sessions had inputCount < 500 — tiny agent sessions (22–368
|
|
351
|
+
chars) regularly yield 1–5 candidates. The only reliably skippable sessions are
|
|
352
|
+
empty ones (0 chars, journal files). Default lowered to 10 to catch only
|
|
353
|
+
truly empty sessions while preserving all signal-bearing content. Closes #597.
|
|
354
|
+
|
|
355
|
+
## [0.8.13] - 2026-06-11
|
|
356
|
+
|
|
357
|
+
### Fixed
|
|
358
|
+
|
|
359
|
+
- **`akm extract` minContentChars gate filtered all sessions.** The threshold was
|
|
360
|
+
checked against `filtered.stats.outputCount` (post-noise-filter chars), but the
|
|
361
|
+
pre-filter strips so much boilerplate that even signal-bearing sessions end up
|
|
362
|
+
below 500 chars of output. All 75 sessions in the first post-deploy run were
|
|
363
|
+
filtered, dropping candidates from 4–13 to 0. Fix: gate on `inputCount` (raw
|
|
364
|
+
session size) instead — a session with < 500 raw chars has nothing worth
|
|
365
|
+
extracting regardless of what the pre-filter produces. Closes #596.
|
|
366
|
+
|
|
367
|
+
## [0.8.12] - 2026-06-11
|
|
368
|
+
|
|
369
|
+
### Fixed
|
|
370
|
+
|
|
371
|
+
- **`akm extract` calling the LLM for noise sessions that never yield candidates.**
|
|
372
|
+
96% of processed sessions (72/75 measured) produced zero candidates, consuming
|
|
373
|
+
~330 s of LLM time per run. The pre-filter had no minimum content threshold —
|
|
374
|
+
sessions as short as 50 chars were sent to the LLM regardless. A new
|
|
375
|
+
`minContentChars` gate (default 500) skips the LLM call when post-filter
|
|
376
|
+
content falls below threshold, cutting extract LLM calls by ~95% on typical
|
|
377
|
+
stashes. Configurable via `profiles.improve.<name>.processes.extract.minContentChars`.
|
|
378
|
+
Closes #595.
|
|
379
|
+
|
|
380
|
+
## [0.8.11] - 2026-06-11
|
|
381
|
+
|
|
382
|
+
### Fixed
|
|
383
|
+
|
|
384
|
+
- **`akm improve --profile <name>` ignored profile's `extract.enabled: false` setting.**
|
|
385
|
+
The session-extraction gate in the preparation stage called
|
|
386
|
+
`isLlmFeatureEnabled(config, "session_extraction")`, which hardcodes a lookup
|
|
387
|
+
against `profiles.improve.default.processes.extract.enabled`. Any non-default
|
|
388
|
+
profile that set `extract.enabled: false` (e.g. `quick-shredder`) was silently
|
|
389
|
+
ignored, causing the extract pass to run regardless. The fix adds a
|
|
390
|
+
`resolveProcessEnabled("extract", improveProfile)` check so the active
|
|
391
|
+
resolved profile gates the pass correctly. Closes #593.
|
|
392
|
+
|
|
393
|
+
## [0.8.10] - 2026-06-11
|
|
394
|
+
|
|
395
|
+
### Fixed
|
|
396
|
+
|
|
397
|
+
- **`akm improve` taking 8–10 minutes per run due to O(n) DB writes for
|
|
398
|
+
profile-filtered refs.** When a profile disables reflect and distill for
|
|
399
|
+
certain asset types, `collectEligibleRefs` marks those refs as
|
|
400
|
+
`profile_filtered_all_passes`. The caller then emitted one `improve_skipped`
|
|
401
|
+
event per ref — a sequential DB write for each. On a ~9 000-ref stash this
|
|
402
|
+
was ~500 s of SQLite writes before any consolidation or memory inference
|
|
403
|
+
began. The fix collapses the per-ref loop into a single summary event
|
|
404
|
+
carrying a `count` field, eliminating ~9 000 sequential writes per run.
|
|
405
|
+
Closes #590.
|
|
406
|
+
|
|
407
|
+
## [0.8.9] - 2026-06-11
|
|
408
|
+
|
|
409
|
+
### Fixed
|
|
410
|
+
|
|
411
|
+
- **`akm improve` validation pass was O(n) in stash size, causing ~510 s overhead
|
|
412
|
+
on large stashes.** For every indexed ref, the preparation phase called
|
|
413
|
+
`findAssetFilePath()` — an async round-trip to the index DB followed by a
|
|
414
|
+
filesystem probe — serially inside a `for…await` loop. With ~9 000 indexed
|
|
415
|
+
refs at ~55 ms each, this loop consumed the entire 600–900 s run budget before
|
|
416
|
+
any reflect, triage, or memory-inference work began. The fix threads
|
|
417
|
+
`filePath` from the planning stage (`collectEligibleRefs`) through
|
|
418
|
+
`ImproveEligibleRef` so the validation pass and the disk-existence guard can
|
|
419
|
+
use the pre-resolved path directly. The async lookup is retained only as a
|
|
420
|
+
fallback for refs that enter via a narrow scope (e.g. `--scope ref:foo`).
|
|
421
|
+
Closes #587.
|
|
422
|
+
|
|
423
|
+
## [0.8.8] - 2026-06-11
|
|
424
|
+
|
|
425
|
+
### Fixed
|
|
426
|
+
|
|
427
|
+
- **SQLite `SQLITE_BUSY` errors under concurrent improve runs.** `busy_timeout`
|
|
428
|
+
was set to 5 000 ms in all three database open paths (`openDatabase`,
|
|
429
|
+
`openExistingDatabase`, `openStateDatabase`). Under a busy cron schedule — or
|
|
430
|
+
when a reindex triggered by memory inference ran concurrently with an event
|
|
431
|
+
write — the 5 s window was routinely exhausted, producing "database is locked"
|
|
432
|
+
failures. Raised to 30 000 ms across all three paths so transient lock
|
|
433
|
+
contention is retried for up to 30 s before surfacing as an error.
|
|
434
|
+
|
|
435
|
+
## [0.8.7] - 2026-06-09
|
|
436
|
+
|
|
437
|
+
### Fixed
|
|
438
|
+
|
|
439
|
+
- **`incrementalSince` duration strings were silently ignored.** Values like
|
|
440
|
+
`"30m"`, `"24h"`, `"7d"` were passed raw to `narrowToIncrementalCandidates`,
|
|
441
|
+
which compared them against ISO timestamps via string sort. All `2026-...`
|
|
442
|
+
timestamps are lexicographically less than `"30m"` (`'2' < '3'`) and `"24h"`
|
|
443
|
+
(`"20" < "24"`), so `isChanged()` always returned `false` and the candidate
|
|
444
|
+
pool was silently emptied rather than filtered to the window. The fix adds
|
|
445
|
+
`parseSinceToIso()`, which resolves human duration strings to absolute ISO
|
|
446
|
+
timestamps before comparison. Values that already look like ISO timestamps
|
|
447
|
+
are passed through unchanged.
|
|
448
|
+
|
|
449
|
+
## [0.8.6] - 2026-06-09
|
|
450
|
+
|
|
451
|
+
### Added
|
|
452
|
+
|
|
453
|
+
- **`consolidate.incrementalSince` profile config field.** Setting
|
|
454
|
+
`incrementalSince: "7d"` (or any duration string) in the `consolidate` block
|
|
455
|
+
of an improve profile narrows the candidate pool to memories modified within
|
|
456
|
+
that window plus their top-5 graph neighbours, keeping each pass focused on
|
|
457
|
+
recent changes. This makes it practical to run consolidation more often than
|
|
458
|
+
once per day (e.g. via `akm-improve-consolidate` every 4 h) without
|
|
459
|
+
re-scanning the full pool every time. The nightly default profile leaves this
|
|
460
|
+
unset (full-pool sweep, same as before). The `incrementalSince` option already
|
|
461
|
+
existed in `akmConsolidate()` but was hardcoded off at the call site; the
|
|
462
|
+
field is now surfaced in the config schema and read from the profile.
|
|
463
|
+
|
|
464
|
+
## [0.8.5] - 2026-06-09
|
|
465
|
+
|
|
466
|
+
### Fixed
|
|
467
|
+
|
|
468
|
+
- **Consolidation starved merge recall; the memory pool grew unbounded.** Commit
|
|
469
|
+
`633ece41` made the `incrementalSince` narrowing unconditional, so every
|
|
470
|
+
consolidation run only judged memories changed since the last run plus their
|
|
471
|
+
immediate vector-neighbors. Stale-but-unmerged duplicate clusters were never
|
|
472
|
+
re-examined, so the eligible pool grew monotonically and never shrank, and
|
|
473
|
+
contradiction detection (which rides on the consolidation pass) went dark.
|
|
474
|
+
Consolidation only runs on the nightly default-profile pass (`quick`/`frequent`
|
|
475
|
+
disable it), so a full-pool sweep is correct and affordable; the override is
|
|
476
|
+
removed. `lastConsolidateTs` still gates whether the pass runs.
|
|
477
|
+
|
|
478
|
+
## [0.8.4] - 2026-06-08
|
|
479
|
+
|
|
480
|
+
### Fixed
|
|
481
|
+
|
|
482
|
+
- **`akm tasks sync` ignored schedule changes.** Sync classified any task already
|
|
483
|
+
present in the OS scheduler as "unchanged" without comparing its installed
|
|
484
|
+
entry, so editing a task's `schedule:` in the `.yml` never reached the crontab —
|
|
485
|
+
the only way to apply a new schedule was to `remove` and re-`add` the task. The
|
|
486
|
+
same gap affected `tasks enable`/`disable`, which merely toggled the existing
|
|
487
|
+
cron line's comment and so re-enabled a stale schedule. Sync now compares the
|
|
488
|
+
backend's installed signature against the signature the current definition would
|
|
489
|
+
produce and reinstalls on drift (reported in a new `updated[]` field);
|
|
490
|
+
`enable`/`disable` reinstall from the current `.yml` instead of toggling in
|
|
491
|
+
place. Backends that can't cheaply read their installed form fall back to an
|
|
492
|
+
idempotent reinstall, so the fix is correct on launchd/schtasks too. The cron
|
|
493
|
+
backend gains `expectedSignature()` and a signature on each `list()` entry.
|
|
494
|
+
|
|
495
|
+
### Added
|
|
496
|
+
|
|
497
|
+
- **`akm improve --skip-if-locked`.** When another improve run already holds the
|
|
498
|
+
lock, the run logs and exits 0 with a no-op result (`skipped.reason:
|
|
499
|
+
"lock-held"`) instead of failing with the "already running" config error
|
|
500
|
+
(exit 78). Intended for high-frequency scheduled runs (e.g. an every-30-min
|
|
501
|
+
`quick` pass) that would otherwise pile up exit-78 failures whenever a longer
|
|
502
|
+
run overlaps them. Default off — the hard error is preserved for interactive
|
|
503
|
+
use. The result is still recorded so the skip is auditable.
|
|
504
|
+
|
|
344
505
|
## [0.8.3] - 2026-06-08
|
|
345
506
|
|
|
346
507
|
### Fixed
|
|
@@ -1992,10 +1992,23 @@ async function checkPreEmitDedup(opts) {
|
|
|
1992
1992
|
* everything changed or the index can't answer (fail-open to preserve merge
|
|
1993
1993
|
* correctness). `since` is an ISO timestamp.
|
|
1994
1994
|
*/
|
|
1995
|
+
/**
|
|
1996
|
+
* Parse a human-readable duration string (e.g. "30m", "24h", "7d") to an ISO
|
|
1997
|
+
* timestamp representing `now - duration`. Returns the input unchanged when it
|
|
1998
|
+
* doesn't match the pattern (assumed to already be an ISO timestamp).
|
|
1999
|
+
*/
|
|
2000
|
+
function parseSinceToIso(since) {
|
|
2001
|
+
const m = since.match(/^(\d+)(m|h|d)$/);
|
|
2002
|
+
if (!m)
|
|
2003
|
+
return since;
|
|
2004
|
+
const multiplier = { m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2]];
|
|
2005
|
+
return new Date(Date.now() - parseInt(m[1], 10) * multiplier).toISOString();
|
|
2006
|
+
}
|
|
1995
2007
|
export function narrowToIncrementalCandidates(memories, since, warnings) {
|
|
2008
|
+
const sinceIso = parseSinceToIso(since);
|
|
1996
2009
|
const isChanged = (m) => {
|
|
1997
2010
|
try {
|
|
1998
|
-
return fs.statSync(m.filePath).mtime.toISOString() >
|
|
2011
|
+
return fs.statSync(m.filePath).mtime.toISOString() > sinceIso;
|
|
1999
2012
|
}
|
|
2000
2013
|
catch {
|
|
2001
2014
|
return true; // never silently drop a memory we cannot stat
|
|
@@ -2037,7 +2050,7 @@ export function narrowToIncrementalCandidates(memories, since, warnings) {
|
|
|
2037
2050
|
closeDatabase(db);
|
|
2038
2051
|
}
|
|
2039
2052
|
const candidates = memories.filter((m) => keep.has(m.name));
|
|
2040
|
-
warnings.push(`Incremental consolidation: ${changed.length} changed + neighbours → ${candidates.length}/${memories.length} memories considered (since ${since}).`);
|
|
2053
|
+
warnings.push(`Incremental consolidation: ${changed.length} changed + neighbours → ${candidates.length}/${memories.length} memories considered (since ${since}${sinceIso !== since ? ` = ${sinceIso}` : ""}).`);
|
|
2041
2054
|
return candidates;
|
|
2042
2055
|
}
|
|
2043
2056
|
function loadMemoriesForSource(source, stashDir, warnings) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.0-beta.
|
|
3
|
+
"version": "0.9.0-beta.4",
|
|
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": [
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"test:unit": "bun test --parallel=${TEST_PARALLEL:-12} --timeout=30000 ./tests --path-ignore-patterns=tests/integration",
|
|
60
60
|
"test:integration": "bun test --parallel=${TEST_PARALLEL:-12} --timeout=30000 ./tests/integration ./tests/commands ./tests/workflows",
|
|
61
61
|
"test:node-smoke": "bun scripts/node-smoke.ts",
|
|
62
|
+
"test:node-compat": "AKM_NODE_COMPAT_TESTS=1 bun test --timeout=120000 tests/integration/node-compat.test.ts",
|
|
62
63
|
"test:sharded": "bun test ./tests --shard=1/4 & bun test ./tests --shard=2/4 & bun test ./tests --shard=3/4 & bun test ./tests --shard=4/4 & wait",
|
|
63
64
|
"test:time": "bun scripts/test-timing-report.ts",
|
|
64
65
|
"lint:isolation": "bun scripts/lint-tests-isolation.ts",
|