akm-cli 0.8.6 → 0.8.14

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 (324) hide show
  1. package/CHANGELOG.md +442 -0
  2. package/dist/assets/help/help-proposals.md +1 -2
  3. package/dist/assets/hints/cli-hints-full.md +34 -19
  4. package/dist/assets/hints/cli-hints-short.md +1 -1
  5. package/dist/assets/profiles/catchup.json +13 -0
  6. package/dist/assets/profiles/consolidate.json +13 -0
  7. package/dist/assets/profiles/frequent.json +13 -0
  8. package/dist/assets/tasks/core/backup.yml +4 -0
  9. package/dist/assets/tasks/core/extract.yml +4 -0
  10. package/dist/assets/tasks/core/improve.yml +4 -0
  11. package/dist/assets/tasks/core/index-refresh.yml +4 -0
  12. package/dist/assets/tasks/core/sync.yml +4 -0
  13. package/dist/assets/tasks/core/update-stashes.yml +4 -0
  14. package/dist/assets/tasks/core/version-check.yml +4 -0
  15. package/dist/assets/templates/html/default.html +78 -0
  16. package/dist/assets/templates/html/health.html +560 -0
  17. package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
  18. package/dist/cli/config-migrate.js +6 -6
  19. package/dist/cli/config-validate.js +4 -4
  20. package/dist/cli/confirm.js +3 -3
  21. package/dist/cli/parse-args.js +1 -1
  22. package/dist/cli/shared.js +72 -19
  23. package/dist/cli-node.mjs +26 -0
  24. package/dist/cli.js +206 -3866
  25. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  26. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  27. package/dist/commands/agent/contribute-cli.js +200 -0
  28. package/dist/commands/completions.js +1 -1
  29. package/dist/commands/config-cli.js +230 -3
  30. package/dist/commands/db-cli.js +2 -2
  31. package/dist/commands/env/env-cli.js +529 -0
  32. package/dist/commands/env/env.js +410 -0
  33. package/dist/commands/env/secret-cli.js +259 -0
  34. package/dist/commands/{secret.js → env/secret.js} +6 -47
  35. package/dist/commands/events.js +4 -4
  36. package/dist/commands/feedback-cli.js +18 -34
  37. package/dist/commands/graph/graph-cli.js +132 -0
  38. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  39. package/dist/commands/health/checks.js +279 -0
  40. package/dist/commands/health/html-report.js +448 -0
  41. package/dist/commands/health.js +189 -266
  42. package/dist/commands/{consolidate.js → improve/consolidate.js} +63 -38
  43. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  44. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  45. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  46. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  47. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  48. package/dist/commands/{extract.js → improve/extract.js} +221 -26
  49. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +30 -4
  50. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
  51. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  52. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
  53. package/dist/commands/{improve.js → improve/improve.js} +672 -292
  54. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  55. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  56. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  57. package/dist/commands/improve/reflect-noise.js +0 -0
  58. package/dist/commands/{reflect.js → improve/reflect.js} +58 -28
  59. package/dist/commands/improve/session-asset.js +248 -0
  60. package/dist/commands/lint/agent-linter.js +1 -1
  61. package/dist/commands/lint/base-linter.js +55 -37
  62. package/dist/commands/lint/command-linter.js +1 -1
  63. package/dist/commands/lint/default-linter.js +1 -1
  64. package/dist/commands/lint/env-key-rules.js +1 -1
  65. package/dist/commands/lint/index.js +19 -25
  66. package/dist/commands/lint/knowledge-linter.js +1 -1
  67. package/dist/commands/lint/memory-linter.js +1 -1
  68. package/dist/commands/lint/registry.js +8 -8
  69. package/dist/commands/lint/skill-linter.js +1 -1
  70. package/dist/commands/lint/task-linter.js +1 -1
  71. package/dist/commands/lint/workflow-linter.js +1 -1
  72. package/dist/commands/lint.js +1 -1
  73. package/dist/commands/observability-cli.js +244 -0
  74. package/dist/commands/proposal/drain-policies.js +3 -3
  75. package/dist/commands/proposal/drain.js +87 -15
  76. package/dist/commands/proposal/proposal-cli.js +490 -0
  77. package/dist/commands/{proposal.js → proposal/proposal.js} +17 -6
  78. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  79. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  80. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  81. package/dist/{core → commands/proposal/validators}/proposals.js +374 -345
  82. package/dist/commands/{curate.js → read/curate.js} +7 -7
  83. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  84. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  85. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  86. package/dist/commands/read/search-cli.js +207 -0
  87. package/dist/commands/{search.js → read/search.js} +22 -27
  88. package/dist/commands/{show.js → read/show.js} +31 -45
  89. package/dist/commands/registry-cli.js +8 -8
  90. package/dist/commands/remember.js +14 -10
  91. package/dist/commands/sources/add-cli.js +293 -0
  92. package/dist/commands/{history.js → sources/history.js} +27 -25
  93. package/dist/commands/{info.js → sources/info.js} +6 -6
  94. package/dist/commands/{init.js → sources/init.js} +6 -6
  95. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  96. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  97. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  98. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  99. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  100. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  101. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  102. package/dist/commands/sources/sources-cli.js +305 -0
  103. package/dist/commands/sources/stash-cli.js +219 -0
  104. package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
  105. package/dist/commands/tasks/default-tasks.js +173 -0
  106. package/dist/commands/tasks/tasks-cli.js +210 -0
  107. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  108. package/dist/commands/wiki-cli.js +307 -0
  109. package/dist/commands/workflow-cli.js +329 -0
  110. package/dist/core/action-contributors.js +1 -1
  111. package/dist/core/assert.js +40 -0
  112. package/dist/core/asset/asset-create.js +54 -0
  113. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  114. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  115. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  116. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  117. package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
  118. package/dist/core/best-effort.js +64 -0
  119. package/dist/core/common.js +32 -18
  120. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  121. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  122. package/dist/core/{config-schema.js → config/config-schema.js} +50 -7
  123. package/dist/core/config/config-types.js +16 -0
  124. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  125. package/dist/core/{config.js → config/config.js} +10 -8
  126. package/dist/core/env-secret-ref.js +90 -0
  127. package/dist/core/errors.js +13 -3
  128. package/dist/core/events.js +27 -4
  129. package/dist/core/file-lock.js +1 -1
  130. package/dist/core/improve-types.js +48 -0
  131. package/dist/core/lesson-lint.js +2 -2
  132. package/dist/core/logs-db.js +304 -0
  133. package/dist/core/paths.js +2 -2
  134. package/dist/core/ripgrep/install.js +2 -2
  135. package/dist/core/ripgrep/resolve.js +2 -2
  136. package/dist/core/state-db.js +195 -60
  137. package/dist/core/text-truncation.js +148 -0
  138. package/dist/core/time.js +1 -1
  139. package/dist/core/write-source.js +98 -85
  140. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  141. package/dist/indexer/{db.js → db/db.js} +128 -118
  142. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  143. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  144. package/dist/indexer/ensure-index.js +4 -4
  145. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  146. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  147. package/dist/indexer/indexer.js +37 -30
  148. package/dist/indexer/init.js +54 -0
  149. package/dist/indexer/manifest.js +10 -10
  150. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +141 -33
  151. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  152. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  153. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  154. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  155. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  156. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  157. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  158. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  159. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  160. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  161. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  162. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  163. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  164. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  165. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  166. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  167. package/dist/integrations/agent/builder-shared.js +39 -0
  168. package/dist/integrations/agent/builders.js +14 -81
  169. package/dist/integrations/agent/config.js +6 -4
  170. package/dist/integrations/agent/detect.js +1 -1
  171. package/dist/integrations/agent/index.js +23 -8
  172. package/dist/integrations/agent/prompts.js +2 -3
  173. package/dist/integrations/agent/runner.js +22 -3
  174. package/dist/integrations/agent/spawn.js +9 -10
  175. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  176. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  177. package/dist/integrations/harnesses/claude/index.js +64 -0
  178. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +32 -5
  179. package/dist/integrations/harnesses/index.js +144 -0
  180. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  181. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  182. package/dist/integrations/harnesses/opencode/index.js +59 -0
  183. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  184. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  185. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  186. package/dist/integrations/harnesses/types.js +43 -0
  187. package/dist/integrations/lockfile.js +7 -16
  188. package/dist/integrations/session-logs/index.js +82 -9
  189. package/dist/llm/call-ai.js +4 -4
  190. package/dist/llm/client.js +146 -6
  191. package/dist/llm/embedder.js +6 -6
  192. package/dist/llm/embedders/local.js +9 -22
  193. package/dist/llm/embedders/remote.js +2 -2
  194. package/dist/llm/embedders/types.js +1 -1
  195. package/dist/llm/graph-extract.js +31 -12
  196. package/dist/llm/index-passes.js +1 -1
  197. package/dist/llm/memory-infer.js +12 -5
  198. package/dist/llm/metadata-enhance.js +2 -2
  199. package/dist/llm/usage-persist.js +77 -0
  200. package/dist/llm/usage-telemetry.js +103 -0
  201. package/dist/output/context.js +9 -46
  202. package/dist/output/html-render.js +73 -0
  203. package/dist/output/renderers.js +88 -58
  204. package/dist/output/shapes/curate.js +7 -3
  205. package/dist/output/shapes/distill.js +7 -3
  206. package/dist/output/shapes/env-list.js +18 -16
  207. package/dist/output/shapes/events.js +5 -4
  208. package/dist/output/shapes/helpers.js +19 -5
  209. package/dist/output/shapes/history.js +7 -3
  210. package/dist/output/shapes/passthrough.js +8 -11
  211. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  212. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  213. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  214. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  215. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  216. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  217. package/dist/output/shapes/registry-search.js +7 -3
  218. package/dist/output/shapes/registry.js +12 -0
  219. package/dist/output/shapes/search.js +7 -3
  220. package/dist/output/shapes/secret-list.js +18 -16
  221. package/dist/output/shapes/show.js +7 -3
  222. package/dist/output/shapes.js +55 -30
  223. package/dist/output/text/add.js +2 -3
  224. package/dist/output/text/clone.js +2 -3
  225. package/dist/output/text/config.js +2 -3
  226. package/dist/output/text/curate.js +4 -3
  227. package/dist/output/text/distill.js +2 -3
  228. package/dist/output/text/enable-disable.js +5 -4
  229. package/dist/output/text/env.js +13 -0
  230. package/dist/output/text/events.js +5 -4
  231. package/dist/output/text/feedback.js +4 -3
  232. package/dist/output/text/helpers.js +123 -40
  233. package/dist/output/text/history.js +2 -3
  234. package/dist/output/text/import.js +2 -3
  235. package/dist/output/text/index.js +2 -3
  236. package/dist/output/text/info.js +2 -3
  237. package/dist/output/text/init.js +2 -3
  238. package/dist/output/text/list.js +2 -3
  239. package/dist/output/text/proposal/producer.js +9 -0
  240. package/dist/output/text/proposal/proposal.js +13 -0
  241. package/dist/output/text/registry-commands.js +8 -7
  242. package/dist/output/text/registry.js +12 -0
  243. package/dist/output/text/remember.js +4 -3
  244. package/dist/output/text/remove.js +2 -3
  245. package/dist/output/text/save.js +2 -3
  246. package/dist/output/text/search.js +4 -3
  247. package/dist/output/text/show.js +4 -3
  248. package/dist/output/text/update.js +2 -3
  249. package/dist/output/text/upgrade.js +2 -3
  250. package/dist/output/text/wiki.js +12 -11
  251. package/dist/output/text/workflow.js +12 -10
  252. package/dist/output/text.js +66 -32
  253. package/dist/registry/build-index.js +11 -10
  254. package/dist/registry/factory.js +1 -1
  255. package/dist/registry/origin-resolve.js +1 -1
  256. package/dist/registry/providers/index.js +2 -2
  257. package/dist/registry/providers/skills-sh.js +91 -72
  258. package/dist/registry/providers/static-index.js +75 -52
  259. package/dist/registry/resolve.js +3 -3
  260. package/dist/runtime.js +242 -0
  261. package/dist/scripts/migrate-storage.js +1654 -683
  262. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +254 -168
  263. package/dist/setup/detect.js +311 -9
  264. package/dist/setup/harness-config-import.js +6 -120
  265. package/dist/setup/setup.js +454 -43
  266. package/dist/sources/include.js +1 -1
  267. package/dist/sources/provider-factory.js +2 -2
  268. package/dist/sources/providers/filesystem.js +3 -3
  269. package/dist/sources/providers/git.js +9 -9
  270. package/dist/sources/providers/index.js +4 -4
  271. package/dist/sources/providers/npm.js +6 -6
  272. package/dist/sources/providers/provider-utils.js +13 -20
  273. package/dist/sources/providers/sync-from-ref.js +5 -5
  274. package/dist/sources/providers/tar-utils.js +2 -2
  275. package/dist/sources/providers/website.js +2 -2
  276. package/dist/sources/resolve.js +5 -5
  277. package/dist/sources/website-ingest.js +5 -5
  278. package/dist/storage/database.js +102 -0
  279. package/dist/storage/engines/sqlite-migrations.js +42 -0
  280. package/dist/storage/locations.js +25 -0
  281. package/dist/storage/repositories/index-db.js +43 -0
  282. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  283. package/dist/tasks/backends/cron.js +4 -4
  284. package/dist/tasks/backends/exec-utils.js +32 -0
  285. package/dist/tasks/backends/index.js +3 -3
  286. package/dist/tasks/backends/launchd.js +7 -14
  287. package/dist/tasks/backends/schtasks.js +7 -16
  288. package/dist/tasks/embedded.js +71 -0
  289. package/dist/tasks/parser.js +2 -2
  290. package/dist/tasks/resolveAkmBin.js +1 -1
  291. package/dist/tasks/runner.js +127 -31
  292. package/dist/tasks/schedule.js +1 -1
  293. package/dist/tasks/validator.js +7 -7
  294. package/dist/text-import-hook.mjs +51 -0
  295. package/dist/version.js +2 -1
  296. package/dist/wiki/wiki.js +7 -7
  297. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  298. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  299. package/dist/workflows/cli.js +1 -1
  300. package/dist/workflows/db.js +54 -32
  301. package/dist/workflows/parser.js +4 -4
  302. package/dist/workflows/renderer.js +5 -5
  303. package/dist/workflows/runtime/agent-identity.js +56 -0
  304. package/dist/workflows/runtime/checkin.js +57 -0
  305. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  306. package/dist/workflows/validate-summary.js +82 -0
  307. package/docs/README.md +1 -1
  308. package/docs/data-and-telemetry.md +6 -6
  309. package/package.json +17 -8
  310. package/dist/commands/add-cli.js +0 -279
  311. package/dist/commands/env.js +0 -213
  312. package/dist/integrations/agent/sdk-runner.js +0 -126
  313. package/dist/output/shapes/vault-list.js +0 -19
  314. package/dist/output/text/proposal-producer.js +0 -8
  315. package/dist/output/text/proposal.js +0 -12
  316. package/dist/output/text/vault.js +0 -16
  317. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  318. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  319. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  320. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  321. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  322. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  323. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  324. /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
