akm-cli 0.9.1 → 0.9.2-alpha.2

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 (350) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/STABILITY.md +1 -1
  5. package/dist/akm +2 -2
  6. package/dist/akm-migrate +2 -2
  7. package/dist/assets/hints/cli-hints-full.md +14 -9
  8. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  10. package/dist/assets/models.json +35 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
  12. package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
  13. package/dist/assets/tasks/core/extract.yml +6 -5
  14. package/dist/assets/tasks/core/improve.yml +6 -5
  15. package/dist/assets/tasks/core/index-refresh.yml +6 -5
  16. package/dist/assets/tasks/core/sync.yml +6 -5
  17. package/dist/assets/tasks/core/version-check.yml +6 -5
  18. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
  19. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
  20. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
  21. package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
  22. package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
  23. package/dist/cli/confirm.js +2 -2
  24. package/dist/cli/parse-args.js +3 -24
  25. package/dist/cli/retired-commands.js +1 -1
  26. package/dist/cli/shared.js +2 -2
  27. package/dist/cli.js +11 -9
  28. package/dist/commands/agent/agent-dispatch.js +55 -89
  29. package/dist/commands/agent/contribute-cli.js +12 -45
  30. package/dist/commands/command/builtin-action.js +32 -0
  31. package/dist/commands/command/command-cli.js +99 -0
  32. package/dist/commands/command/command-execution.js +308 -0
  33. package/dist/commands/command/execution-source-loader.js +176 -0
  34. package/dist/commands/command/portable-template.js +60 -0
  35. package/dist/commands/config-cli.js +10 -4
  36. package/dist/commands/env/env.js +4 -2
  37. package/dist/commands/feedback-cli.js +1 -1
  38. package/dist/commands/health/checks.js +241 -29
  39. package/dist/commands/health/html-report.js +0 -14
  40. package/dist/commands/health/report-view-model.js +0 -1
  41. package/dist/commands/health/surfaces.js +6 -7
  42. package/dist/commands/health/types.js +0 -2
  43. package/dist/commands/health.js +63 -18
  44. package/dist/commands/improve/collapse-detector.js +5 -6
  45. package/dist/commands/improve/consolidate.js +251 -214
  46. package/dist/commands/improve/distill/promote-memory.js +71 -34
  47. package/dist/commands/improve/distill/quality-gate.js +17 -5
  48. package/dist/commands/improve/distill.js +232 -155
  49. package/dist/commands/improve/eligibility.js +112 -79
  50. package/dist/commands/improve/execution.js +57 -0
  51. package/dist/commands/improve/extract-cli.js +5 -5
  52. package/dist/commands/improve/extract-prompt.js +64 -22
  53. package/dist/commands/improve/extract.js +608 -360
  54. package/dist/commands/improve/improve-strategies.js +43 -14
  55. package/dist/commands/improve/improve.js +249 -29
  56. package/dist/commands/improve/loop-stages.js +11 -17
  57. package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
  58. package/dist/commands/improve/outcome-loop.js +22 -38
  59. package/dist/commands/improve/planner.js +134 -0
  60. package/dist/commands/improve/preparation.js +730 -409
  61. package/dist/commands/improve/reflect.js +386 -223
  62. package/dist/commands/improve/run-context.js +3 -4
  63. package/dist/commands/improve/salience.js +6 -58
  64. package/dist/commands/improve/session-asset.js +12 -12
  65. package/dist/commands/lint/index.js +101 -29
  66. package/dist/commands/migrate-cli.js +11 -69
  67. package/dist/commands/migration-tool.js +6 -9
  68. package/dist/commands/models-cli.js +27 -0
  69. package/dist/commands/proposal/drain.js +258 -186
  70. package/dist/commands/proposal/proposal-cli.js +32 -10
  71. package/dist/commands/proposal/proposal.js +2 -5
  72. package/dist/commands/proposal/propose.js +192 -172
  73. package/dist/commands/proposal/repository.js +54 -91
  74. package/dist/commands/proposal/validators/proposal-validators.js +9 -7
  75. package/dist/commands/read/curate.js +53 -22
  76. package/dist/commands/read/registry-search.js +25 -9
  77. package/dist/commands/read/remember-cli.js +14 -2
  78. package/dist/commands/read/search.js +10 -4
  79. package/dist/commands/read/show.js +139 -153
  80. package/dist/commands/registry-cli.js +16 -7
  81. package/dist/commands/remember.js +33 -18
  82. package/dist/commands/sources/add-cli.js +19 -178
  83. package/dist/commands/sources/bundle-cli.js +15 -3
  84. package/dist/commands/sources/dangerous-env-audit.js +135 -0
  85. package/dist/commands/sources/info.js +2 -1
  86. package/dist/commands/sources/installed-stashes.js +901 -177
  87. package/dist/commands/sources/schema-repair.js +174 -95
  88. package/dist/commands/sources/self-update.js +30 -74
  89. package/dist/commands/sources/source-add.js +3 -5
  90. package/dist/commands/sources/sources-cli.js +2 -15
  91. package/dist/commands/sources/update-transaction.js +220 -0
  92. package/dist/commands/tasks/tasks-cli.js +3 -3
  93. package/dist/commands/tasks/tasks.js +736 -317
  94. package/dist/commands/workflow-cli.js +2 -2
  95. package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
  96. package/dist/core/adapter/adapters/akm-adapter.js +85 -35
  97. package/dist/core/adapter/adapters/akm-lint.js +54 -39
  98. package/dist/core/adapter/adapters/akm-metadata.js +45 -45
  99. package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
  100. package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
  101. package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
  102. package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
  103. package/dist/core/adapter/adapters/index.js +0 -9
  104. package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
  105. package/dist/core/adapter/adapters/okf-adapter.js +4 -0
  106. package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
  107. package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
  108. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
  109. package/dist/core/adapter/execution-source.js +308 -0
  110. package/dist/core/adapter/recognize-match.js +36 -13
  111. package/dist/core/adapter/registry.js +0 -9
  112. package/dist/core/asset/stash-meta.js +94 -4
  113. package/dist/core/common.js +6 -11
  114. package/dist/core/config/config-io.js +3 -3
  115. package/dist/core/config/config-schema.js +18 -40
  116. package/dist/core/config/config-sources.js +11 -21
  117. package/dist/core/config/config-walker.js +31 -13
  118. package/dist/core/config/config.js +23 -26
  119. package/dist/core/config/schema/engines.js +8 -7
  120. package/dist/core/config/schema/improve-processes.js +29 -5
  121. package/dist/core/config/schema/index-config.js +0 -27
  122. package/dist/core/config/schema/primitives.js +1 -23
  123. package/dist/core/config/schema/sources-bundles.js +13 -16
  124. package/dist/core/errors.js +2 -0
  125. package/dist/core/events.js +68 -32
  126. package/dist/core/extra-params.js +1 -0
  127. package/dist/core/improve-result.js +315 -0
  128. package/dist/core/lesson-lint.js +0 -6
  129. package/dist/core/maintenance-barrier.js +4 -4
  130. package/dist/core/network-policy.js +152 -0
  131. package/dist/core/paths.js +1 -1
  132. package/dist/core/recognition-util.js +4 -4
  133. package/dist/core/registry-url.js +456 -0
  134. package/dist/core/state/migrations.js +161 -47
  135. package/dist/core/state-db.js +453 -80
  136. package/dist/core/system-error.js +32 -0
  137. package/dist/core/time.js +2 -12
  138. package/dist/core/write-source.js +0 -18
  139. package/dist/execution/directory-identity.js +52 -0
  140. package/dist/execution/executable-identity.js +107 -0
  141. package/dist/execution/guarded-source.js +398 -0
  142. package/dist/execution/json.js +95 -0
  143. package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
  144. package/dist/execution/record.js +55 -0
  145. package/dist/execution/resolved-request.js +730 -0
  146. package/dist/execution/source.js +320 -0
  147. package/dist/indexer/bundle-identity-guard.js +5 -4
  148. package/dist/indexer/db/graph-db.js +33 -0
  149. package/dist/indexer/graph/graph-boost.js +3 -4
  150. package/dist/indexer/graph/graph-extraction.js +562 -373
  151. package/dist/indexer/index-written-assets.js +78 -39
  152. package/dist/indexer/indexer.js +471 -432
  153. package/dist/indexer/installations.js +6 -0
  154. package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
  155. package/dist/indexer/materialize-embeddings.js +155 -0
  156. package/dist/indexer/passes/memory-inference.js +227 -174
  157. package/dist/indexer/passes/metadata.js +263 -118
  158. package/dist/indexer/scan/doc-to-entry.js +7 -10
  159. package/dist/indexer/scan/drain-dir.js +51 -23
  160. package/dist/indexer/search/db-search.js +156 -50
  161. package/dist/indexer/search/fts-query.js +40 -40
  162. package/dist/indexer/search/ranking.js +36 -1
  163. package/dist/indexer/search/search-attribution.js +3 -1
  164. package/dist/indexer/search/search-fields.js +23 -14
  165. package/dist/indexer/search/search-hit-enrichers.js +1 -1
  166. package/dist/indexer/search/search-source.js +7 -16
  167. package/dist/indexer/search/semantic-status.js +10 -1
  168. package/dist/indexer/usage/show-usage.js +105 -0
  169. package/dist/indexer/usage/usage-events.js +7 -2
  170. package/dist/indexer/walk/matchers.js +40 -10
  171. package/dist/indexer/walk/path-resolver.js +5 -2
  172. package/dist/indexer/walk/walker.js +20 -2
  173. package/dist/integrations/agent/builder-shared.js +3 -6
  174. package/dist/integrations/agent/conversation-fallback.js +16 -0
  175. package/dist/integrations/agent/engine-resolution.js +87 -87
  176. package/dist/integrations/agent/execution-cascade.js +566 -0
  177. package/dist/integrations/agent/execution-definitions.js +211 -0
  178. package/dist/integrations/agent/execution-lowering.js +811 -0
  179. package/dist/integrations/agent/execution-preparation.js +67 -0
  180. package/dist/integrations/agent/index.js +0 -2
  181. package/dist/integrations/agent/inline-execution.js +74 -0
  182. package/dist/integrations/agent/model-map.js +515 -0
  183. package/dist/integrations/agent/persona-fallback.js +30 -0
  184. package/dist/integrations/agent/request-lowering.js +186 -0
  185. package/dist/integrations/agent/runner-dispatch.js +230 -37
  186. package/dist/integrations/agent/runner.js +12 -83
  187. package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
  188. package/dist/integrations/harnesses/aider/index.js +0 -1
  189. package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
  190. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  191. package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
  192. package/dist/integrations/harnesses/claude/index.js +1 -5
  193. package/dist/integrations/harnesses/claude/session-log.js +3 -33
  194. package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
  195. package/dist/integrations/harnesses/codex/index.js +0 -1
  196. package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
  197. package/dist/integrations/harnesses/copilot/index.js +0 -1
  198. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
  199. package/dist/integrations/harnesses/gemini/index.js +0 -1
  200. package/dist/integrations/harnesses/index.js +4 -44
  201. package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
  202. package/dist/integrations/harnesses/opencode/index.js +0 -2
  203. package/dist/integrations/harnesses/opencode/session-log.js +14 -204
  204. package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
  205. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
  206. package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
  207. package/dist/integrations/harnesses/openhands/index.js +0 -1
  208. package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
  209. package/dist/integrations/harnesses/pi/index.js +0 -1
  210. package/dist/integrations/harnesses/shared.js +0 -1
  211. package/dist/integrations/harnesses/types.js +1 -3
  212. package/dist/integrations/lockfile.js +82 -79
  213. package/dist/integrations/session-logs/index.js +6 -17
  214. package/dist/integrations/session-logs/provider-base.js +1 -29
  215. package/dist/llm/client.js +10 -5
  216. package/dist/llm/embedder.js +6 -7
  217. package/dist/llm/embedders/local.js +37 -88
  218. package/dist/llm/embedders/types.js +1 -1
  219. package/dist/llm/graph-extract.js +75 -50
  220. package/dist/llm/index-passes.js +43 -5
  221. package/dist/llm/memory-infer.js +8 -6
  222. package/dist/llm/metadata-enhance.js +5 -3
  223. package/dist/llm/structured-call.js +122 -25
  224. package/dist/output/format-exempt.js +1 -1
  225. package/dist/output/render-registry.js +0 -16
  226. package/dist/output/renderers.js +12 -7
  227. package/dist/output/shapes/curate.js +1 -0
  228. package/dist/output/shapes/helpers.js +10 -2
  229. package/dist/output/shapes/passthrough.js +2 -0
  230. package/dist/output/text/command-format.js +31 -33
  231. package/dist/output/text/health-format.js +1 -29
  232. package/dist/output/text/migrate.js +6 -56
  233. package/dist/output/text/proposal-format.js +16 -1
  234. package/dist/output/text/workflow-format.js +16 -0
  235. package/dist/registry/network.js +279 -0
  236. package/dist/registry/pinned-request-helper.js +247 -0
  237. package/dist/registry/pinned-transport.js +717 -0
  238. package/dist/registry/providers/skills-sh.js +18 -6
  239. package/dist/registry/providers/static-index.js +20 -7
  240. package/dist/registry/resolve.js +53 -28
  241. package/dist/scripts/akm-migrate-node.js +19334 -52269
  242. package/dist/scripts/akm-migrate.js +19270 -51612
  243. package/dist/setup/registry-stash-loader.js +64 -20
  244. package/dist/setup/semantic-assets.js +9 -34
  245. package/dist/setup/setup.js +12 -30
  246. package/dist/setup/source-identity.js +17 -0
  247. package/dist/setup/steps/sources.js +36 -15
  248. package/dist/setup/steps/tasks.js +39 -11
  249. package/dist/sources/providers/git-provider.js +3 -3
  250. package/dist/sources/providers/npm.js +2 -2
  251. package/dist/sources/providers/provider-utils.js +4 -3
  252. package/dist/sources/providers/website.js +11 -7
  253. package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
  254. package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
  255. package/dist/sources/website-url.js +73 -0
  256. package/dist/storage/engines/sqlite-migrations.js +81 -26
  257. package/dist/storage/managed-db.js +27 -24
  258. package/dist/storage/repositories/events-repository.js +3 -0
  259. package/dist/storage/repositories/index-connection.js +42 -10
  260. package/dist/storage/repositories/index-entries-repository.js +203 -229
  261. package/dist/storage/repositories/index-entry-mapper.js +8 -12
  262. package/dist/storage/repositories/index-entry-schema.js +255 -0
  263. package/dist/storage/repositories/index-fts-repository.js +64 -71
  264. package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
  265. package/dist/storage/repositories/index-meta-repository.js +0 -11
  266. package/dist/storage/repositories/index-schema.js +74 -350
  267. package/dist/storage/repositories/index-utility-repository.js +12 -17
  268. package/dist/storage/repositories/index-vec-repository.js +56 -7
  269. package/dist/storage/repositories/proposals-repository.js +4 -127
  270. package/dist/storage/repositories/registry-cache.js +2 -1
  271. package/dist/storage/repositories/task-history-repository.js +20 -40
  272. package/dist/storage/repositories/workflow-runs-repository.js +228 -129
  273. package/dist/storage/sqlite-read-snapshot.js +148 -0
  274. package/dist/tasks/backends/cron.js +170 -42
  275. package/dist/tasks/backends/index.js +1 -1
  276. package/dist/tasks/backends/launchd.js +787 -202
  277. package/dist/tasks/backends/schtasks.js +282 -83
  278. package/dist/tasks/embedded.js +7 -7
  279. package/dist/tasks/frozen-script.js +50 -0
  280. package/dist/tasks/resolve-akm-bin.js +5 -1
  281. package/dist/tasks/runner.js +239 -251
  282. package/dist/tasks/runtime-v3.js +281 -0
  283. package/dist/tasks/scheduler-binding.js +272 -0
  284. package/dist/tasks/scheduler-invocation.js +57 -43
  285. package/dist/tasks/scheduler-sync.js +654 -0
  286. package/dist/tasks/source-v3.js +752 -0
  287. package/dist/tasks/standalone-script-entry.js +5 -0
  288. package/dist/tasks/task-id.js +29 -0
  289. package/dist/workflows/authoring/authoring.js +15 -32
  290. package/dist/workflows/exec/dispatch-redaction.js +14 -8
  291. package/dist/workflows/exec/exec-unit.js +7 -28
  292. package/dist/workflows/exec/frozen-judge.js +57 -89
  293. package/dist/workflows/exec/lowering-notices.js +23 -0
  294. package/dist/workflows/exec/native-executor.js +301 -458
  295. package/dist/workflows/exec/param-secrets.js +4 -3
  296. package/dist/workflows/exec/run-workflow.js +26 -32
  297. package/dist/workflows/exec/step-work.js +105 -109
  298. package/dist/workflows/exec/unit-dispatch.js +103 -27
  299. package/dist/workflows/exec/unit-writer.js +3 -3
  300. package/dist/workflows/exec/worktree.js +2 -2
  301. package/dist/workflows/ir/compile.js +86 -72
  302. package/dist/workflows/ir/environment-v4.js +328 -0
  303. package/dist/workflows/ir/freeze-v4.js +122 -0
  304. package/dist/workflows/ir/plan-hash.js +13 -7
  305. package/dist/workflows/ir/schema-v4.js +525 -0
  306. package/dist/workflows/ir/schema.js +25 -284
  307. package/dist/workflows/ir/source-freeze-v4.js +506 -0
  308. package/dist/workflows/parser.js +27 -24
  309. package/dist/workflows/program/schema.js +1 -2
  310. package/dist/workflows/renderer.js +42 -29
  311. package/dist/workflows/resource-limits.js +4 -5
  312. package/dist/workflows/runtime/agent-identity.js +11 -13
  313. package/dist/workflows/runtime/plan-classifier.js +8 -8
  314. package/dist/workflows/runtime/runs.js +27 -43
  315. package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
  316. package/dist/workflows/source-files.js +373 -0
  317. package/dist/workflows/source-ir/compile.js +196 -0
  318. package/dist/workflows/source-ir/github-yaml.js +577 -0
  319. package/dist/workflows/source-ir/ordering.js +38 -0
  320. package/dist/workflows/source-ir/program.js +50 -0
  321. package/dist/workflows/source-ir/result.js +26 -0
  322. package/dist/workflows/source-ir/schema.js +772 -0
  323. package/dist/workflows/source-ir/semantics.js +242 -0
  324. package/dist/workflows/source-ir/uses.js +14 -0
  325. package/docs/README.md +2 -0
  326. package/docs/migration/README.md +3 -1
  327. package/docs/migration/release-notes/0.9.2.md +55 -0
  328. package/docs/migration/release-notes/README.md +5 -0
  329. package/docs/migration/v0.8-to-v0.9.md +76 -1077
  330. package/docs/migration/v0.9.0-troubleshooting.md +104 -516
  331. package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
  332. package/docs/reference/README.md +1 -0
  333. package/docs/reference/cli.md +230 -98
  334. package/docs/reference/configuration.md +159 -36
  335. package/docs/reference/data-and-telemetry.md +19 -1
  336. package/docs/reference/supported-formats.md +23 -3
  337. package/docs/reference/tasks.md +182 -0
  338. package/docs/reference/workflow-schema.md +91 -40
  339. package/docs/reference/workflows.md +33 -6
  340. package/package.json +10 -6
  341. package/schemas/akm-config.json +372 -224
  342. package/schemas/akm-task.json +324 -80
  343. package/schemas/akm-workflow.json +6 -9
  344. package/dist/core/migration-operation.js +0 -75
  345. package/dist/integrations/agent/model-aliases.js +0 -74
  346. package/dist/tasks/parser.js +0 -380
  347. package/dist/tasks/schema.js +0 -123
  348. package/dist/tasks/validator.js +0 -80
  349. package/dist/workflows/ir/freeze.js +0 -320
  350. package/dist/workflows/runtime/document-cache.js +0 -13
