akm-cli 0.7.0 → 0.7.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 (332) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{src/cli.js → cli.js} +22 -8
  3. package/dist/{src/commands → commands}/installed-stashes.js +1 -1
  4. package/dist/{src/commands → commands}/source-add.js +1 -1
  5. package/dist/{src/core → core}/common.js +16 -1
  6. package/dist/{src/core → core}/config.js +5 -2
  7. package/dist/{src/indexer → indexer}/db-search.js +16 -1
  8. package/dist/{src/indexer → indexer}/graph-extraction.js +5 -3
  9. package/dist/{src/indexer → indexer}/indexer.js +27 -11
  10. package/dist/{src/indexer → indexer}/memory-inference.js +47 -58
  11. package/dist/{src/indexer → indexer}/search-source.js +1 -1
  12. package/dist/{src/llm → llm}/client.js +61 -1
  13. package/dist/{src/llm → llm}/embedder.js +8 -5
  14. package/dist/{src/llm → llm}/embedders/local.js +8 -2
  15. package/dist/{src/llm → llm}/embedders/remote.js +4 -2
  16. package/dist/{src/llm → llm}/graph-extract.js +4 -4
  17. package/dist/llm/memory-infer.js +114 -0
  18. package/dist/{src/llm → llm}/metadata-enhance.js +2 -2
  19. package/dist/{src/output → output}/cli-hints.js +2 -0
  20. package/dist/{src/setup → setup}/setup.js +30 -20
  21. package/dist/sources/providers/website.js +27 -0
  22. package/dist/{src/sources/providers/website.js → sources/website-ingest.js} +38 -51
  23. package/docs/README.md +7 -0
  24. package/docs/migration/release-notes/0.7.0.md +14 -0
  25. package/package.json +11 -8
  26. package/dist/src/llm/memory-infer.js +0 -86
  27. package/dist/tests/add-website-source.test.js +0 -119
  28. package/dist/tests/agent/agent-config-loader.test.js +0 -70
  29. package/dist/tests/agent/agent-config.test.js +0 -221
  30. package/dist/tests/agent/agent-detect.test.js +0 -100
  31. package/dist/tests/agent/agent-spawn.test.js +0 -234
  32. package/dist/tests/agent-output.test.js +0 -186
  33. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +0 -103
  34. package/dist/tests/architecture/agent-spawn-seam.test.js +0 -193
  35. package/dist/tests/architecture/llm-stateless-seam.test.js +0 -112
  36. package/dist/tests/asset-ref.test.js +0 -192
  37. package/dist/tests/asset-registry.test.js +0 -103
  38. package/dist/tests/asset-spec.test.js +0 -241
  39. package/dist/tests/bench/attribution.test.js +0 -996
  40. package/dist/tests/bench/cleanup-sigint.test.js +0 -83
  41. package/dist/tests/bench/cleanup.js +0 -234
  42. package/dist/tests/bench/cleanup.test.js +0 -166
  43. package/dist/tests/bench/cli.js +0 -1018
  44. package/dist/tests/bench/cli.test.js +0 -445
  45. package/dist/tests/bench/compare.test.js +0 -556
  46. package/dist/tests/bench/corpus.js +0 -317
  47. package/dist/tests/bench/corpus.test.js +0 -258
  48. package/dist/tests/bench/doctor.js +0 -525
  49. package/dist/tests/bench/driver.js +0 -401
  50. package/dist/tests/bench/driver.test.js +0 -584
  51. package/dist/tests/bench/environment.js +0 -233
  52. package/dist/tests/bench/environment.test.js +0 -199
  53. package/dist/tests/bench/evolve-metrics.js +0 -179
  54. package/dist/tests/bench/evolve-metrics.test.js +0 -187
  55. package/dist/tests/bench/evolve.js +0 -647
  56. package/dist/tests/bench/evolve.test.js +0 -624
  57. package/dist/tests/bench/failure-modes.test.js +0 -349
  58. package/dist/tests/bench/feedback-integrity.test.js +0 -457
  59. package/dist/tests/bench/leakage.test.js +0 -228
  60. package/dist/tests/bench/learning-curve.test.js +0 -134
  61. package/dist/tests/bench/metrics.js +0 -2395
  62. package/dist/tests/bench/metrics.test.js +0 -1150
  63. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +0 -43
  64. package/dist/tests/bench/opencode-config.js +0 -194
  65. package/dist/tests/bench/opencode-config.test.js +0 -370
  66. package/dist/tests/bench/report.js +0 -1885
  67. package/dist/tests/bench/report.test.js +0 -1038
  68. package/dist/tests/bench/run-config.js +0 -355
  69. package/dist/tests/bench/run-config.test.js +0 -298
  70. package/dist/tests/bench/run-curate-test.js +0 -32
  71. package/dist/tests/bench/run-failing-tasks.js +0 -56
  72. package/dist/tests/bench/run-full-bench.js +0 -51
  73. package/dist/tests/bench/run-items36-targeted.js +0 -69
  74. package/dist/tests/bench/run-nano-quick.js +0 -42
  75. package/dist/tests/bench/run-waveg-targeted.js +0 -62
  76. package/dist/tests/bench/runner.js +0 -699
  77. package/dist/tests/bench/runner.test.js +0 -958
  78. package/dist/tests/bench/search-bridge.test.js +0 -331
  79. package/dist/tests/bench/tmp.js +0 -131
  80. package/dist/tests/bench/trajectory.js +0 -116
  81. package/dist/tests/bench/trajectory.test.js +0 -127
  82. package/dist/tests/bench/verifier.js +0 -114
  83. package/dist/tests/bench/verifier.test.js +0 -118
  84. package/dist/tests/bench/workflow-evaluator.js +0 -557
  85. package/dist/tests/bench/workflow-evaluator.test.js +0 -421
  86. package/dist/tests/bench/workflow-spec.js +0 -345
  87. package/dist/tests/bench/workflow-spec.test.js +0 -363
  88. package/dist/tests/bench/workflow-trace.js +0 -472
  89. package/dist/tests/bench/workflow-trace.test.js +0 -254
  90. package/dist/tests/benchmark-search-quality.js +0 -536
  91. package/dist/tests/benchmark-suite.js +0 -1441
  92. package/dist/tests/capture-cli.test.js +0 -112
  93. package/dist/tests/cli-errors.test.js +0 -204
  94. package/dist/tests/commands/events.test.js +0 -370
  95. package/dist/tests/commands/history.test.js +0 -418
  96. package/dist/tests/commands/import.test.js +0 -103
  97. package/dist/tests/commands/proposal-cli.test.js +0 -209
  98. package/dist/tests/commands/reflect-propose-cli.test.js +0 -333
  99. package/dist/tests/commands/remember.test.js +0 -97
  100. package/dist/tests/commands/scope-flags.test.js +0 -300
  101. package/dist/tests/commands/search.test.js +0 -537
  102. package/dist/tests/commands/show-indexer-parity.test.js +0 -117
  103. package/dist/tests/commands/show.test.js +0 -294
  104. package/dist/tests/common.test.js +0 -266
  105. package/dist/tests/completions.test.js +0 -142
  106. package/dist/tests/config-cli.test.js +0 -193
  107. package/dist/tests/config-llm-features.test.js +0 -139
  108. package/dist/tests/config.test.js +0 -569
  109. package/dist/tests/contracts/migration-baseline.test.js +0 -43
  110. package/dist/tests/contracts/reflect-propose-envelope.test.js +0 -139
  111. package/dist/tests/contracts/spec-helpers.js +0 -46
  112. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +0 -228
  113. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +0 -56
  114. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +0 -34
  115. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +0 -94
  116. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +0 -39
  117. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +0 -44
  118. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +0 -47
  119. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +0 -40
  120. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +0 -58
  121. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +0 -34
  122. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +0 -75
  123. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +0 -36
  124. package/dist/tests/core/write-source.test.js +0 -366
  125. package/dist/tests/curate-command.test.js +0 -87
  126. package/dist/tests/db-scoring.test.js +0 -201
  127. package/dist/tests/db.test.js +0 -654
  128. package/dist/tests/distill-cli-flag.test.js +0 -208
  129. package/dist/tests/distill.test.js +0 -515
  130. package/dist/tests/docker-install.test.js +0 -120
  131. package/dist/tests/e2e.test.js +0 -1419
  132. package/dist/tests/embedder.test.js +0 -340
  133. package/dist/tests/embedding-model-config.test.js +0 -379
  134. package/dist/tests/feedback-command.test.js +0 -172
  135. package/dist/tests/file-context.test.js +0 -552
  136. package/dist/tests/fixtures/scripts/git/summarize-diff.js +0 -9
  137. package/dist/tests/fixtures/scripts/lint/eslint-check.js +0 -7
  138. package/dist/tests/fixtures/stashes/load.js +0 -166
  139. package/dist/tests/fixtures/stashes/load.test.js +0 -97
  140. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +0 -12
  141. package/dist/tests/frontmatter.test.js +0 -190
  142. package/dist/tests/fts-field-weighting.test.js +0 -254
  143. package/dist/tests/fuzzy-search.test.js +0 -230
  144. package/dist/tests/git-provider-clone.test.js +0 -45
  145. package/dist/tests/github.test.js +0 -161
  146. package/dist/tests/graph-boost-ranking.test.js +0 -305
  147. package/dist/tests/graph-extraction.test.js +0 -282
  148. package/dist/tests/helpers/usage-events.js +0 -8
  149. package/dist/tests/index-pass-llm.test.js +0 -161
  150. package/dist/tests/indexer.test.js +0 -570
  151. package/dist/tests/info-command.test.js +0 -166
  152. package/dist/tests/init.test.js +0 -69
  153. package/dist/tests/install-script.test.js +0 -246
  154. package/dist/tests/integration/agent-real-profile.test.js +0 -94
  155. package/dist/tests/issue-36-repro.test.js +0 -304
  156. package/dist/tests/issues-191-194.test.js +0 -160
  157. package/dist/tests/lesson-lint.test.js +0 -111
  158. package/dist/tests/llm-client.test.js +0 -115
  159. package/dist/tests/llm-feature-gate.test.js +0 -151
  160. package/dist/tests/llm.test.js +0 -139
  161. package/dist/tests/lockfile.test.js +0 -216
  162. package/dist/tests/manifest.test.js +0 -205
  163. package/dist/tests/markdown.test.js +0 -126
  164. package/dist/tests/matchers-unit.test.js +0 -189
  165. package/dist/tests/memory-inference.test.js +0 -299
  166. package/dist/tests/merge-scoring.test.js +0 -136
  167. package/dist/tests/metadata.test.js +0 -313
  168. package/dist/tests/migration-help.test.js +0 -89
  169. package/dist/tests/origin-resolve.test.js +0 -124
  170. package/dist/tests/output-baseline.test.js +0 -218
  171. package/dist/tests/output-shapes-unit.test.js +0 -478
  172. package/dist/tests/parallel-search.test.js +0 -272
  173. package/dist/tests/parameter-metadata.test.js +0 -365
  174. package/dist/tests/paths.test.js +0 -177
  175. package/dist/tests/progressive-disclosure.test.js +0 -280
  176. package/dist/tests/proposals.test.js +0 -279
  177. package/dist/tests/proposed-quality.test.js +0 -271
  178. package/dist/tests/provider-registry.test.js +0 -32
  179. package/dist/tests/ranking-regression.test.js +0 -548
  180. package/dist/tests/reflect-propose.test.js +0 -455
  181. package/dist/tests/registry-build-index.test.js +0 -394
  182. package/dist/tests/registry-cli.test.js +0 -290
  183. package/dist/tests/registry-index-v2.test.js +0 -430
  184. package/dist/tests/registry-install.test.js +0 -728
  185. package/dist/tests/registry-providers/parity.test.js +0 -189
  186. package/dist/tests/registry-providers/skills-sh.test.js +0 -309
  187. package/dist/tests/registry-providers/static-index.test.js +0 -238
  188. package/dist/tests/registry-resolve.test.js +0 -126
  189. package/dist/tests/registry-search.test.js +0 -923
  190. package/dist/tests/remember-frontmatter.test.js +0 -378
  191. package/dist/tests/remember-unit.test.js +0 -123
  192. package/dist/tests/ripgrep-install.test.js +0 -251
  193. package/dist/tests/ripgrep-resolve.test.js +0 -108
  194. package/dist/tests/ripgrep.test.js +0 -163
  195. package/dist/tests/save-command.test.js +0 -94
  196. package/dist/tests/save-trust-qa-fixes.test.js +0 -270
  197. package/dist/tests/scoring-pipeline.test.js +0 -648
  198. package/dist/tests/search-include-proposed-cli.test.js +0 -118
  199. package/dist/tests/self-update.test.js +0 -442
  200. package/dist/tests/semantic-search-e2e.test.js +0 -512
  201. package/dist/tests/semantic-status.test.js +0 -471
  202. package/dist/tests/setup-run.integration.js +0 -877
  203. package/dist/tests/setup-wizard.test.js +0 -198
  204. package/dist/tests/setup.test.js +0 -131
  205. package/dist/tests/source-add.test.js +0 -11
  206. package/dist/tests/source-clone.test.js +0 -254
  207. package/dist/tests/source-manage.test.js +0 -366
  208. package/dist/tests/source-providers/filesystem.test.js +0 -82
  209. package/dist/tests/source-providers/git.test.js +0 -252
  210. package/dist/tests/source-providers/website.test.js +0 -128
  211. package/dist/tests/source-qa-fixes.test.js +0 -286
  212. package/dist/tests/source-registry.test.js +0 -350
  213. package/dist/tests/source-resolve.test.js +0 -100
  214. package/dist/tests/source-source.test.js +0 -281
  215. package/dist/tests/source.test.js +0 -533
  216. package/dist/tests/tar-utils-scan.test.js +0 -73
  217. package/dist/tests/toggle-components.test.js +0 -73
  218. package/dist/tests/usage-telemetry.test.js +0 -265
  219. package/dist/tests/utility-scoring.test.js +0 -558
  220. package/dist/tests/vault-load-error.test.js +0 -78
  221. package/dist/tests/vault-qa-fixes.test.js +0 -194
  222. package/dist/tests/vault.test.js +0 -429
  223. package/dist/tests/vector-search.test.js +0 -608
  224. package/dist/tests/walker.test.js +0 -252
  225. package/dist/tests/wave2-cluster-bc.test.js +0 -228
  226. package/dist/tests/wave2-cluster-d.test.js +0 -180
  227. package/dist/tests/wave2-cluster-e.test.js +0 -179
  228. package/dist/tests/wiki-qa-fixes.test.js +0 -270
  229. package/dist/tests/wiki.test.js +0 -529
  230. package/dist/tests/workflow-cli.test.js +0 -271
  231. package/dist/tests/workflow-markdown.test.js +0 -171
  232. package/dist/tests/workflow-path-escape.test.js +0 -132
  233. package/dist/tests/workflow-qa-fixes.test.js +0 -395
  234. package/dist/tests/workflows/indexer-rejection.test.js +0 -213
  235. /package/dist/{src/commands → commands}/completions.js +0 -0
  236. /package/dist/{src/commands → commands}/config-cli.js +0 -0
  237. /package/dist/{src/commands → commands}/curate.js +0 -0
  238. /package/dist/{src/commands → commands}/distill.js +0 -0
  239. /package/dist/{src/commands → commands}/events.js +0 -0
  240. /package/dist/{src/commands → commands}/history.js +0 -0
  241. /package/dist/{src/commands → commands}/info.js +0 -0
  242. /package/dist/{src/commands → commands}/init.js +0 -0
  243. /package/dist/{src/commands → commands}/install-audit.js +0 -0
  244. /package/dist/{src/commands → commands}/migration-help.js +0 -0
  245. /package/dist/{src/commands → commands}/proposal.js +0 -0
  246. /package/dist/{src/commands → commands}/propose.js +0 -0
  247. /package/dist/{src/commands → commands}/reflect.js +0 -0
  248. /package/dist/{src/commands → commands}/registry-search.js +0 -0
  249. /package/dist/{src/commands → commands}/remember.js +0 -0
  250. /package/dist/{src/commands → commands}/search.js +0 -0
  251. /package/dist/{src/commands → commands}/self-update.js +0 -0
  252. /package/dist/{src/commands → commands}/show.js +0 -0
  253. /package/dist/{src/commands → commands}/source-clone.js +0 -0
  254. /package/dist/{src/commands → commands}/source-manage.js +0 -0
  255. /package/dist/{src/commands → commands}/vault.js +0 -0
  256. /package/dist/{src/core → core}/asset-ref.js +0 -0
  257. /package/dist/{src/core → core}/asset-registry.js +0 -0
  258. /package/dist/{src/core → core}/asset-spec.js +0 -0
  259. /package/dist/{src/core → core}/errors.js +0 -0
  260. /package/dist/{src/core → core}/events.js +0 -0
  261. /package/dist/{src/core → core}/frontmatter.js +0 -0
  262. /package/dist/{src/core → core}/lesson-lint.js +0 -0
  263. /package/dist/{src/core → core}/markdown.js +0 -0
  264. /package/dist/{src/core → core}/paths.js +0 -0
  265. /package/dist/{src/core → core}/proposals.js +0 -0
  266. /package/dist/{src/core → core}/warn.js +0 -0
  267. /package/dist/{src/core → core}/write-source.js +0 -0
  268. /package/dist/{src/indexer → indexer}/db.js +0 -0
  269. /package/dist/{src/indexer → indexer}/file-context.js +0 -0
  270. /package/dist/{src/indexer → indexer}/graph-boost.js +0 -0
  271. /package/dist/{src/indexer → indexer}/manifest.js +0 -0
  272. /package/dist/{src/indexer → indexer}/matchers.js +0 -0
  273. /package/dist/{src/indexer → indexer}/metadata.js +0 -0
  274. /package/dist/{src/indexer → indexer}/search-fields.js +0 -0
  275. /package/dist/{src/indexer → indexer}/semantic-status.js +0 -0
  276. /package/dist/{src/indexer → indexer}/usage-events.js +0 -0
  277. /package/dist/{src/indexer → indexer}/walker.js +0 -0
  278. /package/dist/{src/integrations → integrations}/agent/config.js +0 -0
  279. /package/dist/{src/integrations → integrations}/agent/detect.js +0 -0
  280. /package/dist/{src/integrations → integrations}/agent/index.js +0 -0
  281. /package/dist/{src/integrations → integrations}/agent/profiles.js +0 -0
  282. /package/dist/{src/integrations → integrations}/agent/prompts.js +0 -0
  283. /package/dist/{src/integrations → integrations}/agent/spawn.js +0 -0
  284. /package/dist/{src/integrations → integrations}/github.js +0 -0
  285. /package/dist/{src/integrations → integrations}/lockfile.js +0 -0
  286. /package/dist/{src/llm → llm}/embedders/cache.js +0 -0
  287. /package/dist/{src/llm → llm}/embedders/types.js +0 -0
  288. /package/dist/{src/llm → llm}/feature-gate.js +0 -0
  289. /package/dist/{src/llm → llm}/index-passes.js +0 -0
  290. /package/dist/{src/output → output}/context.js +0 -0
  291. /package/dist/{src/output → output}/renderers.js +0 -0
  292. /package/dist/{src/output → output}/shapes.js +0 -0
  293. /package/dist/{src/output → output}/text.js +0 -0
  294. /package/dist/{src/registry → registry}/build-index.js +0 -0
  295. /package/dist/{src/registry → registry}/create-provider-registry.js +0 -0
  296. /package/dist/{src/registry → registry}/factory.js +0 -0
  297. /package/dist/{src/registry → registry}/origin-resolve.js +0 -0
  298. /package/dist/{src/registry → registry}/providers/index.js +0 -0
  299. /package/dist/{src/registry → registry}/providers/skills-sh.js +0 -0
  300. /package/dist/{src/registry → registry}/providers/static-index.js +0 -0
  301. /package/dist/{src/registry → registry}/providers/types.js +0 -0
  302. /package/dist/{src/registry → registry}/resolve.js +0 -0
  303. /package/dist/{src/registry → registry}/types.js +0 -0
  304. /package/dist/{src/setup → setup}/detect.js +0 -0
  305. /package/dist/{src/setup → setup}/ripgrep-install.js +0 -0
  306. /package/dist/{src/setup → setup}/ripgrep-resolve.js +0 -0
  307. /package/dist/{src/setup → setup}/steps.js +0 -0
  308. /package/dist/{src/sources → sources}/include.js +0 -0
  309. /package/dist/{src/sources → sources}/provider-factory.js +0 -0
  310. /package/dist/{src/sources → sources}/provider.js +0 -0
  311. /package/dist/{src/sources → sources}/providers/filesystem.js +0 -0
  312. /package/dist/{src/sources → sources}/providers/git.js +0 -0
  313. /package/dist/{src/sources → sources}/providers/index.js +0 -0
  314. /package/dist/{src/sources → sources}/providers/install-types.js +0 -0
  315. /package/dist/{src/sources → sources}/providers/npm.js +0 -0
  316. /package/dist/{src/sources → sources}/providers/provider-utils.js +0 -0
  317. /package/dist/{src/sources → sources}/providers/sync-from-ref.js +0 -0
  318. /package/dist/{src/sources → sources}/providers/tar-utils.js +0 -0
  319. /package/dist/{src/sources → sources}/resolve.js +0 -0
  320. /package/dist/{src/sources → sources}/types.js +0 -0
  321. /package/dist/{src/templates → templates}/wiki-templates.js +0 -0
  322. /package/dist/{src/version.js → version.js} +0 -0
  323. /package/dist/{src/wiki → wiki}/wiki.js +0 -0
  324. /package/dist/{src/workflows → workflows}/authoring.js +0 -0
  325. /package/dist/{src/workflows → workflows}/cli.js +0 -0
  326. /package/dist/{src/workflows → workflows}/db.js +0 -0
  327. /package/dist/{src/workflows → workflows}/document-cache.js +0 -0
  328. /package/dist/{src/workflows → workflows}/parser.js +0 -0
  329. /package/dist/{src/workflows → workflows}/renderer.js +0 -0
  330. /package/dist/{src/workflows → workflows}/runs.js +0 -0
  331. /package/dist/{src/workflows → workflows}/schema.js +0 -0
  332. /package/dist/{src/workflows → workflows}/validator.js +0 -0
