akm-cli 0.8.7 → 0.8.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/CHANGELOG.md +428 -0
  2. package/dist/assets/help/help-proposals.md +1 -2
  3. package/dist/assets/hints/cli-hints-full.md +34 -19
  4. package/dist/assets/hints/cli-hints-short.md +1 -1
  5. package/dist/assets/profiles/catchup.json +13 -0
  6. package/dist/assets/profiles/consolidate.json +13 -0
  7. package/dist/assets/profiles/frequent.json +13 -0
  8. package/dist/assets/tasks/core/backup.yml +4 -0
  9. package/dist/assets/tasks/core/extract.yml +4 -0
  10. package/dist/assets/tasks/core/improve.yml +4 -0
  11. package/dist/assets/tasks/core/index-refresh.yml +4 -0
  12. package/dist/assets/tasks/core/sync.yml +4 -0
  13. package/dist/assets/tasks/core/update-stashes.yml +4 -0
  14. package/dist/assets/tasks/core/version-check.yml +4 -0
  15. package/dist/assets/templates/html/default.html +78 -0
  16. package/dist/assets/templates/html/health.html +560 -0
  17. package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
  18. package/dist/cli/config-migrate.js +6 -6
  19. package/dist/cli/config-validate.js +4 -4
  20. package/dist/cli/confirm.js +3 -3
  21. package/dist/cli/parse-args.js +1 -1
  22. package/dist/cli/shared.js +72 -19
  23. package/dist/cli-node.mjs +26 -0
  24. package/dist/cli.js +206 -3866
  25. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  26. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  27. package/dist/commands/agent/contribute-cli.js +200 -0
  28. package/dist/commands/completions.js +1 -1
  29. package/dist/commands/config-cli.js +230 -3
  30. package/dist/commands/db-cli.js +2 -2
  31. package/dist/commands/env/env-cli.js +529 -0
  32. package/dist/commands/env/env.js +410 -0
  33. package/dist/commands/env/secret-cli.js +259 -0
  34. package/dist/commands/{secret.js → env/secret.js} +6 -47
  35. package/dist/commands/events.js +4 -4
  36. package/dist/commands/feedback-cli.js +18 -34
  37. package/dist/commands/graph/graph-cli.js +132 -0
  38. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  39. package/dist/commands/health/checks.js +279 -0
  40. package/dist/commands/health/html-report.js +448 -0
  41. package/dist/commands/health.js +189 -266
  42. package/dist/commands/{consolidate.js → improve/consolidate.js} +48 -36
  43. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  44. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  45. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  46. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  47. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  48. package/dist/commands/{extract.js → improve/extract.js} +221 -26
  49. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +30 -4
  50. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
  51. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  52. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
  53. package/dist/commands/{improve.js → improve/improve.js} +672 -292
  54. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  55. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  56. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  57. package/dist/commands/improve/reflect-noise.js +0 -0
  58. package/dist/commands/{reflect.js → improve/reflect.js} +58 -28
  59. package/dist/commands/improve/session-asset.js +248 -0
  60. package/dist/commands/lint/agent-linter.js +1 -1
  61. package/dist/commands/lint/base-linter.js +55 -37
  62. package/dist/commands/lint/command-linter.js +1 -1
  63. package/dist/commands/lint/default-linter.js +1 -1
  64. package/dist/commands/lint/env-key-rules.js +1 -1
  65. package/dist/commands/lint/index.js +19 -25
  66. package/dist/commands/lint/knowledge-linter.js +1 -1
  67. package/dist/commands/lint/memory-linter.js +1 -1
  68. package/dist/commands/lint/registry.js +8 -8
  69. package/dist/commands/lint/skill-linter.js +1 -1
  70. package/dist/commands/lint/task-linter.js +1 -1
  71. package/dist/commands/lint/workflow-linter.js +1 -1
  72. package/dist/commands/lint.js +1 -1
  73. package/dist/commands/observability-cli.js +244 -0
  74. package/dist/commands/proposal/drain-policies.js +3 -3
  75. package/dist/commands/proposal/drain.js +87 -15
  76. package/dist/commands/proposal/proposal-cli.js +490 -0
  77. package/dist/commands/{proposal.js → proposal/proposal.js} +17 -6
  78. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  79. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  80. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  81. package/dist/{core → commands/proposal/validators}/proposals.js +374 -345
  82. package/dist/commands/{curate.js → read/curate.js} +7 -7
  83. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  84. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  85. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  86. package/dist/commands/read/search-cli.js +207 -0
  87. package/dist/commands/{search.js → read/search.js} +22 -27
  88. package/dist/commands/{show.js → read/show.js} +31 -45
  89. package/dist/commands/registry-cli.js +8 -8
  90. package/dist/commands/remember.js +14 -10
  91. package/dist/commands/sources/add-cli.js +293 -0
  92. package/dist/commands/{history.js → sources/history.js} +27 -25
  93. package/dist/commands/{info.js → sources/info.js} +6 -6
  94. package/dist/commands/{init.js → sources/init.js} +6 -6
  95. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  96. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  97. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  98. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  99. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  100. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  101. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  102. package/dist/commands/sources/sources-cli.js +305 -0
  103. package/dist/commands/sources/stash-cli.js +219 -0
  104. package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
  105. package/dist/commands/tasks/default-tasks.js +173 -0
  106. package/dist/commands/tasks/tasks-cli.js +210 -0
  107. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  108. package/dist/commands/wiki-cli.js +307 -0
  109. package/dist/commands/workflow-cli.js +329 -0
  110. package/dist/core/action-contributors.js +1 -1
  111. package/dist/core/assert.js +40 -0
  112. package/dist/core/asset/asset-create.js +54 -0
  113. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  114. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  115. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  116. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  117. package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
  118. package/dist/core/best-effort.js +64 -0
  119. package/dist/core/common.js +32 -18
  120. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  121. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  122. package/dist/core/{config-schema.js → config/config-schema.js} +50 -7
  123. package/dist/core/config/config-types.js +16 -0
  124. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  125. package/dist/core/{config.js → config/config.js} +10 -8
  126. package/dist/core/env-secret-ref.js +90 -0
  127. package/dist/core/errors.js +13 -3
  128. package/dist/core/events.js +27 -4
  129. package/dist/core/file-lock.js +1 -1
  130. package/dist/core/improve-types.js +48 -0
  131. package/dist/core/lesson-lint.js +2 -2
  132. package/dist/core/logs-db.js +304 -0
  133. package/dist/core/paths.js +2 -2
  134. package/dist/core/ripgrep/install.js +2 -2
  135. package/dist/core/ripgrep/resolve.js +2 -2
  136. package/dist/core/state-db.js +195 -60
  137. package/dist/core/text-truncation.js +148 -0
  138. package/dist/core/time.js +1 -1
  139. package/dist/core/write-source.js +98 -85
  140. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  141. package/dist/indexer/{db.js → db/db.js} +128 -118
  142. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  143. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  144. package/dist/indexer/ensure-index.js +4 -4
  145. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  146. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  147. package/dist/indexer/indexer.js +37 -30
  148. package/dist/indexer/init.js +54 -0
  149. package/dist/indexer/manifest.js +10 -10
  150. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +141 -33
  151. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  152. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  153. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  154. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  155. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  156. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  157. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  158. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  159. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  160. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  161. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  162. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  163. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  164. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  165. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  166. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  167. package/dist/integrations/agent/builder-shared.js +39 -0
  168. package/dist/integrations/agent/builders.js +14 -81
  169. package/dist/integrations/agent/config.js +6 -4
  170. package/dist/integrations/agent/detect.js +1 -1
  171. package/dist/integrations/agent/index.js +23 -8
  172. package/dist/integrations/agent/prompts.js +2 -3
  173. package/dist/integrations/agent/runner.js +22 -3
  174. package/dist/integrations/agent/spawn.js +9 -10
  175. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  176. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  177. package/dist/integrations/harnesses/claude/index.js +64 -0
  178. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +32 -5
  179. package/dist/integrations/harnesses/index.js +144 -0
  180. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  181. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  182. package/dist/integrations/harnesses/opencode/index.js +59 -0
  183. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  184. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  185. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  186. package/dist/integrations/harnesses/types.js +43 -0
  187. package/dist/integrations/lockfile.js +7 -16
  188. package/dist/integrations/session-logs/index.js +82 -9
  189. package/dist/llm/call-ai.js +4 -4
  190. package/dist/llm/client.js +146 -6
  191. package/dist/llm/embedder.js +6 -6
  192. package/dist/llm/embedders/local.js +9 -22
  193. package/dist/llm/embedders/remote.js +2 -2
  194. package/dist/llm/embedders/types.js +1 -1
  195. package/dist/llm/graph-extract.js +31 -12
  196. package/dist/llm/index-passes.js +1 -1
  197. package/dist/llm/memory-infer.js +12 -5
  198. package/dist/llm/metadata-enhance.js +2 -2
  199. package/dist/llm/usage-persist.js +77 -0
  200. package/dist/llm/usage-telemetry.js +103 -0
  201. package/dist/output/context.js +9 -46
  202. package/dist/output/html-render.js +73 -0
  203. package/dist/output/renderers.js +88 -58
  204. package/dist/output/shapes/curate.js +7 -3
  205. package/dist/output/shapes/distill.js +7 -3
  206. package/dist/output/shapes/env-list.js +18 -16
  207. package/dist/output/shapes/events.js +5 -4
  208. package/dist/output/shapes/helpers.js +19 -5
  209. package/dist/output/shapes/history.js +7 -3
  210. package/dist/output/shapes/passthrough.js +8 -11
  211. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  212. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  213. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  214. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  215. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  216. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  217. package/dist/output/shapes/registry-search.js +7 -3
  218. package/dist/output/shapes/registry.js +12 -0
  219. package/dist/output/shapes/search.js +7 -3
  220. package/dist/output/shapes/secret-list.js +18 -16
  221. package/dist/output/shapes/show.js +7 -3
  222. package/dist/output/shapes.js +55 -30
  223. package/dist/output/text/add.js +2 -3
  224. package/dist/output/text/clone.js +2 -3
  225. package/dist/output/text/config.js +2 -3
  226. package/dist/output/text/curate.js +4 -3
  227. package/dist/output/text/distill.js +2 -3
  228. package/dist/output/text/enable-disable.js +5 -4
  229. package/dist/output/text/env.js +13 -0
  230. package/dist/output/text/events.js +5 -4
  231. package/dist/output/text/feedback.js +4 -3
  232. package/dist/output/text/helpers.js +123 -40
  233. package/dist/output/text/history.js +2 -3
  234. package/dist/output/text/import.js +2 -3
  235. package/dist/output/text/index.js +2 -3
  236. package/dist/output/text/info.js +2 -3
  237. package/dist/output/text/init.js +2 -3
  238. package/dist/output/text/list.js +2 -3
  239. package/dist/output/text/proposal/producer.js +9 -0
  240. package/dist/output/text/proposal/proposal.js +13 -0
  241. package/dist/output/text/registry-commands.js +8 -7
  242. package/dist/output/text/registry.js +12 -0
  243. package/dist/output/text/remember.js +4 -3
  244. package/dist/output/text/remove.js +2 -3
  245. package/dist/output/text/save.js +2 -3
  246. package/dist/output/text/search.js +4 -3
  247. package/dist/output/text/show.js +4 -3
  248. package/dist/output/text/update.js +2 -3
  249. package/dist/output/text/upgrade.js +2 -3
  250. package/dist/output/text/wiki.js +12 -11
  251. package/dist/output/text/workflow.js +12 -10
  252. package/dist/output/text.js +66 -32
  253. package/dist/registry/build-index.js +11 -10
  254. package/dist/registry/factory.js +1 -1
  255. package/dist/registry/origin-resolve.js +1 -1
  256. package/dist/registry/providers/index.js +2 -2
  257. package/dist/registry/providers/skills-sh.js +91 -72
  258. package/dist/registry/providers/static-index.js +75 -52
  259. package/dist/registry/resolve.js +3 -3
  260. package/dist/runtime.js +242 -0
  261. package/dist/scripts/migrate-storage.js +1654 -683
  262. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +254 -168
  263. package/dist/setup/detect.js +311 -9
  264. package/dist/setup/harness-config-import.js +6 -120
  265. package/dist/setup/setup.js +454 -43
  266. package/dist/sources/include.js +1 -1
  267. package/dist/sources/provider-factory.js +2 -2
  268. package/dist/sources/providers/filesystem.js +3 -3
  269. package/dist/sources/providers/git.js +9 -9
  270. package/dist/sources/providers/index.js +4 -4
  271. package/dist/sources/providers/npm.js +6 -6
  272. package/dist/sources/providers/provider-utils.js +13 -20
  273. package/dist/sources/providers/sync-from-ref.js +5 -5
  274. package/dist/sources/providers/tar-utils.js +2 -2
  275. package/dist/sources/providers/website.js +2 -2
  276. package/dist/sources/resolve.js +5 -5
  277. package/dist/sources/website-ingest.js +5 -5
  278. package/dist/storage/database.js +102 -0
  279. package/dist/storage/engines/sqlite-migrations.js +42 -0
  280. package/dist/storage/locations.js +25 -0
  281. package/dist/storage/repositories/index-db.js +43 -0
  282. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  283. package/dist/tasks/backends/cron.js +4 -4
  284. package/dist/tasks/backends/exec-utils.js +32 -0
  285. package/dist/tasks/backends/index.js +3 -3
  286. package/dist/tasks/backends/launchd.js +7 -14
  287. package/dist/tasks/backends/schtasks.js +7 -16
  288. package/dist/tasks/embedded.js +71 -0
  289. package/dist/tasks/parser.js +2 -2
  290. package/dist/tasks/resolveAkmBin.js +1 -1
  291. package/dist/tasks/runner.js +127 -31
  292. package/dist/tasks/schedule.js +1 -1
  293. package/dist/tasks/validator.js +7 -7
  294. package/dist/text-import-hook.mjs +51 -0
  295. package/dist/version.js +2 -1
  296. package/dist/wiki/wiki.js +7 -7
  297. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  298. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  299. package/dist/workflows/cli.js +1 -1
  300. package/dist/workflows/db.js +54 -32
  301. package/dist/workflows/parser.js +4 -4
  302. package/dist/workflows/renderer.js +5 -5
  303. package/dist/workflows/runtime/agent-identity.js +56 -0
  304. package/dist/workflows/runtime/checkin.js +57 -0
  305. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  306. package/dist/workflows/validate-summary.js +82 -0
  307. package/docs/README.md +1 -1
  308. package/docs/data-and-telemetry.md +6 -6
  309. package/package.json +17 -8
  310. package/dist/commands/add-cli.js +0 -279
  311. package/dist/commands/env.js +0 -213
  312. package/dist/integrations/agent/sdk-runner.js +0 -126
  313. package/dist/output/shapes/vault-list.js +0 -19
  314. package/dist/output/text/proposal-producer.js +0 -8
  315. package/dist/output/text/proposal.js +0 -12
  316. package/dist/output/text/vault.js +0 -16
  317. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  318. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  319. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  320. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  321. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  322. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  323. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  324. /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
