akm-cli 0.9.0-beta.55 → 0.9.0-beta.57

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.
@@ -1278,9 +1278,8 @@ export function applyFeedbackToUtilityScore(db, entryId, positiveCount, negative
1278
1278
  /**
1279
1279
  * Re-link detached usage_events to their current entry_ids via entry_ref.
1280
1280
  *
1281
- * After a full rebuild, entry IDs change. This query matches events to their
1282
- * new entry rows using the stable `entry_ref` ("type:name") column so usage
1283
- * history survives a full reindex.
1281
+ * After a full rebuild, entry IDs change. This restores each event's link
1282
+ * using the stable `entry_ref` column so usage history survives a reindex.
1284
1283
  */
1285
1284
  export function relinkUsageEvents(db) {
1286
1285
  bestEffort(() => {
@@ -1297,17 +1296,34 @@ export function relinkUsageEvents(db) {
1297
1296
  WHERE entry_id IS NOT NULL
1298
1297
  AND entry_id NOT IN (SELECT id FROM entries)
1299
1298
  `);
1300
- // Step 2: re-resolve any null entry_id from entry_ref against the
1301
- // current entries table. Picks up entries that were re-created with
1302
- // the same ref (e.g. an asset moved between sources).
1303
- db.exec(`
1304
- UPDATE usage_events SET entry_id = (
1305
- SELECT e.id FROM entries e
1306
- WHERE substr(e.entry_key, length(e.entry_key) - length(usage_events.entry_ref)) = ':' || usage_events.entry_ref
1307
- LIMIT 1
1308
- )
1309
- WHERE entry_id IS NULL AND entry_ref IS NOT NULL
1310
- `);
1299
+ // Step 2: re-resolve any null entry_id from entry_ref against the current
1300
+ // entries table, reusing the SAME canonical resolver the read path uses at
1301
+ // insert time (`findEntryIdByRef` `parseAssetRef`). Resolving per DISTINCT
1302
+ // ref keeps this O(distinct-refs) indexed lookups instead of the previous
1303
+ // O(events × entries) non-indexable `substr(entry_key, …)` scan. It also
1304
+ // fixes a silent correctness bug: the old suffix match compared the RAW
1305
+ // `entry_ref`, so origin-qualified refs ("source//type:name") never matched
1306
+ // an `entry_key` and lost their usage history on every full rebuild.
1307
+ const refs = db
1308
+ .prepare("SELECT DISTINCT entry_ref AS ref FROM usage_events WHERE entry_id IS NULL AND entry_ref IS NOT NULL")
1309
+ .all();
1310
+ const update = db.prepare("UPDATE usage_events SET entry_id = ? WHERE entry_ref = ? AND entry_id IS NULL");
1311
+ const relinkTx = db.transaction(() => {
1312
+ for (const { ref } of refs) {
1313
+ let id;
1314
+ try {
1315
+ id = findEntryIdByRef(db, ref);
1316
+ }
1317
+ catch (err) {
1318
+ if (err instanceof Error && err.name === "UsageError")
1319
+ continue;
1320
+ throw err;
1321
+ }
1322
+ if (id !== undefined)
1323
+ update.run(id, ref);
1324
+ }
1325
+ });
1326
+ relinkTx();
1311
1327
  }, "usage_events table may not exist yet during entry_id re-resolution");
1312
1328
  }
1313
1329
  // ── registry_index_cache helpers ─────────────────────────────────────────────
@@ -29,16 +29,6 @@ const FEEDBACK_REWARD_POSITIVE = 1.0;
29
29
  * Reward 0.0 means "not helpful" (lowest MemRL signal).
30
30
  */
31
31
  const FEEDBACK_REWARD_NEGATIVE = 0.0;
32
- /**
33
- * Maximum total negative utility delta allowed in a single
34
- * `applyFeedbackToUtilityScore` call regardless of negativeCount.
35
- *
36
- * This caps the per-day negative impact (the function is called once per
37
- * feedback event — spamming 10 negatives in one session can move utility
38
- * at most `MAX_NEG_DELTA_PER_CALL`). The cap prevents a noisy negative-
39
- * feedback stream from silently destroying a high-utility asset's ranking.
40
- */
41
- export const MAX_NEG_DELTA_PER_CALL = 0.15;
42
32
  /**
43
33
  * Utility threshold below which a review-needed escalation is triggered.
44
34
  * When a previously high-utility asset (≥ HIGH_UTILITY_THRESHOLD) drops
@@ -57,8 +47,12 @@ export const HIGH_UTILITY_THRESHOLD = 0.5;
57
47
  * reward = weighted average of positive and negative signals
58
48
  * nextUtil = clamp(currentUtil + lr × (reward − currentUtil), 0, 1)
59
49
  *
60
- * The negative impact is additionally capped at {@link MAX_NEG_DELTA_PER_CALL}
61
- * to prevent a noisy feedback stream from silently erasing a high-utility asset.
50
+ * The step is inherently bounded: reward [0, 1] and currentUtil ∈ [0, 1], so
51
+ * a single call moves utility by at most {@link FEEDBACK_LR} in either
52
+ * direction. `reward` is a proportion of the counts, not their magnitude, so
53
+ * with no positive signals the number of negatives is irrelevant (reward is 0
54
+ * whether there is 1 negative or 100). Mixing in positives shifts reward and so
55
+ * the step, but never past the learning-rate bound.
62
56
  *
63
57
  * Pure: no DB access. When both counts are zero, utility is unchanged.
64
58
  */
@@ -73,12 +67,8 @@ export function computeNextUtility(previousUtility, positiveCount, negativeCount
73
67
  : negativeCount > 0 && positiveCount === 0
74
68
  ? FEEDBACK_REWARD_NEGATIVE
75
69
  : (positiveCount * FEEDBACK_REWARD_POSITIVE + negativeCount * FEEDBACK_REWARD_NEGATIVE) / total;
76
- // MemRL bounded-step EMA: lr × (reward − current)
77
- let delta = FEEDBACK_LR * (reward - previousUtility);
78
- // Per-call negative cap: if delta is negative (net negative feedback), cap it.
79
- if (delta < 0) {
80
- delta = Math.max(delta, -MAX_NEG_DELTA_PER_CALL);
81
- }
70
+ // MemRL bounded-step EMA: lr × (reward − current). |delta| ≤ FEEDBACK_LR.
71
+ const delta = FEEDBACK_LR * (reward - previousUtility);
82
72
  const nextUtility = Math.max(0, Math.min(1, previousUtility + delta));
83
73
  const crossedReviewThreshold = previousUtility >= HIGH_UTILITY_THRESHOLD && nextUtility < UTILITY_REVIEW_THRESHOLD;
84
74
  return { previousUtility, nextUtility, crossedReviewThreshold };
@@ -1,7 +1,13 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- const COMMON_PASSTHROUGH = ["HOME", "PATH", "USER", "LANG", "LC_ALL", "TERM", "TMPDIR"];
4
+ // AKM_EVENT_SOURCE carries usage-event provenance (improve/task) so that akm
5
+ // invocations a spawned agent makes are recorded as machine traffic, not user
6
+ // demand (DRIFT-6). Without it in the passthrough whitelist, buildChildEnv drops
7
+ // the stamp at the agent boundary — e.g. `akm wiki ingest` spawns an agent whose
8
+ // `akm curate/show/search` tool-calls then log source='user', silently inflating
9
+ // every lane's read-back (GRR). It is a provenance tag, never a secret.
10
+ const COMMON_PASSTHROUGH = ["HOME", "PATH", "USER", "LANG", "LC_ALL", "TERM", "TMPDIR", "AKM_EVENT_SOURCE"];
5
11
  /**
6
12
  * Built-in profiles for the five agent CLIs the v1 spec calls out
7
13
  * explicitly. The fields here are conservative defaults — every value is
@@ -25,7 +25,9 @@ export function getCachedEmbedding(key) {
25
25
  return cached;
26
26
  }
27
27
  export function setCachedEmbedding(key, value) {
28
- // Evict oldest entry if at capacity
28
+ // Delete first so an overwrite refreshes LRU recency AND is not counted as a
29
+ // new insert: only a genuinely new key at capacity should evict the oldest.
30
+ embedCache.delete(key);
29
31
  if (embedCache.size >= EMBED_CACHE_MAX) {
30
32
  const oldest = embedCache.keys().next().value;
31
33
  if (oldest !== undefined) {
@@ -238,6 +238,11 @@ function tryParseLocalRef(rawRef, explicitPath) {
238
238
  };
239
239
  }
240
240
  function isPathLikeRef(ref) {
241
+ // A leading `@` marks an npm scope (`@scope/pkg`), never a filesystem path —
242
+ // treat it as non-path so it falls through to the npm branch instead of
243
+ // being resolved (and rejected) as an explicit local path.
244
+ if (ref.startsWith("@"))
245
+ return false;
241
246
  if (ref === "." || ref === "..")
242
247
  return true;
243
248
  if (path.isAbsolute(ref))
@@ -9715,15 +9715,12 @@ function computeNextUtility(previousUtility, positiveCount, negativeCount) {
9715
9715
  }
9716
9716
  const total = positiveCount + negativeCount;
9717
9717
  const reward = positiveCount > 0 && negativeCount === 0 ? FEEDBACK_REWARD_POSITIVE : negativeCount > 0 && positiveCount === 0 ? FEEDBACK_REWARD_NEGATIVE : (positiveCount * FEEDBACK_REWARD_POSITIVE + negativeCount * FEEDBACK_REWARD_NEGATIVE) / total;
9718
- let delta = FEEDBACK_LR * (reward - previousUtility);
9719
- if (delta < 0) {
9720
- delta = Math.max(delta, -MAX_NEG_DELTA_PER_CALL);
9721
- }
9718
+ const delta = FEEDBACK_LR * (reward - previousUtility);
9722
9719
  const nextUtility = Math.max(0, Math.min(1, previousUtility + delta));
9723
9720
  const crossedReviewThreshold = previousUtility >= HIGH_UTILITY_THRESHOLD && nextUtility < UTILITY_REVIEW_THRESHOLD;
9724
9721
  return { previousUtility, nextUtility, crossedReviewThreshold };
9725
9722
  }
9726
- var FEEDBACK_LR = 0.1, FEEDBACK_REWARD_POSITIVE = 1, FEEDBACK_REWARD_NEGATIVE = 0, MAX_NEG_DELTA_PER_CALL = 0.15, UTILITY_REVIEW_THRESHOLD = 0.5, HIGH_UTILITY_THRESHOLD = 0.5;
9723
+ var FEEDBACK_LR = 0.1, FEEDBACK_REWARD_POSITIVE = 1, FEEDBACK_REWARD_NEGATIVE = 0, UTILITY_REVIEW_THRESHOLD = 0.5, HIGH_UTILITY_THRESHOLD = 0.5;
9727
9724
 
9728
9725
  // src/indexer/search/fts-query.ts
9729
9726
  function sanitizeFtsQuery(query) {
@@ -11182,7 +11179,7 @@ var init_opencode = __esm(() => {
11182
11179
  // src/integrations/agent/profiles.ts
11183
11180
  var COMMON_PASSTHROUGH, BUILTINS, HEADLESS_BUILTINS, BUILTIN_AGENT_PROFILE_NAMES;
11184
11181
  var init_profiles = __esm(() => {
11185
- COMMON_PASSTHROUGH = ["HOME", "PATH", "USER", "LANG", "LC_ALL", "TERM", "TMPDIR"];
11182
+ COMMON_PASSTHROUGH = ["HOME", "PATH", "USER", "LANG", "LC_ALL", "TERM", "TMPDIR", "AKM_EVENT_SOURCE"];
11186
11183
  BUILTINS = {
11187
11184
  opencode: {
11188
11185
  name: "opencode",
@@ -17572,14 +17569,23 @@ function relinkUsageEvents(db) {
17572
17569
  WHERE entry_id IS NOT NULL
17573
17570
  AND entry_id NOT IN (SELECT id FROM entries)
17574
17571
  `);
17575
- db.exec(`
17576
- UPDATE usage_events SET entry_id = (
17577
- SELECT e.id FROM entries e
17578
- WHERE substr(e.entry_key, length(e.entry_key) - length(usage_events.entry_ref)) = ':' || usage_events.entry_ref
17579
- LIMIT 1
17580
- )
17581
- WHERE entry_id IS NULL AND entry_ref IS NOT NULL
17582
- `);
17572
+ const refs = db.prepare("SELECT DISTINCT entry_ref AS ref FROM usage_events WHERE entry_id IS NULL AND entry_ref IS NOT NULL").all();
17573
+ const update = db.prepare("UPDATE usage_events SET entry_id = ? WHERE entry_ref = ? AND entry_id IS NULL");
17574
+ const relinkTx = db.transaction(() => {
17575
+ for (const { ref } of refs) {
17576
+ let id;
17577
+ try {
17578
+ id = findEntryIdByRef(db, ref);
17579
+ } catch (err) {
17580
+ if (err instanceof Error && err.name === "UsageError")
17581
+ continue;
17582
+ throw err;
17583
+ }
17584
+ if (id !== undefined)
17585
+ update.run(id, ref);
17586
+ }
17587
+ });
17588
+ relinkTx();
17583
17589
  }, "usage_events table may not exist yet during entry_id re-resolution");
17584
17590
  }
17585
17591
  function upsertRegistryIndexCache(db, registryUrl, indexJson, opts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.9.0-beta.55",
3
+ "version": "0.9.0-beta.57",
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": [