@@ -17,34 +17,34 @@
17
17
  */
18
18
  import fs from "node:fs";
19
19
  import path from "node:path";
20
+ import { detectAdapterId } from "../../core/adapter/detect-adapter.js";
20
21
  import { recognizeMatch } from "../../core/adapter/recognize-match.js";
22
+ import { assetPathForName, stashDirFor } from "../../core/asset/asset-placement.js";
21
23
  import { makeBundleRef, parseBundleRef } from "../../core/asset/asset-ref.js";
22
24
  import { parseFrontmatter } from "../../core/asset/frontmatter.js";
23
25
  import { extractSection, markdownFragmentSlugs } from "../../core/asset/markdown.js";
24
26
  import { displayRef, typeNameFromConceptId } from "../../core/asset/resolve-ref.js";
25
- import { META_DIR, parseMetaRef, resolveMetaFilePath } from "../../core/asset/stash-meta.js";
26
- import { asNonEmptyString } from "../../core/common.js";
27
+ import { META_DIR, parseMetaRef, readMetaFile } from "../../core/asset/stash-meta.js";
28
+ import { asNonEmptyString, isWithin } from "../../core/common.js";
27
29
  import { getIndexPassConfig, loadConfig } from "../../core/config/config.js";
28
30
  import { NotFoundError, rethrowIfTestIsolationError, UsageError } from "../../core/errors.js";