@@ -3,20 +3,20 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
- import { SCRIPT_EXTENSIONS } from "../core/asset-spec";
7
- import { isHttpUrl, resolveStashDir, toErrorMessage } from "../core/common";
8
- import { concurrentMap } from "../core/concurrent";
9
- import { getDbPath } from "../core/paths";
10
- import { isVerbose, warn, warnVerbose } from "../core/warn";
11
- import { resolveIndexPassLLM } from "../llm/index-passes";
12
- import { takeWorkflowDocument } from "../workflows/document-cache";
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";
15
- import { applyCuratedFrontmatter, applyWikiFrontmatter, generateMetadataFlat, isEnrichmentComplete, isWorkflowSkipWarning, loadStashFile, shouldIndexStashFile, } from "./metadata";
16
- import { buildSearchText } from "./search-fields";
17
- import { classifySemanticFailure, clearSemanticStatus, deriveSemanticProviderFingerprint, writeSemanticStatus, } from "./semantic-status";
18
- import { ensureUsageEventsSchema, purgeOldUsageEvents } from "./usage-events";
19
- import { walkStashFlat } from "./walker";
6
+ import { SCRIPT_EXTENSIONS } from "../core/asset/asset-spec.js";
7
+ import { isHttpUrl, resolveStashDir, toErrorMessage } from "../core/common.js";
8
+ import { concurrentMap } from "../core/concurrent.js";
9
+ import { getDbPath } from "../core/paths.js";
10
+ import { isVerbose, warn, warnVerbose } from "../core/warn.js";
11
+ import { resolveIndexPassLLM } from "../llm/index-passes.js";
12
+ import { takeWorkflowDocument } from "../workflows/runtime/document-cache.js";
13
+ import { clearStaleCacheEntries, closeDatabase, deleteEntriesByDir, deleteEntriesByIds, deleteEntriesByStashDir, deleteIndexDirStatesByStashDir, getAllEntriesForEmbedding, getEmbeddableEntryCount, getEmbeddingCount, getEntriesByDir, getEntryCount, getIndexDirState, getMeta, isVecAvailable, openDatabase, openExistingDatabase, rebuildFts, relinkUsageEvents, setMeta, upsertEmbedding, upsertEntry, upsertIndexDirState, upsertUtilityScore, upsertWorkflowDocument, warnIfVecMissing, } from "./db/db.js";
14
+ import { deleteStoredGraph } from "./db/graph-db.js";
15
+ import { applyCuratedFrontmatter, applyWikiFrontmatter, generateMetadataFlat, isEnrichmentComplete, isWorkflowSkipWarning, loadStashFile, shouldIndexStashFile, } from "./passes/metadata.js";
16
+ import { buildSearchText } from "./search/search-fields.js";
17
+ import { classifySemanticFailure, clearSemanticStatus, deriveSemanticProviderFingerprint, writeSemanticStatus, } from "./search/semantic-status.js";
18
+ import { ensureUsageEventsSchema, purgeOldUsageEvents } from "./usage/usage-events.js";
19
+ import { walkStashFlat } from "./walk/walker.js";
20
20
  function throwIfAborted(signal) {
21
21
  if (signal?.aborted) {
22
22
  throw signal.reason instanceof Error ? signal.reason : new Error("index interrupted");
@@ -177,7 +177,8 @@ async function runFinalizePhase(ctx) {
177
177
  setMeta(db, "hasEmbeddings", embeddingResult.success ? "1" : "0");
178
178
  warnIfVecMissing(db);
179
179
  const totalEntries = getEntryCount(db);
180
- const verification = verifyIndexState(db, config, totalEntries, embeddingResult);
180
+ const semanticEntryCount = getEmbeddableEntryCount(db);
181
+ const verification = verifyIndexState(db, config, semanticEntryCount, embeddingResult);
181
182
  if (config.semanticSearchMode === "off") {
182
183
  clearSemanticStatus();
183
184
  }
@@ -232,11 +233,17 @@ export async function akmIndex(options) {
232
233
  const clean = options?.clean === true;
233
234
  const dryRun = options?.dryRun === true;
234
235
  // Load config and resolve all stash sources
235
- const { loadConfig } = await import("../core/config.js");
236
+ const { loadConfig } = await import("../core/config/config.js");
236
237
  const config = loadConfig();
238
+ // One-time, read-only guard: warn if the writable stash still holds an
239
+ // un-migrated `vaults/` directory. In 0.9.0 the indexer skips `vaults/`
240
+ // entirely, so an unmigrated vault's `.env` data would silently never be
241
+ // indexed. Non-destructive — only stats, never reads/writes/deletes.
242
+ const { warnOnUnmigratedVaults } = await import("./usage/unmigrated-vaults-guard.js");
243
+ warnOnUnmigratedVaults(stashDir);
237
244
  // Ensure git stash caches are extracted before resolving stash dirs,
238
245
  // so their content directories exist on disk for the walker to discover.
239
- const { ensureSourceCaches, resolveSourceEntries } = await import("./search-source.js");
246
+ const { ensureSourceCaches, resolveSourceEntries } = await import("./search/search-source.js");
240
247
  await ensureSourceCaches(config, { force: full });
241
248
  const allSourceEntries = resolveSourceEntries(stashDir, config);
242
249
  const allSourceDirs = allSourceEntries.map((s) => s.path);
@@ -995,11 +1002,11 @@ function getSemanticSearchLabel(semanticSearchMode, embeddingProvider, vecAvaila
995
1002
  return "disabled";
996
1003
  return `${embeddingProvider} embeddings, ${vecAvailable ? "sqlite-vec" : "JS fallback"}`;
997
1004
  }
998
- function verifyIndexState(db, config, totalEntries, embeddingResult) {
1005
+ function verifyIndexState(db, config, embeddableEntries, embeddingResult) {
999
1006
  const embeddingCount = getEmbeddingCount(db);
1000
1007
  const vecAvailable = isVecAvailable(db);
1001
1008
  const embeddingProvider = getEmbeddingProvider(config.embedding);
1002
- if (totalEntries === 0) {
1009
+ if (embeddableEntries === 0) {
1003
1010
  return {
1004
1011
  ok: true,
1005
1012
  message: "Index ready. No assets were found yet.",
@@ -1007,7 +1014,7 @@ function verifyIndexState(db, config, totalEntries, embeddingResult) {
1007
1014
  semanticSearchMode: config.semanticSearchMode,
1008
1015
  semanticStatus: config.semanticSearchMode === "off" ? "disabled" : "pending",
1009
1016
  embeddingProvider,
1010
- entryCount: totalEntries,
1017
+ entryCount: embeddableEntries,
1011
1018
  embeddingCount,
1012
1019
  vecAvailable,
1013
1020
  };
@@ -1020,20 +1027,20 @@ function verifyIndexState(db, config, totalEntries, embeddingResult) {
1020
1027
  semanticSearchMode: config.semanticSearchMode,
1021
1028
  semanticStatus: "disabled",
1022
1029
  embeddingProvider,
1023
- entryCount: totalEntries,
1030
+ entryCount: embeddableEntries,
1024
1031
  embeddingCount,
1025
1032
  vecAvailable,
1026
1033
  };
1027
1034
  }
1028
- if (embeddingCount >= totalEntries) {
1035
+ if (embeddingCount >= embeddableEntries) {
1029
1036
  return {
1030
1037
  ok: true,
1031
- message: `Semantic search ready (${embeddingCount}/${totalEntries} embeddings, ${vecAvailable ? "sqlite-vec active" : "JS fallback active"}).`,
1038
+ message: `Semantic search ready (${embeddingCount}/${embeddableEntries} embeddings, ${vecAvailable ? "sqlite-vec active" : "JS fallback active"}).`,
1032
1039
  semanticSearchEnabled: true,
1033
1040
  semanticSearchMode: config.semanticSearchMode,
1034
1041
  semanticStatus: vecAvailable ? "ready-vec" : "ready-js",
1035
1042
  embeddingProvider,
1036
- entryCount: totalEntries,
1043
+ entryCount: embeddableEntries,
1037
1044
  embeddingCount,
1038
1045
  vecAvailable,
1039
1046
  };
@@ -1041,7 +1048,7 @@ function verifyIndexState(db, config, totalEntries, embeddingResult) {
1041
1048
  return {
1042
1049
  ok: false,
1043
1050
  message: embeddingResult.message ??
1044
- `Semantic search verification failed (${embeddingCount}/${totalEntries} embeddings available).`,
1051
+ `Semantic search verification failed (${embeddingCount}/${embeddableEntries} embeddings available).`,
1045
1052
  guidance: embeddingProvider === "remote"
1046
1053
  ? "Check your embedding endpoint and credentials, then retry `akm index --full --verbose`."
1047
1054
  : "Retry `akm index --full --verbose`. If it still fails, confirm local model downloads are permitted and see docs/configuration.md for local embedding dependency setup.",
@@ -1049,7 +1056,7 @@ function verifyIndexState(db, config, totalEntries, embeddingResult) {
1049
1056
  semanticSearchMode: config.semanticSearchMode,
1050
1057
  semanticStatus: "blocked",
1051
1058
  embeddingProvider,
1052
- entryCount: totalEntries,
1059
+ entryCount: embeddableEntries,
1053
1060
  embeddingCount,
1054
1061
  vecAvailable,
1055
1062
  };
@@ -1075,8 +1082,8 @@ function resolveIndexedFiles(dirPath, files, stash) {
1075
1082
  return resolved.size > 0 ? [...resolved] : files;
1076
1083
  }
1077
1084
  async function enhanceStashWithLlm(llmConfig, stash, files, summary, signal, db, entryKeys, reEnrich, akmConfig, onEntryDone) {
1078
- const { enhanceMetadata } = await import("../llm/metadata-enhance");
1079
- const { computeBodyHash, getLlmCacheEntry, upsertLlmCacheEntry } = await import("./db.js");
1085
+ const { enhanceMetadata } = await import("../llm/metadata-enhance.js");
1086
+ const { computeBodyHash, getLlmCacheEntry, upsertLlmCacheEntry } = await import("./db/db.js");
1080
1087
  const results = await concurrentMap(stash.entries, async (entry, idx) => {
1081
1088
  if (signal?.aborted)
1082
1089
  return entry;
@@ -1233,8 +1240,8 @@ function mergeLegacyEntry(entry, legacyEntries) {
1233
1240
  * `NotFoundError` with their own messaging.
1234
1241
  */
1235
1242
  export async function lookup(ref) {
1236
- const { loadConfig } = await import("../core/config.js");
1237
- const { resolveSourceEntries } = await import("./search-source.js");
1243
+ const { loadConfig } = await import("../core/config/config.js");
1244
+ const { resolveSourceEntries } = await import("./search/search-source.js");
1238
1245
  const config = loadConfig();
1239
1246
  const sources = resolveSourceEntries(undefined, config);
1240
1247
  if (sources.length === 0)
@@ -0,0 +1,54 @@
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/.
4
+ /**
5
+ * Explicit composition root for the indexer's built-in registrations.
6
+ *
7
+ * Historically two independent lazy gates (`ensureBuiltinsRegistered` in
8
+ * `walk/file-context.ts` and `ensureBuiltinMetadataContributorsRegistered` in
9
+ * `passes/metadata-contributors.ts`) each registered a different built-in set
10
+ * on first use. That implicit, order-dependent wiring is the M1/M2 finding in
11
+ * `docs/technical/code-health-brittleness-audit.md`.
12
+ *
13
+ * `initIndexer()` folds both into a single deterministic, idempotent entry
14
+ * point. It registers, exactly once:
15
+ *
16
+ * 1. Built-in matchers — `registerBuiltinMatchers()` (`walk/matchers.ts`).
17
+ * 2. Built-in renderers — `registerBuiltinRenderers()` (`output/renderers.ts`).
18
+ * 3. Metadata contributors — top-level registration side-effects that run when
19
+ * `output/renderers.ts` and `workflows/renderer.ts` are imported.
20
+ *
21
+ * Importing `output/renderers.ts` satisfies both (2) and the renderer-owned
22
+ * metadata contributors; `workflows/renderer.ts` is imported explicitly for the
23
+ * workflow contributor (it is already transitively pulled in by renderers, but
24
+ * the explicit import preserves the original gate's import set and keeps the
25
+ * wiring self-documenting).
26
+ *
27
+ * Timing is preserved: this stays a *lazy* gate. It is awaited from the same
28
+ * accessor call sites the old gates were awaited from, so no startup work is
29
+ * forced eagerly. The shared promise makes concurrent and repeat calls safe —
30
+ * the registrations run at most once per process.
31
+ */
32
+ let initPromise;
33
+ /**
34
+ * Idempotently register every built-in indexer contributor (matchers,
35
+ * renderers, and metadata contributors).
36
+ *
37
+ * Safe to call repeatedly and concurrently: the registration work runs at most
38
+ * once; subsequent calls await the same resolved promise.
39
+ */
40
+ export function initIndexer() {
41
+ if (!initPromise) {
42
+ initPromise = (async () => {
43
+ const { registerBuiltinMatchers } = await import("./walk/matchers.js");
44
+ // Importing renderers registers the built-in metadata contributors as a
45
+ // load-time side-effect and exposes registerBuiltinRenderers().
46
+ const { registerBuiltinRenderers } = await import("../output/renderers.js");
47
+ // Imported for the workflow metadata contributor's load-time side-effect.
48
+ await import("../workflows/renderer.js");
49
+ registerBuiltinMatchers();
50
+ registerBuiltinRenderers();
51
+ })();
52
+ }
53
+ return initPromise;
54
+ }
@@ -11,16 +11,16 @@
11
11
  */
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
- import { makeAssetRef } from "../core/asset-ref";
15
- import { deriveCanonicalAssetNameFromStashRoot } from "../core/asset-spec";
16
- import { resolveStashDir } from "../core/common";
17
- import { loadConfig } from "../core/config";
18
- import { getDbPath } from "../core/paths";
19
- import { warn } from "../core/warn";
20
- import { closeDatabase, getAllEntries, getEntryCount, getMeta, openExistingDatabase } from "./db";
21
- import { generateMetadataFlat, loadStashFile } from "./metadata";
22
- import { resolveSourceEntries } from "./search-source";
23
- import { walkStashFlat } from "./walker";
14
+ import { makeAssetRef } from "../core/asset/asset-ref.js";
15
+ import { deriveCanonicalAssetNameFromStashRoot } from "../core/asset/asset-spec.js";
16
+ import { resolveStashDir } from "../core/common.js";
17
+ import { loadConfig } from "../core/config/config.js";
18
+ import { getDbPath } from "../core/paths.js";
19
+ import { warn } from "../core/warn.js";
20
+ import { closeDatabase, getAllEntries, getEntryCount, getMeta, openExistingDatabase } from "./db/db.js";
21
+ import { generateMetadataFlat, loadStashFile } from "./passes/metadata.js";
22
+ import { resolveSourceEntries } from "./search/search-source.js";
23
+ import { walkStashFlat } from "./walk/walker.js";
24
24
  const MAX_DESCRIPTION_LENGTH = 80;
25
25
  /**
26
26
  * Truncate a description string to a maximum length, appending "..." if truncated.
@@ -1,19 +1,52 @@
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
+ /**
5
+ * Memory inference pass for `akm index` (#201).
6
+ *
7
+ * Detects memories pending inference, asks the configured LLM to compress each
8
+ * into one higher-signal derived memory, and writes the result back as a new
9
+ * memory file with frontmatter `inferred: true` + a `source:` backref to the
10
+ * parent memory.
11
+ *
12
+ * Pending predicate (see {@link isPendingMemory}):
13
+ * - File lives under `<stashRoot>/memories/` and ends in `.md`.
14
+ * - Frontmatter does NOT have `inferenceProcessed: true` (parent already split).
15
+ * - Frontmatter does NOT have `inferred: true` (this is itself a child fact).
16
+ *
17
+ * Idempotency: after a successful split the parent's frontmatter is rewritten
18
+ * with `inferenceProcessed: true`. A subsequent `akm index` therefore skips
19
+ * the parent without re-running the LLM.
20
+ *
21
+ * Disabling — two orthogonal gates:
22
+ * 1. `profiles.improve.default.processes.memoryInference.enabled = false`
23
+ * blocks the pass at the feature-flag layer (no network call may ever
24
+ * issue). Historically the v1 spec §14 gate, superseded by the 0.8.0
25
+ * profile shape.
26
+ * 2. `index.memory.llm = false` (or no resolvable LLM profile) opts the
27
+ * pass out at the per-pass layer (#208).
28
+ * A pass runs iff both layers allow it. Existing inferred children are
29
+ * NEVER deleted — the user keeps what was already produced.
30
+ *
31
+ * Locked v1 contract:
32
+ * - LLM access is exclusively via `resolveIndexPassLLM("memory", config)`.
33
+ * - All child memory writes go through `writeAssetToSource` in
34
+ * `src/core/write-source.ts`. The parent's frontmatter rewrite is an
35
+ * explicit narrow exception — see {@link markParentProcessed}.
36
+ */
4
37
  import fs from "node:fs";
5
38
  import path from "node:path";
6
- import { parseAssetRef } from "../core/asset-ref";
7
- import { assembleAsset } from "../core/asset-serialize";
8
- import { concurrentMap } from "../core/concurrent";
9
- import { parseFrontmatter, parseFrontmatterBlock } from "../core/frontmatter";
10
- import { warn } from "../core/warn";
11
- import { writeAssetToSource } from "../core/write-source";
12
- import { isProcessEnabled } from "../llm/feature-gate";
13
- import { resolveIndexPassLLM } from "../llm/index-passes";
14
- import * as memoryInfer from "../llm/memory-infer";
15
- import { withLlmCache } from "./llm-cache";
16
- import { walkMarkdownFiles } from "./walker";
39
+ import { parseAssetRef } from "../../core/asset/asset-ref.js";
40
+ import { assembleAsset } from "../../core/asset/asset-serialize.js";
41
+ import { parseFrontmatter, parseFrontmatterBlock } from "../../core/asset/frontmatter.js";
42
+ import { concurrentMap } from "../../core/concurrent.js";
43
+ import { warn } from "../../core/warn.js";
44
+ import { writeAssetToSource } from "../../core/write-source.js";
45
+ import { isProcessEnabled } from "../../llm/feature-gate.js";
46
+ import { resolveIndexPassLLM } from "../../llm/index-passes.js";
47
+ import * as memoryInfer from "../../llm/memory-infer.js";
48
+ import { withLlmCache } from "../db/llm-cache.js";
49
+ import { walkMarkdownFiles } from "../walk/walker.js";
17
50
  /**
18
51
  * Frontmatter keys this pass cares about. Constants so a future rename only
19
52
  * needs to touch one site.
@@ -37,17 +70,24 @@ const FM_CAPTURE_MODE = "captureMode";
37
70
  * Both must allow the call for the pass to run. Either set to `false`
38
71
  * short-circuits to a no-op result.
39
72
  */
40
- export async function runMemoryInferencePass(config, sources, signal, db, reEnrich, onProgress, options = {}) {
73
+ export async function runMemoryInferencePass(ctx) {
74
+ const { config, sources, signal, db, reEnrich, onProgress, options = {} } = ctx;
41
75
  const result = {
42
76
  considered: 0,
43
77
  cacheHits: 0,
78
+ retryAttempts: 0,
44
79
  splitParents: 0,
45
80
  writtenFacts: 0,
46
81
  skippedNoFacts: 0,
47
82
  skippedChildExists: 0,
48
83
  skippedAborted: 0,
49
84
  unaccounted: 0,
85
+ htmlErrorCount: 0,
50
86
  };
87
+ // Mutable sink threaded into compressMemoryToDerivedMemory so the per-call
88
+ // HTML-error categorization (which is otherwise swallowed inside the feature
89
+ // gate) bubbles up into the pass result.
90
+ const inferTelemetry = {};
51
91
  // Gate 1 — feature gate via isProcessEnabled, which reads the 0.8.0 path
52
92
  // (profiles.improve.default.processes.memoryInference.enabled). Defaults to
53
93
  // enabled when the key is absent.
@@ -79,6 +119,26 @@ export async function runMemoryInferencePass(config, sources, signal, db, reEnri
79
119
  // 2026-05-26).
80
120
  if (signal?.aborted)
81
121
  return { aborted: true };
122
+ // Pre-check (#588): when `<parent>.derived.md` is already on disk the
123
+ // inference is by definition complete — the parent only looks pending
124
+ // because `markParentProcessed` never ran (process killed between the
125
+ // child write and the mark) or the child was created externally (e.g.
126
+ // consolidation). Skip the LLM/cache call entirely and mark the parent
127
+ // so it never re-pends. Before this check, production measurements
128
+ // showed ~55% of the pass's LLM budget re-deriving such parents only to
129
+ // discover the existing child after the fact.
130
+ if (fs.existsSync(derivedChildPath(record))) {
131
+ markParentProcessed(record);
132
+ return {
133
+ skipped: false,
134
+ splitParent: false,
135
+ written: 0,
136
+ fromCache: false,
137
+ retryAttempts: 0,
138
+ childExists: true,
139
+ precheck: true,
140
+ };
141
+ }
82
142
  // Incremental cache: skip LLM call when body hash is unchanged and
83
143
  // --re-enrich was not requested. The cache ref is the absolute file path.
84
144
  const validate = (raw) => {
@@ -103,31 +163,58 @@ export async function runMemoryInferencePass(config, sources, signal, db, reEnri
103
163
  // the hit count separately so the operational yield rate
104
164
  // (writtenFacts / freshAttempts) is interpretable as the cache warms.
105
165
  let fromCache = false;
166
+ // Count single bounded retries for transient LLM failures on this
167
+ // candidate. Bumped via the `onRetryAttempt` callback threaded into
168
+ // `chatCompletion`; surfaced as `retryAttempts` telemetry, never as a
169
+ // failure for the same call.
170
+ let retryAttempts = 0;
171
+ const onRetryAttempt = () => {
172
+ retryAttempts += 1;
173
+ };
106
174
  const derived = db
107
175
  ? await withLlmCache(db, record.filePath, record.body, reEnrich ?? false, () => memoryInfer.compressMemoryToDerivedMemory(llmConfig, record.body, signal, config, (evt) => {
108
176
  warn(`[akm] LLM fallback for ${evt.feature}: ${evt.reason}`);
109
- }), validate, undefined, "", {
177
+ }, inferTelemetry, onRetryAttempt), validate, undefined, "", {
110
178
  onCacheHit: () => {
111
179
  fromCache = true;
112
180
  },
113
181
  })
114
182
  : await memoryInfer.compressMemoryToDerivedMemory(llmConfig, record.body, signal, config, (evt) => {
115
183
  warn(`[akm] LLM fallback for ${evt.feature}: ${evt.reason}`);
116
- });
184
+ }, inferTelemetry, onRetryAttempt);
117
185
  if (!derived) {
118
- return { skipped: true, fromCache };
186
+ return { skipped: true, fromCache, retryAttempts };
119
187
  }
120
- const written = await writeDerivedMemory(record, derived);
121
- if (written > 0) {
188
+ const writeOutcome = await writeDerivedMemory(record, derived);
189
+ if (writeOutcome.written > 0) {
122
190
  markParentProcessed(record);
123
- return { skipped: false, splitParent: true, written, fromCache };
191
+ return { skipped: false, splitParent: true, written: writeOutcome.written, fromCache, retryAttempts };
124
192
  }
125
193
  // LLM produced a valid derived draft but no file was written — either
126
- // because `<parent>.derived.md` already exists on disk or
127
- // `writeAssetToSource` threw. Categorise as `childExists` so the
128
- // attempt is accounted for in health metrics rather than vanishing
129
- // into the freshAttempts denominator.
130
- return { skipped: false, splitParent: false, written: 0, fromCache, childExists: true };
194
+ // because `<parent>.derived.md` appeared on disk after the pre-check
195
+ // above (a rare mid-flight race) or `writeAssetToSource` threw.
196
+ // Categorise as `childExists` so the consumed attempt is accounted for
197
+ // in health metrics rather than vanishing into the freshAttempts
198
+ // denominator.
199
+ //
200
+ // When the child exists the inference is, by definition, complete — so
201
+ // mark the parent processed here too (#550), otherwise
202
+ // `isPendingMemory()` re-queues the same parent every run. A genuine
203
+ // write *failure* (`writeAssetToSource` threw) must NOT mark the parent
204
+ // — it should be retried next run — so we key off the explicit
205
+ // `childExists` outcome rather than the conflated `written === 0`.
206
+ if (writeOutcome.childExists) {
207
+ markParentProcessed(record);
208
+ }
209
+ return {
210
+ skipped: false,
211
+ splitParent: false,
212
+ written: 0,
213
+ fromCache,
214
+ retryAttempts,
215
+ childExists: true,
216
+ precheck: false,
217
+ };
131
218
  },
132
219
  // Default concurrency of 4 for cloud APIs. Set `llm.concurrency: 1`
133
220
  // in config.json for local model servers (LM Studio, Ollama).
@@ -151,6 +238,9 @@ export async function runMemoryInferencePass(config, sources, signal, db, reEnri
151
238
  if (res.fromCache) {
152
239
  result.cacheHits += 1;
153
240
  }
241
+ if ("retryAttempts" in res) {
242
+ result.retryAttempts += res.retryAttempts;
243
+ }
154
244
  if (res.skipped) {
155
245
  result.skippedNoFacts += 1;
156
246
  // Intentionally NOT marked processed — a transient LLM failure should
@@ -161,11 +251,16 @@ export async function runMemoryInferencePass(config, sources, signal, db, reEnri
161
251
  result.writtenFacts += res.written;
162
252
  }
163
253
  else if ("childExists" in res && res.childExists) {
164
- // LLM call was consumed but the derived file already existed (or the
165
- // write threw). Track separately so this category is observable in
166
- // health output and stops bleeding into the freshAttempts denominator.
254
+ // Derived child already on disk. Track separately so this category is
255
+ // observable in health output and stops bleeding into the
256
+ // freshAttempts denominator. Pre-check skips (#588) are the routine
257
+ // self-healing path — no LLM attempt was consumed and the parent has
258
+ // been marked processed — so only the rare post-LLM case (mid-flight
259
+ // race or write failure) warrants a per-ref warning.
167
260
  result.skippedChildExists += 1;
168
- warn(`memory inference: derived child for ${pending[i]?.ref ?? "<unknown>"} already existed or write failed; counted as skippedChildExists`);
261
+ if (!res.precheck) {
262
+ warn(`memory inference: derived child for ${pending[i]?.ref ?? "<unknown>"} already existed or write failed; counted as skippedChildExists`);
263
+ }
169
264
  }
170
265
  else {
171
266
  // The per-record state machine should cover every outcome. A hit here
@@ -183,6 +278,7 @@ export async function runMemoryInferencePass(config, sources, signal, db, reEnri
183
278
  currentRef: pending[i]?.ref,
184
279
  });
185
280
  }
281
+ result.htmlErrorCount = inferTelemetry.htmlErrorCount ?? 0;
186
282
  return result;
187
283
  }
188
284
  // ── Pending detection ───────────────────────────────────────────────────────
@@ -260,7 +356,14 @@ function toMemoryName(memoriesDir, filePath) {
260
356
  // user has organised under memories/.
261
357
  return rel.replace(/\\/g, "/").replace(/\.md$/i, "");
262
358
  }
263
- // ── Writing derived memories + marking parent ───────────────────────────────
359
+ /**
360
+ * Absolute path of the derived child for a parent memory. Single source of
361
+ * truth for the `<parent>.derived.md` naming convention — used both by the
362
+ * pre-LLM existence check (#588) and the write path.
363
+ */
364
+ function derivedChildPath(parent) {
365
+ return path.join(parent.stashRoot, "memories", `${parent.name}.derived.md`);
366
+ }
264
367
  async function writeDerivedMemory(parent, derived) {
265
368
  const writeTarget = {
266
369
  kind: "filesystem",
@@ -275,19 +378,24 @@ async function writeDerivedMemory(parent, derived) {
275
378
  };
276
379
  const childName = `${parent.name}.derived`;
277
380
  const childRefStr = `memory:${childName}`;
278
- const childPath = path.join(parent.stashRoot, "memories", `${childName}.md`);
279
- if (fs.existsSync(childPath)) {
280
- return 0;
381
+ if (fs.existsSync(derivedChildPath(parent))) {
382
+ // The derived child appeared on disk after the caller's pre-check (#588)
383
+ // — a rare mid-flight race. Report `childExists` so the caller marks the
384
+ // parent processed (#550) instead of re-queueing it forever.
385
+ return { written: 0, childExists: true };
281
386
  }
282
387
  try {
283
388
  const content = renderDerivedMemory(parent, derived);
284
389
  const childRef = parseAssetRef(childRefStr);
285
390
  await writeAssetToSource(writeTarget, writeConfig, childRef, content);
286
- return 1;
391
+ return { written: 1, childExists: false };
287
392
  }
288
393
  catch (err) {
289
394
  warn(`memory inference: failed to write derived memory ${childName}: ${err instanceof Error ? err.message : String(err)}`);
290
- return 0;
395
+ // A genuine write failure — the parent must remain pending so it is
396
+ // retried on the next run. `childExists: false` keeps it from being
397
+ // marked processed.
398
+ return { written: 0, childExists: false };
291
399
  }
292
400
  }
293
401
  function renderDerivedMemory(parent, derived) {
@@ -2,15 +2,17 @@
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
4
  const contributors = [];
5
- let builtinsPromise;
5
+ /**
6
+ * Ensure that all built-in indexer contributors are registered.
7
+ *
8
+ * Delegates to the single `initIndexer()` composition root (see
9
+ * `src/indexer/init.ts`). Imported dynamically to keep this a lazy gate and to
10
+ * avoid a static import cycle (init -> renderers -> metadata-contributors).
11
+ * Called on first use of getMetadataContributors; idempotent.
12
+ */
6
13
  async function ensureBuiltinMetadataContributorsRegistered() {
7
- if (!builtinsPromise) {
8
- builtinsPromise = (async () => {
9
- await import("../output/renderers.js");
10
- await import("../workflows/renderer.js");
11
- })();
12
- }
13
- return builtinsPromise;
14
+ const { initIndexer } = await import("../init.js");
15
+ await initIndexer();
14
16
  }
15
17
  export function registerMetadataContributor(contributor) {
16
18
  contributors.push(contributor);
@@ -3,12 +3,12 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
- import { deriveCanonicalAssetName, deriveCanonicalAssetNameFromStashRoot, isRelevantAssetFile, } from "../core/asset-spec";
7
- import { asNonEmptyString, isAssetType, writeFileAtomic } from "../core/common";
8
- import { parseFrontmatter } from "../core/frontmatter";
9
- import { isVerbose, warn } from "../core/warn";
10
- import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "./file-context";
11
- import { applyMetadataContributors } from "./metadata-contributors";
6
+ import { deriveCanonicalAssetName, deriveCanonicalAssetNameFromStashRoot, isRelevantAssetFile, } from "../../core/asset/asset-spec.js";
7
+ import { parseFrontmatter } from "../../core/asset/frontmatter.js";
8
+ import { asNonEmptyString, isAssetType, writeFileAtomic } from "../../core/common.js";
9
+ import { isVerbose, warn } from "../../core/warn.js";
10
+ import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "../walk/file-context.js";
11
+ import { applyMetadataContributors } from "./metadata-contributors.js";
12
12
  export const SCOPE_KEYS = ["user", "agent", "run", "channel"];
13
13
  // ── Load / Write ────────────────────────────────────────────────────────────
14
14
  const STASH_FILENAME = ".stash.json";
@@ -480,18 +480,15 @@ export function shouldIndexStashFile(stashRoot, file, options) {
480
480
  const segments = relPath.split(/[\\/]+/).filter(Boolean);
481
481
  if (segments.length === 0)
482
482
  return true;
483
- // Skip env / vault .env files that have a sibling .sensitive marker file.
484
- if ((segments[0] === "env" || segments[0] === "vaults") &&
485
- (file.endsWith(".env") || path.basename(file) === ".env")) {
483
+ // Skip env .env files that have a sibling .sensitive marker file.
484
+ if (segments[0] === "env" && (file.endsWith(".env") || path.basename(file) === ".env")) {
486
485
  const markerPath = file.replace(/\.env$/, ".sensitive");
487
486
  if (fs.existsSync(markerPath))
488
487
  return false;
489
488
  }
490
- // Deprecation: once a stash has migrated to the `env/` directory, the legacy
491
- // `vaults/` copy is frozen. Skip indexing it so the same keys are not
492
- // double-surfaced under both `vault:` and `env:`. (Pre-migration stashes
493
- // with no `env/` dir still index `vaults/` normally.)
494
- if (segments[0] === "vaults" && fs.existsSync(path.join(stashRoot, "env"))) {
489
+ // The legacy `vaults/` directory (frozen copy left by the 0.8 migration) is
490
+ // never indexed the `vault` asset type was removed in 0.9.0.
491
+ if (segments[0] === "vaults") {
495
492
  return false;
496
493
  }
497
494
  // Skip secret files that are themselves a `.sensitive` marker, or that have a
@@ -853,11 +850,10 @@ async function buildEntryFromFile(file, assetType, canonicalName, dirPath, pkgMe
853
850
  }
854
851
  }
855
852
  // Extract @param from script files.
856
- // Env / vault files (.env) and secret files (whole-file secrets) are
857
- // deliberately excluded — their contents are secrets and must never be
858
- // parsed for @param or any other metadata that could embed a value into the
859
- // entry.
860
- if (ext !== ".md" && assetType !== "env" && assetType !== "vault" && assetType !== "secret") {
853
+ // Env files (.env) and secret files (whole-file secrets) are deliberately
854
+ // excluded — their contents are secrets and must never be parsed for @param
855
+ // or any other metadata that could embed a value into the entry.
856
+ if (ext !== ".md" && assetType !== "env" && assetType !== "secret") {
861
857
  const content = ctx.content();
862
858
  const scriptParams = extractScriptParameters(file, content);
863
859
  if (scriptParams)