akm-cli 0.8.2 → 0.9.0-beta.0

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 (316) hide show
  1. package/CHANGELOG.md +187 -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/cli/config-migrate.js +6 -6
  16. package/dist/cli/config-validate.js +4 -4
  17. package/dist/cli/confirm.js +3 -3
  18. package/dist/cli/parse-args.js +1 -1
  19. package/dist/cli/shared.js +51 -14
  20. package/dist/cli-node.mjs +26 -0
  21. package/dist/cli.js +171 -3862
  22. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  23. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  24. package/dist/commands/agent/contribute-cli.js +200 -0
  25. package/dist/commands/completions.js +1 -1
  26. package/dist/commands/config-cli.js +240 -3
  27. package/dist/commands/config-edit.js +344 -0
  28. package/dist/commands/db-cli.js +2 -2
  29. package/dist/commands/env/env-cli.js +529 -0
  30. package/dist/commands/env/env.js +410 -0
  31. package/dist/commands/env/secret-cli.js +259 -0
  32. package/dist/commands/{secret.js → env/secret.js} +6 -47
  33. package/dist/commands/events.js +4 -4
  34. package/dist/commands/feedback-cli.js +18 -34
  35. package/dist/commands/graph/graph-cli.js +132 -0
  36. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  37. package/dist/commands/health/checks.js +279 -0
  38. package/dist/commands/health.js +94 -262
  39. package/dist/commands/{consolidate.js → improve/consolidate.js} +48 -36
  40. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  41. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  42. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  43. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  44. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  45. package/dist/commands/{extract.js → improve/extract.js} +185 -26
  46. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +4 -4
  47. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
  48. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  49. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
  50. package/dist/commands/{improve.js → improve/improve.js} +509 -245
  51. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  52. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  53. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  54. package/dist/commands/{reflect.js → improve/reflect.js} +33 -28
  55. package/dist/commands/improve/session-asset.js +248 -0
  56. package/dist/commands/lint/agent-linter.js +1 -1
  57. package/dist/commands/lint/base-linter.js +55 -37
  58. package/dist/commands/lint/command-linter.js +1 -1
  59. package/dist/commands/lint/default-linter.js +1 -1
  60. package/dist/commands/lint/env-key-rules.js +1 -1
  61. package/dist/commands/lint/index.js +19 -25
  62. package/dist/commands/lint/knowledge-linter.js +1 -1
  63. package/dist/commands/lint/memory-linter.js +1 -1
  64. package/dist/commands/lint/registry.js +8 -8
  65. package/dist/commands/lint/skill-linter.js +1 -1
  66. package/dist/commands/lint/task-linter.js +1 -1
  67. package/dist/commands/lint/workflow-linter.js +1 -1
  68. package/dist/commands/lint.js +1 -1
  69. package/dist/commands/observability-cli.js +244 -0
  70. package/dist/commands/proposal/drain-policies.js +3 -3
  71. package/dist/commands/proposal/drain.js +15 -10
  72. package/dist/commands/proposal/proposal-cli.js +478 -0
  73. package/dist/commands/{proposal.js → proposal/proposal.js} +5 -5
  74. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  75. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  76. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  77. package/dist/{core → commands/proposal/validators}/proposals.js +13 -7
  78. package/dist/commands/{curate.js → read/curate.js} +7 -7
  79. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  80. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  81. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  82. package/dist/commands/read/search-cli.js +207 -0
  83. package/dist/commands/{search.js → read/search.js} +22 -27
  84. package/dist/commands/{show.js → read/show.js} +31 -45
  85. package/dist/commands/registry-cli.js +8 -8
  86. package/dist/commands/remember.js +8 -8
  87. package/dist/commands/sources/add-cli.js +293 -0
  88. package/dist/commands/{history.js → sources/history.js} +27 -25
  89. package/dist/commands/{info.js → sources/info.js} +6 -6
  90. package/dist/commands/{init.js → sources/init.js} +6 -6
  91. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  92. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  93. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  94. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  95. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  96. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  97. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  98. package/dist/commands/sources/sources-cli.js +305 -0
  99. package/dist/commands/sources/stash-cli.js +219 -0
  100. package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
  101. package/dist/commands/tasks/default-tasks.js +173 -0
  102. package/dist/commands/tasks/tasks-cli.js +210 -0
  103. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  104. package/dist/commands/wiki-cli.js +307 -0
  105. package/dist/commands/workflow-cli.js +329 -0
  106. package/dist/core/action-contributors.js +1 -1
  107. package/dist/core/assert.js +40 -0
  108. package/dist/core/asset/asset-create.js +54 -0
  109. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  110. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  111. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  112. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  113. package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
  114. package/dist/core/best-effort.js +64 -0
  115. package/dist/core/common.js +32 -18
  116. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  117. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  118. package/dist/core/{config-schema.js → config/config-schema.js} +45 -1
  119. package/dist/core/config/config-types.js +16 -0
  120. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  121. package/dist/core/{config.js → config/config.js} +10 -8
  122. package/dist/core/env-secret-ref.js +90 -0
  123. package/dist/core/errors.js +13 -3
  124. package/dist/core/events.js +27 -4
  125. package/dist/core/file-lock.js +1 -1
  126. package/dist/core/improve-types.js +48 -0
  127. package/dist/core/lesson-lint.js +2 -2
  128. package/dist/core/paths.js +2 -2
  129. package/dist/core/ripgrep/install.js +2 -2
  130. package/dist/core/ripgrep/resolve.js +2 -2
  131. package/dist/core/state-db.js +88 -46
  132. package/dist/core/text-truncation.js +148 -0
  133. package/dist/core/time.js +1 -1
  134. package/dist/core/write-source.js +98 -85
  135. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  136. package/dist/indexer/{db.js → db/db.js} +126 -116
  137. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  138. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  139. package/dist/indexer/ensure-index.js +4 -4
  140. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  141. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  142. package/dist/indexer/indexer.js +37 -30
  143. package/dist/indexer/init.js +54 -0
  144. package/dist/indexer/manifest.js +10 -10
  145. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +92 -23
  146. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  147. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  148. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  149. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  150. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  151. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  152. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  153. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  154. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  155. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  156. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  157. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  158. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  159. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  160. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  161. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  162. package/dist/integrations/agent/builder-shared.js +39 -0
  163. package/dist/integrations/agent/builders.js +14 -81
  164. package/dist/integrations/agent/config.js +6 -4
  165. package/dist/integrations/agent/detect.js +1 -1
  166. package/dist/integrations/agent/index.js +23 -8
  167. package/dist/integrations/agent/prompts.js +2 -3
  168. package/dist/integrations/agent/runner.js +22 -3
  169. package/dist/integrations/agent/spawn.js +9 -10
  170. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  171. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  172. package/dist/integrations/harnesses/claude/index.js +64 -0
  173. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +16 -1
  174. package/dist/integrations/harnesses/index.js +144 -0
  175. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  176. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  177. package/dist/integrations/harnesses/opencode/index.js +59 -0
  178. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  179. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  180. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  181. package/dist/integrations/harnesses/types.js +43 -0
  182. package/dist/integrations/lockfile.js +7 -16
  183. package/dist/integrations/session-logs/index.js +82 -9
  184. package/dist/llm/call-ai.js +4 -4
  185. package/dist/llm/client.js +131 -6
  186. package/dist/llm/embedder.js +6 -6
  187. package/dist/llm/embedders/local.js +9 -22
  188. package/dist/llm/embedders/remote.js +2 -2
  189. package/dist/llm/embedders/types.js +1 -1
  190. package/dist/llm/graph-extract.js +31 -12
  191. package/dist/llm/index-passes.js +1 -1
  192. package/dist/llm/memory-infer.js +12 -5
  193. package/dist/llm/metadata-enhance.js +2 -2
  194. package/dist/output/context.js +6 -44
  195. package/dist/output/renderers.js +88 -58
  196. package/dist/output/shapes/curate.js +7 -3
  197. package/dist/output/shapes/distill.js +7 -3
  198. package/dist/output/shapes/env-list.js +18 -16
  199. package/dist/output/shapes/events.js +5 -4
  200. package/dist/output/shapes/helpers.js +2 -4
  201. package/dist/output/shapes/history.js +7 -3
  202. package/dist/output/shapes/passthrough.js +8 -11
  203. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  204. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  205. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  206. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  207. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  208. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  209. package/dist/output/shapes/registry-search.js +7 -3
  210. package/dist/output/shapes/registry.js +12 -0
  211. package/dist/output/shapes/search.js +7 -3
  212. package/dist/output/shapes/secret-list.js +18 -16
  213. package/dist/output/shapes/show.js +7 -3
  214. package/dist/output/shapes.js +55 -30
  215. package/dist/output/text/add.js +2 -3
  216. package/dist/output/text/clone.js +2 -3
  217. package/dist/output/text/config.js +2 -3
  218. package/dist/output/text/curate.js +4 -3
  219. package/dist/output/text/distill.js +2 -3
  220. package/dist/output/text/enable-disable.js +5 -4
  221. package/dist/output/text/env.js +13 -0
  222. package/dist/output/text/events.js +5 -4
  223. package/dist/output/text/feedback.js +4 -3
  224. package/dist/output/text/helpers.js +54 -39
  225. package/dist/output/text/history.js +2 -3
  226. package/dist/output/text/import.js +2 -3
  227. package/dist/output/text/index.js +2 -3
  228. package/dist/output/text/info.js +2 -3
  229. package/dist/output/text/init.js +2 -3
  230. package/dist/output/text/list.js +2 -3
  231. package/dist/output/text/proposal/producer.js +9 -0
  232. package/dist/output/text/proposal/proposal.js +13 -0
  233. package/dist/output/text/registry-commands.js +8 -7
  234. package/dist/output/text/registry.js +12 -0
  235. package/dist/output/text/remember.js +4 -3
  236. package/dist/output/text/remove.js +2 -3
  237. package/dist/output/text/save.js +2 -3
  238. package/dist/output/text/search.js +4 -3
  239. package/dist/output/text/show.js +4 -3
  240. package/dist/output/text/update.js +2 -3
  241. package/dist/output/text/upgrade.js +2 -3
  242. package/dist/output/text/wiki.js +12 -11
  243. package/dist/output/text/workflow.js +12 -10
  244. package/dist/output/text.js +66 -32
  245. package/dist/registry/build-index.js +11 -10
  246. package/dist/registry/factory.js +1 -1
  247. package/dist/registry/origin-resolve.js +1 -1
  248. package/dist/registry/providers/index.js +2 -2
  249. package/dist/registry/providers/skills-sh.js +91 -72
  250. package/dist/registry/providers/static-index.js +75 -52
  251. package/dist/registry/resolve.js +3 -3
  252. package/dist/runtime.js +242 -0
  253. package/dist/scripts/migrate-storage.js +1594 -673
  254. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +240 -166
  255. package/dist/setup/detect.js +311 -9
  256. package/dist/setup/harness-config-import.js +6 -120
  257. package/dist/setup/setup.js +454 -43
  258. package/dist/sources/include.js +1 -1
  259. package/dist/sources/provider-factory.js +2 -2
  260. package/dist/sources/providers/filesystem.js +3 -3
  261. package/dist/sources/providers/git.js +9 -9
  262. package/dist/sources/providers/index.js +4 -4
  263. package/dist/sources/providers/npm.js +6 -6
  264. package/dist/sources/providers/provider-utils.js +13 -20
  265. package/dist/sources/providers/sync-from-ref.js +5 -5
  266. package/dist/sources/providers/tar-utils.js +2 -2
  267. package/dist/sources/providers/website.js +2 -2
  268. package/dist/sources/resolve.js +5 -5
  269. package/dist/sources/website-ingest.js +5 -5
  270. package/dist/storage/database.js +102 -0
  271. package/dist/storage/engines/sqlite-migrations.js +42 -0
  272. package/dist/storage/locations.js +25 -0
  273. package/dist/storage/repositories/index-db.js +43 -0
  274. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  275. package/dist/tasks/backends/cron.js +4 -4
  276. package/dist/tasks/backends/exec-utils.js +32 -0
  277. package/dist/tasks/backends/index.js +3 -3
  278. package/dist/tasks/backends/launchd.js +7 -14
  279. package/dist/tasks/backends/schtasks.js +7 -16
  280. package/dist/tasks/embedded.js +71 -0
  281. package/dist/tasks/parser.js +2 -2
  282. package/dist/tasks/resolveAkmBin.js +1 -1
  283. package/dist/tasks/runner.js +28 -15
  284. package/dist/tasks/schedule.js +1 -1
  285. package/dist/tasks/validator.js +7 -7
  286. package/dist/text-import-hook.mjs +51 -0
  287. package/dist/version.js +2 -1
  288. package/dist/wiki/wiki.js +7 -7
  289. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  290. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  291. package/dist/workflows/cli.js +1 -1
  292. package/dist/workflows/db.js +50 -32
  293. package/dist/workflows/parser.js +4 -4
  294. package/dist/workflows/renderer.js +5 -5
  295. package/dist/workflows/runtime/agent-identity.js +56 -0
  296. package/dist/workflows/runtime/checkin.js +57 -0
  297. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  298. package/dist/workflows/validate-summary.js +82 -0
  299. package/docs/README.md +1 -1
  300. package/docs/data-and-telemetry.md +6 -6
  301. package/package.json +16 -8
  302. package/dist/commands/add-cli.js +0 -279
  303. package/dist/commands/env.js +0 -213
  304. package/dist/integrations/agent/sdk-runner.js +0 -126
  305. package/dist/output/shapes/vault-list.js +0 -19
  306. package/dist/output/text/proposal-producer.js +0 -8
  307. package/dist/output/text/proposal.js +0 -12
  308. package/dist/output/text/vault.js +0 -16
  309. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  310. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  311. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  312. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  313. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  314. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  315. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  316. /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