29
- import { appendEvent, readEvents } from "../../core/events.js";
30
- import { withStateDbTelemetry } from "../../core/state-db.js";
31
+ import { appendEvent } from "../../core/events.js";
32
+ import { SCRIPT_EXTENSIONS } from "../../core/recognition-util.js";
31
33
  import { presentationFor } from "../../core/type-presentation.js";
34
+ import { warn } from "../../core/warn.js";
32
35
  import { hasGraphData } from "../../indexer/db/graph-db.js";
33
36
  import { listRelatedPathsForFile } from "../../indexer/graph/graph-boost.js";
34
37
  import { extractGraphForSingleFile } from "../../indexer/graph/graph-extraction.js";
35
- import { lookupBundleRef } from "../../indexer/indexer.js";
38
+ import { lookupBundleRef, lookupBundleRefWithResolution } from "../../indexer/indexer.js";
36
39
  import { ensurePrimaryIndexForRead, resolveReadSources } from "../../indexer/read-preflight.js";
37
- import { usageEventAttributionMetadata } from "../../indexer/search/search-attribution.js";
38
- import { buildEditHint, findSourceForPath, isEditable, resolveSourceEntries } from "../../indexer/search/search-source.js";
39
- import { insertUsageEvent } from "../../indexer/usage/usage-events.js";
40
+ import { buildEditHint, findSourceForPath, isEditable, resolveSourceEntries, } from "../../indexer/search/search-source.js";
41
+ import { recentShowCount, recordShowUsage } from "../../indexer/usage/show-usage.js";
40
42
  import { buildFileContext, buildRenderContext, getRenderer, } from "../../indexer/walk/file-context.js";
