akm-cli 0.8.0-rc2 → 0.8.1

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.
Files changed (313) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +238 -3
  2. package/README.md +22 -6
  3. package/SECURITY.md +93 -0
  4. package/dist/assets/help/help-accept.md +12 -0
  5. package/dist/assets/help/help-improve.md +81 -0
  6. package/dist/{commands → assets}/help/help-proposals.md +7 -4
  7. package/dist/assets/help/help-reject.md +11 -0
  8. package/dist/{output → assets/hints}/cli-hints-full.md +60 -32
  9. package/dist/{output → assets/hints}/cli-hints-short.md +10 -7
  10. package/dist/assets/profiles/default.json +15 -0
  11. package/dist/assets/profiles/graph-refresh.json +13 -0
  12. package/dist/assets/profiles/memory-focus.json +12 -0
  13. package/dist/assets/profiles/quick.json +15 -0
  14. package/dist/assets/profiles/thorough.json +15 -0
  15. package/dist/assets/prompts/extract-session.md +80 -0
  16. package/dist/assets/prompts/graph-extract-user-prompt.md +35 -0
  17. package/dist/assets/tasks/graph-refresh-weekly.yml +10 -0
  18. package/dist/cli/config-migrate.js +144 -0
  19. package/dist/cli/config-validate.js +39 -0
  20. package/dist/cli/confirm.js +73 -0
  21. package/dist/cli/parse-args.js +93 -3
  22. package/dist/cli/shared.js +129 -0
  23. package/dist/cli.js +2141 -1268
  24. package/dist/commands/add-cli.js +279 -0
  25. package/dist/commands/agent-dispatch.js +20 -12
  26. package/dist/commands/agent-support.js +11 -5
  27. package/dist/commands/completions.js +3 -0
  28. package/dist/commands/config-cli.js +129 -517
  29. package/dist/commands/consolidate.js +1557 -147
  30. package/dist/commands/curate.js +44 -3
  31. package/dist/commands/db-cli.js +23 -0
  32. package/dist/commands/distill-promotion-policy.js +5 -3
  33. package/dist/commands/distill.js +906 -100
  34. package/dist/commands/env.js +213 -0
  35. package/dist/commands/eval-cases.js +3 -0
  36. package/dist/commands/events.js +3 -0
  37. package/dist/commands/extract-cli.js +127 -0
  38. package/dist/commands/extract-prompt.js +217 -0
  39. package/dist/commands/extract.js +477 -0
  40. package/dist/commands/feedback-cli.js +331 -0
  41. package/dist/commands/graph.js +260 -5
  42. package/dist/commands/health.js +1042 -55
  43. package/dist/commands/history.js +51 -16
  44. package/dist/commands/improve-auto-accept.js +97 -0
  45. package/dist/commands/improve-cli.js +236 -0
  46. package/dist/commands/improve-profiles.js +138 -0
  47. package/dist/commands/improve-result-file.js +167 -0
  48. package/dist/commands/improve.js +1736 -346
  49. package/dist/commands/info.js +26 -28
  50. package/dist/commands/init.js +49 -1
  51. package/dist/commands/installed-stashes.js +6 -23
  52. package/dist/commands/knowledge.js +3 -0
  53. package/dist/commands/lint/agent-linter.js +3 -0
  54. package/dist/commands/lint/base-linter.js +199 -5
  55. package/dist/commands/lint/command-linter.js +3 -0
  56. package/dist/commands/lint/default-linter.js +3 -0
  57. package/dist/commands/lint/env-key-rules.js +154 -0
  58. package/dist/commands/lint/index.js +92 -3
  59. package/dist/commands/lint/knowledge-linter.js +3 -0
  60. package/dist/commands/lint/markdown-insertion.js +343 -0
  61. package/dist/commands/lint/memory-linter.js +3 -0
  62. package/dist/commands/lint/registry.js +3 -0
  63. package/dist/commands/lint/skill-linter.js +3 -0
  64. package/dist/commands/lint/task-linter.js +15 -12
  65. package/dist/commands/lint/types.js +3 -0
  66. package/dist/commands/lint/workflow-linter.js +3 -0
  67. package/dist/commands/lint.js +3 -0
  68. package/dist/commands/migration-help.js +5 -2
  69. package/dist/commands/proposal-drain-policies.js +128 -0
  70. package/dist/commands/proposal-drain.js +477 -0
  71. package/dist/commands/proposal.js +60 -6
  72. package/dist/commands/propose.js +24 -19
  73. package/dist/commands/reflect.js +1004 -94
  74. package/dist/commands/registry-cli.js +150 -0
  75. package/dist/commands/registry-search.js +3 -0
  76. package/dist/commands/remember-cli.js +257 -0
  77. package/dist/commands/remember.js +15 -6
  78. package/dist/commands/schema-repair.js +88 -15
  79. package/dist/commands/search.js +99 -14
  80. package/dist/commands/secret.js +173 -0
  81. package/dist/commands/self-update.js +3 -0
  82. package/dist/commands/show.js +32 -13
  83. package/dist/commands/source-add.js +7 -35
  84. package/dist/commands/source-clone.js +3 -0
  85. package/dist/commands/source-manage.js +3 -0
  86. package/dist/commands/tasks.js +161 -95
  87. package/dist/commands/url-checker.js +3 -0
  88. package/dist/core/action-contributors.js +3 -0
  89. package/dist/core/asset-ref.js +13 -2
  90. package/dist/core/asset-registry.js +9 -2
  91. package/dist/core/asset-serialize.js +88 -0
  92. package/dist/core/asset-spec.js +61 -5
  93. package/dist/core/common.js +93 -5
  94. package/dist/core/concurrent.js +3 -0
  95. package/dist/core/config-io.js +347 -0
  96. package/dist/core/config-migration.js +622 -0
  97. package/dist/core/config-schema.js +558 -0
  98. package/dist/core/config-sources.js +108 -0
  99. package/dist/core/config-types.js +4 -0
  100. package/dist/core/config-walker.js +337 -0
  101. package/dist/core/config.js +366 -1077
  102. package/dist/core/errors.js +42 -20
  103. package/dist/core/events.js +31 -25
  104. package/dist/core/file-lock.js +104 -0
  105. package/dist/core/frontmatter.js +75 -10
  106. package/dist/core/lesson-lint.js +3 -0
  107. package/dist/core/markdown.js +3 -0
  108. package/dist/core/memory-belief.js +62 -0
  109. package/dist/core/memory-contradiction-detect.js +274 -0
  110. package/dist/core/memory-improve.js +142 -14
  111. package/dist/core/parse.js +3 -0
  112. package/dist/core/paths.js +218 -50
  113. package/dist/core/proposal-quality-validators.js +380 -0
  114. package/dist/core/proposal-validators.js +11 -3
  115. package/dist/core/proposals.js +464 -5
  116. package/dist/core/state-db.js +349 -56
  117. package/dist/core/text-truncation.js +107 -0
  118. package/dist/core/time.js +3 -0
  119. package/dist/core/tty.js +59 -0
  120. package/dist/core/warn.js +7 -2
  121. package/dist/core/write-source.js +12 -0
  122. package/dist/indexer/db-backup.js +391 -0
  123. package/dist/indexer/db-search.js +136 -28
  124. package/dist/indexer/db.js +661 -166
  125. package/dist/indexer/ensure-index.js +3 -0
  126. package/dist/indexer/file-context.js +3 -0
  127. package/dist/indexer/graph-boost.js +162 -40
  128. package/dist/indexer/graph-db.js +241 -51
  129. package/dist/indexer/graph-dedup.js +3 -7
  130. package/dist/indexer/graph-extraction.js +242 -149
  131. package/dist/indexer/index-context.js +3 -9
  132. package/dist/indexer/indexer.js +86 -16
  133. package/dist/indexer/llm-cache.js +24 -19
  134. package/dist/indexer/manifest.js +3 -0
  135. package/dist/indexer/matchers.js +184 -11
  136. package/dist/indexer/memory-inference.js +94 -50
  137. package/dist/indexer/metadata-contributors.js +3 -0
  138. package/dist/indexer/metadata.js +110 -50
  139. package/dist/indexer/path-resolver.js +3 -0
  140. package/dist/indexer/project-context.js +192 -0
  141. package/dist/indexer/ranking-contributors.js +134 -7
  142. package/dist/indexer/ranking.js +8 -1
  143. package/dist/indexer/search-fields.js +5 -9
  144. package/dist/indexer/search-hit-enrichers.js +91 -2
  145. package/dist/indexer/search-source.js +20 -1
  146. package/dist/indexer/semantic-status.js +4 -1
  147. package/dist/indexer/staleness-detect.js +447 -0
  148. package/dist/indexer/usage-events.js +12 -9
  149. package/dist/indexer/walker.js +3 -0
  150. package/dist/integrations/agent/builders.js +135 -0
  151. package/dist/integrations/agent/config.js +121 -401
  152. package/dist/integrations/agent/detect.js +3 -0
  153. package/dist/integrations/agent/index.js +6 -14
  154. package/dist/integrations/agent/model-aliases.js +55 -0
  155. package/dist/integrations/agent/profiles.js +3 -0
  156. package/dist/integrations/agent/prompts.js +137 -8
  157. package/dist/integrations/agent/runner.js +208 -0
  158. package/dist/integrations/agent/sdk-runner.js +8 -2
  159. package/dist/integrations/agent/spawn.js +54 -14
  160. package/dist/integrations/github.js +3 -0
  161. package/dist/integrations/lockfile.js +22 -51
  162. package/dist/integrations/session-logs/index.js +4 -0
  163. package/dist/integrations/session-logs/inline-refs.js +35 -0
  164. package/dist/integrations/session-logs/pre-filter.js +152 -0
  165. package/dist/integrations/session-logs/providers/claude-code.js +226 -0
  166. package/dist/integrations/session-logs/providers/opencode.js +231 -25
  167. package/dist/integrations/session-logs/types.js +3 -0
  168. package/dist/llm/call-ai.js +14 -26
  169. package/dist/llm/client.js +16 -2
  170. package/dist/llm/embedder.js +20 -29
  171. package/dist/llm/embedders/cache.js +3 -7
  172. package/dist/llm/embedders/local.js +42 -1
  173. package/dist/llm/embedders/remote.js +20 -8
  174. package/dist/llm/embedders/types.js +3 -7
  175. package/dist/llm/feature-gate.js +92 -56
  176. package/dist/llm/graph-extract.js +402 -31
  177. package/dist/llm/index-passes.js +44 -29
  178. package/dist/llm/memory-infer.js +30 -2
  179. package/dist/llm/metadata-enhance.js +3 -7
  180. package/dist/output/cli-hints.js +7 -4
  181. package/dist/output/context.js +60 -8
  182. package/dist/output/renderers.js +170 -194
  183. package/dist/output/shapes/curate.js +56 -0
  184. package/dist/output/shapes/distill.js +10 -0
  185. package/dist/output/shapes/env-list.js +19 -0
  186. package/dist/output/shapes/events.js +11 -0
  187. package/dist/output/shapes/helpers.js +424 -0
  188. package/dist/output/shapes/history.js +7 -0
  189. package/dist/output/shapes/passthrough.js +105 -0
  190. package/dist/output/shapes/proposal-accept.js +7 -0
  191. package/dist/output/shapes/proposal-diff.js +7 -0
  192. package/dist/output/shapes/proposal-list.js +7 -0
  193. package/dist/output/shapes/proposal-producer.js +11 -0
  194. package/dist/output/shapes/proposal-reject.js +7 -0
  195. package/dist/output/shapes/proposal-show.js +7 -0
  196. package/dist/output/shapes/registry-search.js +6 -0
  197. package/dist/output/shapes/registry.js +30 -0
  198. package/dist/output/shapes/search.js +6 -0
  199. package/dist/output/shapes/secret-list.js +19 -0
  200. package/dist/output/shapes/show.js +6 -0
  201. package/dist/output/shapes/vault-list.js +19 -0
  202. package/dist/output/shapes.js +51 -549
  203. package/dist/output/text/add.js +6 -0
  204. package/dist/output/text/clone.js +6 -0
  205. package/dist/output/text/config.js +6 -0
  206. package/dist/output/text/curate.js +6 -0
  207. package/dist/output/text/distill.js +7 -0
  208. package/dist/output/text/enable-disable.js +7 -0
  209. package/dist/output/text/events.js +10 -0
  210. package/dist/output/text/feedback.js +6 -0
  211. package/dist/output/text/helpers.js +1059 -0
  212. package/dist/output/text/history.js +7 -0
  213. package/dist/output/text/import.js +6 -0
  214. package/dist/output/text/index.js +6 -0
  215. package/dist/output/text/info.js +6 -0
  216. package/dist/output/text/init.js +6 -0
  217. package/dist/output/text/list.js +6 -0
  218. package/dist/output/text/proposal-producer.js +8 -0
  219. package/dist/output/text/proposal.js +12 -0
  220. package/dist/output/text/registry-commands.js +11 -0
  221. package/dist/output/text/registry.js +30 -0
  222. package/dist/output/text/remember.js +6 -0
  223. package/dist/output/text/remove.js +6 -0
  224. package/dist/output/text/save.js +6 -0
  225. package/dist/output/text/search.js +6 -0
  226. package/dist/output/text/show.js +6 -0
  227. package/dist/output/text/update.js +6 -0
  228. package/dist/output/text/upgrade.js +6 -0
  229. package/dist/output/text/vault.js +16 -0
  230. package/dist/output/text/wiki.js +15 -0
  231. package/dist/output/text/workflow.js +14 -0
  232. package/dist/output/text.js +44 -1329
  233. package/dist/registry/build-index.js +3 -0
  234. package/dist/registry/create-provider-registry.js +3 -0
  235. package/dist/registry/factory.js +4 -1
  236. package/dist/registry/origin-resolve.js +3 -0
  237. package/dist/registry/providers/index.js +3 -0
  238. package/dist/registry/providers/skills-sh.js +11 -2
  239. package/dist/registry/providers/static-index.js +10 -1
  240. package/dist/registry/providers/types.js +3 -24
  241. package/dist/registry/resolve.js +11 -16
  242. package/dist/registry/types.js +3 -0
  243. package/dist/scripts/migrate-storage.js +17767 -0
  244. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
  245. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  246. package/dist/setup/detect.js +3 -0
  247. package/dist/setup/ripgrep-install.js +3 -0
  248. package/dist/setup/ripgrep-resolve.js +3 -0
  249. package/dist/setup/setup.js +306 -67
  250. package/dist/setup/steps.js +3 -15
  251. package/dist/sources/include.js +3 -0
  252. package/dist/sources/provider-factory.js +3 -11
  253. package/dist/sources/provider.js +3 -20
  254. package/dist/sources/providers/filesystem.js +19 -23
  255. package/dist/sources/providers/git.js +171 -21
  256. package/dist/sources/providers/index.js +3 -0
  257. package/dist/sources/providers/install-types.js +3 -13
  258. package/dist/sources/providers/npm.js +3 -4
  259. package/dist/sources/providers/provider-utils.js +3 -0
  260. package/dist/sources/providers/sync-from-ref.js +3 -11
  261. package/dist/sources/providers/tar-utils.js +3 -0
  262. package/dist/sources/providers/website.js +18 -22
  263. package/dist/sources/resolve.js +3 -0
  264. package/dist/sources/types.js +3 -0
  265. package/dist/sources/website-ingest.js +3 -0
  266. package/dist/tasks/backends/cron.js +3 -0
  267. package/dist/tasks/backends/exec-utils.js +3 -0
  268. package/dist/tasks/backends/index.js +3 -11
  269. package/dist/tasks/backends/launchd.js +4 -1
  270. package/dist/tasks/backends/schtasks.js +4 -1
  271. package/dist/tasks/parser.js +51 -38
  272. package/dist/tasks/resolveAkmBin.js +3 -0
  273. package/dist/tasks/runner.js +35 -9
  274. package/dist/tasks/schedule.js +20 -1
  275. package/dist/tasks/schema.js +5 -3
  276. package/dist/tasks/validator.js +6 -3
  277. package/dist/version.js +3 -0
  278. package/dist/wiki/wiki-templates.js +6 -3
  279. package/dist/wiki/wiki.js +4 -1
  280. package/dist/workflows/authoring.js +4 -1
  281. package/dist/workflows/cli.js +3 -0
  282. package/dist/workflows/db.js +140 -10
  283. package/dist/workflows/document-cache.js +3 -10
  284. package/dist/workflows/parser.js +3 -0
  285. package/dist/workflows/renderer.js +3 -0
  286. package/dist/workflows/runs.js +18 -1
  287. package/dist/workflows/schema.js +3 -0
  288. package/dist/workflows/scope-key.js +3 -0
  289. package/dist/workflows/validator.js +5 -9
  290. package/docs/README.md +7 -2
  291. package/docs/data-and-telemetry.md +225 -0
  292. package/docs/migration/release-notes/0.7.5.md +2 -2
  293. package/docs/migration/release-notes/0.8.0.md +57 -5
  294. package/docs/migration/v0.7-to-v0.8.md +1378 -0
  295. package/package.json +28 -11
  296. package/.github/LICENSE +0 -374
  297. package/dist/commands/help/help-accept.md +0 -9
  298. package/dist/commands/help/help-improve.md +0 -53
  299. package/dist/commands/help/help-reject.md +0 -8
  300. package/dist/commands/install-audit.js +0 -385
  301. package/dist/commands/vault.js +0 -310
  302. package/dist/indexer/match-contributors.js +0 -141
  303. package/dist/integrations/agent/pipeline.js +0 -39
  304. package/dist/integrations/agent/runners.js +0 -31
  305. package/dist/llm/prompts/graph-extract-user-prompt.md +0 -12
  306. /package/dist/{tasks → assets}/backends/launchd-template.xml +0 -0
  307. /package/dist/{tasks → assets}/backends/schtasks-template.xml +0 -0
  308. /package/dist/{commands → assets}/help/help-propose.md +0 -0
  309. /package/dist/{wiki → assets/wiki}/index-template.md +0 -0
  310. /package/dist/{wiki → assets/wiki}/ingest-workflow-template.md +0 -0
  311. /package/dist/{wiki → assets/wiki}/log-template.md +0 -0
  312. /package/dist/{wiki → assets/wiki}/schema-template.md +0 -0
  313. /package/dist/{workflows → assets/workflows}/workflow-template.md +0 -0