@@ -1,294 +0,0 @@
1
- import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { akmShowUnified as akmShow } from "../../src/commands/show";
6
- import { saveConfig } from "../../src/core/config";
7
- // Trigger source-provider self-registration
8
- import "../../src/sources/providers/index";
9
- const createdTmpDirs = [];
10
- function createTmpDir(prefix = "akm-show-") {
11
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
12
- createdTmpDirs.push(dir);
13
- return dir;
14
- }
15
- function writeFile(filePath, content = "") {
16
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
17
- fs.writeFileSync(filePath, content);
18
- }
19
- afterAll(() => {
20
- for (const dir of createdTmpDirs) {
21
- fs.rmSync(dir, { recursive: true, force: true });
22
- }
23
- });
24
- const originalXdgCacheHome = process.env.XDG_CACHE_HOME;
25
- const originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
26
- const originalStashDir = process.env.AKM_STASH_DIR;
27
- let testCacheDir = "";
28
- let testConfigDir = "";
29
- let stashDir = "";
30
- beforeEach(() => {
31
- testCacheDir = createTmpDir("akm-show-cache-");
32
- testConfigDir = createTmpDir("akm-show-config-");
33
- stashDir = createTmpDir("akm-show-stash-");
34
- for (const sub of ["scripts", "skills", "commands", "agents", "knowledge"]) {
35
- fs.mkdirSync(path.join(stashDir, sub), { recursive: true });
36
- }
37
- process.env.XDG_CACHE_HOME = testCacheDir;
38
- process.env.XDG_CONFIG_HOME = testConfigDir;
39
- process.env.AKM_STASH_DIR = stashDir;
40
- });
41
- afterEach(() => {
42
- if (originalXdgCacheHome === undefined) {
43
- delete process.env.XDG_CACHE_HOME;
44
- }
45
- else {
46
- process.env.XDG_CACHE_HOME = originalXdgCacheHome;
47
- }
48
- if (originalXdgConfigHome === undefined) {
49
- delete process.env.XDG_CONFIG_HOME;
50
- }
51
- else {
52
- process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
53
- }
54
- if (originalStashDir === undefined) {
55
- delete process.env.AKM_STASH_DIR;
56
- }
57
- else {
58
- process.env.AKM_STASH_DIR = originalStashDir;
59
- }
60
- if (testCacheDir) {
61
- fs.rmSync(testCacheDir, { recursive: true, force: true });
62
- testCacheDir = "";
63
- }
64
- if (testConfigDir) {
65
- fs.rmSync(testConfigDir, { recursive: true, force: true });
66
- testConfigDir = "";
67
- }
68
- });
69
- // ── Installed ref with missing asset ─────────────────────────────────────────
70
- describe("akmShow installed ref", () => {
71
- test("throws with add guidance when origin is not installed", async () => {
72
- const installedStashRoot = createTmpDir("akm-show-installed-root-");
73
- // Create the type subdirectory so it is a valid stash root, but do NOT
74
- // create the actual asset file.
75
- fs.mkdirSync(path.join(installedStashRoot, "scripts"), { recursive: true });
76
- saveConfig({
77
- semanticSearchMode: "off",
78
- installed: [
79
- {
80
- id: "test-pkg",
81
- source: "npm",
82
- ref: "test-pkg",
83
- artifactUrl: "https://example.com/test-pkg.tgz",
84
- stashRoot: installedStashRoot,
85
- cacheDir: installedStashRoot,
86
- installedAt: new Date().toISOString(),
87
- },
88
- ],
89
- });
90
- // Use an origin that is NOT installed so resolveSourcesForOrigin returns
91
- // empty, triggering the add-guidance error path.
92
- await expect(akmShow({ ref: "npm:@other/missing-pkg//script:missing.sh" })).rejects.toThrow(/akm add/);
93
- });
94
- test("resolves installed-stash style nested agent refs", async () => {
95
- const installedStashRoot = createTmpDir("akm-show-installed-agent-");
96
- writeFile(path.join(installedStashRoot, "tools", "agents", "svelte-file-editor.md"), ["---", "name: svelte-file-editor", "description: Svelte editor", "---", "Use Svelte tools."].join("\n"));
97
- saveConfig({
98
- semanticSearchMode: "off",
99
- installed: [
100
- {
101
- id: "github:sveltejs/ai-tools",
102
- source: "github",
103
- ref: "github:sveltejs/ai-tools",
104
- artifactUrl: "https://example.com/svelte-tools.tgz",
105
- stashRoot: installedStashRoot,
106
- cacheDir: installedStashRoot,
107
- installedAt: new Date().toISOString(),
108
- },
109
- ],
110
- });
111
- const result = await akmShow({ ref: "github:sveltejs/ai-tools//agent:tools/agents/svelte-file-editor" });
112
- expect(result.type).toBe("agent");
113
- expect(result.origin).toBe("github:sveltejs/ai-tools");
114
- expect(result.path).toContain(path.join("tools", "agents", "svelte-file-editor.md"));
115
- expect(result.prompt).toContain("Use Svelte tools.");
116
- });
117
- test("resolves installed-stash style nested skill refs", async () => {
118
- const installedStashRoot = createTmpDir("akm-show-installed-skill-");
119
- writeFile(path.join(installedStashRoot, "tools", "skills", "svelte-code-writer", "SKILL.md"), ["---", "name: svelte-code-writer", "description: Svelte writer", "---", "# Svelte writer"].join("\n"));
120
- saveConfig({
121
- semanticSearchMode: "off",
122
- installed: [
123
- {
124
- id: "github:sveltejs/ai-tools",
125
- source: "github",
126
- ref: "github:sveltejs/ai-tools",
127
- artifactUrl: "https://example.com/svelte-tools.tgz",
128
- stashRoot: installedStashRoot,
129
- cacheDir: installedStashRoot,
130
- installedAt: new Date().toISOString(),
131
- },
132
- ],
133
- });
134
- const result = await akmShow({ ref: "github:sveltejs/ai-tools//skill:tools/skills/svelte-code-writer" });
135
- expect(result.type).toBe("skill");
136
- expect(result.origin).toBe("github:sveltejs/ai-tools");
137
- expect(result.path).toContain(path.join("tools", "skills", "svelte-code-writer", "SKILL.md"));
138
- expect(result.content).toContain("# Svelte writer");
139
- });
140
- });
141
- // ── Search path resolution ───────────────────────────────────────────────────
142
- describe("akmShow search path", () => {
143
- test("resolves from search path directories", async () => {
144
- const searchPathDir = createTmpDir("akm-show-searchpath-");
145
- writeFile(path.join(searchPathDir, "scripts", "deploy.sh"), "#!/usr/bin/env bash\necho deploy\n");
146
- saveConfig({ semanticSearchMode: "off", sources: [{ type: "filesystem", path: searchPathDir }] });
147
- const result = await akmShow({ ref: "script:deploy.sh" });
148
- expect(result.type).toBe("script");
149
- expect(result.name).toBe("deploy.sh");
150
- expect(result.path).toContain(searchPathDir);
151
- });
152
- });
153
- // ── editability flags ────────────────────────────────────────────────────────
154
- describe("akmShow editability", () => {
155
- test("working stash asset has editable true", async () => {
156
- writeFile(path.join(stashDir, "scripts", "local.sh"), "#!/usr/bin/env bash\necho local\n");
157
- saveConfig({ semanticSearchMode: "off" });
158
- const result = await akmShow({ ref: "script:local.sh" });
159
- expect(result.type).toBe("script");
160
- expect(result.origin).toBeNull();
161
- expect(result.action).toContain("Execute the run command");
162
- expect(result.editable).toBe(true);
163
- expect(result.editHint).toBeUndefined();
164
- });
165
- test("search path asset has editable true", async () => {
166
- const searchPathDir = createTmpDir("akm-show-searchpath-editable-");
167
- writeFile(path.join(searchPathDir, "scripts", "remote.sh"), "#!/usr/bin/env bash\necho remote\n");
168
- saveConfig({ semanticSearchMode: "off", sources: [{ type: "filesystem", path: searchPathDir }] });
169
- const result = await akmShow({ ref: "script:remote.sh" });
170
- expect(result.type).toBe("script");
171
- expect(result.origin).toBeNull();
172
- expect(result.editable).toBe(true);
173
- expect(result.editHint).toBeUndefined();
174
- });
175
- test("installed (cache-managed) asset has editable false with editHint", async () => {
176
- const installedStashRoot = createTmpDir("akm-show-installed-resolve-");
177
- writeFile(path.join(installedStashRoot, "scripts", "deploy.sh"), "#!/usr/bin/env bash\necho deploy\n");
178
- saveConfig({
179
- semanticSearchMode: "off",
180
- installed: [
181
- {
182
- id: "installed-pkg",
183
- source: "npm",
184
- ref: "npm:installed-pkg",
185
- artifactUrl: "https://example.com/installed-pkg.tgz",
186
- stashRoot: installedStashRoot,
187
- cacheDir: installedStashRoot,
188
- installedAt: new Date().toISOString(),
189
- },
190
- ],
191
- });
192
- const result = await akmShow({ ref: "script:deploy.sh" });
193
- expect(result.type).toBe("script");
194
- expect(result.origin).toBe("installed-pkg");
195
- expect(result.editable).toBe(false);
196
- expect(result.editHint).toContain("akm clone");
197
- expect(result.editHint).toContain("script:deploy.sh");
198
- });
199
- });
200
- // ── Content-based classification via new renderer pipeline ─────────────────
201
- describe("akmShow content-based classification", () => {
202
- test("model alone in commands/ stays a command (directory wins over weak agent signal)", async () => {
203
- // model is shared frontmatter (OpenCode convention). In commands/,
204
- // the directory matcher (specificity 10) beats the model-only agent
205
- // signal (specificity 8), so this stays a command.
206
- writeFile(path.join(stashDir, "commands", "deploy.md"), ["---", "model: gpt-4", "description: Deploy command", "---", "Deploy $ARGUMENTS."].join("\n"));
207
- saveConfig({ semanticSearchMode: "off" });
208
- const result = await akmShow({ ref: "command:deploy.md" });
209
- expect(result.type).toBe("command");
210
- expect(result.template).toBe("Deploy $ARGUMENTS.");
211
- expect(result.modelHint).toBe("gpt-4");
212
- expect(result.parameters).toEqual(["ARGUMENTS"]);
213
- });
214
- test("tools frontmatter in commands/ overrides to agent (strong signal)", async () => {
215
- // tools/toolPolicy are agent-exclusive signals at specificity 20,
216
- // which beats the commands/ directory matcher at 10.
217
- writeFile(path.join(stashDir, "commands", "hybrid.md"), ["---", "tools:", " read: allow", "model: gpt-4", "---", "You are a hybrid agent."].join("\n"));
218
- saveConfig({ semanticSearchMode: "off" });
219
- const result = await akmShow({ ref: "command:hybrid.md" });
220
- expect(result.type).toBe("agent");
221
- expect(result.action).toContain("verbatim");
222
- expect(result.prompt).toContain("You are a hybrid agent.");
223
- });
224
- test("command in commands/ directory extracts OpenCode-style frontmatter", async () => {
225
- writeFile(path.join(stashDir, "commands", "deploy.md"), [
226
- "---",
227
- "description: Deploy to production",
228
- "model: claude-sonnet-4-20250514",
229
- "agent: build",
230
- "---",
231
- "Deploy $ARGUMENTS to production.",
232
- ].join("\n"));
233
- saveConfig({ semanticSearchMode: "off" });
234
- const result = await akmShow({ ref: "command:deploy.md" });
235
- expect(result.type).toBe("command");
236
- expect(result.template).toBe("Deploy $ARGUMENTS to production.");
237
- expect(result.description).toBe("Deploy to production");
238
- expect(result.modelHint).toBe("claude-sonnet-4-20250514");
239
- expect(result.agent).toBe("build");
240
- expect(result.parameters).toEqual(["ARGUMENTS"]);
241
- });
242
- test("command parameter extraction includes positional placeholders", async () => {
243
- writeFile(path.join(stashDir, "commands", "positional.md"), ["---", "description: Positional args", "---", "Run release $1 with notes from $2 and flag $9."].join("\n"));
244
- saveConfig({ semanticSearchMode: "off" });
245
- const result = await akmShow({ ref: "command:positional.md" });
246
- expect(result.type).toBe("command");
247
- expect(result.parameters).toEqual(["$1", "$2", "$9"]);
248
- });
249
- test("command parameter extraction includes named placeholders", async () => {
250
- writeFile(path.join(stashDir, "commands", "named.md"), ["---", "description: Named args", "---", "Deploy {{env}} with {{version}} using {{env}} again."].join("\n"));
251
- saveConfig({ semanticSearchMode: "off" });
252
- const result = await akmShow({ ref: "command:named.md" });
253
- expect(result.type).toBe("command");
254
- expect(result.parameters).toEqual(["env", "version"]);
255
- });
256
- test("script in scripts/ directory uses new renderer pipeline", async () => {
257
- writeFile(path.join(stashDir, "scripts", "build.sh"), "#!/usr/bin/env bash\necho build\n");
258
- saveConfig({ semanticSearchMode: "off" });
259
- const result = await akmShow({ ref: "script:build.sh" });
260
- expect(result.type).toBe("script");
261
- expect(result.run).toBeDefined();
262
- expect(result.run).toContain("bash");
263
- });
264
- test("$ARGUMENTS in body classifies .md as command even outside commands/", async () => {
265
- writeFile(path.join(stashDir, "knowledge", "deploy-cmd.md"), ["---", "description: Deploy helper", "---", "Deploy $ARGUMENTS to staging."].join("\n"));
266
- saveConfig({ semanticSearchMode: "off" });
267
- // $ARGUMENTS placeholder (specificity 18) beats knowledge/ directory hint (10)
268
- const result = await akmShow({ ref: "knowledge:deploy-cmd.md" });
269
- expect(result.type).toBe("command");
270
- expect(result.template).toBe("Deploy $ARGUMENTS to staging.");
271
- expect(result.description).toBe("Deploy helper");
272
- });
273
- test("agent frontmatter classifies .md as command even outside commands/", async () => {
274
- writeFile(path.join(stashDir, "agents", "build-cmd.md"), ["---", "agent: build", "description: Build dispatch", "---", "Build the project."].join("\n"));
275
- saveConfig({ semanticSearchMode: "off" });
276
- // agent frontmatter (specificity 18) beats agents/ directory hint (15)
277
- const result = await akmShow({ ref: "agent:build-cmd.md" });
278
- expect(result.type).toBe("command");
279
- expect(result.template).toBe("Build the project.");
280
- expect(result.agent).toBe("build");
281
- });
282
- test("knowledge view modes work through new renderer pipeline", async () => {
283
- writeFile(path.join(stashDir, "knowledge", "guide.md"), ["# Intro", "Welcome.", "", "## Setup", "Install things.", "", "## Usage", "Use things."].join("\n"));
284
- saveConfig({ semanticSearchMode: "off" });
285
- const tocResult = await akmShow({ ref: "knowledge:guide.md", view: { mode: "toc" } });
286
- expect(tocResult.content).toContain("Intro");
287
- expect(tocResult.content).toContain("Setup");
288
- const sectionResult = await akmShow({
289
- ref: "knowledge:guide.md",
290
- view: { mode: "section", heading: "Setup" },
291
- });
292
- expect(sectionResult.content).toContain("Install things.");
293
- });
294
- });
@@ -1,266 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { hasErrnoCode, isAssetType, isWithin, jsonWithByteCap, ResponseTooLargeError, readBodyWithByteCap, resolveStashDir, toPosix, } from "../src/core/common";
6
- // ── resolveStashDir ──────────────────────────────────────────────────────────
7
- describe("resolveStashDir", () => {
8
- const origEnv = process.env.AKM_STASH_DIR;
9
- const origXdgConfigHome = process.env.XDG_CONFIG_HOME;
10
- const origHome = process.env.HOME;
11
- let testConfigHome;
12
- beforeEach(() => {
13
- testConfigHome = fs.mkdtempSync(path.join(os.tmpdir(), "akm-common-test-config-"));
14
- process.env.XDG_CONFIG_HOME = testConfigHome;
15
- });
16
- afterEach(() => {
17
- if (origEnv === undefined) {
18
- delete process.env.AKM_STASH_DIR;
19
- }
20
- else {
21
- process.env.AKM_STASH_DIR = origEnv;
22
- }
23
- if (origXdgConfigHome === undefined) {
24
- delete process.env.XDG_CONFIG_HOME;
25
- }
26
- else {
27
- process.env.XDG_CONFIG_HOME = origXdgConfigHome;
28
- }
29
- if (origHome === undefined) {
30
- delete process.env.HOME;
31
- }
32
- else {
33
- process.env.HOME = origHome;
34
- }
35
- if (testConfigHome) {
36
- fs.rmSync(testConfigHome, { recursive: true, force: true });
37
- }
38
- });
39
- test("throws when no stash dir is configured and default does not exist", () => {
40
- delete process.env.AKM_STASH_DIR;
41
- // Point HOME to a tmp dir without an akm subdirectory
42
- const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "akm-common-test-home-"));
43
- process.env.HOME = tmpHome;
44
- try {
45
- expect(() => resolveStashDir()).toThrow("No stash directory found");
46
- }
47
- finally {
48
- fs.rmSync(tmpHome, { recursive: true, force: true });
49
- }
50
- });
51
- test("throws when AKM_STASH_DIR points to nonexistent path", () => {
52
- process.env.AKM_STASH_DIR = "/nonexistent/path/that/does/not/exist";
53
- expect(() => resolveStashDir()).toThrow("Unable to read");
54
- });
55
- test("throws when AKM_STASH_DIR path is a file, not a directory", () => {
56
- const tmpFile = path.join(os.tmpdir(), `akm-common-test-file-${Date.now()}`);
57
- fs.writeFileSync(tmpFile, "not a directory");
58
- try {
59
- process.env.AKM_STASH_DIR = tmpFile;
60
- expect(() => resolveStashDir()).toThrow("must point to a directory");
61
- }
62
- finally {
63
- fs.unlinkSync(tmpFile);
64
- }
65
- });
66
- test("returns resolved path for valid AKM_STASH_DIR", () => {
67
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-common-test-"));
68
- try {
69
- process.env.AKM_STASH_DIR = tmpDir;
70
- const result = resolveStashDir();
71
- expect(result).toBe(path.resolve(tmpDir));
72
- }
73
- finally {
74
- fs.rmSync(tmpDir, { recursive: true, force: true });
75
- }
76
- });
77
- test("reads stashDir from config.json when env var is not set", () => {
78
- delete process.env.AKM_STASH_DIR;
79
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-common-test-stash-"));
80
- try {
81
- const configDir = path.join(testConfigHome, "akm");
82
- fs.mkdirSync(configDir, { recursive: true });
83
- fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify({ stashDir: tmpDir }));
84
- const result = resolveStashDir();
85
- expect(result).toBe(path.resolve(tmpDir));
86
- }
87
- finally {
88
- fs.rmSync(tmpDir, { recursive: true, force: true });
89
- }
90
- });
91
- test("uses default stash dir when it exists", () => {
92
- delete process.env.AKM_STASH_DIR;
93
- const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "akm-common-test-home-"));
94
- const defaultStash = path.join(tmpHome, "akm");
95
- fs.mkdirSync(defaultStash, { recursive: true });
96
- process.env.HOME = tmpHome;
97
- try {
98
- const result = resolveStashDir();
99
- expect(result).toBe(defaultStash);
100
- }
101
- finally {
102
- fs.rmSync(tmpHome, { recursive: true, force: true });
103
- }
104
- });
105
- test("env var takes precedence over config.json stashDir", () => {
106
- const envDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-common-test-env-"));
107
- const configDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-common-test-cfg-"));
108
- try {
109
- process.env.AKM_STASH_DIR = envDir;
110
- const configRoot = path.join(testConfigHome, "akm");
111
- fs.mkdirSync(configRoot, { recursive: true });
112
- fs.writeFileSync(path.join(configRoot, "config.json"), JSON.stringify({ stashDir: configDir }));
113
- const result = resolveStashDir();
114
- expect(result).toBe(path.resolve(envDir));
115
- }
116
- finally {
117
- fs.rmSync(envDir, { recursive: true, force: true });
118
- fs.rmSync(configDir, { recursive: true, force: true });
119
- }
120
- });
121
- });
122
- // ── toPosix ──────────────────────────────────────────────────────────────────
123
- describe("toPosix", () => {
124
- test("already-posix paths are unchanged", () => {
125
- expect(toPosix("foo/bar/baz")).toBe("foo/bar/baz");
126
- });
127
- test("backslash paths are converted to forward slashes", () => {
128
- expect(toPosix("foo\\bar\\baz")).toBe("foo/bar/baz");
129
- });
130
- test("mixed separators are normalized", () => {
131
- expect(toPosix("foo\\bar/baz")).toBe("foo/bar/baz");
132
- });
133
- test("empty string returns empty string", () => {
134
- expect(toPosix("")).toBe("");
135
- });
136
- });
137
- // ── hasErrnoCode ─────────────────────────────────────────────────────────────
138
- describe("hasErrnoCode", () => {
139
- test("returns true for error with matching code", () => {
140
- const err = Object.assign(new Error("fail"), { code: "ENOENT" });
141
- expect(hasErrnoCode(err, "ENOENT")).toBe(true);
142
- });
143
- test("returns false for error with non-matching code", () => {
144
- const err = Object.assign(new Error("fail"), { code: "EACCES" });
145
- expect(hasErrnoCode(err, "ENOENT")).toBe(false);
146
- });
147
- test("returns false for string error", () => {
148
- expect(hasErrnoCode("some string error", "ENOENT")).toBe(false);
149
- });
150
- test("returns false for null", () => {
151
- expect(hasErrnoCode(null, "ENOENT")).toBe(false);
152
- });
153
- test("returns false for object without code property", () => {
154
- expect(hasErrnoCode({ message: "fail" }, "ENOENT")).toBe(false);
155
- });
156
- test("returns false for undefined", () => {
157
- expect(hasErrnoCode(undefined, "ENOENT")).toBe(false);
158
- });
159
- });
160
- // ── isAssetType ──────────────────────────────────────────────────────────────
161
- describe("isAssetType", () => {
162
- test("returns true for all valid types", () => {
163
- expect(isAssetType("skill")).toBe(true);
164
- expect(isAssetType("command")).toBe(true);
165
- expect(isAssetType("agent")).toBe(true);
166
- expect(isAssetType("knowledge")).toBe(true);
167
- expect(isAssetType("script")).toBe(true);
168
- });
169
- test("returns false for invalid strings", () => {
170
- expect(isAssetType("widget")).toBe(false);
171
- expect(isAssetType("")).toBe(false);
172
- expect(isAssetType("tool")).toBe(false);
173
- expect(isAssetType("plugin")).toBe(false);
174
- });
175
- });
176
- // ── isWithin ────────────────────────────────────────────────────────────────
177
- describe("isWithin", () => {
178
- test("returns true for path inside root", () => {
179
- expect(isWithin("/root/sub/file.txt", "/root")).toBe(true);
180
- });
181
- test("returns true for path equal to root", () => {
182
- expect(isWithin("/root", "/root")).toBe(true);
183
- });
184
- test("returns false for path outside root", () => {
185
- expect(isWithin("/other/file.txt", "/root")).toBe(false);
186
- });
187
- test("returns false for parent traversal", () => {
188
- expect(isWithin("/root/../etc/passwd", "/root")).toBe(false);
189
- });
190
- test("returns true for nested subdirectory", () => {
191
- expect(isWithin("/root/a/b/c/d.txt", "/root")).toBe(true);
192
- });
193
- test("returns false for sibling directory with similar prefix", () => {
194
- expect(isWithin("/root-other/file.txt", "/root")).toBe(false);
195
- });
196
- });
197
- // ── readBodyWithByteCap / jsonWithByteCap ────────────────────────────────────
198
- describe("readBodyWithByteCap", () => {
199
- function makeResponse(body, init) {
200
- const res = new Response(body, { headers: init?.headers });
201
- if (init?.url)
202
- Object.defineProperty(res, "url", { value: init.url });
203
- return res;
204
- }
205
- test("reads small bodies verbatim", async () => {
206
- const text = await readBodyWithByteCap(makeResponse("hello world"), 1024);
207
- expect(text).toBe("hello world");
208
- });
209
- test("handles empty bodies", async () => {
210
- const text = await readBodyWithByteCap(makeResponse(""), 1024);
211
- expect(text).toBe("");
212
- });
213
- test("refuses before reading if Content-Length exceeds cap", async () => {
214
- const body = "x".repeat(1000);
215
- const response = makeResponse(body, {
216
- headers: { "content-length": "1000" },
217
- url: "http://example.invalid/too-big",
218
- });
219
- await expect(readBodyWithByteCap(response, 100)).rejects.toBeInstanceOf(ResponseTooLargeError);
220
- });
221
- test("aborts mid-stream when Content-Length is absent but body exceeds cap", async () => {
222
- const stream = new ReadableStream({
223
- start(controller) {
224
- // 5 chunks of 1000 bytes each = 5000 bytes; cap at 2500.
225
- for (let i = 0; i < 5; i++) {
226
- controller.enqueue(new TextEncoder().encode("x".repeat(1000)));
227
- }
228
- controller.close();
229
- },
230
- });
231
- const response = new Response(stream);
232
- await expect(readBodyWithByteCap(response, 2500)).rejects.toBeInstanceOf(ResponseTooLargeError);
233
- });
234
- test("accepts body right at the cap", async () => {
235
- const body = "x".repeat(100);
236
- const text = await readBodyWithByteCap(makeResponse(body), 100);
237
- expect(text.length).toBe(100);
238
- });
239
- test("decodes multi-chunk UTF-8 bodies correctly", async () => {
240
- const stream = new ReadableStream({
241
- start(controller) {
242
- controller.enqueue(new TextEncoder().encode("hello "));
243
- controller.enqueue(new TextEncoder().encode("world"));
244
- controller.close();
245
- },
246
- });
247
- const response = new Response(stream);
248
- const text = await readBodyWithByteCap(response, 1024);
249
- expect(text).toBe("hello world");
250
- });
251
- });
252
- describe("jsonWithByteCap", () => {
253
- test("parses small JSON bodies", async () => {
254
- const data = await jsonWithByteCap(new Response(JSON.stringify({ hello: "world" })), 1024);
255
- expect(data.hello).toBe("world");
256
- });
257
- test("rejects oversized JSON before parse", async () => {
258
- const big = JSON.stringify({ data: "x".repeat(2000) });
259
- const response = new Response(big, { headers: { "content-length": String(big.length) } });
260
- await expect(jsonWithByteCap(response, 500)).rejects.toBeInstanceOf(ResponseTooLargeError);
261
- });
262
- test("propagates JSON.parse errors for malformed input", async () => {
263
- const response = new Response("{not json");
264
- await expect(jsonWithByteCap(response, 1024)).rejects.toThrow();
265
- });
266
- });