akm-cli 0.7.0-rc1 → 0.7.1

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 (314) hide show
  1. package/dist/{src/cli.js → cli.js} +100 -16
  2. package/dist/{src/commands → commands}/config-cli.js +42 -0
  3. package/dist/{src/commands → commands}/history.js +78 -7
  4. package/dist/{src/commands → commands}/registry-search.js +69 -6
  5. package/dist/{src/commands → commands}/search.js +30 -3
  6. package/dist/{src/commands → commands}/show.js +29 -0
  7. package/dist/{src/commands → commands}/source-add.js +5 -1
  8. package/dist/{src/commands → commands}/source-manage.js +7 -1
  9. package/dist/{src/core → core}/config.js +28 -0
  10. package/dist/{src/indexer → indexer}/db-search.js +1 -0
  11. package/dist/{src/indexer → indexer}/indexer.js +16 -2
  12. package/dist/{src/indexer → indexer}/matchers.js +1 -1
  13. package/dist/{src/indexer → indexer}/search-source.js +4 -2
  14. package/dist/{src/integrations → integrations}/agent/profiles.js +1 -1
  15. package/dist/{src/integrations → integrations}/agent/spawn.js +67 -16
  16. package/dist/{src/integrations → integrations}/github.js +9 -3
  17. package/dist/{src/llm → llm}/embedders/remote.js +37 -3
  18. package/dist/{src/output → output}/cli-hints.js +15 -2
  19. package/dist/{src/output → output}/renderers.js +3 -1
  20. package/dist/{src/output → output}/shapes.js +8 -1
  21. package/dist/{src/output → output}/text.js +156 -3
  22. package/dist/{src/registry → registry}/build-index.js +5 -4
  23. package/dist/{src/registry → registry}/providers/static-index.js +3 -1
  24. package/dist/{src/setup → setup}/setup.js +9 -0
  25. package/dist/{src/wiki → wiki}/wiki.js +54 -6
  26. package/dist/{src/workflows → workflows}/runs.js +37 -3
  27. package/package.json +8 -8
  28. package/dist/tests/add-website-source.test.js +0 -119
  29. package/dist/tests/agent/agent-config-loader.test.js +0 -70
  30. package/dist/tests/agent/agent-config.test.js +0 -221
  31. package/dist/tests/agent/agent-detect.test.js +0 -100
  32. package/dist/tests/agent/agent-spawn.test.js +0 -234
  33. package/dist/tests/agent-output.test.js +0 -186
  34. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +0 -103
  35. package/dist/tests/architecture/agent-spawn-seam.test.js +0 -193
  36. package/dist/tests/architecture/llm-stateless-seam.test.js +0 -112
  37. package/dist/tests/asset-ref.test.js +0 -192
  38. package/dist/tests/asset-registry.test.js +0 -103
  39. package/dist/tests/asset-spec.test.js +0 -241
  40. package/dist/tests/bench/attribution.test.js +0 -995
  41. package/dist/tests/bench/cleanup-sigint.test.js +0 -83
  42. package/dist/tests/bench/cleanup.js +0 -203
  43. package/dist/tests/bench/cleanup.test.js +0 -166
  44. package/dist/tests/bench/cli.js +0 -683
  45. package/dist/tests/bench/cli.test.js +0 -177
  46. package/dist/tests/bench/compare.test.js +0 -556
  47. package/dist/tests/bench/corpus.js +0 -314
  48. package/dist/tests/bench/corpus.test.js +0 -258
  49. package/dist/tests/bench/driver.js +0 -346
  50. package/dist/tests/bench/driver.test.js +0 -443
  51. package/dist/tests/bench/evolve-metrics.js +0 -179
  52. package/dist/tests/bench/evolve-metrics.test.js +0 -187
  53. package/dist/tests/bench/evolve.js +0 -580
  54. package/dist/tests/bench/evolve.test.js +0 -616
  55. package/dist/tests/bench/failure-modes.test.js +0 -300
  56. package/dist/tests/bench/feedback-integrity.test.js +0 -456
  57. package/dist/tests/bench/leakage.test.js +0 -125
  58. package/dist/tests/bench/learning-curve.test.js +0 -133
  59. package/dist/tests/bench/metrics.js +0 -2319
  60. package/dist/tests/bench/metrics.test.js +0 -1144
  61. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +0 -43
  62. package/dist/tests/bench/report.js +0 -1821
  63. package/dist/tests/bench/report.test.js +0 -989
  64. package/dist/tests/bench/runner.js +0 -536
  65. package/dist/tests/bench/runner.test.js +0 -958
  66. package/dist/tests/bench/search-bridge.test.js +0 -331
  67. package/dist/tests/bench/tmp.js +0 -41
  68. package/dist/tests/bench/trajectory.js +0 -116
  69. package/dist/tests/bench/trajectory.test.js +0 -127
  70. package/dist/tests/bench/verifier.js +0 -109
  71. package/dist/tests/bench/verifier.test.js +0 -118
  72. package/dist/tests/bench/workflow-evaluator.js +0 -557
  73. package/dist/tests/bench/workflow-evaluator.test.js +0 -421
  74. package/dist/tests/bench/workflow-spec.js +0 -358
  75. package/dist/tests/bench/workflow-spec.test.js +0 -363
  76. package/dist/tests/bench/workflow-trace.js +0 -438
  77. package/dist/tests/bench/workflow-trace.test.js +0 -254
  78. package/dist/tests/benchmark-search-quality.js +0 -536
  79. package/dist/tests/benchmark-suite.js +0 -1441
  80. package/dist/tests/capture-cli.test.js +0 -112
  81. package/dist/tests/cli-errors.test.js +0 -203
  82. package/dist/tests/commands/events.test.js +0 -370
  83. package/dist/tests/commands/history.test.js +0 -223
  84. package/dist/tests/commands/import.test.js +0 -103
  85. package/dist/tests/commands/proposal-cli.test.js +0 -209
  86. package/dist/tests/commands/reflect-propose-cli.test.js +0 -333
  87. package/dist/tests/commands/remember.test.js +0 -97
  88. package/dist/tests/commands/scope-flags.test.js +0 -300
  89. package/dist/tests/commands/search.test.js +0 -537
  90. package/dist/tests/commands/show-indexer-parity.test.js +0 -117
  91. package/dist/tests/commands/show.test.js +0 -294
  92. package/dist/tests/common.test.js +0 -266
  93. package/dist/tests/completions.test.js +0 -142
  94. package/dist/tests/config-cli.test.js +0 -193
  95. package/dist/tests/config-llm-features.test.js +0 -139
  96. package/dist/tests/config.test.js +0 -544
  97. package/dist/tests/contracts/migration-baseline.test.js +0 -43
  98. package/dist/tests/contracts/reflect-propose-envelope.test.js +0 -139
  99. package/dist/tests/contracts/spec-helpers.js +0 -46
  100. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +0 -228
  101. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +0 -56
  102. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +0 -34
  103. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +0 -94
  104. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +0 -39
  105. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +0 -44
  106. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +0 -47
  107. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +0 -40
  108. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +0 -58
  109. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +0 -34
  110. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +0 -75
  111. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +0 -36
  112. package/dist/tests/core/write-source.test.js +0 -366
  113. package/dist/tests/curate-command.test.js +0 -87
  114. package/dist/tests/db-scoring.test.js +0 -201
  115. package/dist/tests/db.test.js +0 -654
  116. package/dist/tests/distill-cli-flag.test.js +0 -208
  117. package/dist/tests/distill.test.js +0 -515
  118. package/dist/tests/docker-install.test.js +0 -120
  119. package/dist/tests/e2e.test.js +0 -1398
  120. package/dist/tests/embedder.test.js +0 -340
  121. package/dist/tests/embedding-model-config.test.js +0 -379
  122. package/dist/tests/feedback-command.test.js +0 -172
  123. package/dist/tests/file-context.test.js +0 -552
  124. package/dist/tests/fixtures/scripts/git/summarize-diff.js +0 -9
  125. package/dist/tests/fixtures/scripts/lint/eslint-check.js +0 -7
  126. package/dist/tests/fixtures/stashes/load.js +0 -166
  127. package/dist/tests/fixtures/stashes/load.test.js +0 -88
  128. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +0 -12
  129. package/dist/tests/frontmatter.test.js +0 -190
  130. package/dist/tests/fts-field-weighting.test.js +0 -254
  131. package/dist/tests/fuzzy-search.test.js +0 -230
  132. package/dist/tests/git-provider-clone.test.js +0 -45
  133. package/dist/tests/github.test.js +0 -161
  134. package/dist/tests/graph-boost-ranking.test.js +0 -305
  135. package/dist/tests/graph-extraction.test.js +0 -282
  136. package/dist/tests/helpers/usage-events.js +0 -8
  137. package/dist/tests/index-pass-llm.test.js +0 -161
  138. package/dist/tests/indexer.test.js +0 -559
  139. package/dist/tests/info-command.test.js +0 -166
  140. package/dist/tests/init.test.js +0 -69
  141. package/dist/tests/install-script.test.js +0 -246
  142. package/dist/tests/integration/agent-real-profile.test.js +0 -94
  143. package/dist/tests/issue-36-repro.test.js +0 -304
  144. package/dist/tests/issues-191-194.test.js +0 -160
  145. package/dist/tests/lesson-lint.test.js +0 -111
  146. package/dist/tests/llm-client.test.js +0 -115
  147. package/dist/tests/llm-feature-gate.test.js +0 -151
  148. package/dist/tests/llm.test.js +0 -139
  149. package/dist/tests/lockfile.test.js +0 -216
  150. package/dist/tests/manifest.test.js +0 -205
  151. package/dist/tests/markdown.test.js +0 -126
  152. package/dist/tests/matchers-unit.test.js +0 -189
  153. package/dist/tests/memory-inference.test.js +0 -299
  154. package/dist/tests/merge-scoring.test.js +0 -136
  155. package/dist/tests/metadata.test.js +0 -313
  156. package/dist/tests/migration-help.test.js +0 -89
  157. package/dist/tests/origin-resolve.test.js +0 -124
  158. package/dist/tests/output-baseline.test.js +0 -217
  159. package/dist/tests/output-shapes-unit.test.js +0 -476
  160. package/dist/tests/parallel-search.test.js +0 -272
  161. package/dist/tests/parameter-metadata.test.js +0 -365
  162. package/dist/tests/paths.test.js +0 -177
  163. package/dist/tests/progressive-disclosure.test.js +0 -280
  164. package/dist/tests/proposals.test.js +0 -279
  165. package/dist/tests/proposed-quality.test.js +0 -271
  166. package/dist/tests/provider-registry.test.js +0 -32
  167. package/dist/tests/ranking-regression.test.js +0 -548
  168. package/dist/tests/reflect-propose.test.js +0 -455
  169. package/dist/tests/registry-build-index.test.js +0 -378
  170. package/dist/tests/registry-cli.test.js +0 -290
  171. package/dist/tests/registry-index-v2.test.js +0 -430
  172. package/dist/tests/registry-install.test.js +0 -728
  173. package/dist/tests/registry-providers/parity.test.js +0 -189
  174. package/dist/tests/registry-providers/skills-sh.test.js +0 -309
  175. package/dist/tests/registry-providers/static-index.test.js +0 -204
  176. package/dist/tests/registry-resolve.test.js +0 -126
  177. package/dist/tests/registry-search.test.js +0 -723
  178. package/dist/tests/remember-frontmatter.test.js +0 -380
  179. package/dist/tests/remember-unit.test.js +0 -123
  180. package/dist/tests/ripgrep-install.test.js +0 -251
  181. package/dist/tests/ripgrep-resolve.test.js +0 -108
  182. package/dist/tests/ripgrep.test.js +0 -163
  183. package/dist/tests/save-command.test.js +0 -94
  184. package/dist/tests/save-trust-qa-fixes.test.js +0 -270
  185. package/dist/tests/scoring-pipeline.test.js +0 -648
  186. package/dist/tests/search-include-proposed-cli.test.js +0 -118
  187. package/dist/tests/self-update.test.js +0 -442
  188. package/dist/tests/semantic-search-e2e.test.js +0 -512
  189. package/dist/tests/semantic-status.test.js +0 -471
  190. package/dist/tests/setup-run.integration.js +0 -877
  191. package/dist/tests/setup-wizard.test.js +0 -198
  192. package/dist/tests/setup.test.js +0 -131
  193. package/dist/tests/source-add.test.js +0 -11
  194. package/dist/tests/source-clone.test.js +0 -254
  195. package/dist/tests/source-manage.test.js +0 -366
  196. package/dist/tests/source-providers/filesystem.test.js +0 -82
  197. package/dist/tests/source-providers/git.test.js +0 -252
  198. package/dist/tests/source-providers/website.test.js +0 -128
  199. package/dist/tests/source-qa-fixes.test.js +0 -268
  200. package/dist/tests/source-registry.test.js +0 -350
  201. package/dist/tests/source-resolve.test.js +0 -100
  202. package/dist/tests/source-source.test.js +0 -221
  203. package/dist/tests/source.test.js +0 -533
  204. package/dist/tests/tar-utils-scan.test.js +0 -73
  205. package/dist/tests/toggle-components.test.js +0 -73
  206. package/dist/tests/usage-telemetry.test.js +0 -265
  207. package/dist/tests/utility-scoring.test.js +0 -558
  208. package/dist/tests/vault-load-error.test.js +0 -78
  209. package/dist/tests/vault-qa-fixes.test.js +0 -194
  210. package/dist/tests/vault.test.js +0 -429
  211. package/dist/tests/vector-search.test.js +0 -608
  212. package/dist/tests/walker.test.js +0 -252
  213. package/dist/tests/wave2-cluster-bc.test.js +0 -228
  214. package/dist/tests/wave2-cluster-d.test.js +0 -180
  215. package/dist/tests/wave2-cluster-e.test.js +0 -179
  216. package/dist/tests/wiki-qa-fixes.test.js +0 -270
  217. package/dist/tests/wiki.test.js +0 -529
  218. package/dist/tests/workflow-cli.test.js +0 -271
  219. package/dist/tests/workflow-markdown.test.js +0 -171
  220. package/dist/tests/workflow-path-escape.test.js +0 -132
  221. package/dist/tests/workflow-qa-fixes.test.js +0 -377
  222. package/dist/tests/workflows/indexer-rejection.test.js +0 -213
  223. /package/dist/{src/commands → commands}/completions.js +0 -0
  224. /package/dist/{src/commands → commands}/curate.js +0 -0
  225. /package/dist/{src/commands → commands}/distill.js +0 -0
  226. /package/dist/{src/commands → commands}/events.js +0 -0
  227. /package/dist/{src/commands → commands}/info.js +0 -0
  228. /package/dist/{src/commands → commands}/init.js +0 -0
  229. /package/dist/{src/commands → commands}/install-audit.js +0 -0
  230. /package/dist/{src/commands → commands}/installed-stashes.js +0 -0
  231. /package/dist/{src/commands → commands}/migration-help.js +0 -0
  232. /package/dist/{src/commands → commands}/proposal.js +0 -0
  233. /package/dist/{src/commands → commands}/propose.js +0 -0
  234. /package/dist/{src/commands → commands}/reflect.js +0 -0
  235. /package/dist/{src/commands → commands}/remember.js +0 -0
  236. /package/dist/{src/commands → commands}/self-update.js +0 -0
  237. /package/dist/{src/commands → commands}/source-clone.js +0 -0
  238. /package/dist/{src/commands → commands}/vault.js +0 -0
  239. /package/dist/{src/core → core}/asset-ref.js +0 -0
  240. /package/dist/{src/core → core}/asset-registry.js +0 -0
  241. /package/dist/{src/core → core}/asset-spec.js +0 -0
  242. /package/dist/{src/core → core}/common.js +0 -0
  243. /package/dist/{src/core → core}/errors.js +0 -0
  244. /package/dist/{src/core → core}/events.js +0 -0
  245. /package/dist/{src/core → core}/frontmatter.js +0 -0
  246. /package/dist/{src/core → core}/lesson-lint.js +0 -0
  247. /package/dist/{src/core → core}/markdown.js +0 -0
  248. /package/dist/{src/core → core}/paths.js +0 -0
  249. /package/dist/{src/core → core}/proposals.js +0 -0
  250. /package/dist/{src/core → core}/warn.js +0 -0
  251. /package/dist/{src/core → core}/write-source.js +0 -0
  252. /package/dist/{src/indexer → indexer}/db.js +0 -0
  253. /package/dist/{src/indexer → indexer}/file-context.js +0 -0
  254. /package/dist/{src/indexer → indexer}/graph-boost.js +0 -0
  255. /package/dist/{src/indexer → indexer}/graph-extraction.js +0 -0
  256. /package/dist/{src/indexer → indexer}/manifest.js +0 -0
  257. /package/dist/{src/indexer → indexer}/memory-inference.js +0 -0
  258. /package/dist/{src/indexer → indexer}/metadata.js +0 -0
  259. /package/dist/{src/indexer → indexer}/search-fields.js +0 -0
  260. /package/dist/{src/indexer → indexer}/semantic-status.js +0 -0
  261. /package/dist/{src/indexer → indexer}/usage-events.js +0 -0
  262. /package/dist/{src/indexer → indexer}/walker.js +0 -0
  263. /package/dist/{src/integrations → integrations}/agent/config.js +0 -0
  264. /package/dist/{src/integrations → integrations}/agent/detect.js +0 -0
  265. /package/dist/{src/integrations → integrations}/agent/index.js +0 -0
  266. /package/dist/{src/integrations → integrations}/agent/prompts.js +0 -0
  267. /package/dist/{src/integrations → integrations}/lockfile.js +0 -0
  268. /package/dist/{src/llm → llm}/client.js +0 -0
  269. /package/dist/{src/llm → llm}/embedder.js +0 -0
  270. /package/dist/{src/llm → llm}/embedders/cache.js +0 -0
  271. /package/dist/{src/llm → llm}/embedders/local.js +0 -0
  272. /package/dist/{src/llm → llm}/embedders/types.js +0 -0
  273. /package/dist/{src/llm → llm}/feature-gate.js +0 -0
  274. /package/dist/{src/llm → llm}/graph-extract.js +0 -0
  275. /package/dist/{src/llm → llm}/index-passes.js +0 -0
  276. /package/dist/{src/llm → llm}/memory-infer.js +0 -0
  277. /package/dist/{src/llm → llm}/metadata-enhance.js +0 -0
  278. /package/dist/{src/output → output}/context.js +0 -0
  279. /package/dist/{src/registry → registry}/create-provider-registry.js +0 -0
  280. /package/dist/{src/registry → registry}/factory.js +0 -0
  281. /package/dist/{src/registry → registry}/origin-resolve.js +0 -0
  282. /package/dist/{src/registry → registry}/providers/index.js +0 -0
  283. /package/dist/{src/registry → registry}/providers/skills-sh.js +0 -0
  284. /package/dist/{src/registry → registry}/providers/types.js +0 -0
  285. /package/dist/{src/registry → registry}/resolve.js +0 -0
  286. /package/dist/{src/registry → registry}/types.js +0 -0
  287. /package/dist/{src/setup → setup}/detect.js +0 -0
  288. /package/dist/{src/setup → setup}/ripgrep-install.js +0 -0
  289. /package/dist/{src/setup → setup}/ripgrep-resolve.js +0 -0
  290. /package/dist/{src/setup → setup}/steps.js +0 -0
  291. /package/dist/{src/sources → sources}/include.js +0 -0
  292. /package/dist/{src/sources → sources}/provider-factory.js +0 -0
  293. /package/dist/{src/sources → sources}/provider.js +0 -0
  294. /package/dist/{src/sources → sources}/providers/filesystem.js +0 -0
  295. /package/dist/{src/sources → sources}/providers/git.js +0 -0
  296. /package/dist/{src/sources → sources}/providers/index.js +0 -0
  297. /package/dist/{src/sources → sources}/providers/install-types.js +0 -0
  298. /package/dist/{src/sources → sources}/providers/npm.js +0 -0
  299. /package/dist/{src/sources → sources}/providers/provider-utils.js +0 -0
  300. /package/dist/{src/sources → sources}/providers/sync-from-ref.js +0 -0
  301. /package/dist/{src/sources → sources}/providers/tar-utils.js +0 -0
  302. /package/dist/{src/sources → sources}/providers/website.js +0 -0
  303. /package/dist/{src/sources → sources}/resolve.js +0 -0
  304. /package/dist/{src/sources → sources}/types.js +0 -0
  305. /package/dist/{src/templates → templates}/wiki-templates.js +0 -0
  306. /package/dist/{src/version.js → version.js} +0 -0
  307. /package/dist/{src/workflows → workflows}/authoring.js +0 -0
  308. /package/dist/{src/workflows → workflows}/cli.js +0 -0
  309. /package/dist/{src/workflows → workflows}/db.js +0 -0
  310. /package/dist/{src/workflows → workflows}/document-cache.js +0 -0
  311. /package/dist/{src/workflows → workflows}/parser.js +0 -0
  312. /package/dist/{src/workflows → workflows}/renderer.js +0 -0
  313. /package/dist/{src/workflows → workflows}/schema.js +0 -0
  314. /package/dist/{src/workflows → workflows}/validator.js +0 -0