@@ -1,12 +1,17 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  import fs from "node:fs";
2
5
  import path from "node:path";
6
+ import { SCRIPT_EXTENSIONS } from "../core/asset-spec";
3
7
  import { isHttpUrl, resolveStashDir, toErrorMessage } from "../core/common";
4
8
  import { concurrentMap } from "../core/concurrent";
5
9
  import { getDbPath } from "../core/paths";
6
10
  import { isVerbose, warn, warnVerbose } from "../core/warn";
7
11
  import { resolveIndexPassLLM } from "../llm/index-passes";
8
12
  import { takeWorkflowDocument } from "../workflows/document-cache";
9
- import { clearStaleCacheEntries, closeDatabase, deleteEntriesByDir, deleteEntriesByStashDir, deleteIndexDirStatesByStashDir, getAllEntriesForEmbedding, getEmbeddingCount, getEntriesByDir, getEntryCount, getIndexDirState, getMeta, isVecAvailable, openDatabase, openExistingDatabase, rebuildFts, relinkUsageEvents, setMeta, upsertEmbedding, upsertEntry, upsertIndexDirState, upsertUtilityScore, upsertWorkflowDocument, warnIfVecMissing, } from "./db";
13
+ import { clearStaleCacheEntries, closeDatabase, deleteEntriesByDir, deleteEntriesByIds, deleteEntriesByStashDir, deleteIndexDirStatesByStashDir, getAllEntriesForEmbedding, getEmbeddingCount, getEntriesByDir, getEntryCount, getIndexDirState, getMeta, isVecAvailable, openDatabase, openExistingDatabase, rebuildFts, relinkUsageEvents, setMeta, upsertEmbedding, upsertEntry, upsertIndexDirState, upsertUtilityScore, upsertWorkflowDocument, warnIfVecMissing, } from "./db";
14
+ import { deleteStoredGraph } from "./graph-db";
10
15
  import { applyCuratedFrontmatter, applyWikiFrontmatter, generateMetadataFlat, isEnrichmentComplete, isWorkflowSkipWarning, loadStashFile, shouldIndexStashFile, } from "./metadata";