@@ -12,15 +12,16 @@
12
12
  */
13
13
  import fs from "node:fs";
14
14
  import path from "node:path";
15
- import { fetchWithRetry, jsonWithByteCap } from "../core/common";
16
- import { getCacheDir } from "../core/paths";
17
- import { generateMetadataFlat, loadStashFile } from "../indexer/metadata";
18
- import { walkStashFlat } from "../indexer/walker";
19
- import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "../integrations/github";
20
- import { copyIncludedPaths, findNearestIncludeConfig } from "../sources/include";
21
- import { detectStashRoot } from "../sources/providers/provider-utils";
22
- import { extractTarGzSecure } from "../sources/providers/tar-utils";
23
- import { parseRegistryIndex } from "./providers/static-index";
15
+ import { fetchWithRetry, jsonWithByteCap } from "../core/common.js";
16
+ import { getCacheDir } from "../core/paths.js";
17
+ import { generateMetadataFlat, loadStashFile } from "../indexer/passes/metadata.js";
18
+ import { walkStashFlat } from "../indexer/walk/walker.js";
19
+ import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "../integrations/github.js";
20
+ import { writeResponseToFile } from "../runtime.js";
21
+ import { copyIncludedPaths, findNearestIncludeConfig } from "../sources/include.js";
22
+ import { detectStashRoot } from "../sources/providers/provider-utils.js";
23
+ import { extractTarGzSecure } from "../sources/providers/tar-utils.js";
24
+ import { parseRegistryIndex } from "./providers/static-index.js";
24
25
  const DEFAULT_NPM_REGISTRY_BASE = "https://registry.npmjs.org";