@@ -10,12 +10,13 @@
10
10
  *
11
11
  * `llm.ts` re-exports everything from this module for backward compatibility.
12
12
  */
13
- import { fetchWithTimeout } from "../core/common";
14
- import { resolveSecret } from "../core/config";
15
- import { escapeJsonStringControls, parseJsonResponse, stripCodeFences, stripThinkBlocks } from "../core/parse";
13
+ import { fetchWithTimeout } from "../core/common.js";
14
+ import { resolveSecret } from "../core/config/config.js";
15
+ import { escapeJsonStringControls, parseJsonResponse, stripCodeFences, stripThinkBlocks } from "../core/parse.js";
16
+ import { warnVerbose } from "../core/warn.js";
16
17
  // Re-export shared parse utilities so existing importers of `client.ts` continue
17
18
  // to resolve `parseJsonResponse` and `parseEmbeddedJsonResponse` from this module.
18
- export { escapeJsonStringControls, parseEmbeddedJsonResponse, parseJsonResponse, stripCodeFences, stripThinkBlocks, } from "../core/parse";
19
+ export { escapeJsonStringControls, parseEmbeddedJsonResponse, parseJsonResponse, stripCodeFences, stripThinkBlocks, } from "../core/parse.js";
19
20
  /** Maximum length of an LLM error response body included in thrown errors. */