41
- import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
42
- import { resolveIndexPassLLM } from "../../llm/index-passes.js";
43
+ import { resolveIndexPassExecution } from "../../llm/index-passes.js";
43
44
  import { resolveSourcesForOrigin } from "../../registry/origin-resolve.js";
44
45
  import { resolveStorageLocations } from "../../storage/locations.js";
45
46
  import { closeDatabase, openExistingDatabase } from "../../storage/repositories/index-connection.js";
46
47
  import { TELEMETRY_BUSY_TIMEOUT_MS, withIndexDb } from "../../storage/repositories/index-db.js";
47
- import { findEntryIdByRef, getEntryById, getEntryIdByFilePath, getItemRefById, } from "../../storage/repositories/index-entries-repository.js";
48
48
  import { computeBodyHash } from "../../storage/repositories/index-llm-cache-repository.js";
49
49
  // Eagerly import source providers to trigger self-registration.
50
50
  import "../../sources/providers/index.js";
@@ -52,8 +52,9 @@ import { getCurrentWorkflowScopeKey } from "../../workflows/authoring/scope-key.
52
52
  import { buildWorkflowAction } from "../../workflows/renderer.js";
53
53
  import { getActiveWorkflowRun } from "../../workflows/runtime/runs.js";
54
54
  /**
55
- * Unified show: queries the local FTS5 index, then falls back to on-disk
56
- * type-dir resolution if the index has no row. Spec §6.2; no remote provider
55
+ * Unified show: queries the local FTS5 index, then reads that row's file path.
56
+ * A physical owner can arbitrate collisions during lookup, but show never
57
+ * renders an asset without an indexed row. Spec §6.2; no provider or disk
57
58
  * fallback.
58
59
  *
59
60
  * When `detail` is `"brief"` or `"summary"`, the response omits
@@ -70,6 +71,12 @@ export async function akmShowUnified(input) {
70
71
  if (metaRef)
71
72
  return showStashMeta(metaRef);
72
73
  }
74
+ // Env/secret bodies have no safe fragment surface. Reject from the canonical
75
+ // ref namespace before auto-index or lookup can touch authored bytes. This is
76
+ // deliberately independent of on-disk suffix probing: secret filenames keep
77
+ // their natural extension, and a misspelled extensionless ref must not move
78
+ // the sensitive-fragment policy behind a not-found result.
79
+ assertSensitiveFragmentUnsupported(parseBundleRef(ref));
73
80
  // Auto-index when stale so the index is current before lookup.
74
81
  const { primarySource } = resolveReadSources();
75
82
  await ensurePrimaryIndexForRead(primarySource);
@@ -86,7 +93,7 @@ export async function akmShowUnified(input) {
86
93
  if (!input.skipLogging) {
87
94
  const consumedRef = result.ref ?? makeBundleRef(undefined, parseBundleRef(ref).conceptId);
88
95
  const priorShowCount = recentShowCount(consumedRef);
89
- logShowEvent(consumedRef, result.type, result.name, input.eventSource, result.path);
96
+ recordShowUsage(consumedRef, result.type, result.name, input.eventSource, result.path);
90
97
  if (priorShowCount >= 2) {
91
98
  // Agent has shown this same asset 3+ times — inject a loop-break hint.
92
99
  result.showLoopWarning = priorShowCount + 1;
@@ -111,18 +118,17 @@ async function showStashMeta(metaRef) {
111
118
  }
112
119
  const config = loadConfig();
113
120
  for (const source of sources) {
114
- const filePath = resolveMetaFilePath(source.path, metaRef.name);
115
- if (!filePath)
121
+ const metaFile = readMetaFile(source.path, metaRef.name);
122
+ if (!metaFile)
116
123
  continue;
117
- const content = fs.readFileSync(filePath, "utf8");
118
- const editable = isEditable(filePath, config, allSources);
124
+ const editable = isEditable(metaFile.path, config, allSources);
119
125
  appendEvent({ eventType: "show", ref: `meta:${metaRef.name}`, metadata: { type: "meta", name: metaRef.name } });
120
126
  return {
121
127
  type: "meta",
122
128
  name: metaRef.name,
123
- path: filePath,
129
+ path: metaFile.path,
124
130
  ref: `meta:${metaRef.name}`,
125
- content,
131
+ content: metaFile.content,
126
132
  origin: source.registryId ?? null,
127
133
  editable,
128
134
  };
@@ -168,127 +174,43 @@ function enforceScopeOrThrow(filePath, ref, scope) {
168
174
  }
169
175
  }
170
176
  }
171
- /**
172
- * Count how many times `ref` has been shown in the current session by reading
173
- * recent events. Returns the count BEFORE the current invocation.
174
- */
175
- function recentShowCount(ref) {
176
- try {
177
- const { events } = readEvents({
178
- type: "show",
179
- ref,
180
- since: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
181
- });
182
- return events.length;
183
- }
184
- catch {
185
- return 0;
186
- }
187
- }
188
- function logShowEvent(ref, type, name, eventSource = "user", filePath) {
189
- // Emit a structured event to events.jsonl so workflow-trace consumers
190
- // detect akm show invocations without relying on stdout scraping.
191
- const eventRef = makeBundleRef(parseBundleRef(ref).bundle, parseBundleRef(ref).conceptId);
192
- appendEvent({ eventType: "show", ref: eventRef, metadata: { type, name } });
193
- // Detect if this show is a selection from a recent search result.
194
- try {
195
- // D7: bound the query to the last 60 s so we never scan unbounded history
196
- const { events: recentSearches } = readEvents({
197
- type: "search",
198
- since: new Date(Date.now() - 60_000).toISOString(),
199
- });
200
- const cutoffMs = Date.now() - 60_000;
201
- const matchingSearch = [...recentSearches].reverse().find((e) => {
202
- if (!e.ts || new Date(e.ts).getTime() < cutoffMs)
203
- return false;
204
- const refs = e.metadata?.resultRefs ?? [];
205
- return refs.includes(ref);
206
- });
207
- if (matchingSearch) {
208
- appendEvent({
209
- eventType: "select",
210
- ref,
211
- metadata: {
212
- query: matchingSearch.metadata?.query,
213
- searchTs: matchingSearch.ts,
214
- rankPosition: (matchingSearch.metadata?.resultRefs ?? []).indexOf(ref),
215
- },
216
- });
217
- }
218
- }
219
- catch {
220
- /* fire-and-forget — select is best-effort */
221
- }
222
- try {
223
- withIndexDb((db) => {
224
- const entryId = filePath ? getEntryIdByFilePath(db, filePath) : findEntryIdByRef(db, eventRef);
225
- if (entryId === undefined)
226
- return;
227
- // Usage events carry only the resolved row's fully-qualified item_ref.
228
- // A disk-only show has no durable indexed identity yet, so it does not
229
- // create a per-entry usage row.
230
- const entryRef = getItemRefById(db, entryId);
231
- if (!entryRef)
232
- return;
233
- const entry = getEntryById(db, entryId);
234
- withStateDbTelemetry((stateDb) => {
235
- insertUsageEvent(stateDb, {
236
- event_type: "show",
237
- entry_ref: entryRef,
238
- entry_id: entryId,
239
- metadata: usageEventAttributionMetadata(entry?.entry.derivedFrom ? { memoryInference: { exposure: "direct" } } : undefined, entryRef),
240
- source: eventSource,
241
- });
242
- }, TELEMETRY_BUSY_TIMEOUT_MS);
243
- }, { busyTimeoutMs: TELEMETRY_BUSY_TIMEOUT_MS });
244
- }
245
- catch (err) {
246
- rethrowIfTestIsolationError(err);
247
- /* fire-and-forget */
248
- }
249
- }
250
177
  /** @internal Use akmShowUnified() for all external callers. */