25
26
  const REQUIRED_KEYWORDS = ["akm-stash"];
26
27
  const GITHUB_TOPICS = ["akm-stash"];
@@ -194,7 +195,7 @@ async function inspectArchive(url, headers) {
194
195
  if (!response.ok) {
195
196
  throw new Error(`Failed to fetch archive (${response.status}) from ${url}`);
196
197
  }
197
- await Bun.write(archivePath, response);
198
+ await writeResponseToFile(archivePath, response);
198
199
  // Reuse the secure extraction from registry-install which validates entries,
199
200
  // uses --no-same-owner, strips components, and runs a post-extraction scan.
200
201
  extractTarGzSecure(archivePath, extractDir);
@@ -17,7 +17,7 @@
17
17
  * The legacy alias in `src/registry-provider.ts` is kept as a thin re-export
18
18
  * for transitional callers and will be removed after the dust settles.
19
19
  */
20
- import { createProviderRegistry } from "./create-provider-registry";
20
+ import { createProviderRegistry } from "./create-provider-registry.js";
21
21
  // ── Factory map ─────────────────────────────────────────────────────────────
22
22
  const registry = createProviderRegistry();
23
23
  export function registerProvider(type, factory) {
@@ -2,7 +2,7 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import path from "node:path";
5
- import { parseRegistryRef } from "./resolve";
5
+ import { parseRegistryRef } from "./resolve.js";
6
6
  /**
7
7
  * Given an origin string (from an AssetRef) and the full list of stash
8
8
  * sources, return the subset of sources to search.
@@ -10,5 +10,5 @@
10
10
  *
11
11
  * Mirrors the pattern used by `sources/providers/index.ts`.
12
12
  */
13
- import "./static-index";
14
- import "./skills-sh";
13
+ import "./static-index.js";
14
+ import "./skills-sh.js";
@@ -1,13 +1,47 @@
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 { fetchWithRetry } from "../../core/common";
5
- import { rethrowIfTestIsolationError } from "../../core/errors";
6
- import { closeDatabase, getRegistryIndexCache, openDatabase, upsertRegistryIndexCache } from "../../indexer/db";
7
- import { registerProvider } from "../factory";
4
+ import { fetchWithRetry } from "../../core/common.js";
5
+ import { rethrowIfTestIsolationError } from "../../core/errors.js";
6
+ import { closeDatabase, getRegistryIndexCache, openDatabase, upsertRegistryIndexCache } from "../../indexer/db/db.js";
7
+ import { md5Hex } from "../../runtime.js";
8
+ import { registerProvider } from "../factory.js";
8
9
  // ── Constants ───────────────────────────────────────────────────────────────
9
10
  /** Per-query cache TTL in milliseconds (15 minutes). */
10
11
  const QUERY_CACHE_TTL_MS = 15 * 60 * 1000;
12
+ // ── Cache DB lifecycle ────────────────────────────────────────────────────────
13
+ /**
14
+ * RAII-style lifecycle helper for the registry cache DB. Opens the DB (treating
15
+ * a failed open exactly like the legacy fall-through: the bun-test isolation
16
+ * guard is re-thrown, any other failure yields `db = undefined`), runs `fn`,
17
+ * and guarantees the DB is closed in a `finally` after `fn` has fully settled
18
+ * (the await is required: the callbacks are async, and closing before they
19
+ * settle would tear the DB down mid-write).
20
+ */
21
+ async function withRegistryCacheDb(fn) {
22
+ let db;
23
+ try {
24
+ db = openDatabase();
25
+ }
26
+ catch (err) {
27
+ // Never mask the bun-test isolation guard as "DB unavailable".
28
+ rethrowIfTestIsolationError(err);
29
+ db = undefined;
30
+ }
31
+ try {
32
+ return await fn(db);
33
+ }
34
+ finally {
35
+ if (db) {
36
+ try {
37
+ closeDatabase(db);
38
+ }
39
+ catch {
40
+ /* ignore */
41
+ }
42
+ }
43
+ }
44
+ }
11
45
  // ── Provider class ──────────────────────────────────────────────────────────
12
46
  class SkillsShProvider {
13
47
  type = "skills-sh";
@@ -91,82 +125,73 @@ class SkillsShProvider {
91
125
  async fetchSkills(query, limit) {
92
126
  // Build a stable DB cache key for this query
93
127
  const dbCacheKey = this.queryDbCacheKey(query, limit);
94
- // ── Step 1: Try DB cache (index.db) ───────────────────────────────────
95
- let db;
96
- let dbCacheResult;
97
- try {
98
- db = openDatabase();
99
- dbCacheResult = getRegistryIndexCache(db, dbCacheKey, QUERY_CACHE_TTL_MS);
100
- }
101
- catch (err) {
102
- // Never mask the bun-test isolation guard as "DB unavailable" — see
103
- // rethrowIfTestIsolationError in src/core/errors.ts. Without this,
104
- // a leaky test silently gets a cold cache + fresh fetch instead of
105
- // the loud TEST_ISOLATION_MISSING failure the guard intends.
106
- rethrowIfTestIsolationError(err);
107
- // index.db not available yet (pre-migration install or test env) — fall through
108
- }
109
- if (dbCacheResult) {
128
+ return withRegistryCacheDb(async (db) => {
129
+ // ── Step 1: Try DB cache (index.db) ───────────────────────────────────
130
+ let dbCacheResult;
110
131
  try {
111
- const parsed = JSON.parse(dbCacheResult.indexJson);
112
- if (Array.isArray(parsed)) {
113
- const entries = parsed.filter(isValidSkillsEntry);
114
- if (db)
115
- closeDatabase(db);
116
- return entries;
132
+ if (db) {
133
+ dbCacheResult = getRegistryIndexCache(db, dbCacheKey, QUERY_CACHE_TTL_MS);
117
134
  }
118
135
  }
119
- catch {
120
- /* corrupt DB entryfall through */
136
+ catch (err) {
137
+ // Never mask the bun-test isolation guard as "DB unavailable"see
138
+ // rethrowIfTestIsolationError in src/core/errors.ts. Without this,
139
+ // a leaky test silently gets a cold cache + fresh fetch instead of
140
+ // the loud TEST_ISOLATION_MISSING failure the guard intends.
141
+ rethrowIfTestIsolationError(err);
142
+ // index.db not available yet (pre-migration install or test env) — fall through
121
143
  }
122
- }
123
- // ── Step 2: Fetch from API ─────────────────────────────────────────────
124
- const baseUrl = this.config.url.replace(/\/+$/, "");
125
- const url = `${baseUrl}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`;
126
- try {
127
- const response = await fetchWithRetry(url, undefined, { timeout: 10_000, retries: 1 });
128
- if (!response.ok) {
129
- throw new Error(`HTTP ${response.status}`);
130
- }
131
- const data = (await response.json());
132
- const entries = parseSkillsResponse(data);
133
- // Write to DB cache (primary)
134
- if (db) {
144
+ if (dbCacheResult) {
135
145
  try {
136
- upsertRegistryIndexCache(db, dbCacheKey, JSON.stringify(entries));
146
+ const parsed = JSON.parse(dbCacheResult.indexJson);
147
+ if (Array.isArray(parsed)) {
148
+ const entries = parsed.filter(isValidSkillsEntry);
149
+ return entries;
150
+ }
137
151
  }
138
152
  catch {
139
- /* best-effort */
153
+ /* corrupt DB entry — fall through */
140
154
  }
141
- closeDatabase(db);
142
155
  }
143
- return entries;
144
- }
145
- catch (err) {
146
- if (db) {
147
- try {
148
- closeDatabase(db);
156
+ // ── Step 2: Fetch from API ─────────────────────────────────────────────
157
+ const baseUrl = this.config.url.replace(/\/+$/, "");
158
+ const url = `${baseUrl}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`;
159
+ try {
160
+ const response = await fetchWithRetry(url, undefined, { timeout: 10_000, retries: 1 });
161
+ if (!response.ok) {
162
+ throw new Error(`HTTP ${response.status}`);
149
163
  }
150
- catch {
151
- /* ignore */
164
+ const data = (await response.json());
165
+ const entries = parseSkillsResponse(data);
166
+ // Write to DB cache (primary)
167
+ if (db) {
168
+ try {
169
+ upsertRegistryIndexCache(db, dbCacheKey, JSON.stringify(entries));
170
+ }
171
+ catch {
172
+ /* best-effort */
173
+ }
152
174
  }
175
+ return entries;
153
176
  }
154
- // Fetch failed — use stale DB cache if available
155
- if (dbCacheResult) {
156
- try {
157
- const parsed = JSON.parse(dbCacheResult.indexJson);
158
- if (Array.isArray(parsed)) {
159
- const entries = parsed.filter(isValidSkillsEntry);
160
- if (entries.length > 0)
161
- return entries;
177
+ catch (err) {
178
+ // Fetch failed — use stale DB cache if available
179
+ if (dbCacheResult) {
180
+ try {
181
+ const parsed = JSON.parse(dbCacheResult.indexJson);
182
+ if (Array.isArray(parsed)) {
183
+ const entries = parsed.filter(isValidSkillsEntry);
184
+ if (entries.length > 0)
185
+ return entries;
186
+ }
187
+ }
188
+ catch {
189
+ /* ignore */
162
190
  }
163
191
  }
164
- catch {
165
- /* ignore */
166
- }
192
+ throw err;
167
193
  }
168
- throw err;
169
- }
194
+ });
170
195
  }
171
196
  mapToHits(entries) {
172
197
  if (entries.length === 0)
@@ -221,13 +246,7 @@ class SkillsShProvider {
221
246
  }
222
247
  // ── DB cache key ────────────────────────────────────────────────────────
223
248
  queryDbCacheKey(query, limit) {
224
- const hasher = new Bun.CryptoHasher("md5");
225
- hasher.update(this.config.url);
226
- hasher.update("\0");
227
- hasher.update(query.trim().toLowerCase());
228
- hasher.update("\0");
229
- hasher.update(String(limit));
230
- const hash = hasher.digest("hex");
249
+ const hash = md5Hex(`${this.config.url}\0${query.trim().toLowerCase()}\0${String(limit)}`);
231
250
  return `skills-sh:${hash}`;
232
251
  }
233
252
  }
@@ -1,11 +1,11 @@
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 { fetchWithRetry, jsonWithByteCap, toErrorMessage } from "../../core/common";
5
- import { rethrowIfTestIsolationError } from "../../core/errors";
6
- import { closeDatabase, getRegistryIndexCache, openDatabase, upsertRegistryIndexCache } from "../../indexer/db";
7
- import { asString } from "../../integrations/github";
8
- import { registerProvider } from "../factory";
4
+ import { fetchWithRetry, jsonWithByteCap, toErrorMessage } from "../../core/common.js";
5
+ import { rethrowIfTestIsolationError } from "../../core/errors.js";
6
+ import { closeDatabase, getRegistryIndexCache, openDatabase, upsertRegistryIndexCache } from "../../indexer/db/db.js";
7
+ import { asString } from "../../integrations/github.js";
8
+ import { registerProvider } from "../factory.js";
9
9
  // ── Constants ───────────────────────────────────────────────────────────────
10
10
  /** Cache TTL in milliseconds (1 hour). */
11
11
  const CACHE_TTL_MS = 60 * 60 * 1000;
@@ -115,58 +115,28 @@ function assetHitToPreview(hit) {
115
115
  // ── Self-register ───────────────────────────────────────────────────────────
116
116
  registerProvider("static-index", (config) => new StaticIndexProvider(config));
117
117
  // ── Index loading with cache ────────────────────────────────────────────────
118
- async function loadIndex(entry) {
119
- // ── Step 1: Try DB cache (index.db) ─────────────────────────────────────
118
+ /**
119
+ * RAII-style lifecycle helper for the registry cache DB. Opens the DB (treating
120
+ * a failed open exactly like the legacy fall-through: the bun-test isolation
121
+ * guard is re-thrown, any other failure yields `db = undefined`), runs `fn`,
122
+ * and guarantees the DB is closed in a `finally` after `fn` has fully settled
123
+ * (the await is required: the callbacks are async, and closing before they
124
+ * settle would tear the DB down mid-write).
125
+ */
126
+ async function withRegistryCacheDb(fn) {
120
127
  let db;
121
- let dbCacheResult;
122
128
  try {
123
129
  db = openDatabase();
124
- dbCacheResult = getRegistryIndexCache(db, entry.url, CACHE_TTL_MS);
125
130
  }
126
131
  catch (err) {
127
- // Never mask the bun-test isolation guard as "DB unavailable" — see
128
- // rethrowIfTestIsolationError in src/core/errors.ts. Without this, a
129
- // leaky test silently gets a cold cache instead of the loud
130
- // TEST_ISOLATION_MISSING failure the guard intends.
132
+ // Never mask the bun-test isolation guard as "DB unavailable".
131
133
  rethrowIfTestIsolationError(err);
132
- // index.db not available yet (pre-migration install or test env) — fall through
134
+ db = undefined;
133
135
  }
134
- if (dbCacheResult) {
135
- const index = parseRegistryIndex(JSON.parse(dbCacheResult.indexJson));
136
- if (index) {
137
- if (db)
138
- closeDatabase(db);
139
- return index;
140
- }
141
- }
142
- // ── Step 2: Fetch fresh index from remote ────────────────────────────────
143
136
  try {
144
- const response = await fetchWithRetry(entry.url, undefined, { timeout: 10_000 });
145
- if (!response.ok) {
146
- throw new Error(`HTTP ${response.status}`);
147
- }
148
- // Cap at 50 MB — registry indexes can grow large but unbounded
149
- // responses from a compromised server would OOM us.
150
- const data = await jsonWithByteCap(response, 50 * 1024 * 1024);
151
- const index = parseRegistryIndex(data);
152
- if (index) {
153
- // Write to DB cache (primary)
154
- if (db) {
155
- try {
156
- const etag = response.headers.get("etag") ?? undefined;
157
- const lastModified = response.headers.get("last-modified") ?? undefined;
158
- upsertRegistryIndexCache(db, entry.url, JSON.stringify(index), { etag, lastModified });
159
- }
160
- catch {
161
- /* best-effort */
162
- }
163
- closeDatabase(db);
164
- }
165
- return index;
166
- }
167
- throw new Error("Invalid registry index format");
137
+ return await fn(db);
168
138
  }
169
- catch (err) {
139
+ finally {
170
140
  if (db) {
171
141
  try {
172
142
  closeDatabase(db);
@@ -175,14 +145,67 @@ async function loadIndex(entry) {
175
145
  /* ignore */
176
146
  }
177
147
  }
178
- // Fetch failed — use stale DB cache if available
148
+ }
149
+ }
150
+ async function loadIndex(entry) {
151
+ return withRegistryCacheDb(async (db) => {
152
+ // ── Step 1: Try DB cache (index.db) ─────────────────────────────────────
153
+ let dbCacheResult;
154
+ try {
155
+ if (db) {
156
+ dbCacheResult = getRegistryIndexCache(db, entry.url, CACHE_TTL_MS);
157
+ }
158
+ }
159
+ catch (err) {
160
+ // Never mask the bun-test isolation guard as "DB unavailable" — see
161
+ // rethrowIfTestIsolationError in src/core/errors.ts. Without this, a
162
+ // leaky test silently gets a cold cache instead of the loud
163
+ // TEST_ISOLATION_MISSING failure the guard intends.
164
+ rethrowIfTestIsolationError(err);
165
+ // index.db read failed (pre-migration install or test env) — fall through
166
+ }
179
167
  if (dbCacheResult) {
180
168
  const index = parseRegistryIndex(JSON.parse(dbCacheResult.indexJson));
181
- if (index)
169
+ if (index) {
182
170
  return index;
171
+ }
183
172
  }
184
- throw err;
185
- }
173
+ // ── Step 2: Fetch fresh index from remote ────────────────────────────────
174
+ try {
175
+ const response = await fetchWithRetry(entry.url, undefined, { timeout: 10_000 });
176
+ if (!response.ok) {
177
+ throw new Error(`HTTP ${response.status}`);
178
+ }
179
+ // Cap at 50 MB — registry indexes can grow large but unbounded
180
+ // responses from a compromised server would OOM us.
181
+ const data = await jsonWithByteCap(response, 50 * 1024 * 1024);
182
+ const index = parseRegistryIndex(data);
183
+ if (index) {
184
+ // Write to DB cache (primary)
185
+ if (db) {
186
+ try {
187
+ const etag = response.headers.get("etag") ?? undefined;
188
+ const lastModified = response.headers.get("last-modified") ?? undefined;
189
+ upsertRegistryIndexCache(db, entry.url, JSON.stringify(index), { etag, lastModified });
190
+ }
191
+ catch {
192
+ /* best-effort */
193
+ }
194
+ }
195
+ return index;
196
+ }
197
+ throw new Error("Invalid registry index format");
198
+ }
199
+ catch (err) {
200
+ // Fetch failed — use stale DB cache if available
201
+ if (dbCacheResult) {
202
+ const index = parseRegistryIndex(JSON.parse(dbCacheResult.indexJson));
203
+ if (index)
204
+ return index;
205
+ }
206
+ throw err;
207
+ }
208
+ });
186
209
  }
187
210
  export function isCacheExpired(mtimeMs) {
188
211
  return Date.now() - mtimeMs > CACHE_TTL_MS;
@@ -6,9 +6,9 @@ import fs from "node:fs";
6
6
  import os from "node:os";
7
7
  import path from "node:path";
8
8
  import { fileURLToPath, pathToFileURL } from "node:url";
9
- import { fetchWithRetry, jsonWithByteCap } from "../core/common";
10
- import { NotFoundError, UsageError } from "../core/errors";
11
- import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "../integrations/github";
9
+ import { fetchWithRetry, jsonWithByteCap } from "../core/common.js";
10
+ import { NotFoundError, UsageError } from "../core/errors.js";
11
+ import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "../integrations/github.js";
12
12
  /**
13
13
  * Validate that a URL is safe to pass to git.
14
14
  * Allowlists https:, http:, ssh:, git: schemes and git@ SSH shorthand.