11
16
  import { buildSearchText } from "./search-fields";
12
17
  import { classifySemanticFailure, clearSemanticStatus, deriveSemanticProviderFingerprint, writeSemanticStatus, } from "./semantic-status";
@@ -64,6 +69,7 @@ async function runSourceCachePhase(ctx) {
64
69
  ctx.hadRemovedSources = true;
65
70
  deleteEntriesByStashDir(db, dir);
66
71
  deleteIndexDirStatesByStashDir(db, dir);
72
+ deleteStoredGraph(db, dir);
67
73
  }
68
74
  }
69
75
  }
@@ -193,6 +199,29 @@ async function runFinalizePhase(ctx) {
193
199
  // suppress unused warning — sources was previously used inline
194
200
  void sources;
195
201
  }
202
+ // ── Clean pass ───────────────────────────────────────────────────────────────
203
+ /**
204
+ * Post-index clean pass: scan the `entries` table for rows whose source file
205
+ * no longer exists on disk and remove them (unless `dryRun` is true).
206
+ *
207
+ * Only rows with a non-empty `file_path` are checked — remote/virtual entries
208
+ * that have no local path are always skipped.
209
+ */
210
+ function runCleanPass(db, dryRun) {
211
+ const allEntries = db.prepare("SELECT id, entry_key AS ref, file_path AS path FROM entries").all();
212
+ // Only check entries that have a non-empty local path (skip remote/virtual).
213
+ const localEntries = allEntries.filter((e) => typeof e.path === "string" && e.path.trim() !== "");
214
+ const missing = localEntries.filter((e) => !fs.existsSync(e.path));
215
+ if (!dryRun && missing.length > 0) {
216
+ deleteEntriesByIds(db, missing.map((e) => e.id));
217
+ }
218
+ return {
219
+ checked: localEntries.length,
220
+ removed: dryRun ? 0 : missing.length,
221
+ removedRefs: missing.map((e) => e.ref),
222
+ dryRun,
223
+ };
224
+ }
196
225
  // ── Indexer ──────────────────────────────────────────────────────────────────