@@ -1,70 +0,0 @@
1
- /**
2
- * Integration test: the AkmConfig loader propagates the `agent` block
3
- * through `loadConfig()` (and through the on-disk JSONC parser, exercising
4
- * the `pickKnownKeys` path).
5
- *
6
- * The acceptance criterion "config schema accepts an optional agent block"
7
- * lives at the loader boundary, not just the parser; this test pins the
8
- * end-to-end shape.
9
- */
10
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
- import fs from "node:fs";
12
- import os from "node:os";
13
- import path from "node:path";
14
- let tmpHome;
15
- let originalHome;
16
- let originalXdg;
17
- beforeEach(() => {
18
- tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "akm-agent-cfg-"));
19
- originalHome = process.env.HOME;
20
- originalXdg = process.env.XDG_CONFIG_HOME;
21
- process.env.HOME = tmpHome;
22
- process.env.XDG_CONFIG_HOME = path.join(tmpHome, ".config");
23
- });
24
- afterEach(() => {
25
- if (originalHome === undefined)
26
- delete process.env.HOME;
27
- else
28
- process.env.HOME = originalHome;
29
- if (originalXdg === undefined)
30
- delete process.env.XDG_CONFIG_HOME;
31
- else
32
- process.env.XDG_CONFIG_HOME = originalXdg;
33
- fs.rmSync(tmpHome, { recursive: true, force: true });
34
- });
35
- describe("AkmConfig loader — agent block", () => {
36
- test("loads agent.default + agent.profiles from disk", async () => {
37
- const { getConfigPath, loadUserConfig, resetConfigCache } = await import("../../src/core/config");
38
- const cfgPath = getConfigPath();
39
- fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
40
- fs.writeFileSync(cfgPath, JSON.stringify({
41
- semanticSearchMode: "auto",
42
- agent: {
43
- default: "claude",
44
- timeoutMs: 45000,
45
- profiles: {
46
- claude: { args: ["--print"] },
47
- rover: { bin: "rover-cli", parseOutput: "json" },
48
- },
49
- // Unknown key — must not throw at load.
50
- mystery: 1,
51
- },
52
- }, null, 2));
53
- resetConfigCache();
54
- const cfg = loadUserConfig();
55
- expect(cfg.agent?.default).toBe("claude");
56
- expect(cfg.agent?.timeoutMs).toBe(45000);
57
- expect(cfg.agent?.profiles?.claude?.args).toEqual(["--print"]);
58
- expect(cfg.agent?.profiles?.rover?.bin).toBe("rover-cli");
59
- expect(cfg.agent?.profiles?.rover?.parseOutput).toBe("json");
60
- });
61
- test("agent block absent → cfg.agent is undefined → requireAgentProfile throws", async () => {
62
- const { loadUserConfig, resetConfigCache } = await import("../../src/core/config");
63
- const { requireAgentProfile } = await import("../../src/integrations/agent/config");
64
- const { ConfigError } = await import("../../src/core/errors");
65
- resetConfigCache();
66
- const cfg = loadUserConfig();
67
- expect(cfg.agent).toBeUndefined();
68
- expect(() => requireAgentProfile(cfg.agent)).toThrow(ConfigError);
69
- });
70
- });
@@ -1,221 +0,0 @@
1
- /**
2
- * Tests for the `agent.*` config block parser and profile resolver.
3
- *
4
- * Acceptance coverage:
5
- * • Parser accepts the documented shape.
6
- * • Unknown keys are warn-and-ignored (no throw).
7
- * • Built-in profiles resolve for opencode, claude, codex, gemini, aider.
8
- * • Missing block surfaces a stable ConfigError via requireAgentProfile.
9
- */
10
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
11
- const warnings = [];
12
- // NOTE: `mock.module` in Bun is process-global — once installed it persists
13
- // across test files run in the same `bun test` invocation. So this mock has
14
- // to remain a faithful drop-in for the real `src/core/warn` module:
15
- //
16
- // 1. Every export that the real module ships must be represented here,
17
- // otherwise tests in other files that import a missing export get
18
- // `undefined` and silently break (issue #273).
19
- // 2. `warn()` must also forward to `console.warn` so other test files that
20
- // capture stderr (e.g. the noise-gate tests in
21
- // tests/workflows/indexer-rejection.test.ts) continue to see the calls.
22
- // We push to the local `warnings[]` so this file's own assertions still
23
- // work, AND forward to `console.warn` so callers that intercept it
24
- // still observe what was emitted.
25
- let mockedQuiet = false;
26
- let mockedVerbose = false;
27
- mock.module("../../src/core/warn", () => ({
28
- warn: (...args) => {
29
- warnings.push(args.join(" "));
30
- if (!mockedQuiet)
31
- console.warn(...args);
32
- },
33
- warnVerbose: (...args) => {
34
- if (!mockedVerbose)
35
- return;
36
- warnings.push(args.join(" "));
37
- if (!mockedQuiet)
38
- console.warn(...args);
39
- },
40
- setQuiet: (value) => {
41
- mockedQuiet = value;
42
- },
43
- resetQuiet: () => {
44
- mockedQuiet = false;
45
- },
46
- isQuiet: () => mockedQuiet,
47
- setVerbose: (value) => {
48
- mockedVerbose = value;
49
- },
50
- resetVerbose: () => {
51
- mockedVerbose = false;
52
- },
53
- isVerbose: () => {
54
- const env = process.env.AKM_VERBOSE?.trim().toLowerCase();
55
- if (env === "1" || env === "true" || env === "yes" || env === "on")
56
- return true;
57
- if (env === "0" || env === "false" || env === "no" || env === "off")
58
- return false;
59
- return mockedVerbose;
60
- },
61
- }));
62
- beforeEach(() => {
63
- warnings.length = 0;
64
- });
65
- afterEach(() => {
66
- warnings.length = 0;
67
- });
68
- describe("parseAgentConfig", () => {
69
- test("returns undefined when block is absent", async () => {
70
- const { parseAgentConfig } = await import("../../src/integrations/agent/config");
71
- expect(parseAgentConfig(undefined)).toBeUndefined();
72
- expect(warnings).toHaveLength(0);
73
- });
74
- test("warns and returns undefined for non-object root", async () => {
75
- const { parseAgentConfig } = await import("../../src/integrations/agent/config");
76
- expect(parseAgentConfig("oops")).toBeUndefined();
77
- expect(warnings.some((w) => w.includes('"agent"'))).toBe(true);
78
- });
79
- test("accepts the documented shape", async () => {
80
- const { parseAgentConfig } = await import("../../src/integrations/agent/config");
81
- const parsed = parseAgentConfig({
82
- default: "opencode",
83
- timeoutMs: 30000,
84
- profiles: {
85
- opencode: { bin: "opencode", args: ["--non-interactive"], stdio: "captured" },
86
- },
87
- });
88
- expect(parsed?.default).toBe("opencode");
89
- expect(parsed?.timeoutMs).toBe(30000);
90
- expect(parsed?.profiles?.opencode).toEqual({
91
- bin: "opencode",
92
- args: ["--non-interactive"],
93
- stdio: "captured",
94
- });
95
- });
96
- test("warn-and-ignore unknown top-level keys (no throw)", async () => {
97
- const { parseAgentConfig } = await import("../../src/integrations/agent/config");
98
- const parsed = parseAgentConfig({
99
- default: "claude",
100
- moonRoutingTable: { foo: "bar" }, // unknown
101
- });
102
- expect(parsed?.default).toBe("claude");
103
- expect(warnings.some((w) => w.includes("moonRoutingTable"))).toBe(true);
104
- });
105
- test("warn-and-ignore unknown per-profile keys", async () => {
106
- const { parseAgentConfig } = await import("../../src/integrations/agent/config");
107
- const parsed = parseAgentConfig({
108
- profiles: {
109
- custom: { bin: "ok", quirks: "nope" },
110
- },
111
- });
112
- expect(parsed?.profiles?.custom?.bin).toBe("ok");
113
- expect(warnings.some((w) => w.includes("quirks"))).toBe(true);
114
- });
115
- test("warn-and-ignore malformed timeoutMs", async () => {
116
- const { parseAgentConfig } = await import("../../src/integrations/agent/config");
117
- const parsed = parseAgentConfig({ timeoutMs: "60s" });
118
- expect(parsed?.timeoutMs).toBeUndefined();
119
- expect(warnings.some((w) => w.includes("timeoutMs"))).toBe(true);
120
- });
121
- test("rejects non-string args entries", async () => {
122
- const { parseAgentConfig } = await import("../../src/integrations/agent/config");
123
- const parsed = parseAgentConfig({
124
- profiles: { opencode: { args: ["--ok", 5, "--also-ok"] } },
125
- });
126
- expect(parsed?.profiles?.opencode?.args).toEqual(["--ok", "--also-ok"]);
127
- expect(warnings.some((w) => w.includes("args"))).toBe(true);
128
- });
129
- test("rejects bad stdio mode", async () => {
130
- const { parseAgentConfig } = await import("../../src/integrations/agent/config");
131
- const parsed = parseAgentConfig({
132
- profiles: { opencode: { stdio: "weird" } },
133
- });
134
- expect(parsed?.profiles?.opencode?.stdio).toBeUndefined();
135
- expect(warnings.some((w) => w.includes("stdio"))).toBe(true);
136
- });
137
- });
138
- describe("built-in profile resolution", () => {
139
- test("resolves opencode, claude, codex, gemini, aider out of the box", async () => {
140
- const { BUILTIN_AGENT_PROFILE_NAMES, getBuiltinAgentProfile } = await import("../../src/integrations/agent/profiles");
141
- expect(BUILTIN_AGENT_PROFILE_NAMES).toEqual(["aider", "claude", "codex", "gemini", "opencode"]);
142
- for (const name of ["opencode", "claude", "codex", "gemini", "aider"]) {
143
- const profile = getBuiltinAgentProfile(name);
144
- expect(profile).toBeDefined();
145
- expect(profile?.bin).toBeTruthy();
146
- expect(profile?.envPassthrough).toContain("PATH");
147
- }
148
- });
149
- test("user override merges on top of built-in", async () => {
150
- const { resolveAgentProfile } = await import("../../src/integrations/agent/config");
151
- const merged = resolveAgentProfile("opencode", { args: ["--scripted"], stdio: "captured" });
152
- expect(merged?.bin).toBe("opencode"); // built-in default
153
- expect(merged?.args).toEqual(["--scripted"]); // override
154
- expect(merged?.stdio).toBe("captured"); // override
155
- expect(merged?.envPassthrough).toContain("PATH"); // built-in retained
156
- });
157
- test("user-defined profile (no built-in) requires bin", async () => {
158
- const { resolveAgentProfile } = await import("../../src/integrations/agent/config");
159
- expect(resolveAgentProfile("rover", undefined)).toBeUndefined();
160
- expect(resolveAgentProfile("rover", {})).toBeUndefined();
161
- const ok = resolveAgentProfile("rover", { bin: "rover-cli", args: ["--silent"] });
162
- expect(ok?.bin).toBe("rover-cli");
163
- expect(ok?.args).toEqual(["--silent"]);
164
- expect(ok?.stdio).toBe("captured");
165
- });
166
- test("envPassthrough merges base + override", async () => {
167
- const { resolveAgentProfile } = await import("../../src/integrations/agent/config");
168
- const merged = resolveAgentProfile("opencode", { envPassthrough: ["MY_TOKEN"] });
169
- expect(merged?.envPassthrough).toContain("PATH"); // from built-in
170
- expect(merged?.envPassthrough).toContain("MY_TOKEN"); // from override
171
- });
172
- test("listAgentProfileNames includes built-ins plus user-defined", async () => {
173
- const { listAgentProfileNames } = await import("../../src/integrations/agent/config");
174
- const names = listAgentProfileNames({ profiles: { rover: { bin: "rover" } } });
175
- expect(names).toContain("rover");
176
- expect(names).toContain("opencode");
177
- expect(names).toContain("claude");
178
- });
179
- });
180
- describe("requireAgentProfile", () => {
181
- test("throws ConfigError when the agent block is missing", async () => {
182
- const { requireAgentProfile } = await import("../../src/integrations/agent/config");
183
- const { ConfigError } = await import("../../src/core/errors");
184
- let caught;
185
- try {
186
- requireAgentProfile(undefined);
187
- }
188
- catch (err) {
189
- caught = err;
190
- }
191
- expect(caught).toBeInstanceOf(ConfigError);
192
- expect(caught.message).toContain("agent commands are disabled");
193
- const hint = caught.hint();
194
- expect(hint).toBeTruthy();
195
- expect(hint).toContain("akm setup");
196
- });
197
- test("throws when no default and no requested name", async () => {
198
- const { requireAgentProfile } = await import("../../src/integrations/agent/config");
199
- const { ConfigError } = await import("../../src/core/errors");
200
- let caught;
201
- try {
202
- requireAgentProfile({});
203
- }
204
- catch (err) {
205
- caught = err;
206
- }
207
- expect(caught).toBeInstanceOf(ConfigError);
208
- expect(caught.message).toContain("require a profile");
209
- });
210
- test("resolves the requested profile when valid", async () => {
211
- const { requireAgentProfile } = await import("../../src/integrations/agent/config");
212
- const profile = requireAgentProfile({ default: "claude" });
213
- expect(profile.name).toBe("claude");
214
- expect(profile.bin).toBe("claude");
215
- });
216
- test("explicit requested name beats config default", async () => {
217
- const { requireAgentProfile } = await import("../../src/integrations/agent/config");
218
- const profile = requireAgentProfile({ default: "claude" }, "codex");
219
- expect(profile.name).toBe("codex");
220
- });
221
- });
@@ -1,100 +0,0 @@
1
- /**
2
- * Tests for setup-time agent CLI detection.
3
- *
4
- * Acceptance coverage:
5
- * • Detects every built-in profile bin via the injected `which` probe.
6
- * • Picks the first available profile as the default.
7
- * • Honours an existing `agent.default` when that profile is still
8
- * available (round-trip stability).
9
- * • Returns `undefined` when nothing is installed.
10
- * • `stepAgentCliDetection` produces a config-shaped result the wizard
11
- * can `apply()`.
12
- */
13
- import { describe, expect, test } from "bun:test";
14
- import { detectAgentCliProfiles, pickDefaultAgentProfile } from "../../src/integrations/agent/detect";
15
- function whichOnly(installed) {
16
- const set = new Set(installed);
17
- return (bin) => (set.has(bin) ? `/usr/local/bin/${bin}` : undefined);
18
- }
19
- describe("detectAgentCliProfiles", () => {
20
- test("reports every built-in profile, available iff bin found", () => {
21
- const results = detectAgentCliProfiles(undefined, whichOnly(["claude", "codex"]));
22
- const names = results.map((r) => r.name).sort();
23
- expect(names).toEqual(["aider", "claude", "codex", "gemini", "opencode"]);
24
- expect(results.find((r) => r.name === "claude")?.available).toBe(true);
25
- expect(results.find((r) => r.name === "codex")?.available).toBe(true);
26
- expect(results.find((r) => r.name === "gemini")?.available).toBe(false);
27
- });
28
- test("includes user-defined profiles via the resolver", () => {
29
- const results = detectAgentCliProfiles({ profiles: { rover: { bin: "rover-cli" } } }, whichOnly(["rover-cli"]));
30
- const rover = results.find((r) => r.name === "rover");
31
- expect(rover?.available).toBe(true);
32
- expect(rover?.resolvedPath).toContain("rover-cli");
33
- });
34
- test("returns nothing-installed when the probe always says no", () => {
35
- const results = detectAgentCliProfiles(undefined, whichOnly([]));
36
- expect(results.every((r) => !r.available)).toBe(true);
37
- });
38
- });
39
- describe("pickDefaultAgentProfile", () => {
40
- test("picks the first available result when no existing default", () => {
41
- const picked = pickDefaultAgentProfile([
42
- { name: "aider", bin: "aider", available: false },
43
- { name: "claude", bin: "claude", available: true },
44
- { name: "codex", bin: "codex", available: true },
45
- ]);
46
- expect(picked).toBe("claude");
47
- });
48
- test("keeps an existing available default", () => {
49
- const picked = pickDefaultAgentProfile([
50
- { name: "claude", bin: "claude", available: true },
51
- { name: "codex", bin: "codex", available: true },
52
- ], "codex");
53
- expect(picked).toBe("codex");
54
- });
55
- test("falls back when the existing default is no longer available", () => {
56
- const picked = pickDefaultAgentProfile([
57
- { name: "claude", bin: "claude", available: true },
58
- { name: "codex", bin: "codex", available: false },
59
- ], "codex");
60
- expect(picked).toBe("claude");
61
- });
62
- test("returns undefined when nothing is available", () => {
63
- const picked = pickDefaultAgentProfile([
64
- { name: "claude", bin: "claude", available: false },
65
- { name: "codex", bin: "codex", available: false },
66
- ]);
67
- expect(picked).toBeUndefined();
68
- });
69
- });
70
- describe("stepAgentCliDetection (setup wizard)", () => {
71
- test("persists default + leaves block absent when nothing detected & no prior config", async () => {
72
- const { stepAgentCliDetection } = await import("../../src/setup/setup");
73
- const result = stepAgentCliDetection({ semanticSearchMode: "auto" }, () => [
74
- { name: "claude", bin: "claude", available: false },
75
- { name: "codex", bin: "codex", available: false },
76
- ]);
77
- expect(result.agent).toBeUndefined();
78
- expect(result.detections).toHaveLength(2);
79
- });
80
- test("writes agent.default to the first detected profile", async () => {
81
- const { stepAgentCliDetection } = await import("../../src/setup/setup");
82
- const result = stepAgentCliDetection({ semanticSearchMode: "auto" }, () => [
83
- { name: "claude", bin: "claude", available: false },
84
- { name: "codex", bin: "codex", available: true },
85
- ]);
86
- expect(result.agent?.default).toBe("codex");
87
- });
88
- test("preserves user-overridden default when still available", async () => {
89
- const { stepAgentCliDetection } = await import("../../src/setup/setup");
90
- const result = stepAgentCliDetection({
91
- semanticSearchMode: "auto",
92
- agent: { default: "aider", profiles: { aider: { args: ["--no-auto-commits"] } } },
93
- }, () => [
94
- { name: "claude", bin: "claude", available: true },
95
- { name: "aider", bin: "aider", available: true },
96
- ]);
97
- expect(result.agent?.default).toBe("aider");
98
- expect(result.agent?.profiles?.aider?.args).toEqual(["--no-auto-commits"]);
99
- });
100
- });
@@ -1,234 +0,0 @@
1
- /**
2
- * Tests for the agent CLI spawn wrapper (`runAgent`).
3
- *
4
- * Acceptance coverage:
5
- * • Captured stdio collects stdout/stderr.
6
- * • Hard timeout maps to `reason: "timeout"`.
7
- * • Non-zero exit maps to `reason: "non_zero_exit"`.
8
- * • Synchronous spawn failure maps to `reason: "spawn_failed"`.
9
- * • Malformed JSON output (when `parseOutput: "json"`) maps to
10
- * `reason: "parse_error"`.
11
- * • Successful run returns `ok: true`, captured `stdout`, parsed JSON.
12
- *
13
- * The wrapper takes a `spawn` injection point so we never touch real
14
- * binaries here. Where we do touch a real subprocess (one fast `bun -e`
15
- * timeout test) we keep the timeout small and deterministic.
16
- */
17
- import { describe, expect, test } from "bun:test";
18
- import { runAgent } from "../../src/integrations/agent/spawn";
19
- function makeProfile(overrides = {}) {
20
- return {
21
- name: "test-agent",
22
- bin: "test-agent",
23
- args: [],
24
- stdio: "captured",
25
- envPassthrough: ["PATH"],
26
- parseOutput: "text",
27
- ...overrides,
28
- };
29
- }
30
- function asReadableStream(text) {
31
- const bytes = new TextEncoder().encode(text);
32
- return new ReadableStream({
33
- start(controller) {
34
- controller.enqueue(bytes);
35
- controller.close();
36
- },
37
- });
38
- }
39
- function fakeSpawnFn(config) {
40
- const state = { kills: 0 };
41
- const spawn = () => {
42
- if (config.throwSync)
43
- throw config.throwSync;
44
- let resolveExit = () => { };
45
- const exited = new Promise((resolve, reject) => {
46
- resolveExit = resolve;
47
- if (config.rejectExit) {
48
- reject(config.rejectExit);
49
- }
50
- else if (!config.hangsUntilKilled) {
51
- resolve(config.exitCode);
52
- }
53
- });
54
- const proc = {
55
- exitCode: config.hangsUntilKilled ? null : config.exitCode,
56
- exited,
57
- stdout: asReadableStream(config.stdout ?? ""),
58
- stderr: asReadableStream(config.stderr ?? ""),
59
- stdin: null,
60
- kill() {
61
- state.kills += 1;
62
- // Simulate process exit on signal.
63
- resolveExit(143);
64
- },
65
- };
66
- return proc;
67
- };
68
- return { spawn, kills: 0 };
69
- }
70
- describe("runAgent — captured stdio", () => {
71
- test("returns ok:true with stdout/stderr on exit 0", async () => {
72
- const { spawn } = fakeSpawnFn({ exitCode: 0, stdout: "hello\n", stderr: "" });
73
- const result = await runAgent(makeProfile(), "go", { spawn });
74
- expect(result.ok).toBe(true);
75
- expect(result.exitCode).toBe(0);
76
- expect(result.stdout).toBe("hello\n");
77
- expect(result.reason).toBeUndefined();
78
- expect(typeof result.durationMs).toBe("number");
79
- });
80
- test("non-zero exit yields structured `non_zero_exit`", async () => {
81
- const { spawn } = fakeSpawnFn({ exitCode: 7, stderr: "boom" });
82
- const result = await runAgent(makeProfile(), "go", { spawn });
83
- expect(result.ok).toBe(false);
84
- expect(result.reason).toBe("non_zero_exit");
85
- expect(result.exitCode).toBe(7);
86
- expect(result.stderr).toBe("boom");
87
- expect(result.error).toContain("exited with code 7");
88
- });
89
- test("synchronous spawn failure yields `spawn_failed`", async () => {
90
- const { spawn } = fakeSpawnFn({ exitCode: 0, throwSync: new Error("ENOENT: command not found") });
91
- const result = await runAgent(makeProfile(), "go", { spawn });
92
- expect(result.ok).toBe(false);
93
- expect(result.reason).toBe("spawn_failed");
94
- expect(result.error).toContain("ENOENT");
95
- expect(result.exitCode).toBeNull();
96
- });
97
- test("rejected proc.exited yields `spawn_failed`", async () => {
98
- const { spawn } = fakeSpawnFn({ exitCode: 0, rejectExit: new Error("kernel ate it") });
99
- const result = await runAgent(makeProfile(), "go", { spawn });
100
- expect(result.ok).toBe(false);
101
- expect(result.reason).toBe("spawn_failed");
102
- });
103
- });
104
- describe("runAgent — timeout", () => {
105
- test("kills the subprocess and reports `timeout`", async () => {
106
- // Drive the timer manually so the assertion is deterministic.
107
- let timerCallback;
108
- const fakeSet = ((cb) => {
109
- timerCallback = cb;
110
- return 1;
111
- });
112
- const fakeClear = (() => { });
113
- const { spawn } = fakeSpawnFn({ exitCode: 0, hangsUntilKilled: true });
114
- const promise = runAgent(makeProfile(), "go", {
115
- spawn,
116
- setTimeoutFn: fakeSet,
117
- clearTimeoutFn: fakeClear,
118
- timeoutMs: 100,
119
- });
120
- // Kick the deadline.
121
- expect(timerCallback).toBeDefined();
122
- timerCallback?.();
123
- const result = await promise;
124
- expect(result.ok).toBe(false);
125
- expect(result.reason).toBe("timeout");
126
- expect(result.error).toContain("100ms");
127
- });
128
- test("real timeout against `bun -e` sleeping past the deadline (deterministic & fast)", async () => {
129
- const profile = makeProfile({ bin: "bun", args: ["-e", "await new Promise(r => setTimeout(r, 5000))"] });
130
- const start = Date.now();
131
- const result = await runAgent(profile, undefined, { timeoutMs: 250 });
132
- const elapsed = Date.now() - start;
133
- expect(result.ok).toBe(false);
134
- expect(result.reason).toBe("timeout");
135
- // Should bail well before the 5-second sleep would complete.
136
- expect(elapsed).toBeLessThan(2000);
137
- });
138
- });
139
- describe("runAgent — JSON parse mode", () => {
140
- test("parses JSON stdout and surfaces it via `parsed`", async () => {
141
- const { spawn } = fakeSpawnFn({ exitCode: 0, stdout: '{"role":"agent"}' });
142
- const result = await runAgent(makeProfile({ parseOutput: "json" }), "go", { spawn });
143
- expect(result.ok).toBe(true);
144
- expect(result.parsed).toEqual({ role: "agent" });
145
- });
146
- test("malformed JSON yields `parse_error`", async () => {
147
- const { spawn } = fakeSpawnFn({ exitCode: 0, stdout: "not json {" });
148
- const result = await runAgent(makeProfile({ parseOutput: "json" }), "go", { spawn });
149
- expect(result.ok).toBe(false);
150
- expect(result.reason).toBe("parse_error");
151
- expect(result.error).toBeTruthy();
152
- });
153
- // ── #284 GAP-HIGH 10: parseOutput=json + non-zero exit + non-JSON stderr ──
154
- test("parseOutput=json + non-zero exit: non_zero_exit precedence (parse_error suppressed)", async () => {
155
- // Non-zero exit must surface as `non_zero_exit`, not `parse_error`, even
156
- // when the stdout/stderr payload is malformed JSON. The exit code is the
157
- // primary failure signal; parse failures are downstream of a successful run.
158
- const { spawn } = fakeSpawnFn({
159
- exitCode: 5,
160
- stdout: "not json {",
161
- stderr: "agent panic: kernel ate my JSON",
162
- });
163
- const result = await runAgent(makeProfile({ parseOutput: "json" }), "go", { spawn });
164
- expect(result.ok).toBe(false);
165
- expect(result.reason).toBe("non_zero_exit");
166
- expect(result.exitCode).toBe(5);
167
- expect(result.stderr).toBe("agent panic: kernel ate my JSON");
168
- expect(result.parsed).toBeUndefined();
169
- });
170
- });
171
- // ── #284 GAP-HIGH 11: timeoutMs precedence ────────────────────────────────
172
- describe("runAgent — timeoutMs precedence", () => {
173
- test("options.timeoutMs overrides profile.timeoutMs", async () => {
174
- // Both profile and options carry a timeoutMs. Options must win.
175
- let timerCallback;
176
- let observedDeadlineMs;
177
- const fakeSet = ((cb, ms) => {
178
- timerCallback = cb;
179
- observedDeadlineMs = ms;
180
- return 1;
181
- });
182
- const fakeClear = (() => { });
183
- const { spawn } = fakeSpawnFn({ exitCode: 0, hangsUntilKilled: true });
184
- const profile = makeProfile({ timeoutMs: 999_999 });
185
- const promise = runAgent(profile, "go", {
186
- spawn,
187
- setTimeoutFn: fakeSet,
188
- clearTimeoutFn: fakeClear,
189
- timeoutMs: 250,
190
- });
191
- expect(observedDeadlineMs).toBe(250); // override won
192
- timerCallback?.();
193
- const result = await promise;
194
- expect(result.reason).toBe("timeout");
195
- });
196
- });
197
- describe("runAgent — argument and env construction", () => {
198
- test("appends prompt after profile.args and options.args", async () => {
199
- let capturedCmd;
200
- const spawn = (cmd) => {
201
- capturedCmd = cmd;
202
- return {
203
- exitCode: 0,
204
- exited: Promise.resolve(0),
205
- stdout: asReadableStream(""),
206
- stderr: asReadableStream(""),
207
- stdin: null,
208
- kill() { },
209
- };
210
- };
211
- await runAgent(makeProfile({ args: ["--profile-arg"] }), "the-prompt", { spawn, args: ["--call-arg"] });
212
- expect(capturedCmd).toEqual(["test-agent", "--profile-arg", "--call-arg", "the-prompt"]);
213
- });
214
- test("env is filtered by envPassthrough plus profile/options env", async () => {
215
- let capturedEnv;
216
- const spawn = (_cmd, opts) => {
217
- capturedEnv = opts.env;
218
- return {
219
- exitCode: 0,
220
- exited: Promise.resolve(0),
221
- stdout: asReadableStream(""),
222
- stderr: asReadableStream(""),
223
- stdin: null,
224
- kill() { },
225
- };
226
- };
227
- await runAgent(makeProfile({ envPassthrough: ["KEEP_ME"], env: { PROFILE_VAR: "1" } }), undefined, {
228
- spawn,
229
- env: { CALL_VAR: "2" },
230
- envSource: { KEEP_ME: "yes", DROP_ME: "no" },
231
- });
232
- expect(capturedEnv).toEqual({ KEEP_ME: "yes", PROFILE_VAR: "1", CALL_VAR: "2" });
233
- });
234
- });