20
21
  const ERROR_BODY_MAX_LEN = 200;
21
22
  /**
@@ -46,6 +47,27 @@ export function redactErrorBody(input) {
46
47
  }
47
48
  return out;
48
49
  }
50
+ /**
51
+ * Detect a response body that is an HTML document rather than the expected
52
+ * JSON. LM Studio (and similar local providers) can serve their web UI on
53
+ * partial-load / startup failures, producing an HTML page where the OpenAI
54
+ * API contract promises JSON.
55
+ */
56
+ function isHtmlResponse(body) {
57
+ const lower = body.trimStart().toLowerCase();
58
+ return lower.startsWith("<!doctype html") || lower.startsWith("<html");
59
+ }
60
+ /**
61
+ * Produce a short plain-text excerpt of an HTML body for inclusion in error
62
+ * messages: strip tags, collapse whitespace, and truncate.
63
+ */
64
+ function htmlExcerpt(body) {
65
+ const text = body
66
+ .replace(/<[^>]*>/g, " ")
67
+ .replace(/\s+/g, " ")
68
+ .trim();
69
+ return text.length > ERROR_BODY_MAX_LEN ? `${text.slice(0, ERROR_BODY_MAX_LEN)}…` : text;
70
+ }
49
71
  export class LlmCallError extends Error {
50
72
  code;
51
73
  statusCode;
@@ -56,8 +78,95 @@ export class LlmCallError extends Error {
56
78
  this.name = "LlmCallError";
57
79
  }
58
80
  }