197
226
  export async function akmIndex(options) {
198
227
  const stashDir = options?.stashDir || resolveStashDir();
@@ -200,6 +229,8 @@ export async function akmIndex(options) {
200
229
  const signal = options?.signal;
201
230
  const reEnrich = options?.reEnrich === true;
202
231
  const full = options?.full === true;
232
+ const clean = options?.clean === true;
233
+ const dryRun = options?.dryRun === true;
203
234
  // Load config and resolve all stash sources
204
235
  const { loadConfig } = await import("../core/config.js");
205
236
  const config = loadConfig();
@@ -269,6 +300,14 @@ export async function akmIndex(options) {
269
300
  // ────────────────────────────────────────────────────────────────────────
270
301
  const { _verification: verification, _totalEntries: totalEntries } = ctx;
271
302
  const { timing } = ctx;
303
+ // ── Clean pass ───────────────────────────────────────────────────────────
304
+ // After the normal index completes, remove entries whose source files no
305
+ // longer exist on disk. Remote entries (empty file_path) are skipped.
306
+ let cleanResult;
307
+ if (clean) {
308
+ cleanResult = runCleanPass(db, dryRun);
309
+ }
310
+ // ────────────────────────────────────────────────────────────────────────
272
311
  return {
273
312
  stashDir,
274
313
  totalEntries,
@@ -283,9 +322,10 @@ export async function akmIndex(options) {
283
322
  totalMs: Date.now() - timing.t0,
284
323
  walkMs: timing.tWalkEnd - timing.tWalkStart,
285
324
  llmMs: timing.tLlmEnd - timing.tWalkEnd,
286
- embedMs: timing.tEmbedEnd - timing.tFtsEnd,
287
- ftsMs: timing.tFtsEnd - timing.tLlmEnd,
325
+ embedMs: timing.tEmbedEnd - timing.tLlmEnd,
326
+ ftsMs: timing.tFtsEnd - timing.tEmbedEnd,
288
327
  },
328
+ ...(cleanResult !== undefined ? { clean: cleanResult } : {}),
289
329
  };
290
330
  }
291
331
  finally {
@@ -567,7 +607,20 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
567
607
  reason: persistedReason,
568
608
  });
569
609
  if (persistedRows === 0) {
570
- warn(`[index] zero-row ${dirPath}: ${persistedReason}`);
610
+ // Warn only when the dir had files that *could* produce entries (.md or
611
+ // known script extensions). Dirs with only non-indexable types (.json,
612
+ // .yaml, .conf, .env, .gitkeep) or deduped-only rows are expected and
613
+ // not actionable at normal log level.
614
+ const hasIndexableExtension = files.some((f) => {
615
+ const ext = path.extname(f).toLowerCase();
616
+ return ext === ".md" || SCRIPT_EXTENSIONS.has(ext);
617
+ });
618
+ if (persistedReason !== "deduped-zero-row" && hasIndexableExtension) {
619
+ warn(`[index] zero-row ${dirPath}: ${persistedReason}`);
620
+ }
621
+ else {
622
+ warnVerbose(`[index] zero-row ${dirPath}: ${persistedReason}`);
623
+ }
571
624
  }
572
625
  }