251
178
  export async function showLocal(input) {
252
179
  const parsed = parseBundleRef(input.ref);
180
+ assertSensitiveFragmentUnsupported(parsed);
253
181
  const assetParts = typeNameFromConceptId(parsed.conceptId);
254
182
  const config = loadConfig();
255
183
  const allSources = resolveSourceEntries(input.stashDir);
256
184
  const searchSources = resolveSourcesForOrigin(parsed.bundle, allSources);
257
185
  const allSourceDirs = searchSources.map((s) => s.path);
258
- let indexedEntry = null;
259
- try {
260
- indexedEntry = await lookupBundleRef(parsed);
261
- }
262
- catch (err) {
263
- rethrowIfTestIsolationError(err);
264
- indexedEntry = null;
265
- }
266
- const resolvedAssetPath = indexedEntry?.filePath ??
267
- (assetParts
268
- ? await resolveAssetPath({ type: assetParts.type, name: assetParts.name, origin: parsed.bundle }, {
269
- stashDir: input.stashDir,
270
- mode: "disk-only",
271
- })
272
- : null);
273
- const assetPath = resolvedAssetPath ?? undefined;
274
- const displayType = indexedEntry?.type ?? assetParts?.type ?? "asset";
275
- const displayName = indexedEntry?.name ?? assetParts?.name ?? parsed.conceptId;
276
- if (!assetPath && parsed.bundle && searchSources.length === 0) {
186
+ const resolution = await lookupBundleRefWithResolution(parsed);
187
+ if (resolution.indexError !== undefined)
188
+ throw resolution.indexError;
189
+ const indexedEntry = resolution.entry;
190
+ const assetPath = indexedEntry?.filePath;
191
+ if (!indexedEntry && parsed.bundle && searchSources.length === 0) {
277
192
  const installCmd = `akm bundle add ${parsed.bundle}`;
278
193
  throw new NotFoundError(`Stash asset not found for ref: ${makeBundleRef(parsed.bundle, parsed.conceptId)}. ` +
279
194
  `Stash "${parsed.bundle}" is not installed. Run: ${installCmd}`);
280
195
  }
281
- if (!assetPath) {
196
+ if (!indexedEntry && !resolution.owner) {
197
+ const unsupportedExtension = existingUnsupportedScriptExtension(assetParts, searchSources);
198
+ if (unsupportedExtension !== undefined) {
199
+ const displayExtension = unsupportedExtension || "no extension";
200
+ throw new NotFoundError(`Script ref "${makeBundleRef(parsed.bundle, parsed.conceptId)}" resolves to an existing file with ` +
201
+ `unsupported extension "${displayExtension}". Script refs must use a supported script extension: ` +
202
+ `${[...SCRIPT_EXTENSIONS].join(", ")}.`, "ASSET_NOT_FOUND");
203
+ }
204
+ }
205
+ if (!indexedEntry || !assetPath) {
282
206
  throw new NotFoundError(`Stash asset not found for ref: ${makeBundleRef(parsed.bundle, parsed.conceptId)}. ` +
283
207
  "Check the name with `akm search` or verify the asset exists in your stash.");
284
208
  }
285
- if (indexedEntry) {
286
- try {
287
- fs.accessSync(assetPath, fs.constants.R_OK);
288
- }
289
- catch (error) {
290
- throwIndexedPathNotFound(error, input.ref);
291
- }
209
+ try {
210
+ fs.accessSync(assetPath, fs.constants.R_OK);
211
+ }
212
+ catch (error) {
213
+ throwIndexedPathNotFound(error, input.ref);
292
214
  }
293
215
  const source = findSourceForPath(assetPath, allSources);
294
216
  const sourceStashDir = source?.path ?? allSourceDirs[0];
@@ -297,25 +219,24 @@ export async function showLocal(input) {
297
219
  "Run `akm bundle create` to create the stash directory, or check `akm bundle list` for configured paths.");
298
220
  }
299
221
  const fileCtx = buildFileContext(sourceStashDir, assetPath);
300
- const indexedRenderer = indexedEntry ? rendererForIndexedEntry(indexedEntry, fileCtx) : undefined;
222
+ const presentedName = indexedEntry.name;
223
+ const indexedRenderer = rendererForIndexedEntry(indexedEntry, fileCtx);
301
224
  let response;
302
225
  try {
303
- if (indexedEntry && indexedRenderer === null) {
226
+ if (indexedRenderer === null) {
304
227
  response = buildIndexedProjectionResponse(indexedEntry, assetPath, parsed.fragment);
305
228
  }
306
229
  else {
307
- const match = indexedEntry && typeof indexedRenderer === "string"
308
- ? indexedMatch(indexedEntry, indexedRenderer)
309
- : recognizeMatch(fileCtx);
230
+ const match = typeof indexedRenderer === "string" ? indexedMatch(indexedEntry, indexedRenderer) : recognizeMatch(fileCtx);
310
231
  if (!match) {
311
232
  throw new UsageError(`Could not display asset "${makeBundleRef(parsed.bundle, parsed.conceptId)}" — unsupported file type or unrecognized layout`);
312
233
  }
313
- match.meta = { ...match.meta, name: displayName };
234
+ match.meta = { ...match.meta, name: presentedName };
314
235
  const renderer = await getRenderer(match.renderer);
315
236
  if (!renderer) {
316
237
  throw new UsageError(`Renderer "${match.renderer}" not found for asset: ${makeBundleRef(parsed.bundle, parsed.conceptId)}`);
317
238
  }
318
- const renderBundle = indexedEntry ? indexedEntry.bundleId : source?.registryId;
239
+ const renderBundle = indexedEntry.bundleId;
319
240
  const renderDefaultBundle = config.defaultBundle ?? (source?.path === allSources[0]?.path ? renderBundle : undefined);
320
241
  const renderCtx = buildRenderContext(fileCtx, match, allSourceDirs, renderBundle, renderDefaultBundle);
321
242
  response = renderer.buildShowResponse(renderCtx);
@@ -323,26 +244,22 @@ export async function showLocal(input) {
323
244
  if (!match.renderer.endsWith("-md")) {
324
245
  throw new UsageError(`Fragments are not supported for ${makeBundleRef(parsed.bundle, parsed.conceptId)}. Only Markdown documents support heading fragments.`, "INVALID_FLAG_VALUE");
325
246
  }
326
- applyMarkdownFragment(response, fileCtx.content(), parsed.fragment, displayName);
247
+ applyMarkdownFragment(response, fileCtx.content(), parsed.fragment, presentedName);
327
248
  }
328
249
  }
329
250
  }
330
251
  catch (error) {
331
- if (indexedEntry)
332
- throwIndexedPathNotFound(error, input.ref);
333
- throw error;
334
- }
335
- if (indexedEntry) {
336
- response.type = indexedEntry.type;
337
- response.name = indexedEntry.name;
252
+ throwIndexedPathNotFound(error, input.ref);
338
253
  }
254
+ response.type = indexedEntry.type;
255
+ response.name = indexedEntry.name;
339
256
  const isPrimaryStash = source !== undefined && source.path === allSources[0]?.path;
340
257
  const canonicalRef = displayRef({
341
- type: displayType,
342
- name: displayName,
343
- conceptId: indexedEntry?.conceptId,
344
- bundleId: indexedEntry ? indexedEntry.bundleId : source?.registryId,
345
- }, config.defaultBundle ?? (isPrimaryStash ? indexedEntry?.bundleId : undefined));
258
+ type: indexedEntry.type,
259
+ name: presentedName,
260
+ conceptId: indexedEntry.conceptId,
261
+ bundleId: indexedEntry.bundleId,
262
+ }, config.defaultBundle ?? (isPrimaryStash ? indexedEntry.bundleId : undefined));
346
263
  if (response.type === "workflow")
347
264
  response.action = buildWorkflowAction(canonicalRef);
348
265
  // 07 P1-D: provenance-aware toolPolicy CEILING. An agent's self-declared
@@ -400,21 +317,83 @@ export async function showLocal(input) {
400
317
  }
401
318
  return fullResponse;
402
319
  }
320
+ /** Reject body fragments for namespaces whose authored bytes are sensitive. */
321
+ function assertSensitiveFragmentUnsupported(ref) {
322
+ if (ref.fragment === undefined)
323
+ return;
324
+ const type = typeNameFromConceptId(ref.conceptId)?.type;
325
+ if (type !== "env" && type !== "secret")
326
+ return;
327
+ throw new UsageError(`Fragments are not supported for ${makeBundleRef(ref.bundle, ref.conceptId)}. Sensitive ${type} assets do not expose body fragments.`, "INVALID_FLAG_VALUE");
328
+ }
329
+ /**
330
+ * Return the unsupported extension only when the exact canonical AKM script
331
+ * path exists as a contained regular file. Physical owner arbitration runs
332
+ * first; this is a diagnostic for its miss, never an alternate owner or
333
+ * runnable-file classifier. No authored bytes are read.
334
+ */
335
+ function existingUnsupportedScriptExtension(assetParts, sources) {
336
+ if (assetParts?.type !== "script")
337
+ return undefined;
338
+ const extension = path.extname(assetParts.name);
339
+ if (SCRIPT_EXTENSIONS.has(extension.toLowerCase()))
340
+ return undefined;
341
+ const scriptDir = stashDirFor("script");
342
+ if (!scriptDir)
343
+ return undefined;
344
+ for (const source of sources) {
345
+ try {
346
+ if ((source.adapterId ?? detectAdapterId(source.path)) !== "akm")
347
+ continue;
348
+ const sourceRoot = path.resolve(source.path);
349
+ const candidate = path.resolve(assetPathForName("script", path.join(sourceRoot, scriptDir), assetParts.name));
350
+ if (!isWithin(candidate, sourceRoot))
351
+ continue;
352
+ const authoredStat = fs.lstatSync(candidate);
353
+ if (!authoredStat.isFile() && !authoredStat.isSymbolicLink())
354
+ continue;
355
+ const realRoot = fs.realpathSync(sourceRoot);
356
+ const realCandidate = fs.realpathSync(candidate);
357
+ if (!isWithin(realCandidate, realRoot) || !fs.statSync(realCandidate).isFile())
358
+ continue;
359
+ return extension;
360
+ }
361
+ catch {
362
+ // Missing, unreadable, dangling, or otherwise unsafe paths remain normal
363
+ // not-found misses; this diagnostic never widens physical ownership.
364
+ }
365
+ }
366
+ return undefined;
367
+ }
403
368
  /**
404
369
  * #624-P3 — opt-in inline graph extraction for `akm show`. Best-effort and
405
370
  * timeout-bounded: never throws, never hangs, never mutates the response.
406
371
  *
407
372
  * Preconditions (caller already checked the flag): a model must be configured
408
- * (model-available guard via {@link resolveIndexPassLLM}) and the asset must be
409
- * ungraphed ({@link hasGraphData}). Extraction races a 30s timeout so `show`
410
- * cannot block on a slow provider; any timeout/error/missing-model path is
411
- * swallowed and `show` returns its already-assembled response unchanged.
373
+ * (model-available guard via {@link resolveIndexPassExecution}) and the asset
374
+ * must be ungraphed ({@link hasGraphData}). Extraction races a 30s timeout so
375
+ * `show` cannot block on a slow provider; any timeout/error/missing-model path
376
+ * is swallowed and `show` returns its already-assembled response unchanged.
412
377
  */
413
378
  async function maybeExtractGraphInline(config, sourceStashDir, assetPath) {
414
379
  try {
415
- // Model-available guard no provider configured silent skip, no LLM call.
416
- if (!resolveIndexPassLLM("graph", config))
380
+ // Resolve readiness and the symbolic runner once. The inline dispatch must
381
+ // consume this same snapshot even if models.json changes while show runs.
382
+ const graphExecution = resolveIndexPassExecution("graph", config);
383
+ if (!graphExecution.runner)
417
384
  return;
385
+ const emittedNoticeKeys = new Set();
386
+ const reportNotices = (notices) => {
387
+ for (const notice of notices) {
388
+ const key = JSON.stringify(notice);
389
+ if (emittedNoticeKeys.has(key))
390
+ continue;
391
+ emittedNoticeKeys.add(key);
392
+ const field = typeof notice.field === "string" ? ` field=${notice.field}` : "";
393
+ warn(`[akm] lazy graph extraction notice ${notice.code} adapter=${notice.adapter}${field}: ${notice.message}`);
394
+ }
395
+ };
396
+ reportNotices(graphExecution.notices);
418
397
  let alreadyGraphed = false;
419
398
  let bodyHash;
420
399
  try {
@@ -439,7 +418,14 @@ async function maybeExtractGraphInline(config, sourceStashDir, assetPath) {
439
418
  timer = setTimeout(resolve, 30_000);
440
419
  });
441
420
  try {
442
- await Promise.race([extractGraphForSingleFile(db, sourceStashDir, assetPath, bodyHash, { config }), timeout]);
421
+ await Promise.race([
422
+ extractGraphForSingleFile(db, sourceStashDir, assetPath, bodyHash, {
423
+ config,
424
+ llmRunner: graphExecution.runner,
425
+ onNotices: reportNotices,
426
+ }),
427
+ timeout,
428
+ ]);
443
429
  }
444
430
  finally {
445
431
  if (timer)
@@ -4,6 +4,7 @@
4
4
  import { defineGroupCommand, defineJsonCommand, output } from "../cli/shared.js";
5
5
  import { getEffectiveRegistries, loadUserConfig, mutateConfig } from "../core/config/config.js";
6
6
  import { NotFoundError, UsageError } from "../core/errors.js";
7
+ import { formatRegistryUrl, hasRegistryUrlCredentials, REGISTRY_CREDENTIALS_UNSUPPORTED, registryEntryForOutput, } from "../core/registry-url.js";
7
8
  import { warn } from "../core/warn.js";
8
9
  export const registryCommand = defineGroupCommand({
9
10
  meta: { name: "registry", description: "Manage bundle registries" },
@@ -14,7 +15,7 @@ export const registryCommand = defineGroupCommand({
14
15
  const config = loadUserConfig();
15
16
  // R-066 #5: mirror the shared config↔default-fallback helper instead
16
17
  // of re-deriving it inline.
17
- output("registry-list", { registries: getEffectiveRegistries(config) });
18
+ output("registry-list", { registries: getEffectiveRegistries(config).map(registryEntryForOutput) });
18
19
  },
19
20
  }),
20
21
  add: defineJsonCommand({
@@ -23,7 +24,7 @@ export const registryCommand = defineGroupCommand({
23
24
  url: { type: "positional", description: "Registry index URL", required: true },
24
25
  name: { type: "string", description: "Human-friendly name for the registry" },
25
26
  provider: { type: "string", description: "Provider type (e.g. static-index, skills-sh)" },
26
- options: { type: "string", description: 'Provider options as JSON (e.g. \'{"apiKey":"key"}\').' },
27
+ options: { type: "string", description: "Provider-specific options as JSON." },
27
28
  "allow-insecure": {
28
29
  type: "boolean",
29
30
  description: "Allow a plain HTTP registry URL (otherwise rejected)",
@@ -31,6 +32,9 @@ export const registryCommand = defineGroupCommand({
31
32
  },
32
33
  },
33
34
  async run({ args }) {
35
+ if (hasRegistryUrlCredentials(args.url)) {
36
+ throw new UsageError(REGISTRY_CREDENTIALS_UNSUPPORTED);
37
+ }
34
38
  if (!args.url.startsWith("http")) {
35
39
  throw new UsageError("Registry URL must start with http:// or https://");
36
40
  }
@@ -65,7 +69,7 @@ export const registryCommand = defineGroupCommand({
65
69
  return { ...config, registries };
66
70
  }).config;
67
71
  output("registry-add", {
68
- registries: updated.registries ?? [],
72
+ registries: (updated.registries ?? []).map(registryEntryForOutput),
69
73
  added,
70
74
  ...(!added ? { message: "Registry URL already configured" } : {}),
71
75
  });
@@ -80,15 +84,20 @@ export const registryCommand = defineGroupCommand({
80
84
  async run({ args }) {
81
85
  const config = loadUserConfig();
82
86
  const registries = [...(config.registries ?? [])];
87
+ const displayTarget = hasRegistryUrlCredentials(args.target) ||
88
+ args.target.startsWith("http://") ||
89
+ args.target.startsWith("https://")
90
+ ? formatRegistryUrl(args.target)
91
+ : args.target;
83
92
  const idx = registries.findIndex((r) => r.url === args.target || r.name === args.target);
84
93
  if (idx === -1) {
85
94
  // Was a success envelope with `removed: false` and exit 0, so
86
95
  // `akm registry remove typo && ...` proceeded as if it had removed
87
96
  // something. A missing target is exit 1, like every other not-found.
88
- throw new NotFoundError(`No registry matching "${args.target}" is configured.`, "SOURCE_NOT_FOUND", "Run `akm registry list` to see configured registries.");
97
+ throw new NotFoundError(`No registry matching "${displayTarget}" is configured.`, "SOURCE_NOT_FOUND", "Run `akm registry list` to see configured registries.");
89
98
  }
90
99
  const { confirmDestructive } = await import("../cli/confirm.js");
91
- const confirmed = await confirmDestructive(`Remove registry "${args.target}"? This cannot be undone.`, {
100
+ const confirmed = await confirmDestructive(`Remove registry "${displayTarget}"? This cannot be undone.`, {
92
101
  yes: args.yes === true,
93
102
  });
94
103
  if (!confirmed) {
@@ -105,9 +114,9 @@ export const registryCommand = defineGroupCommand({
105
114
  return { ...latest, registries: current };
106
115
  }).config;
107
116
  output("registry-remove", {
108
- registries: updated.registries ?? [],
117
+ registries: (updated.registries ?? []).map(registryEntryForOutput),
109
118
  removed: removed !== undefined,
110
- ...(removed ? { entry: removed } : { message: "No matching registry found" }),
119
+ ...(removed ? { entry: registryEntryForOutput(removed) } : { message: "No matching registry found" }),
111
120
  });
112
121
  },
113
122
  }),
@@ -11,12 +11,14 @@
11
11
  import { serializeFrontmatter } from "../core/asset/asset-serialize.js";
12
12
  import { toErrorMessage, tryReadStdinText } from "../core/common.js";
13
13
  import { loadConfig } from "../core/config/config.js";
14
- import { UsageError } from "../core/errors.js";
14
+ import { ConfigError, UsageError } from "../core/errors.js";
15
15
  import { parseEmbeddedJsonResponse } from "../core/parse.js";
16
16
  import { DURATION_UNITS, parseDuration as parseDurationSpec } from "../core/time.js";
17
17
  import { warn } from "../core/warn.js";
18
18
  import { SCOPE_KEYS } from "../indexer/passes/metadata.js";
19
- import { getDefaultLlmConfig } from "../integrations/agent/engine-resolution.js";
19
+ import { callStructured } from "../llm/structured-call.js";
20
+ import { withLlmStage } from "../llm/usage-telemetry.js";
21
+ import { resolveImproveLlmExecution } from "./improve/execution.js";
20
22
  /**
21
23
  * Parse a shorthand duration string to a number of milliseconds.
22
24
  * Supports the CLI-wide canonical grammar: `30d` (days), `12h` (hours),
@@ -140,21 +142,20 @@ function detectObservedAt(body) {
140
142
  const LLM_ENRICH_TIMEOUT_MS = 10_000;
141
143
  /**
142
144
  * Attempt LLM enrichment of memory metadata. Returns merged metadata
143
- * fields on success. On timeout, unreachable, or invalid JSON returns
144
- * empty result and emits a warning. Never throws; always resolves.
145
+ * fields on success. Provider, timeout, and parse failures return an empty
146
+ * result and emit a warning. Invalid configuration remains a hard pre-write
147
+ * error and preserves its original {@link ConfigError}.
145
148
  */
146
149
  export async function runLlmEnrich(body) {
147
150
  const config = loadConfig();
148
- const llmConfig = getDefaultLlmConfig(config);
149
- if (!llmConfig) {
151
+ const resolved = resolveImproveLlmExecution({ config, processName: "remember-enrich" });
152
+ if (!resolved) {
150
153
  warn("Warning: --enrich requires an LLM to be configured. Run `akm setup` to configure one.");
151
154
  return { tags: [] };
152
155
  }
153
- const { chatCompletion } = await import("../llm/client.js");
154
- // #576: attribute this entry point's LLM call to the `remember` stage. The
155
- // wrapper is ambient if a usage sink is active it tags the record; if not,
156
- // it is a no-op.
157
- const { withLlmStage } = await import("../llm/usage-telemetry.js");
156
+ const runner = resolved.runner;
157
+ const noticesByKey = new Map(resolved.notices.map((notice) => [JSON.stringify(notice), notice]));
158
+ const noticeFields = () => noticesByKey.size > 0 ? { notices: Object.freeze([...noticesByKey.values()]) } : {};
158
159
  const prompt = `You are a memory tagger for a developer knowledge base.
159
160
  Given the memory text below, return ONLY a JSON object with these fields:
160
161
  - "tags": array of 1-5 short lowercase keyword tags
@@ -170,10 +171,22 @@ Return ONLY the JSON object, no prose, no markdown fences.`;
170
171
  const result = await (async () => {
171
172
  try {
172
173
  return await Promise.race([
173
- withLlmStage("remember", () => chatCompletion(llmConfig, [
174
- { role: "system", content: "Return only valid JSON. No prose." },
175
- { role: "user", content: prompt },
176
- ], { maxTokens: 256, temperature: 0.1 })),
174
+ withLlmStage("remember", () => callStructured({
175
+ feature: "remember_enrich",
176
+ runner,
177
+ messages: [
178
+ { role: "system", content: "Return only valid JSON. No prose." },
179
+ { role: "user", content: prompt },
180
+ ],
181
+ request: { maxTokens: 256, temperature: 0.1 },
182
+ onNotices: (value) => {
183
+ for (const notice of value)
184
+ noticesByKey.set(JSON.stringify(notice), notice);
185
+ },
186
+ parse: (raw) => raw ?? "",
187
+ onError: () => "",
188
+ fallback: "",
189
+ })),
177
190
  new Promise((_, reject) => {
178
191
  timeoutHandle = setTimeout(() => reject(new Error("LLM enrichment timed out")), LLM_ENRICH_TIMEOUT_MS);
179
192
  }),
@@ -188,7 +201,7 @@ Return ONLY the JSON object, no prose, no markdown fences.`;
188
201
  const parsed = parseEmbeddedJsonResponse(result);
189
202
  if (!parsed) {
190
203
  warn("Warning: --enrich received invalid JSON from the LLM. Writing memory without enrichment.");
191
- return { tags: [] };
204
+ return { tags: [], ...noticeFields() };
192
205
  }
193
206
  const tags = Array.isArray(parsed.tags)
194
207
  ? parsed.tags.filter((t) => typeof t === "string" && t.trim().length > 0)
@@ -197,11 +210,13 @@ Return ONLY the JSON object, no prose, no markdown fences.`;
197
210
  const observed_at = typeof parsed.observed_at === "string" && /^\d{4}-\d{2}-\d{2}$/.test(parsed.observed_at.trim())
198
211
  ? parsed.observed_at.trim()
199
212
  : undefined;
200
- return { tags, description, observed_at };
213
+ return { tags, description, observed_at, ...noticeFields() };
201
214
  }
202
215
  catch (err) {
216
+ if (err instanceof ConfigError)
217
+ throw err;
203
218
  warn(`Warning: --enrich failed (${toErrorMessage(err)}). Writing memory without enrichment.`);
204
- return { tags: [] };
219
+ return { tags: [], ...noticeFields() };
205
220
  }
206
221
  }
207
222
  // R-061(c): `resolveRememberContentArg` / `wasRememberFlagValueConsumedAsContent`