81
+ // ── Single bounded retry for transient failures ─────────────────────────────
82
+ /** Lower bound of the jittered retry backoff (inclusive), in milliseconds. */
83
+ const RETRY_BACKOFF_MIN_MS = 200;
84
+ /** Upper bound of the jittered retry backoff (exclusive-ish), in milliseconds. */
85
+ const RETRY_BACKOFF_MAX_MS = 800;
86
+ /**
87
+ * Fraction of the effective timeout budget that, once consumed by the first
88
+ * attempt, causes the retry to be skipped — there is not enough budget left
89
+ * for a meaningful second attempt.
90
+ */
91
+ const RETRY_BUDGET_FRACTION = 0.9;
92
+ /**
93
+ * Sleep for `ms` milliseconds. Extracted as a named helper so tests can stub
94
+ * the backoff via the internal `sleep` option on {@link chatCompletion} and
95
+ * avoid real delays.
96
+ */
97
+ export function sleep(ms) {
98
+ return new Promise((resolve) => setTimeout(resolve, ms));
99
+ }
100
+ /** Compute a uniform jittered backoff in the [200, 800)ms range. */
101
+ function retryBackoffMs() {
102
+ return RETRY_BACKOFF_MIN_MS + Math.random() * (RETRY_BACKOFF_MAX_MS - RETRY_BACKOFF_MIN_MS);
103
+ }
104
+ /**
105
+ * Detect whether an error message indicates a context-size-exceeded condition.
106
+ * Mirrors the heuristic in `graph-extract.ts` — retrying a context overflow
107
+ * cannot shrink the input, so it must not be retried.
108
+ */
109
+ function looksLikeContextOverflow(message) {
110
+ const lower = message.toLowerCase();
111
+ return (lower.includes("context") &&
112
+ (lower.includes("context size") ||
113
+ lower.includes("context length") ||
114
+ lower.includes("context_window") ||
115
+ lower.includes("prompt too long") ||
116
+ lower.includes("exceeds")));
117
+ }
118
+ /**
119
+ * Decide whether a first-attempt {@link LlmCallError} is eligible for a single
120
+ * retry. Retryable: HTTP 5xx (`provider_error` with statusCode >= 500) and
121
+ * `network_error` whose message looks like a transient connection reset
122
+ * (ECONNRESET / EPIPE / "fetch failed"). NOT retryable: 4xx, `rate_limited`
123
+ * (429), `timeout`, `parse_error`, and context-overflow-classified errors.
124
+ */
125
+ function isRetryable(err) {
126
+ if (looksLikeContextOverflow(err.message))
127
+ return false;
128
+ if (err.code === "provider_error") {
129
+ return typeof err.statusCode === "number" && err.statusCode >= 500;
130
+ }
131
+ if (err.code === "network_error") {
132
+ const lower = err.message.toLowerCase();
133
+ return lower.includes("econnreset") || lower.includes("epipe") || lower.includes("fetch failed");
134
+ }
135
+ return false;
136
+ }
59
137
  export async function chatCompletion(config, messages, options) {
60
- const timeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 120_000;
138
+ const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 120_000;
139
+ const started = Date.now();
140
+ try {
141
+ return await chatCompletionAttempt(config, messages, options, effectiveTimeoutMs);
142
+ }
143
+ catch (err) {
144
+ if (!(err instanceof LlmCallError) || !isRetryable(err))
145
+ throw err;
146
+ // Timeout-budget guard: if the first attempt already burned most of the
147
+ // budget, a second attempt cannot complete — skip the retry.
148
+ const elapsed = Date.now() - started;
149
+ const remaining = effectiveTimeoutMs - elapsed;
150
+ if (elapsed >= effectiveTimeoutMs * RETRY_BUDGET_FRACTION || remaining <= 0) {
151
+ throw err;
152
+ }
153
+ // Signal the caller so it can bump `retryAttempts` (NOT `failureCount`).
154
+ options?.onRetryAttempt?.();
155
+ // Log the first failure at debug (verbose-only) level; the retry outcome is
156
+ // authoritative.
157
+ warnVerbose(`[akm] LLM transient failure (${err.code}); retrying once: ${err.message}`);
158
+ const wait = retryBackoffMs();
159
+ await (options?.sleep ?? sleep)(wait);
160
+ // The retry must not exceed the original budget.
161
+ return await chatCompletionAttempt(config, messages, options, remaining);
162
+ }
163
+ }
164
+ /**
165
+ * A single chat-completion attempt: one HTTP request/response cycle with no
166
+ * retry. {@link chatCompletion} wraps this with a single bounded retry for
167
+ * transient failures.
168
+ */
169
+ async function chatCompletionAttempt(config, messages, options, timeoutMs) {
61
170
  const headers = { "Content-Type": "application/json" };
62
171
  const resolvedKey = resolveSecret(config.apiKey);
63
172
  if (resolvedKey) {
@@ -110,12 +219,28 @@ export async function chatCompletion(config, messages, options) {
110
219
  if (status === 429) {
111
220
  throw new LlmCallError(`LLM request rate limited (429) ${config.endpoint}: ${safeBody}`, "rate_limited", status);
112
221
  }
222
+ if (status >= 500 && isHtmlResponse(rawBody)) {
223
+ throw new LlmCallError(`LLM provider returned HTML instead of JSON (${status}) ${config.endpoint}: ${htmlExcerpt(rawBody)}`, "provider_html_error", status);
224
+ }
113
225
  if (status >= 500) {
114
226
  throw new LlmCallError(`LLM provider error (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
115
227
  }
116
228
  throw new LlmCallError(`LLM request failed (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
117
229
  }
118
- const json = (await response.json());
230
+ // A 2xx response is still an error if the body is HTML where JSON was
231
+ // expected (e.g. a provider serving its web UI). Read the raw body first so
232
+ // we can categorize an HTML page distinctly from a malformed-JSON parse_error.
233
+ const rawOkBody = await response.text();
234
+ if (isHtmlResponse(rawOkBody)) {
235
+ throw new LlmCallError(`LLM provider returned HTML instead of JSON (${response.status}) ${config.endpoint}: ${htmlExcerpt(rawOkBody)}`, "provider_html_error", response.status);
236
+ }
237
+ let json;
238
+ try {
239
+ json = JSON.parse(rawOkBody);
240
+ }
241
+ catch {
242
+ throw new LlmCallError(`LLM response was not valid JSON ${config.endpoint}: ${redactErrorBody(rawOkBody)}`, "parse_error", response.status);
243
+ }
119
244
  const content = (json.choices?.[0]?.message?.content ?? "").trim();
120
245
  const reasoning = (json.choices?.[0]?.message?.reasoning_content ?? "").trim();
121
246
  return content || reasoning;
@@ -1,12 +1,12 @@
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
- import { embedCacheKey, getCachedEmbedding, setCachedEmbedding } from "./embedders/cache";
5
- import { isTransformersAvailable, LocalEmbedder } from "./embedders/local";
6
- import { hasRemoteEndpoint, RemoteEmbedder } from "./embedders/remote";
4
+ import { embedCacheKey, getCachedEmbedding, setCachedEmbedding } from "./embedders/cache.js";
5
+ import { isTransformersAvailable, LocalEmbedder } from "./embedders/local.js";
6
+ import { hasRemoteEndpoint, RemoteEmbedder } from "./embedders/remote.js";
7
7
  // ── Re-exports (public API) ─────────────────────────────────────────────────
8
- export { clearEmbeddingCache } from "./embedders/cache";
9
- export { DEFAULT_LOCAL_MODEL, isTransformersAvailable } from "./embedders/local";
8
+ export { clearEmbeddingCache } from "./embedders/cache.js";
9
+ export { DEFAULT_LOCAL_MODEL, isTransformersAvailable } from "./embedders/local.js";
10
10
  // ── Singleton local embedder ────────────────────────────────────────────────
11
11
  // `_localEmbedder` is an intentional module-level singleton but constructed
12
12
  // lazily on first use. The underlying @huggingface/transformers pipeline is
@@ -76,7 +76,7 @@ export async function embedBatch(texts, embeddingConfig, signal) {
76
76
  // (notably `db.ts`) can pull the math function without dragging in this
77
77
  // facade and its `@huggingface/transformers` import chain. Re-export
78
78
  // preserves the existing public API.
79
- export { cosineSimilarity } from "./embedders/types";
79
+ export { cosineSimilarity } from "./embedders/types.js";
80
80
  // ── Availability check ──────────────────────────────────────────────────────
81
81
  /**
82
82
  * Check whether embedding is available with a detailed reason on failure.
@@ -10,8 +10,9 @@
10
10
  * shared instance for the production code path.
11
11
  */
12
12
  import path from "node:path";
13
- import { getCacheDir } from "../../core/paths";
14
- import { warn } from "../../core/warn";
13
+ import { getCacheDir } from "../../core/paths.js";
14
+ import { warn } from "../../core/warn.js";
15
+ import { getDirname, resolveModule } from "../../runtime.js";
15
16
  /**
16
17
  * Default local transformer model for embeddings.
17
18
  * `bge-small-en-v1.5` scores higher on MTEB benchmarks than the previous
@@ -169,31 +170,17 @@ function shouldRetryWithoutExplicitDtype(error) {
169
170
  }
170
171
  /**
171
172
  * Check whether the `@huggingface/transformers` package can be resolved.
172
- * Uses `Bun.resolveSync` so we never load the module (which would trigger
173
- * heavy WASM/model side-effects) just to test availability.
174
- *
175
- * Falls back to `require.resolve` when `Bun.resolveSync` is unavailable
176
- * (e.g. running under Node), so the function still works in mixed runtimes.
173
+ * Uses the runtime boundary's `resolveModule` so we never load the module
174
+ * (which would trigger heavy WASM/model side-effects) just to test
175
+ * availability. `resolveModule` uses `Bun.resolveSync` on Bun and
176
+ * `require.resolve` on Node.
177
177
  */
178
178
  export function isTransformersAvailable() {
179
179
  try {
180
- if (typeof Bun !== "undefined" && typeof Bun.resolveSync === "function") {
181
- Bun.resolveSync("@huggingface/transformers", import.meta.dir);
182
- return true;
183
- }
184
- }
185
- catch {
186
- return false;
187
- }
188
- try {
189
- const req = globalThis.require;
190
- if (req && typeof req.resolve === "function") {
191
- req.resolve("@huggingface/transformers");
192
- return true;
193
- }
180
+ resolveModule("@huggingface/transformers", getDirname(import.meta.url));
181
+ return true;
194
182
  }
195
183
  catch {
196
184
  return false;
197
185
  }
198
- return false;
199
186
  }
@@ -7,8 +7,8 @@
7
7
  * Calls the configured `/embeddings` endpoint and L2-normalizes the returned
8
8
  * vectors so the scoring pipeline's L2-to-cosine conversion is correct.
9
9
  */
10
- import { fetchWithTimeout, isHttpUrl } from "../../core/common";
11
- import { resolveSecret } from "../../core/config";
10
+ import { fetchWithTimeout, isHttpUrl } from "../../core/common.js";
11
+ import { resolveSecret } from "../../core/config/config.js";
12
12
  const DEFAULT_REMOTE_BATCH_SIZE = 100;
13
13
  /** Cheap token estimator: 4 chars ≈ 1 token. Used in verbose logging and error messages. */
14
14
  export function estimateTokenCount(text) {
@@ -32,4 +32,4 @@ export function cosineSimilarity(a, b) {
32
32
  }
33
33
  // Imported lazily to keep this types module dependency-free where possible;
34
34
  // `warn` is a thin printf wrapper so the cost is negligible.
35
- import { warn } from "../../core/warn";
35
+ import { warn } from "../../core/warn.js";
@@ -21,10 +21,10 @@
21
21
  * straight through.
22
22
  */
23
23
  import userPromptTemplate from "../assets/prompts/graph-extract-user-prompt.md" with { type: "text" };
24
- import { toErrorMessage } from "../core/common";
25
- import { warn, warnVerbose } from "../core/warn";
26
- import { chatCompletion, parseEmbeddedJsonResponse } from "./client";
27
- import { tryLlmFeature } from "./feature-gate";
24
+ import { toErrorMessage } from "../core/common.js";
25
+ import { warn, warnVerbose } from "../core/warn.js";
26
+ import { chatCompletion, LlmCallError, parseEmbeddedJsonResponse } from "./client.js";
27
+ import { tryLlmFeature } from "./feature-gate.js";
28
28
  /**
29
29
  * Separator token used between assets in a batch prompt.
30
30
  * Chosen to be visually clear and unlikely to appear verbatim in asset bodies.
@@ -48,14 +48,22 @@ const USER_PROMPT_PREFIX = userPromptTemplate
48
48
  /**
49
49
  * Detect whether an error message indicates a context size exceeded condition.
50
50
  * Covers common patterns from OpenAI-compatible APIs (LM Studio, Ollama, etc).
51
+ *
52
+ * Requires BOTH a context keyword AND token-count/overflow evidence so that
53
+ * model prose merely mentioning "context size" / "context length" (e.g. gemma
54
+ * narrating about a document) does not get misclassified as a provider
55
+ * context-limit error (#496).
51
56
  */
52
- function isContextSizeError(message) {
57
+ export function isContextSizeError(message) {
53
58
  const lower = message.toLowerCase();
54
- return (lower.includes("context size") ||
55
- lower.includes("context length") ||
56
- lower.includes("context_window") ||
57
- lower.includes("prompt too long") ||
58
- (lower.includes("exceeds") && lower.includes("context")));
59
+ const contextKw = /context (size|length|window)|prompt too long|exceeds.*context/.test(lower);
60
+ if (!contextKw) {
61
+ return false;
62
+ }
63
+ const evidence = /\b\d+\s*(token|tokens|tk)\b/.test(lower) ||
64
+ /max(imum)?\s+(context|token|input)/.test(lower) ||
65
+ /exceeded|over.*limit|too.*long/.test(lower);
66
+ return evidence;
59
67
  }
60
68
  const GENERIC_ENTITIES = new Set([
61
69
  "agent",
@@ -529,6 +537,7 @@ export async function extractGraphFromBodies(llmConfig, bodies, signal, akmConfi
529
537
  temperature: 0.1,
530
538
  timeoutMs: llmConfig.timeoutMs,
531
539
  signal,
540
+ onRetryAttempt: () => bumpTelemetry(options.telemetry, "retryAttempts"),
532
541
  });
533
542
  if (!raw)
534
543
  return null;
@@ -671,7 +680,12 @@ export async function extractGraphFromBody(llmConfig, body, signal, akmConfig, o
671
680
  const raw = await chatCompletion(llmConfig, [
672
681
  { role: "system", content: SYSTEM_PROMPT },
673
682
  { role: "user", content: userPrompt },
674
- ], { temperature: 0.1, timeoutMs: llmConfig.timeoutMs, signal });
683
+ ], {
684
+ temperature: 0.1,
685
+ timeoutMs: llmConfig.timeoutMs,
686
+ signal,
687
+ onRetryAttempt: () => bumpTelemetry(options.telemetry, "retryAttempts"),
688
+ });
675
689
  if (!raw)
676
690
  return empty();
677
691
  const parsed = parseEmbeddedJsonResponse(raw);
@@ -696,6 +710,11 @@ export async function extractGraphFromBody(llmConfig, body, signal, akmConfig, o
696
710
  `Consider increasing llm.contextLength in config.json.`);
697
711
  return empty("context_limit", "failed");
698
712
  }
713
+ else if (err instanceof LlmCallError && err.code === "provider_html_error") {
714
+ bumpTelemetry(options.telemetry, "htmlErrorCount");
715
+ warn(`graph extraction: provider returned HTML instead of JSON for asset; promptChars=${userPrompt.length}${formatContextHint(llmConfig)}: ${errMsg}`);
716
+ return empty("llm_error", "failed");
717
+ }
699
718
  else {
700
719
  bumpTelemetry(options.telemetry, "failureCount");
701
720
  warn(`graph extraction failed for asset; promptChars=${userPrompt.length}${formatContextHint(llmConfig)}: ${errMsg}`);
@@ -708,4 +727,4 @@ export async function extractGraphFromBody(llmConfig, body, signal, akmConfig, o
708
727
  });
709
728
  }
710
729
  // deduplicateGraph moved to src/indexer/graph-dedup.ts (pure utility, no LLM calls).
711
- export { deduplicateGraph } from "../indexer/graph-dedup";
730
+ export { deduplicateGraph } from "../indexer/graph/graph-dedup.js";
@@ -1,7 +1,7 @@
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
- import { getDefaultLlmConfig, getIndexPassConfig } from "../core/config";
4
+ import { getDefaultLlmConfig, getIndexPassConfig } from "../core/config/config.js";
5
5
  /**
6
6
  * Map a pass name (as used by callers — "memory", "graph", etc.) to the
7
7
  * matching key under `profiles.improve.default.processes`. Pass names with
@@ -18,10 +18,10 @@
18
18
  * the connection via `resolveIndexPassLLM("memory", config)` and pass it
19
19
  * straight through.
20
20
  */
21
- import { toErrorMessage } from "../core/common";
22
- import { warn } from "../core/warn";
23
- import { chatCompletion, parseEmbeddedJsonResponse } from "./client";
24
- import { tryLlmFeature } from "./feature-gate";
21
+ import { toErrorMessage } from "../core/common.js";
22
+ import { warn } from "../core/warn.js";
23
+ import { chatCompletion, LlmCallError, parseEmbeddedJsonResponse } from "./client.js";
24
+ import { tryLlmFeature } from "./feature-gate.js";
25
25
  /** Hard cap on body chars sent to the model — pragmatic and matches `runLlmEnrich`. */
26
26
  const MAX_BODY_CHARS = 4000;
27
27
  const SYSTEM_PROMPT = "You compress a developer memory into one high-signal derived memory for later retrieval. " +
@@ -66,7 +66,7 @@ const DERIVED_MEMORY_JSON_SCHEMA = {
66
66
  * Routes through `tryLlmFeature("memory_inference", ...)` so the feature gate
67
67
  * and onFallback hook are honoured uniformly (Fix C5).
68
68
  */
69
- export async function compressMemoryToDerivedMemory(llmConfig, body, signal, akmConfig, onFallback) {
69
+ export async function compressMemoryToDerivedMemory(llmConfig, body, signal, akmConfig, onFallback, telemetry, onRetryAttempt) {
70
70
  const trimmedBody = body.trim();
71
71
  if (!trimmedBody)
72
72
  return undefined;
@@ -81,6 +81,7 @@ export async function compressMemoryToDerivedMemory(llmConfig, body, signal, akm
81
81
  timeoutMs: llmConfig.timeoutMs,
82
82
  signal,
83
83
  responseSchema: DERIVED_MEMORY_JSON_SCHEMA,
84
+ onRetryAttempt,
84
85
  });
85
86
  if (!raw)
86
87
  return undefined;
@@ -113,6 +114,12 @@ export async function compressMemoryToDerivedMemory(llmConfig, body, signal, akm
113
114
  return { title, description, tags, searchHints, content };
114
115
  }
115
116
  catch (err) {
117
+ if (err instanceof LlmCallError && err.code === "provider_html_error") {
118
+ if (telemetry)
119
+ telemetry.htmlErrorCount = (telemetry.htmlErrorCount ?? 0) + 1;
120
+ warn(`memory inference: provider returned HTML instead of JSON; skipping memory: ${toErrorMessage(err)}`);
121
+ return undefined;
122
+ }
116
123
  warn(`memory inference failed: ${toErrorMessage(err)}`);
117
124
  return undefined;
118
125
  }
@@ -1,8 +1,8 @@
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
- import { chatCompletion, parseJsonResponse } from "./client";
5
- import { tryLlmFeature } from "./feature-gate";
4
+ import { chatCompletion, parseJsonResponse } from "./client.js";
5
+ import { tryLlmFeature } from "./feature-gate.js";
6
6
  const SYSTEM_PROMPT = `You are a metadata generator for a developer asset registry. Given a script/skill/command/agent entry, generate improved metadata. Respond with ONLY valid JSON, no markdown fencing.`;
7
7
  /**
8
8
  * Use an LLM to enhance a stash entry's metadata: improve description,
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * Initialized from `cli.ts` before `runMain`.
13
13
  */
14
- import { UsageError } from "../core/errors";
14
+ import { UsageError } from "../core/errors.js";
15
15
  export const OUTPUT_FORMATS = ["json", "yaml", "text", "jsonl", "md"];
16
16
  export const DETAIL_LEVELS = ["brief", "normal", "full"];
17
17
  export const SHAPE_MODES = ["human", "agent", "summary"];
@@ -75,51 +75,13 @@ export function resolveOutputMode(argv, defaults = {}) {
75
75
  const format = parseOutputFormat(parseFlagValue(argv, "--format")) ?? defaults?.format ?? "json";
76
76
  const rawDetail = parseFlagValue(argv, "--detail");
77
77
  const rawShape = parseFlagValue(argv, "--shape");
78
- const usedForAgent = hasBooleanFlag(argv, "--for-agent");
79
- // Back-compat: the projection presets `summary`/`agent` used to live on
80
- // `--detail`. They moved to `--shape` in 0.8 (removed from `--detail` in
81
- // 0.9.0). Map the legacy spellings onto `--shape` + warn, and treat the
82
- // verbosity axis as `normal` (the prior effective behaviour).
83
- let detailForVerbosity = rawDetail;
84
- let shapeFromLegacyDetail;
85
- if (rawDetail === "summary" || rawDetail === "agent") {
86
- // Only nudge toward `--shape` when the caller did not already pass an
87
- // explicit `--shape` (which wins below). Otherwise the "use --shape <x>"
88
- // advice would name a projection the caller did not request.
89
- if (rawShape === undefined)
90
- emitDetailShapeDeprecation(rawDetail);
91
- shapeFromLegacyDetail = rawDetail;
92
- detailForVerbosity = "normal";
93
- }
94
- else if (rawDetail === "per-run") {
95
- // Legacy `akm health --detail per-run` (→ `--group-by run`). The health
96
- // command owns the back-compat warning + mapping; the global singleton must
97
- // not reject the value here, so fall through to the default verbosity.
98
- detailForVerbosity = undefined;
99
- }
100
- if (usedForAgent) {
101
- emitForAgentDeprecation();
102
- }
103
- const detail = parseDetailLevel(detailForVerbosity) ?? defaults?.detail ?? "brief";
104
- // Precedence: explicit `--shape` wins; then legacy `--detail summary|agent`;
105
- // then legacy `--for-agent`; default `human`.
106
- const shape = parseShapeMode(rawShape) ?? shapeFromLegacyDetail ?? (usedForAgent ? "agent" : "human");
78
+ // `--detail` is verbosity only (brief|normal|full); the projection presets
79
+ // (`summary`/`agent`) and the `--for-agent` boolean were removed in 0.9.0 —
80
+ // use `--shape`. Unknown `--detail` values fall through to the default.
81
+ const detail = parseDetailLevel(rawDetail) ?? defaults?.detail ?? "brief";
82
+ const shape = parseShapeMode(rawShape) ?? "human";
107
83
  return { format, detail, shape, forAgent: shape === "agent" };
108
84
  }
109
- /** Suppress deprecation warnings under `--quiet` (mirrors the rest of the CLI). */
110
- function isQuietArgv() {
111
- return process.argv.includes("--quiet") || process.argv.includes("-q");
112
- }
113
- function emitDetailShapeDeprecation(value) {
114
- if (isQuietArgv())
115
- return;
116
- process.stderr.write(`warning: '--detail ${value}' is deprecated; use '--shape ${value}'. Removed in 0.9.0.\n`);
117
- }
118
- function emitForAgentDeprecation() {
119
- if (isQuietArgv())
120
- return;
121
- process.stderr.write("warning: '--for-agent' is deprecated; use '--shape agent'. Removed in 0.9.0.\n");
122
- }
123
85
  let _mode;
124
86
  /**
125
87
  * Initialize the process-level output mode. Must be called once at startup