573
626
  });
@@ -884,14 +937,24 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal) {
884
937
  throwIfAborted(signal);
885
938
  // Wrap all embedding upserts in a single transaction so partial
886
939
  // state is rolled back on failure rather than leaving the table half-filled.
940
+ let storedCount = 0;
941
+ let skippedCount = 0;
887
942
  db.transaction(() => {
888
943
  for (let i = 0; i < allEntries.length; i++) {
889
- upsertEmbedding(db, allEntries[i].id, embeddings[i]);
944
+ if (upsertEmbedding(db, allEntries[i].id, embeddings[i])) {
945
+ storedCount++;
946
+ }
947
+ else {
948
+ skippedCount++;
949
+ }
890
950
  }
891
951
  })();
952
+ if (skippedCount > 0) {
953
+ warn(`[embed] ${skippedCount} embedding${skippedCount === 1 ? "" : "s"} skipped (entry deleted between queue and write)`);
954
+ }
892
955
  onProgress({
893
956
  phase: "embeddings",
894
- message: `Stored ${embeddings.length} embedding${embeddings.length === 1 ? "" : "s"}.`,
957
+ message: `Stored ${storedCount} embedding${storedCount === 1 ? "" : "s"}.`,
895
958
  });
896
959
  setMeta(db, "embeddingFingerprint", currentFingerprint);
897
960
  return { success: true };
@@ -1258,18 +1321,25 @@ export function recomputeUtilityScores(db) {
1258
1321
  const emaDecay = EMA_DECAY ** elapsedDays;
1259
1322
  const emaNew = 1 - emaDecay; // complement so weights still sum to 1
1260
1323
  // Single aggregate query instead of N+1 per-entry queries.
1261
- // Only processes entries that actually have usage events.
1324
+ // Only processes entries that actually have usage events AND still exist
1325
+ // in `entries`. The latter check is critical: usage_events has no FK to
1326
+ // entries, so its entry_id can become stale (entry deleted, re-keyed,
1327
+ // moved between sources). Without the JOIN, writing the derived row to
1328
+ // utility_scores (which DOES have an FK) raises "FOREIGN KEY constraint
1329
+ // failed" and rolls back the whole finalize transaction — failing every
1330
+ // index run.
1262
1331
  const usageRows = db
1263
1332
  .prepare(`
1264
- SELECT entry_id,
1265
- SUM(CASE WHEN event_type = 'search' THEN 1 ELSE 0 END) AS search_count,
1266
- SUM(CASE WHEN event_type = 'show' THEN 1 ELSE 0 END) AS show_count,
1267
- SUM(CASE WHEN event_type = 'feedback' AND signal = 'positive' THEN 1 ELSE 0 END) AS positive_feedback_count,
1268
- SUM(CASE WHEN event_type = 'feedback' AND signal = 'negative' THEN 1 ELSE 0 END) AS negative_feedback_count,
1269
- MAX(created_at) AS last_used_at
1270
- FROM usage_events
1271
- WHERE entry_id IS NOT NULL
1272
- GROUP BY entry_id
1333
+ SELECT u.entry_id,
1334
+ SUM(CASE WHEN u.event_type = 'search' THEN 1 ELSE 0 END) AS search_count,
1335
+ SUM(CASE WHEN u.event_type = 'show' THEN 1 ELSE 0 END) AS show_count,
1336
+ SUM(CASE WHEN u.event_type = 'feedback' AND u.signal = 'positive' THEN 1 ELSE 0 END) AS positive_feedback_count,
1337
+ SUM(CASE WHEN u.event_type = 'feedback' AND u.signal = 'negative' THEN 1 ELSE 0 END) AS negative_feedback_count,
1338
+ MAX(u.created_at) AS last_used_at
1339
+ FROM usage_events u
1340
+ JOIN entries e ON e.id = u.entry_id
1341
+ WHERE u.entry_id IS NOT NULL
1342
+ GROUP BY u.entry_id
1273
1343
  `)
1274
1344
  .all();
1275
1345
  if (usageRows.length === 0) {
@@ -1,33 +1,38 @@
1
- /**
2
- * Generic LLM-result cache wrapper shared across indexer passes.
3
- *
4
- * Each pass that calls an LLM and wants to skip re-processing unchanged
5
- * content can delegate the cache check/write to `withLlmCache` instead of
6
- * duplicating the hash-compute → lookup → write pattern inline.
7
- */
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
4
  import { computeBodyHash, getLlmCacheEntry, upsertLlmCacheEntry } from "./db";
9
5
  /**
10
6
  * Generic LLM cache wrapper. Returns cached result if body unchanged,
11
7
  * otherwise calls llmFn(), caches the result, and returns it.
12
8
  * Returns undefined if llmFn() returns undefined or throws.
13
9
  *
14
- * @param db - SQLite database holding the LLM result cache.
15
- * @param cacheKey - Stable identifier for this asset (typically its absolute path).
16
- * @param body - The content being processed; its hash determines cache validity.
17
- * @param reEnrich - When true the cache is bypassed and llmFn() is always called.
18
- * @param llmFn - Async function that performs the actual LLM call.
19
- * @param validate - Converts the raw parsed JSON back into the pass-specific type;
20
- * returns undefined when the cached data is unusable.
10
+ * @param db - SQLite database holding the LLM result cache.
11
+ * @param cacheKey - Stable identifier for this asset (typically its absolute path).
12
+ * @param body - The content being processed; its hash determines cache validity.
13
+ * @param reEnrich - When true the cache is bypassed and llmFn() is always called.
14
+ * @param llmFn - Async function that performs the actual LLM call.
15
+ * @param validate - Converts the raw parsed JSON back into the pass-specific type;
16
+ * returns undefined when the cached data is unusable.
17
+ * @param precomputedHash - Optional precomputed SHA-256 of `body`. When provided,
18
+ * the wrapper skips its internal hashing — callers that
19
+ * already hashed the body (e.g. to reuse it elsewhere)
20
+ * should pass this to avoid the redundant work.
21
+ * @param cacheVariant - Namespace token for the cache row (e.g. staleness-detect
22
+ * sets one so its rows do not collide with memory-inference).
23
+ * @param hooks - Optional event sink for telemetry (see {@link WithLlmCacheHooks}).
21
24
  */
22
- export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate) {
23
- const bodyHash = computeBodyHash(body);
25
+ export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate, precomputedHash, cacheVariant = "", hooks) {
26
+ const bodyHash = precomputedHash ?? computeBodyHash(body);
24
27
  if (!reEnrich) {
25
28
  try {
26
- const cached = getLlmCacheEntry(db, cacheKey, bodyHash);
29
+ const cached = getLlmCacheEntry(db, cacheKey, bodyHash, cacheVariant);
27
30
  if (cached) {
28
31
  const result = validate(JSON.parse(cached.resultJson));
29
- if (result !== undefined)
32
+ if (result !== undefined) {
33
+ hooks?.onCacheHit?.();
30
34
  return result;
35
+ }
31
36
  }
32
37
  }
33
38
  catch {
@@ -37,7 +42,7 @@ export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate
37
42
  const result = await llmFn();
38
43
  if (result !== undefined) {
39
44
  try {
40
- upsertLlmCacheEntry(db, cacheKey, bodyHash, JSON.stringify(result));
45
+ upsertLlmCacheEntry(db, cacheKey, bodyHash, JSON.stringify(result), cacheVariant);
41
46
  }
42
47
  catch {
43
48
  // Cache write failure is non-fatal
@@ -1,3 +1,6 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  /**
2
5
  * Manifest: compact asset listing for cheap capability discovery.
3
6
  *
@@ -1,15 +1,185 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  /**
2
5
  * Built-in asset matchers for the akm file classification system.
3
6
  *
4
- * Classification facts now live in `match-contributors.ts`. This module keeps
5
- * the existing matcher API and registration order intact by adapting those
6
- * facts back into `MatchResult` values.
7
+ * Each private `classifyBy*` function encapsulates the classification logic for
8
+ * one heuristic. The public `*Matcher` exports compose those facts into the
9
+ * `MatchResult` shape expected by the rest of the indexer.
7
10
  */
8
11
  import { defaultRendererRegistry } from "../core/asset-registry";
12
+ import { SCRIPT_EXTENSIONS } from "../core/asset-spec";
13
+ import { looksLikeWorkflow } from "../workflows/parser";
9
14
  import { registerMatcher } from "./file-context";
10
- import { directoryContributor, extensionContributor, parentDirHintContributor, smartMdContributor, wikiContributor, } from "./match-contributors";
11
- function toMatchResult(ctx, contributor) {
12
- const fact = contributor.classify(ctx);
15
+ // ---------------------------------------------------------------------------
16
+ // Private data
17
+ // ---------------------------------------------------------------------------
18
+ const DIR_TYPE_MAP = [
19
+ {
20
+ dir: "scripts",
21
+ type: "script",
22
+ test: (ext) => SCRIPT_EXTENSIONS.has(ext),
23
+ },
24
+ {
25
+ dir: "commands",
26
+ type: "command",
27
+ test: (ext) => ext === ".md",
28
+ },
29
+ {
30
+ dir: "agents",
31
+ type: "agent",
32
+ test: (ext) => ext === ".md",
33
+ },
34
+ {
35
+ dir: "knowledge",
36
+ type: "knowledge",
37
+ test: (ext) => ext === ".md",
38
+ },
39
+ {
40
+ dir: "workflows",
41
+ type: "workflow",
42
+ test: (ext) => ext === ".md",
43
+ },
44
+ {
45
+ dir: "memories",
46
+ type: "memory",
47
+ test: (ext) => ext === ".md",
48
+ },
49
+ {
50
+ dir: "lessons",
51
+ type: "lesson",
52
+ test: (ext) => ext === ".md",
53
+ },
54
+ {
55
+ dir: "env",
56
+ type: "env",
57
+ test: (_, fileName) => fileName === ".env" || fileName.endsWith(".env"),
58
+ },
59
+ {
60
+ dir: "vaults",
61
+ type: "vault",
62
+ test: (_, fileName) => fileName === ".env" || fileName.endsWith(".env"),
63
+ },
64
+ {
65
+ dir: "secrets",
66
+ type: "secret",
67
+ // Any regular file under secrets/ is a secret value, except the lock and
68
+ // sensitive-marker sidecars. The whole file is the value (no extension or
69
+ // body parsing — see the secret-file renderer + indexer guards).
70
+ test: (_, fileName) => !fileName.endsWith(".lock") && !fileName.endsWith(".sensitive"),
71
+ },
72
+ {
73
+ dir: "tasks",
74
+ type: "task",
75
+ test: (ext) => ext === ".md",
76
+ },
77
+ ];
78
+ const COMMAND_PLACEHOLDER_RE = /\$ARGUMENTS|\$[123]\b/;
79
+ // Files that should never be treated as the typed asset for the surrounding
80
+ // directory (e.g. `workflows/README.md` is documentation, not a workflow).
81
+ // Lower-cased and matched case-insensitively against `ctx.fileName`. They are
82
+ // still indexable — falling through to `classifyBySmartMd` typically routes
83
+ // them to the generic `knowledge` type.
84
+ const TYPED_DIR_DOC_FILES = new Set(["readme.md"]);
85
+ function isTypedDirDocFile(fileName) {
86
+ return TYPED_DIR_DOC_FILES.has(fileName.toLowerCase());
87
+ }
88
+ // ---------------------------------------------------------------------------
89
+ // Private helpers
90
+ // ---------------------------------------------------------------------------
91
+ function matchDirectoryHint(dirName, ctx, specificity) {
92
+ if (dirName === "skills" && ctx.fileName === "SKILL.md") {
93
+ return { type: "skill", specificity };
94
+ }
95
+ for (const rule of DIR_TYPE_MAP) {
96
+ if (rule.dir === dirName && rule.test(ctx.ext, ctx.fileName)) {
97
+ // Skip `README.md` (case-insensitive) so `workflows/README.md`,
98
+ // `agents/README.md`, etc. are not parsed as the typed asset and don't
99
+ // trip the workflow/agent metadata validators. They still get indexed
100
+ // as `knowledge` via the smart-md matcher.
101
+ if (isTypedDirDocFile(ctx.fileName))
102
+ return null;
103
+ return { type: rule.type, specificity };
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+ function classifyByExtension(ctx) {
109
+ if (ctx.fileName === "SKILL.md" && !ctx.ancestorDirs.includes("wikis")) {
110
+ return { type: "skill", specificity: 25 };
111
+ }
112
+ if (SCRIPT_EXTENSIONS.has(ctx.ext)) {
113
+ return { type: "script", specificity: 3 };
114
+ }
115
+ return null;
116
+ }
117
+ function classifyByDirectory(ctx) {
118
+ for (const dir of ctx.ancestorDirs) {
119
+ const result = matchDirectoryHint(dir, ctx, 10);
120
+ if (result)
121
+ return result;
122
+ }
123
+ return null;
124
+ }
125
+ function classifyByParentDirHint(ctx) {
126
+ const { parentDir, ext, fileName } = ctx;
127
+ if (parentDir === "skills" && (fileName === "SKILL.md" || ext === ".md")) {
128
+ return { type: "skill", specificity: 15 };
129
+ }
130
+ return matchDirectoryHint(parentDir, ctx, 15);
131
+ }
132
+ function classifyBySmartMd(ctx) {
133
+ if (ctx.ext !== ".md")
134
+ return null;
135
+ // Never read the body of a file under secrets/ — the whole file is the
136
+ // secret value. The directory matcher classifies it as `secret` without
137
+ // touching content; bailing here keeps classifyBySmartMd from calling
138
+ // ctx.content()/frontmatter() on secret material.
139
+ if (ctx.ancestorDirs.includes("secrets"))
140
+ return null;
141
+ // README.md is documentation, never a workflow/agent/command even when the
142
+ // body shape would otherwise classify (e.g. step-list inside a project
143
+ // README under workflows/). Fall straight through to `knowledge`.
144
+ if (isTypedDirDocFile(ctx.fileName)) {
145
+ return { type: "knowledge", specificity: 5 };
146
+ }
147
+ const body = ctx.content();
148
+ if (looksLikeWorkflow(body)) {
149
+ return { type: "workflow", specificity: 19 };
150
+ }
151
+ const fm = ctx.frontmatter();
152
+ if (fm) {
153
+ if ("toolPolicy" in fm || "tools" in fm) {
154
+ return { type: "agent", specificity: 20 };
155
+ }
156
+ if ("agent" in fm) {
157
+ return { type: "command", specificity: 18 };
158
+ }
159
+ }
160
+ if (COMMAND_PLACEHOLDER_RE.test(body)) {
161
+ return { type: "command", specificity: 18 };
162
+ }
163
+ if (fm && "model" in fm) {
164
+ return { type: "agent", specificity: 8 };
165
+ }
166
+ return { type: "knowledge", specificity: 5 };
167
+ }
168
+ function classifyByWiki(ctx) {
169
+ if (ctx.ext !== ".md")
170
+ return null;
171
+ const idx = ctx.ancestorDirs.indexOf("wikis");
172
+ if (idx < 0)
173
+ return null;
174
+ if (idx + 1 >= ctx.ancestorDirs.length)
175
+ return null;
176
+ return { type: "wiki", specificity: 20 };
177
+ }
178
+ // ---------------------------------------------------------------------------
179
+ // Adapter: MatchFact → MatchResult
180
+ // ---------------------------------------------------------------------------
181
+ function toMatchResult(ctx, classify) {
182
+ const fact = classify(ctx);
13
183
  if (!fact)
14
184
  return null;
15
185
  const renderer = defaultRendererRegistry.rendererNameFor(fact.type);
@@ -22,20 +192,23 @@ function toMatchResult(ctx, contributor) {
22
192
  ...(fact.meta ? { meta: fact.meta } : {}),
23
193
  };
24
194
  }
195
+ // ---------------------------------------------------------------------------
196
+ // Public matchers (API unchanged)
197
+ // ---------------------------------------------------------------------------
25
198
  export function extensionMatcher(ctx) {
26
- return toMatchResult(ctx, extensionContributor);
199
+ return toMatchResult(ctx, classifyByExtension);
27
200
  }
28
201
  export function directoryMatcher(ctx) {
29
- return toMatchResult(ctx, directoryContributor);
202
+ return toMatchResult(ctx, classifyByDirectory);
30
203
  }
31
204
  export function parentDirHintMatcher(ctx) {
32
- return toMatchResult(ctx, parentDirHintContributor);
205
+ return toMatchResult(ctx, classifyByParentDirHint);
33
206
  }
34
207
  export function smartMdMatcher(ctx) {
35
- return toMatchResult(ctx, smartMdContributor);
208
+ return toMatchResult(ctx, classifyBySmartMd);
36
209
  }
37
210
  export function wikiMatcher(ctx) {
38
- return toMatchResult(ctx, wikiContributor);
211
+ return toMatchResult(ctx, classifyByWiki);
39
212
  }
40
213
  const builtinMatchers = [
41
214
  extensionMatcher,