jonsoc 1.1.49 → 1.1.50

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 (421) hide show
  1. package/bin/jonsoc +264 -256
  2. package/package.json +8 -142
  3. package/postinstall.mjs +15 -0
  4. package/AGENTS.md +0 -27
  5. package/Dockerfile +0 -18
  6. package/PUBLISHING_GUIDE.md +0 -151
  7. package/README.md +0 -58
  8. package/bunfig.toml +0 -7
  9. package/package.json.placeholder +0 -11
  10. package/parsers-config.ts +0 -253
  11. package/script/build.ts +0 -115
  12. package/script/publish-registries.ts +0 -197
  13. package/script/publish.ts +0 -110
  14. package/script/schema.ts +0 -47
  15. package/script/seed-e2e.ts +0 -50
  16. package/src/acp/README.md +0 -164
  17. package/src/acp/agent.ts +0 -1437
  18. package/src/acp/session.ts +0 -105
  19. package/src/acp/types.ts +0 -22
  20. package/src/agent/agent.ts +0 -347
  21. package/src/agent/generate.txt +0 -75
  22. package/src/agent/prompt/compaction.txt +0 -12
  23. package/src/agent/prompt/explore.txt +0 -18
  24. package/src/agent/prompt/summary.txt +0 -11
  25. package/src/agent/prompt/title.txt +0 -44
  26. package/src/auth/index.ts +0 -73
  27. package/src/brand/index.ts +0 -89
  28. package/src/bun/index.ts +0 -139
  29. package/src/bus/bus-event.ts +0 -43
  30. package/src/bus/global.ts +0 -10
  31. package/src/bus/index.ts +0 -105
  32. package/src/cli/bootstrap.ts +0 -17
  33. package/src/cli/cmd/acp.ts +0 -69
  34. package/src/cli/cmd/agent.ts +0 -257
  35. package/src/cli/cmd/auth.ts +0 -405
  36. package/src/cli/cmd/cmd.ts +0 -7
  37. package/src/cli/cmd/debug/agent.ts +0 -166
  38. package/src/cli/cmd/debug/config.ts +0 -16
  39. package/src/cli/cmd/debug/file.ts +0 -97
  40. package/src/cli/cmd/debug/index.ts +0 -48
  41. package/src/cli/cmd/debug/lsp.ts +0 -52
  42. package/src/cli/cmd/debug/ripgrep.ts +0 -87
  43. package/src/cli/cmd/debug/scrap.ts +0 -16
  44. package/src/cli/cmd/debug/skill.ts +0 -16
  45. package/src/cli/cmd/debug/snapshot.ts +0 -52
  46. package/src/cli/cmd/export.ts +0 -88
  47. package/src/cli/cmd/generate.ts +0 -38
  48. package/src/cli/cmd/github.ts +0 -1548
  49. package/src/cli/cmd/import.ts +0 -99
  50. package/src/cli/cmd/mcp.ts +0 -765
  51. package/src/cli/cmd/models.ts +0 -77
  52. package/src/cli/cmd/pr.ts +0 -112
  53. package/src/cli/cmd/run.ts +0 -395
  54. package/src/cli/cmd/serve.ts +0 -20
  55. package/src/cli/cmd/session.ts +0 -135
  56. package/src/cli/cmd/stats.ts +0 -402
  57. package/src/cli/cmd/tui/app.tsx +0 -923
  58. package/src/cli/cmd/tui/attach.ts +0 -39
  59. package/src/cli/cmd/tui/component/border.tsx +0 -21
  60. package/src/cli/cmd/tui/component/dialog-agent.tsx +0 -31
  61. package/src/cli/cmd/tui/component/dialog-command.tsx +0 -162
  62. package/src/cli/cmd/tui/component/dialog-error-log.tsx +0 -155
  63. package/src/cli/cmd/tui/component/dialog-mcp.tsx +0 -86
  64. package/src/cli/cmd/tui/component/dialog-model.tsx +0 -234
  65. package/src/cli/cmd/tui/component/dialog-provider.tsx +0 -256
  66. package/src/cli/cmd/tui/component/dialog-session-list.tsx +0 -114
  67. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +0 -31
  68. package/src/cli/cmd/tui/component/dialog-stash.tsx +0 -87
  69. package/src/cli/cmd/tui/component/dialog-status.tsx +0 -164
  70. package/src/cli/cmd/tui/component/dialog-tag.tsx +0 -44
  71. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +0 -50
  72. package/src/cli/cmd/tui/component/dynamic-layout.tsx +0 -86
  73. package/src/cli/cmd/tui/component/inspector-overlay.tsx +0 -247
  74. package/src/cli/cmd/tui/component/logo.tsx +0 -88
  75. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +0 -653
  76. package/src/cli/cmd/tui/component/prompt/frecency.tsx +0 -89
  77. package/src/cli/cmd/tui/component/prompt/history.tsx +0 -108
  78. package/src/cli/cmd/tui/component/prompt/index.tsx +0 -1347
  79. package/src/cli/cmd/tui/component/prompt/stash.tsx +0 -101
  80. package/src/cli/cmd/tui/component/textarea-keybindings.ts +0 -73
  81. package/src/cli/cmd/tui/component/tips.tsx +0 -153
  82. package/src/cli/cmd/tui/component/todo-item.tsx +0 -32
  83. package/src/cli/cmd/tui/context/args.tsx +0 -14
  84. package/src/cli/cmd/tui/context/directory.ts +0 -13
  85. package/src/cli/cmd/tui/context/error-log.tsx +0 -56
  86. package/src/cli/cmd/tui/context/exit.tsx +0 -26
  87. package/src/cli/cmd/tui/context/helper.tsx +0 -25
  88. package/src/cli/cmd/tui/context/inspector.tsx +0 -57
  89. package/src/cli/cmd/tui/context/keybind.tsx +0 -108
  90. package/src/cli/cmd/tui/context/kv.tsx +0 -53
  91. package/src/cli/cmd/tui/context/layout.tsx +0 -240
  92. package/src/cli/cmd/tui/context/local.tsx +0 -402
  93. package/src/cli/cmd/tui/context/prompt.tsx +0 -18
  94. package/src/cli/cmd/tui/context/route.tsx +0 -51
  95. package/src/cli/cmd/tui/context/sdk.tsx +0 -94
  96. package/src/cli/cmd/tui/context/sync.tsx +0 -449
  97. package/src/cli/cmd/tui/context/theme/aura.json +0 -69
  98. package/src/cli/cmd/tui/context/theme/ayu.json +0 -80
  99. package/src/cli/cmd/tui/context/theme/carbonfox.json +0 -248
  100. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +0 -233
  101. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +0 -233
  102. package/src/cli/cmd/tui/context/theme/catppuccin.json +0 -112
  103. package/src/cli/cmd/tui/context/theme/cobalt2.json +0 -228
  104. package/src/cli/cmd/tui/context/theme/cursor.json +0 -249
  105. package/src/cli/cmd/tui/context/theme/dracula.json +0 -219
  106. package/src/cli/cmd/tui/context/theme/everforest.json +0 -241
  107. package/src/cli/cmd/tui/context/theme/flexoki.json +0 -237
  108. package/src/cli/cmd/tui/context/theme/github.json +0 -233
  109. package/src/cli/cmd/tui/context/theme/gruvbox.json +0 -242
  110. package/src/cli/cmd/tui/context/theme/jonsoc.json +0 -245
  111. package/src/cli/cmd/tui/context/theme/kanagawa.json +0 -77
  112. package/src/cli/cmd/tui/context/theme/lucent-orng.json +0 -237
  113. package/src/cli/cmd/tui/context/theme/material.json +0 -235
  114. package/src/cli/cmd/tui/context/theme/matrix.json +0 -77
  115. package/src/cli/cmd/tui/context/theme/mercury.json +0 -252
  116. package/src/cli/cmd/tui/context/theme/monokai.json +0 -221
  117. package/src/cli/cmd/tui/context/theme/nightowl.json +0 -221
  118. package/src/cli/cmd/tui/context/theme/nord.json +0 -223
  119. package/src/cli/cmd/tui/context/theme/one-dark.json +0 -84
  120. package/src/cli/cmd/tui/context/theme/orng.json +0 -249
  121. package/src/cli/cmd/tui/context/theme/osaka-jade.json +0 -93
  122. package/src/cli/cmd/tui/context/theme/palenight.json +0 -222
  123. package/src/cli/cmd/tui/context/theme/rosepine.json +0 -234
  124. package/src/cli/cmd/tui/context/theme/solarized.json +0 -223
  125. package/src/cli/cmd/tui/context/theme/synthwave84.json +0 -226
  126. package/src/cli/cmd/tui/context/theme/tokyonight.json +0 -243
  127. package/src/cli/cmd/tui/context/theme/vercel.json +0 -245
  128. package/src/cli/cmd/tui/context/theme/vesper.json +0 -218
  129. package/src/cli/cmd/tui/context/theme/zenburn.json +0 -223
  130. package/src/cli/cmd/tui/context/theme.tsx +0 -1153
  131. package/src/cli/cmd/tui/event.ts +0 -48
  132. package/src/cli/cmd/tui/hooks/use-command-registry.tsx +0 -184
  133. package/src/cli/cmd/tui/routes/home.tsx +0 -198
  134. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +0 -64
  135. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +0 -109
  136. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +0 -26
  137. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +0 -47
  138. package/src/cli/cmd/tui/routes/session/footer.tsx +0 -91
  139. package/src/cli/cmd/tui/routes/session/git-commit.tsx +0 -59
  140. package/src/cli/cmd/tui/routes/session/git-history.tsx +0 -122
  141. package/src/cli/cmd/tui/routes/session/header.tsx +0 -185
  142. package/src/cli/cmd/tui/routes/session/index.tsx +0 -2363
  143. package/src/cli/cmd/tui/routes/session/navigator-ui.tsx +0 -214
  144. package/src/cli/cmd/tui/routes/session/navigator.tsx +0 -1124
  145. package/src/cli/cmd/tui/routes/session/panel-explorer.tsx +0 -553
  146. package/src/cli/cmd/tui/routes/session/panel-viewer.tsx +0 -386
  147. package/src/cli/cmd/tui/routes/session/permission.tsx +0 -501
  148. package/src/cli/cmd/tui/routes/session/question.tsx +0 -507
  149. package/src/cli/cmd/tui/routes/session/sidebar.tsx +0 -365
  150. package/src/cli/cmd/tui/routes/session/vcs-diff-viewer.tsx +0 -37
  151. package/src/cli/cmd/tui/routes/ui-settings.tsx +0 -449
  152. package/src/cli/cmd/tui/thread.ts +0 -172
  153. package/src/cli/cmd/tui/ui/dialog-alert.tsx +0 -90
  154. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +0 -83
  155. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +0 -204
  156. package/src/cli/cmd/tui/ui/dialog-help.tsx +0 -38
  157. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +0 -77
  158. package/src/cli/cmd/tui/ui/dialog-select.tsx +0 -384
  159. package/src/cli/cmd/tui/ui/dialog.tsx +0 -170
  160. package/src/cli/cmd/tui/ui/link.tsx +0 -28
  161. package/src/cli/cmd/tui/ui/spinner.ts +0 -375
  162. package/src/cli/cmd/tui/ui/toast.tsx +0 -100
  163. package/src/cli/cmd/tui/util/clipboard.ts +0 -255
  164. package/src/cli/cmd/tui/util/editor.ts +0 -32
  165. package/src/cli/cmd/tui/util/signal.ts +0 -7
  166. package/src/cli/cmd/tui/util/terminal.ts +0 -114
  167. package/src/cli/cmd/tui/util/transcript.ts +0 -98
  168. package/src/cli/cmd/tui/worker.ts +0 -152
  169. package/src/cli/cmd/uninstall.ts +0 -362
  170. package/src/cli/cmd/upgrade.ts +0 -73
  171. package/src/cli/cmd/web.ts +0 -81
  172. package/src/cli/error.ts +0 -57
  173. package/src/cli/network.ts +0 -53
  174. package/src/cli/ui.ts +0 -119
  175. package/src/cli/upgrade.ts +0 -25
  176. package/src/command/index.ts +0 -131
  177. package/src/command/template/initialize.txt +0 -10
  178. package/src/command/template/review.txt +0 -99
  179. package/src/config/config.ts +0 -1404
  180. package/src/config/markdown.ts +0 -93
  181. package/src/env/index.ts +0 -26
  182. package/src/file/ignore.ts +0 -83
  183. package/src/file/index.ts +0 -432
  184. package/src/file/ripgrep.ts +0 -407
  185. package/src/file/time.ts +0 -69
  186. package/src/file/watcher.ts +0 -127
  187. package/src/flag/flag.ts +0 -80
  188. package/src/format/formatter.ts +0 -357
  189. package/src/format/index.ts +0 -137
  190. package/src/global/index.ts +0 -58
  191. package/src/id/id.ts +0 -83
  192. package/src/ide/index.ts +0 -76
  193. package/src/index.ts +0 -208
  194. package/src/installation/index.ts +0 -258
  195. package/src/lsp/client.ts +0 -252
  196. package/src/lsp/index.ts +0 -485
  197. package/src/lsp/language.ts +0 -119
  198. package/src/lsp/server.ts +0 -2046
  199. package/src/mcp/auth.ts +0 -135
  200. package/src/mcp/index.ts +0 -934
  201. package/src/mcp/oauth-callback.ts +0 -200
  202. package/src/mcp/oauth-provider.ts +0 -155
  203. package/src/patch/index.ts +0 -680
  204. package/src/permission/arity.ts +0 -163
  205. package/src/permission/index.ts +0 -210
  206. package/src/permission/next.ts +0 -280
  207. package/src/plugin/codex.ts +0 -500
  208. package/src/plugin/copilot.ts +0 -283
  209. package/src/plugin/index.ts +0 -135
  210. package/src/project/bootstrap.ts +0 -35
  211. package/src/project/instance.ts +0 -91
  212. package/src/project/project.ts +0 -371
  213. package/src/project/state.ts +0 -66
  214. package/src/project/vcs.ts +0 -151
  215. package/src/provider/auth.ts +0 -147
  216. package/src/provider/models-macro.ts +0 -14
  217. package/src/provider/models.ts +0 -114
  218. package/src/provider/provider.ts +0 -1220
  219. package/src/provider/sdk/openai-compatible/src/README.md +0 -5
  220. package/src/provider/sdk/openai-compatible/src/index.ts +0 -2
  221. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +0 -100
  222. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +0 -303
  223. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +0 -22
  224. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +0 -18
  225. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +0 -22
  226. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +0 -207
  227. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +0 -1732
  228. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +0 -177
  229. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +0 -1
  230. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +0 -88
  231. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +0 -128
  232. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +0 -115
  233. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +0 -65
  234. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +0 -104
  235. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +0 -103
  236. package/src/provider/transform.ts +0 -742
  237. package/src/pty/index.ts +0 -241
  238. package/src/question/index.ts +0 -176
  239. package/src/scheduler/index.ts +0 -61
  240. package/src/server/error.ts +0 -36
  241. package/src/server/event.ts +0 -7
  242. package/src/server/mdns.ts +0 -59
  243. package/src/server/routes/config.ts +0 -92
  244. package/src/server/routes/experimental.ts +0 -208
  245. package/src/server/routes/file.ts +0 -227
  246. package/src/server/routes/global.ts +0 -135
  247. package/src/server/routes/mcp.ts +0 -225
  248. package/src/server/routes/permission.ts +0 -68
  249. package/src/server/routes/project.ts +0 -82
  250. package/src/server/routes/provider.ts +0 -165
  251. package/src/server/routes/pty.ts +0 -169
  252. package/src/server/routes/question.ts +0 -98
  253. package/src/server/routes/session.ts +0 -939
  254. package/src/server/routes/tui.ts +0 -379
  255. package/src/server/server.ts +0 -663
  256. package/src/session/compaction.ts +0 -225
  257. package/src/session/index.ts +0 -498
  258. package/src/session/llm.ts +0 -288
  259. package/src/session/message-v2.ts +0 -740
  260. package/src/session/message.ts +0 -189
  261. package/src/session/processor.ts +0 -406
  262. package/src/session/prompt/anthropic-20250930.txt +0 -168
  263. package/src/session/prompt/anthropic.txt +0 -172
  264. package/src/session/prompt/anthropic_spoof.txt +0 -1
  265. package/src/session/prompt/beast.txt +0 -149
  266. package/src/session/prompt/build-switch.txt +0 -5
  267. package/src/session/prompt/codex_header.txt +0 -81
  268. package/src/session/prompt/copilot-gpt-5.txt +0 -145
  269. package/src/session/prompt/gemini.txt +0 -157
  270. package/src/session/prompt/max-steps.txt +0 -16
  271. package/src/session/prompt/plan-reminder-anthropic.txt +0 -67
  272. package/src/session/prompt/plan.txt +0 -26
  273. package/src/session/prompt/qwen.txt +0 -111
  274. package/src/session/prompt.ts +0 -1815
  275. package/src/session/retry.ts +0 -90
  276. package/src/session/revert.ts +0 -121
  277. package/src/session/status.ts +0 -76
  278. package/src/session/summary.ts +0 -150
  279. package/src/session/system.ts +0 -156
  280. package/src/session/todo.ts +0 -37
  281. package/src/share/share-next.ts +0 -204
  282. package/src/share/share.ts +0 -95
  283. package/src/shell/shell.ts +0 -67
  284. package/src/skill/index.ts +0 -1
  285. package/src/skill/skill.ts +0 -135
  286. package/src/snapshot/index.ts +0 -236
  287. package/src/storage/storage.ts +0 -227
  288. package/src/tool/apply_patch.ts +0 -279
  289. package/src/tool/apply_patch.txt +0 -33
  290. package/src/tool/bash.ts +0 -258
  291. package/src/tool/bash.txt +0 -115
  292. package/src/tool/batch.ts +0 -175
  293. package/src/tool/batch.txt +0 -24
  294. package/src/tool/codesearch.ts +0 -132
  295. package/src/tool/codesearch.txt +0 -12
  296. package/src/tool/edit.ts +0 -645
  297. package/src/tool/edit.txt +0 -10
  298. package/src/tool/external-directory.ts +0 -32
  299. package/src/tool/glob.ts +0 -77
  300. package/src/tool/glob.txt +0 -6
  301. package/src/tool/grep.ts +0 -154
  302. package/src/tool/grep.txt +0 -8
  303. package/src/tool/invalid.ts +0 -17
  304. package/src/tool/ls.ts +0 -121
  305. package/src/tool/ls.txt +0 -1
  306. package/src/tool/lsp.ts +0 -96
  307. package/src/tool/lsp.txt +0 -19
  308. package/src/tool/multiedit.ts +0 -46
  309. package/src/tool/multiedit.txt +0 -41
  310. package/src/tool/plan-enter.txt +0 -14
  311. package/src/tool/plan-exit.txt +0 -13
  312. package/src/tool/plan.ts +0 -130
  313. package/src/tool/question.ts +0 -33
  314. package/src/tool/question.txt +0 -10
  315. package/src/tool/read.ts +0 -202
  316. package/src/tool/read.txt +0 -12
  317. package/src/tool/registry.ts +0 -162
  318. package/src/tool/skill.ts +0 -82
  319. package/src/tool/task.ts +0 -188
  320. package/src/tool/task.txt +0 -60
  321. package/src/tool/todo.ts +0 -53
  322. package/src/tool/todoread.txt +0 -14
  323. package/src/tool/todowrite.txt +0 -167
  324. package/src/tool/tool.ts +0 -88
  325. package/src/tool/truncation.ts +0 -106
  326. package/src/tool/webfetch.ts +0 -182
  327. package/src/tool/webfetch.txt +0 -13
  328. package/src/tool/websearch.ts +0 -150
  329. package/src/tool/websearch.txt +0 -14
  330. package/src/tool/write.ts +0 -80
  331. package/src/tool/write.txt +0 -8
  332. package/src/util/archive.ts +0 -16
  333. package/src/util/color.ts +0 -19
  334. package/src/util/context.ts +0 -25
  335. package/src/util/defer.ts +0 -12
  336. package/src/util/eventloop.ts +0 -20
  337. package/src/util/filesystem.ts +0 -93
  338. package/src/util/fn.ts +0 -11
  339. package/src/util/format.ts +0 -20
  340. package/src/util/iife.ts +0 -3
  341. package/src/util/keybind.ts +0 -103
  342. package/src/util/lazy.ts +0 -18
  343. package/src/util/locale.ts +0 -81
  344. package/src/util/lock.ts +0 -98
  345. package/src/util/log.ts +0 -180
  346. package/src/util/queue.ts +0 -32
  347. package/src/util/rpc.ts +0 -66
  348. package/src/util/scrap.ts +0 -10
  349. package/src/util/signal.ts +0 -12
  350. package/src/util/timeout.ts +0 -14
  351. package/src/util/token.ts +0 -7
  352. package/src/util/wildcard.ts +0 -56
  353. package/src/worktree/index.ts +0 -524
  354. package/sst-env.d.ts +0 -9
  355. package/test/acp/agent-interface.test.ts +0 -51
  356. package/test/acp/event-subscription.test.ts +0 -436
  357. package/test/agent/agent.test.ts +0 -638
  358. package/test/bun.test.ts +0 -53
  359. package/test/cli/cmd/tui/fileref.test.ts +0 -30
  360. package/test/cli/github-action.test.ts +0 -129
  361. package/test/cli/github-remote.test.ts +0 -80
  362. package/test/cli/tui/navigator_logic.test.ts +0 -99
  363. package/test/cli/tui/transcript.test.ts +0 -297
  364. package/test/cli/ui.test.ts +0 -80
  365. package/test/config/agent-color.test.ts +0 -66
  366. package/test/config/config.test.ts +0 -1613
  367. package/test/config/fixtures/empty-frontmatter.md +0 -4
  368. package/test/config/fixtures/frontmatter.md +0 -28
  369. package/test/config/fixtures/no-frontmatter.md +0 -1
  370. package/test/config/markdown.test.ts +0 -192
  371. package/test/file/ignore.test.ts +0 -10
  372. package/test/file/path-traversal.test.ts +0 -198
  373. package/test/fixture/fixture.ts +0 -45
  374. package/test/fixture/lsp/fake-lsp-server.js +0 -77
  375. package/test/ide/ide.test.ts +0 -82
  376. package/test/keybind.test.ts +0 -421
  377. package/test/lsp/client.test.ts +0 -95
  378. package/test/mcp/headers.test.ts +0 -153
  379. package/test/mcp/oauth-browser.test.ts +0 -261
  380. package/test/patch/patch.test.ts +0 -348
  381. package/test/permission/arity.test.ts +0 -33
  382. package/test/permission/next.test.ts +0 -690
  383. package/test/permission-task.test.ts +0 -319
  384. package/test/plugin/codex.test.ts +0 -123
  385. package/test/preload.ts +0 -67
  386. package/test/project/project.test.ts +0 -120
  387. package/test/provider/amazon-bedrock.test.ts +0 -268
  388. package/test/provider/gitlab-duo.test.ts +0 -286
  389. package/test/provider/provider.test.ts +0 -2149
  390. package/test/provider/transform.test.ts +0 -1631
  391. package/test/question/question.test.ts +0 -300
  392. package/test/scheduler.test.ts +0 -73
  393. package/test/server/session-list.test.ts +0 -39
  394. package/test/server/session-select.test.ts +0 -78
  395. package/test/session/compaction.test.ts +0 -293
  396. package/test/session/llm.test.ts +0 -90
  397. package/test/session/message-v2.test.ts +0 -786
  398. package/test/session/retry.test.ts +0 -131
  399. package/test/session/revert-compact.test.ts +0 -285
  400. package/test/session/session.test.ts +0 -71
  401. package/test/skill/skill.test.ts +0 -185
  402. package/test/snapshot/snapshot.test.ts +0 -939
  403. package/test/tool/__snapshots__/tool.test.ts.snap +0 -9
  404. package/test/tool/apply_patch.test.ts +0 -499
  405. package/test/tool/bash.test.ts +0 -320
  406. package/test/tool/external-directory.test.ts +0 -126
  407. package/test/tool/fixtures/large-image.png +0 -0
  408. package/test/tool/fixtures/models-api.json +0 -33453
  409. package/test/tool/grep.test.ts +0 -109
  410. package/test/tool/question.test.ts +0 -105
  411. package/test/tool/read.test.ts +0 -332
  412. package/test/tool/registry.test.ts +0 -76
  413. package/test/tool/truncation.test.ts +0 -159
  414. package/test/util/filesystem.test.ts +0 -39
  415. package/test/util/format.test.ts +0 -59
  416. package/test/util/iife.test.ts +0 -36
  417. package/test/util/lazy.test.ts +0 -50
  418. package/test/util/lock.test.ts +0 -72
  419. package/test/util/timeout.test.ts +0 -21
  420. package/test/util/wildcard.test.ts +0 -75
  421. package/tsconfig.json +0 -16
@@ -1,1613 +0,0 @@
1
- import { test, expect, describe, mock } from "bun:test"
2
- import { Config } from "../../src/config/config"
3
- import { Instance } from "../../src/project/instance"
4
- import { Auth } from "../../src/auth"
5
- import { tmpdir } from "../fixture/fixture"
6
- import path from "path"
7
- import fs from "fs/promises"
8
- import { pathToFileURL } from "url"
9
-
10
- test("loads config with defaults when no files exist", async () => {
11
- await using tmp = await tmpdir()
12
- await Instance.provide({
13
- directory: tmp.path,
14
- fn: async () => {
15
- const config = await Config.get()
16
- expect(config.username).toBeDefined()
17
- },
18
- })
19
- })
20
-
21
- test("loads JSON config file", async () => {
22
- await using tmp = await tmpdir({
23
- init: async (dir) => {
24
- await Bun.write(
25
- path.join(dir, "jonsoc.json"),
26
- JSON.stringify({
27
- $schema: "https://jonsoc.com/config.json",
28
- model: "test/model",
29
- username: "testuser",
30
- }),
31
- )
32
- },
33
- })
34
- await Instance.provide({
35
- directory: tmp.path,
36
- fn: async () => {
37
- const config = await Config.get()
38
- expect(config.model).toBe("test/model")
39
- expect(config.username).toBe("testuser")
40
- },
41
- })
42
- })
43
-
44
- test("loads JSONC config file", async () => {
45
- await using tmp = await tmpdir({
46
- init: async (dir) => {
47
- await Bun.write(
48
- path.join(dir, "jonsoc.jsonc"),
49
- `{
50
- // This is a comment
51
- "$schema": "https://jonsoc.com/config.json",
52
- "model": "test/model",
53
- "username": "testuser"
54
- }`,
55
- )
56
- },
57
- })
58
- await Instance.provide({
59
- directory: tmp.path,
60
- fn: async () => {
61
- const config = await Config.get()
62
- expect(config.model).toBe("test/model")
63
- expect(config.username).toBe("testuser")
64
- },
65
- })
66
- })
67
-
68
- test("merges multiple config files with correct precedence", async () => {
69
- await using tmp = await tmpdir({
70
- init: async (dir) => {
71
- await Bun.write(
72
- path.join(dir, "jonsoc.jsonc"),
73
- JSON.stringify({
74
- $schema: "https://jonsoc.com/config.json",
75
- model: "base",
76
- username: "base",
77
- }),
78
- )
79
- await Bun.write(
80
- path.join(dir, "jonsoc.json"),
81
- JSON.stringify({
82
- $schema: "https://jonsoc.com/config.json",
83
- model: "override",
84
- }),
85
- )
86
- },
87
- })
88
- await Instance.provide({
89
- directory: tmp.path,
90
- fn: async () => {
91
- const config = await Config.get()
92
- expect(config.model).toBe("override")
93
- expect(config.username).toBe("base")
94
- },
95
- })
96
- })
97
-
98
- test("handles environment variable substitution", async () => {
99
- const originalEnv = process.env["TEST_VAR"]
100
- process.env["TEST_VAR"] = "test_theme"
101
-
102
- try {
103
- await using tmp = await tmpdir({
104
- init: async (dir) => {
105
- await Bun.write(
106
- path.join(dir, "jonsoc.json"),
107
- JSON.stringify({
108
- $schema: "https://jonsoc.com/config.json",
109
- theme: "{env:TEST_VAR}",
110
- }),
111
- )
112
- },
113
- })
114
- await Instance.provide({
115
- directory: tmp.path,
116
- fn: async () => {
117
- const config = await Config.get()
118
- expect(config.theme).toBe("test_theme")
119
- },
120
- })
121
- } finally {
122
- if (originalEnv !== undefined) {
123
- process.env["TEST_VAR"] = originalEnv
124
- } else {
125
- delete process.env["TEST_VAR"]
126
- }
127
- }
128
- })
129
-
130
- test("preserves env variables when adding $schema to config", async () => {
131
- const originalEnv = process.env["PRESERVE_VAR"]
132
- process.env["PRESERVE_VAR"] = "secret_value"
133
-
134
- try {
135
- await using tmp = await tmpdir({
136
- init: async (dir) => {
137
- // Config without $schema - should trigger auto-add
138
- await Bun.write(
139
- path.join(dir, "jonsoc.json"),
140
- JSON.stringify({
141
- theme: "{env:PRESERVE_VAR}",
142
- }),
143
- )
144
- },
145
- })
146
- await Instance.provide({
147
- directory: tmp.path,
148
- fn: async () => {
149
- const config = await Config.get()
150
- expect(config.theme).toBe("secret_value")
151
-
152
- // Read the file to verify the env variable was preserved
153
- const content = await Bun.file(path.join(tmp.path, "jonsoc.json")).text()
154
- expect(content).toContain("{env:PRESERVE_VAR}")
155
- expect(content).not.toContain("secret_value")
156
- expect(content).toContain("$schema")
157
- },
158
- })
159
- } finally {
160
- if (originalEnv !== undefined) {
161
- process.env["PRESERVE_VAR"] = originalEnv
162
- } else {
163
- delete process.env["PRESERVE_VAR"]
164
- }
165
- }
166
- })
167
-
168
- test("handles file inclusion substitution", async () => {
169
- await using tmp = await tmpdir({
170
- init: async (dir) => {
171
- await Bun.write(path.join(dir, "included.txt"), "test_theme")
172
- await Bun.write(
173
- path.join(dir, "jonsoc.json"),
174
- JSON.stringify({
175
- $schema: "https://jonsoc.com/config.json",
176
- theme: "{file:included.txt}",
177
- }),
178
- )
179
- },
180
- })
181
- await Instance.provide({
182
- directory: tmp.path,
183
- fn: async () => {
184
- const config = await Config.get()
185
- expect(config.theme).toBe("test_theme")
186
- },
187
- })
188
- })
189
-
190
- test("validates config schema and throws on invalid fields", async () => {
191
- await using tmp = await tmpdir({
192
- init: async (dir) => {
193
- await Bun.write(
194
- path.join(dir, "jonsoc.json"),
195
- JSON.stringify({
196
- $schema: "https://jonsoc.com/config.json",
197
- invalid_field: "should cause error",
198
- }),
199
- )
200
- },
201
- })
202
- await Instance.provide({
203
- directory: tmp.path,
204
- fn: async () => {
205
- // Strict schema should throw an error for invalid fields
206
- await expect(Config.get()).rejects.toThrow()
207
- },
208
- })
209
- })
210
-
211
- test("throws error for invalid JSON", async () => {
212
- await using tmp = await tmpdir({
213
- init: async (dir) => {
214
- await Bun.write(path.join(dir, "jonsoc.json"), "{ invalid json }")
215
- },
216
- })
217
- await Instance.provide({
218
- directory: tmp.path,
219
- fn: async () => {
220
- await expect(Config.get()).rejects.toThrow()
221
- },
222
- })
223
- })
224
-
225
- test("handles agent configuration", async () => {
226
- await using tmp = await tmpdir({
227
- init: async (dir) => {
228
- await Bun.write(
229
- path.join(dir, "jonsoc.json"),
230
- JSON.stringify({
231
- $schema: "https://jonsoc.com/config.json",
232
- agent: {
233
- test_agent: {
234
- model: "test/model",
235
- temperature: 0.7,
236
- description: "test agent",
237
- },
238
- },
239
- }),
240
- )
241
- },
242
- })
243
- await Instance.provide({
244
- directory: tmp.path,
245
- fn: async () => {
246
- const config = await Config.get()
247
- expect(config.agent?.["test_agent"]).toEqual(
248
- expect.objectContaining({
249
- model: "test/model",
250
- temperature: 0.7,
251
- description: "test agent",
252
- }),
253
- )
254
- },
255
- })
256
- })
257
-
258
- test("handles command configuration", async () => {
259
- await using tmp = await tmpdir({
260
- init: async (dir) => {
261
- await Bun.write(
262
- path.join(dir, "jonsoc.json"),
263
- JSON.stringify({
264
- $schema: "https://jonsoc.com/config.json",
265
- command: {
266
- test_command: {
267
- template: "test template",
268
- description: "test command",
269
- agent: "test_agent",
270
- },
271
- },
272
- }),
273
- )
274
- },
275
- })
276
- await Instance.provide({
277
- directory: tmp.path,
278
- fn: async () => {
279
- const config = await Config.get()
280
- expect(config.command?.["test_command"]).toEqual({
281
- template: "test template",
282
- description: "test command",
283
- agent: "test_agent",
284
- })
285
- },
286
- })
287
- })
288
-
289
- test("migrates autoshare to share field", async () => {
290
- await using tmp = await tmpdir({
291
- init: async (dir) => {
292
- await Bun.write(
293
- path.join(dir, "jonsoc.json"),
294
- JSON.stringify({
295
- $schema: "https://jonsoc.com/config.json",
296
- autoshare: true,
297
- }),
298
- )
299
- },
300
- })
301
- await Instance.provide({
302
- directory: tmp.path,
303
- fn: async () => {
304
- const config = await Config.get()
305
- expect(config.share).toBe("auto")
306
- expect(config.autoshare).toBe(true)
307
- },
308
- })
309
- })
310
-
311
- test("migrates mode field to agent field", async () => {
312
- await using tmp = await tmpdir({
313
- init: async (dir) => {
314
- await Bun.write(
315
- path.join(dir, "jonsoc.json"),
316
- JSON.stringify({
317
- $schema: "https://jonsoc.com/config.json",
318
- mode: {
319
- test_mode: {
320
- model: "test/model",
321
- temperature: 0.5,
322
- },
323
- },
324
- }),
325
- )
326
- },
327
- })
328
- await Instance.provide({
329
- directory: tmp.path,
330
- fn: async () => {
331
- const config = await Config.get()
332
- expect(config.agent?.["test_mode"]).toEqual({
333
- model: "test/model",
334
- temperature: 0.5,
335
- mode: "primary",
336
- options: {},
337
- permission: {},
338
- })
339
- },
340
- })
341
- })
342
-
343
- test("loads config from .jonsoc directory", async () => {
344
- await using tmp = await tmpdir({
345
- init: async (dir) => {
346
- const jonsocDir = path.join(dir, ".jonsoc")
347
- await fs.mkdir(jonsocDir, { recursive: true })
348
- const agentDir = path.join(jonsocDir, "agent")
349
- await fs.mkdir(agentDir, { recursive: true })
350
-
351
- await Bun.write(
352
- path.join(agentDir, "test.md"),
353
- `---
354
- model: test/model
355
- ---
356
- Test agent prompt`,
357
- )
358
- },
359
- })
360
- await Instance.provide({
361
- directory: tmp.path,
362
- fn: async () => {
363
- const config = await Config.get()
364
- expect(config.agent?.["test"]).toEqual(
365
- expect.objectContaining({
366
- name: "test",
367
- model: "test/model",
368
- prompt: "Test agent prompt",
369
- }),
370
- )
371
- },
372
- })
373
- })
374
-
375
- test("loads agents from .jonsoc/agents (plural)", async () => {
376
- await using tmp = await tmpdir({
377
- init: async (dir) => {
378
- const jonsocDir = path.join(dir, ".jonsoc")
379
- await fs.mkdir(jonsocDir, { recursive: true })
380
-
381
- const agentsDir = path.join(jonsocDir, "agents")
382
- await fs.mkdir(path.join(agentsDir, "nested"), { recursive: true })
383
-
384
- await Bun.write(
385
- path.join(agentsDir, "helper.md"),
386
- `---
387
- model: test/model
388
- mode: subagent
389
- ---
390
- Helper agent prompt`,
391
- )
392
-
393
- await Bun.write(
394
- path.join(agentsDir, "nested", "child.md"),
395
- `---
396
- model: test/model
397
- mode: subagent
398
- ---
399
- Nested agent prompt`,
400
- )
401
- },
402
- })
403
-
404
- await Instance.provide({
405
- directory: tmp.path,
406
- fn: async () => {
407
- const config = await Config.get()
408
-
409
- expect(config.agent?.["helper"]).toMatchObject({
410
- name: "helper",
411
- model: "test/model",
412
- mode: "subagent",
413
- prompt: "Helper agent prompt",
414
- })
415
-
416
- expect(config.agent?.["nested/child"]).toMatchObject({
417
- name: "nested/child",
418
- model: "test/model",
419
- mode: "subagent",
420
- prompt: "Nested agent prompt",
421
- })
422
- },
423
- })
424
- })
425
-
426
- test("loads commands from .jonsoc/command (singular)", async () => {
427
- await using tmp = await tmpdir({
428
- init: async (dir) => {
429
- const jonsocDir = path.join(dir, ".jonsoc")
430
- await fs.mkdir(jonsocDir, { recursive: true })
431
-
432
- const commandDir = path.join(jonsocDir, "command")
433
- await fs.mkdir(path.join(commandDir, "nested"), { recursive: true })
434
-
435
- await Bun.write(
436
- path.join(commandDir, "hello.md"),
437
- `---
438
- description: Test command
439
- ---
440
- Hello from singular command`,
441
- )
442
-
443
- await Bun.write(
444
- path.join(commandDir, "nested", "child.md"),
445
- `---
446
- description: Nested command
447
- ---
448
- Nested command template`,
449
- )
450
- },
451
- })
452
-
453
- await Instance.provide({
454
- directory: tmp.path,
455
- fn: async () => {
456
- const config = await Config.get()
457
-
458
- expect(config.command?.["hello"]).toEqual({
459
- description: "Test command",
460
- template: "Hello from singular command",
461
- })
462
-
463
- expect(config.command?.["nested/child"]).toEqual({
464
- description: "Nested command",
465
- template: "Nested command template",
466
- })
467
- },
468
- })
469
- })
470
-
471
- test("loads commands from .jonsoc/commands (plural)", async () => {
472
- await using tmp = await tmpdir({
473
- init: async (dir) => {
474
- const jonsocDir = path.join(dir, ".jonsoc")
475
- await fs.mkdir(jonsocDir, { recursive: true })
476
-
477
- const commandsDir = path.join(jonsocDir, "commands")
478
- await fs.mkdir(path.join(commandsDir, "nested"), { recursive: true })
479
-
480
- await Bun.write(
481
- path.join(commandsDir, "hello.md"),
482
- `---
483
- description: Test command
484
- ---
485
- Hello from plural commands`,
486
- )
487
-
488
- await Bun.write(
489
- path.join(commandsDir, "nested", "child.md"),
490
- `---
491
- description: Nested command
492
- ---
493
- Nested command template`,
494
- )
495
- },
496
- })
497
-
498
- await Instance.provide({
499
- directory: tmp.path,
500
- fn: async () => {
501
- const config = await Config.get()
502
-
503
- expect(config.command?.["hello"]).toEqual({
504
- description: "Test command",
505
- template: "Hello from plural commands",
506
- })
507
-
508
- expect(config.command?.["nested/child"]).toEqual({
509
- description: "Nested command",
510
- template: "Nested command template",
511
- })
512
- },
513
- })
514
- })
515
-
516
- test("updates config and writes to file", async () => {
517
- await using tmp = await tmpdir()
518
- await Instance.provide({
519
- directory: tmp.path,
520
- fn: async () => {
521
- const newConfig = { model: "updated/model" }
522
- await Config.update(newConfig as any)
523
-
524
- const writtenConfig = JSON.parse(await Bun.file(path.join(tmp.path, "config.json")).text())
525
- expect(writtenConfig.model).toBe("updated/model")
526
- },
527
- })
528
- })
529
-
530
- test("gets config directories", async () => {
531
- await using tmp = await tmpdir()
532
- await Instance.provide({
533
- directory: tmp.path,
534
- fn: async () => {
535
- const dirs = await Config.directories()
536
- expect(dirs.length).toBeGreaterThanOrEqual(1)
537
- },
538
- })
539
- })
540
-
541
- test("resolves scoped npm plugins in config", async () => {
542
- await using tmp = await tmpdir({
543
- init: async (dir) => {
544
- const pluginDir = path.join(dir, "node_modules", "@scope", "plugin")
545
- await fs.mkdir(pluginDir, { recursive: true })
546
-
547
- await Bun.write(
548
- path.join(dir, "package.json"),
549
- JSON.stringify({ name: "config-fixture", version: "1.0.0", type: "module" }, null, 2),
550
- )
551
-
552
- await Bun.write(
553
- path.join(pluginDir, "package.json"),
554
- JSON.stringify(
555
- {
556
- name: "@scope/plugin",
557
- version: "1.0.0",
558
- type: "module",
559
- main: "./index.js",
560
- },
561
- null,
562
- 2,
563
- ),
564
- )
565
-
566
- await Bun.write(path.join(pluginDir, "index.js"), "export default {}\n")
567
-
568
- await Bun.write(
569
- path.join(dir, "jonsoc.json"),
570
- JSON.stringify({ $schema: "https://jonsoc.com/config.json", plugin: ["@scope/plugin"] }, null, 2),
571
- )
572
- },
573
- })
574
-
575
- await Instance.provide({
576
- directory: tmp.path,
577
- fn: async () => {
578
- const config = await Config.get()
579
- const pluginEntries = config.plugin ?? []
580
-
581
- const baseUrl = pathToFileURL(path.join(tmp.path, "jonsoc.json")).href
582
- const expected = import.meta.resolve("@scope/plugin", baseUrl)
583
-
584
- expect(pluginEntries.includes(expected)).toBe(true)
585
-
586
- const scopedEntry = pluginEntries.find((entry) => entry === expected)
587
- expect(scopedEntry).toBeDefined()
588
- expect(scopedEntry?.includes("/node_modules/@scope/plugin/")).toBe(true)
589
- },
590
- })
591
- })
592
-
593
- test("merges plugin arrays from global and local configs", async () => {
594
- await using tmp = await tmpdir({
595
- init: async (dir) => {
596
- // Create a nested project structure with local .jonsoc config
597
- const projectDir = path.join(dir, "project")
598
- const jonsocDir = path.join(projectDir, ".jonsoc")
599
- await fs.mkdir(jonsocDir, { recursive: true })
600
-
601
- // Global config with plugins
602
- await Bun.write(
603
- path.join(dir, "jonsoc.json"),
604
- JSON.stringify({
605
- $schema: "https://jonsoc.com/config.json",
606
- plugin: ["global-plugin-1", "global-plugin-2"],
607
- }),
608
- )
609
-
610
- // Local .jonsoc config with different plugins
611
- await Bun.write(
612
- path.join(jonsocDir, "jonsoc.json"),
613
- JSON.stringify({
614
- $schema: "https://jonsoc.com/config.json",
615
- plugin: ["local-plugin-1"],
616
- }),
617
- )
618
- },
619
- })
620
-
621
- await Instance.provide({
622
- directory: path.join(tmp.path, "project"),
623
- fn: async () => {
624
- const config = await Config.get()
625
- const plugins = config.plugin ?? []
626
-
627
- // Should contain both global and local plugins
628
- expect(plugins.some((p) => p.includes("global-plugin-1"))).toBe(true)
629
- expect(plugins.some((p) => p.includes("global-plugin-2"))).toBe(true)
630
- expect(plugins.some((p) => p.includes("local-plugin-1"))).toBe(true)
631
-
632
- // Should have all 3 plugins (not replaced, but merged)
633
- const pluginNames = plugins.filter((p) => p.includes("global-plugin") || p.includes("local-plugin"))
634
- expect(pluginNames.length).toBeGreaterThanOrEqual(3)
635
- },
636
- })
637
- })
638
-
639
- test("does not error when only custom agent is a subagent", async () => {
640
- await using tmp = await tmpdir({
641
- init: async (dir) => {
642
- const jonsocDir = path.join(dir, ".jonsoc")
643
- await fs.mkdir(jonsocDir, { recursive: true })
644
- const agentDir = path.join(jonsocDir, "agent")
645
- await fs.mkdir(agentDir, { recursive: true })
646
-
647
- await Bun.write(
648
- path.join(agentDir, "helper.md"),
649
- `---
650
- model: test/model
651
- mode: subagent
652
- ---
653
- Helper subagent prompt`,
654
- )
655
- },
656
- })
657
- await Instance.provide({
658
- directory: tmp.path,
659
- fn: async () => {
660
- const config = await Config.get()
661
- expect(config.agent?.["helper"]).toMatchObject({
662
- name: "helper",
663
- model: "test/model",
664
- mode: "subagent",
665
- prompt: "Helper subagent prompt",
666
- })
667
- },
668
- })
669
- })
670
-
671
- test("merges instructions arrays from global and local configs", async () => {
672
- await using tmp = await tmpdir({
673
- init: async (dir) => {
674
- const projectDir = path.join(dir, "project")
675
- const jonsocDir = path.join(projectDir, ".jonsoc")
676
- await fs.mkdir(jonsocDir, { recursive: true })
677
-
678
- await Bun.write(
679
- path.join(dir, "jonsoc.json"),
680
- JSON.stringify({
681
- $schema: "https://jonsoc.com/config.json",
682
- instructions: ["global-instructions.md", "shared-rules.md"],
683
- }),
684
- )
685
-
686
- await Bun.write(
687
- path.join(jonsocDir, "jonsoc.json"),
688
- JSON.stringify({
689
- $schema: "https://jonsoc.com/config.json",
690
- instructions: ["local-instructions.md"],
691
- }),
692
- )
693
- },
694
- })
695
-
696
- await Instance.provide({
697
- directory: path.join(tmp.path, "project"),
698
- fn: async () => {
699
- const config = await Config.get()
700
- const instructions = config.instructions ?? []
701
-
702
- expect(instructions).toContain("global-instructions.md")
703
- expect(instructions).toContain("shared-rules.md")
704
- expect(instructions).toContain("local-instructions.md")
705
- expect(instructions.length).toBe(3)
706
- },
707
- })
708
- })
709
-
710
- test("deduplicates duplicate instructions from global and local configs", async () => {
711
- await using tmp = await tmpdir({
712
- init: async (dir) => {
713
- const projectDir = path.join(dir, "project")
714
- const jonsocDir = path.join(projectDir, ".jonsoc")
715
- await fs.mkdir(jonsocDir, { recursive: true })
716
-
717
- await Bun.write(
718
- path.join(dir, "jonsoc.json"),
719
- JSON.stringify({
720
- $schema: "https://jonsoc.com/config.json",
721
- instructions: ["duplicate.md", "global-only.md"],
722
- }),
723
- )
724
-
725
- await Bun.write(
726
- path.join(jonsocDir, "jonsoc.json"),
727
- JSON.stringify({
728
- $schema: "https://jonsoc.com/config.json",
729
- instructions: ["duplicate.md", "local-only.md"],
730
- }),
731
- )
732
- },
733
- })
734
-
735
- await Instance.provide({
736
- directory: path.join(tmp.path, "project"),
737
- fn: async () => {
738
- const config = await Config.get()
739
- const instructions = config.instructions ?? []
740
-
741
- expect(instructions).toContain("global-only.md")
742
- expect(instructions).toContain("local-only.md")
743
- expect(instructions).toContain("duplicate.md")
744
-
745
- const duplicates = instructions.filter((i) => i === "duplicate.md")
746
- expect(duplicates.length).toBe(1)
747
- expect(instructions.length).toBe(3)
748
- },
749
- })
750
- })
751
-
752
- test("deduplicates duplicate plugins from global and local configs", async () => {
753
- await using tmp = await tmpdir({
754
- init: async (dir) => {
755
- // Create a nested project structure with local .jonsoc config
756
- const projectDir = path.join(dir, "project")
757
- const jonsocDir = path.join(projectDir, ".jonsoc")
758
- await fs.mkdir(jonsocDir, { recursive: true })
759
-
760
- // Global config with plugins
761
- await Bun.write(
762
- path.join(dir, "jonsoc.json"),
763
- JSON.stringify({
764
- $schema: "https://jonsoc.com/config.json",
765
- plugin: ["duplicate-plugin", "global-plugin-1"],
766
- }),
767
- )
768
-
769
- // Local .jonsoc config with some overlapping plugins
770
- await Bun.write(
771
- path.join(jonsocDir, "jonsoc.json"),
772
- JSON.stringify({
773
- $schema: "https://jonsoc.com/config.json",
774
- plugin: ["duplicate-plugin", "local-plugin-1"],
775
- }),
776
- )
777
- },
778
- })
779
-
780
- await Instance.provide({
781
- directory: path.join(tmp.path, "project"),
782
- fn: async () => {
783
- const config = await Config.get()
784
- const plugins = config.plugin ?? []
785
-
786
- // Should contain all unique plugins
787
- expect(plugins.some((p) => p.includes("global-plugin-1"))).toBe(true)
788
- expect(plugins.some((p) => p.includes("local-plugin-1"))).toBe(true)
789
- expect(plugins.some((p) => p.includes("duplicate-plugin"))).toBe(true)
790
-
791
- // Should deduplicate the duplicate plugin
792
- const duplicatePlugins = plugins.filter((p) => p.includes("duplicate-plugin"))
793
- expect(duplicatePlugins.length).toBe(1)
794
-
795
- // Should have exactly 3 unique plugins
796
- const pluginNames = plugins.filter(
797
- (p) => p.includes("global-plugin") || p.includes("local-plugin") || p.includes("duplicate-plugin"),
798
- )
799
- expect(pluginNames.length).toBe(3)
800
- },
801
- })
802
- })
803
-
804
- // Legacy tools migration tests
805
-
806
- test("migrates legacy tools config to permissions - allow", async () => {
807
- await using tmp = await tmpdir({
808
- init: async (dir) => {
809
- await Bun.write(
810
- path.join(dir, "jonsoc.json"),
811
- JSON.stringify({
812
- $schema: "https://jonsoc.com/config.json",
813
- agent: {
814
- test: {
815
- tools: {
816
- bash: true,
817
- read: true,
818
- },
819
- },
820
- },
821
- }),
822
- )
823
- },
824
- })
825
- await Instance.provide({
826
- directory: tmp.path,
827
- fn: async () => {
828
- const config = await Config.get()
829
- expect(config.agent?.["test"]?.permission).toEqual({
830
- bash: "allow",
831
- read: "allow",
832
- })
833
- },
834
- })
835
- })
836
-
837
- test("migrates legacy tools config to permissions - deny", async () => {
838
- await using tmp = await tmpdir({
839
- init: async (dir) => {
840
- await Bun.write(
841
- path.join(dir, "jonsoc.json"),
842
- JSON.stringify({
843
- $schema: "https://jonsoc.com/config.json",
844
- agent: {
845
- test: {
846
- tools: {
847
- bash: false,
848
- webfetch: false,
849
- },
850
- },
851
- },
852
- }),
853
- )
854
- },
855
- })
856
- await Instance.provide({
857
- directory: tmp.path,
858
- fn: async () => {
859
- const config = await Config.get()
860
- expect(config.agent?.["test"]?.permission).toEqual({
861
- bash: "deny",
862
- webfetch: "deny",
863
- })
864
- },
865
- })
866
- })
867
-
868
- test("migrates legacy write tool to edit permission", async () => {
869
- await using tmp = await tmpdir({
870
- init: async (dir) => {
871
- await Bun.write(
872
- path.join(dir, "jonsoc.json"),
873
- JSON.stringify({
874
- $schema: "https://jonsoc.com/config.json",
875
- agent: {
876
- test: {
877
- tools: {
878
- write: true,
879
- },
880
- },
881
- },
882
- }),
883
- )
884
- },
885
- })
886
- await Instance.provide({
887
- directory: tmp.path,
888
- fn: async () => {
889
- const config = await Config.get()
890
- expect(config.agent?.["test"]?.permission).toEqual({
891
- edit: "allow",
892
- })
893
- },
894
- })
895
- })
896
-
897
- test("migrates legacy edit tool to edit permission", async () => {
898
- await using tmp = await tmpdir({
899
- init: async (dir) => {
900
- await Bun.write(
901
- path.join(dir, "jonsoc.json"),
902
- JSON.stringify({
903
- $schema: "https://jonsoc.com/config.json",
904
- agent: {
905
- test: {
906
- tools: {
907
- edit: false,
908
- },
909
- },
910
- },
911
- }),
912
- )
913
- },
914
- })
915
- await Instance.provide({
916
- directory: tmp.path,
917
- fn: async () => {
918
- const config = await Config.get()
919
- expect(config.agent?.["test"]?.permission).toEqual({
920
- edit: "deny",
921
- })
922
- },
923
- })
924
- })
925
-
926
- test("migrates legacy patch tool to edit permission", async () => {
927
- await using tmp = await tmpdir({
928
- init: async (dir) => {
929
- await Bun.write(
930
- path.join(dir, "jonsoc.json"),
931
- JSON.stringify({
932
- $schema: "https://jonsoc.com/config.json",
933
- agent: {
934
- test: {
935
- tools: {
936
- patch: true,
937
- },
938
- },
939
- },
940
- }),
941
- )
942
- },
943
- })
944
- await Instance.provide({
945
- directory: tmp.path,
946
- fn: async () => {
947
- const config = await Config.get()
948
- expect(config.agent?.["test"]?.permission).toEqual({
949
- edit: "allow",
950
- })
951
- },
952
- })
953
- })
954
-
955
- test("migrates legacy multiedit tool to edit permission", async () => {
956
- await using tmp = await tmpdir({
957
- init: async (dir) => {
958
- await Bun.write(
959
- path.join(dir, "jonsoc.json"),
960
- JSON.stringify({
961
- $schema: "https://jonsoc.com/config.json",
962
- agent: {
963
- test: {
964
- tools: {
965
- multiedit: false,
966
- },
967
- },
968
- },
969
- }),
970
- )
971
- },
972
- })
973
- await Instance.provide({
974
- directory: tmp.path,
975
- fn: async () => {
976
- const config = await Config.get()
977
- expect(config.agent?.["test"]?.permission).toEqual({
978
- edit: "deny",
979
- })
980
- },
981
- })
982
- })
983
-
984
- test("migrates mixed legacy tools config", async () => {
985
- await using tmp = await tmpdir({
986
- init: async (dir) => {
987
- await Bun.write(
988
- path.join(dir, "jonsoc.json"),
989
- JSON.stringify({
990
- $schema: "https://jonsoc.com/config.json",
991
- agent: {
992
- test: {
993
- tools: {
994
- bash: true,
995
- write: true,
996
- read: false,
997
- webfetch: true,
998
- },
999
- },
1000
- },
1001
- }),
1002
- )
1003
- },
1004
- })
1005
- await Instance.provide({
1006
- directory: tmp.path,
1007
- fn: async () => {
1008
- const config = await Config.get()
1009
- expect(config.agent?.["test"]?.permission).toEqual({
1010
- bash: "allow",
1011
- edit: "allow",
1012
- read: "deny",
1013
- webfetch: "allow",
1014
- })
1015
- },
1016
- })
1017
- })
1018
-
1019
- test("merges legacy tools with existing permission config", async () => {
1020
- await using tmp = await tmpdir({
1021
- init: async (dir) => {
1022
- await Bun.write(
1023
- path.join(dir, "jonsoc.json"),
1024
- JSON.stringify({
1025
- $schema: "https://jonsoc.com/config.json",
1026
- agent: {
1027
- test: {
1028
- permission: {
1029
- glob: "allow",
1030
- },
1031
- tools: {
1032
- bash: true,
1033
- },
1034
- },
1035
- },
1036
- }),
1037
- )
1038
- },
1039
- })
1040
- await Instance.provide({
1041
- directory: tmp.path,
1042
- fn: async () => {
1043
- const config = await Config.get()
1044
- expect(config.agent?.["test"]?.permission).toEqual({
1045
- glob: "allow",
1046
- bash: "allow",
1047
- })
1048
- },
1049
- })
1050
- })
1051
-
1052
- test("permission config preserves key order", async () => {
1053
- await using tmp = await tmpdir({
1054
- init: async (dir) => {
1055
- await Bun.write(
1056
- path.join(dir, "jonsoc.json"),
1057
- JSON.stringify({
1058
- $schema: "https://jonsoc.com/config.json",
1059
- permission: {
1060
- "*": "deny",
1061
- edit: "ask",
1062
- write: "ask",
1063
- external_directory: "ask",
1064
- read: "allow",
1065
- todowrite: "allow",
1066
- todoread: "allow",
1067
- "thoughts_*": "allow",
1068
- "reasoning_model_*": "allow",
1069
- "tools_*": "allow",
1070
- "pr_comments_*": "allow",
1071
- },
1072
- }),
1073
- )
1074
- },
1075
- })
1076
- await Instance.provide({
1077
- directory: tmp.path,
1078
- fn: async () => {
1079
- const config = await Config.get()
1080
- expect(Object.keys(config.permission!)).toEqual([
1081
- "*",
1082
- "edit",
1083
- "write",
1084
- "external_directory",
1085
- "read",
1086
- "todowrite",
1087
- "todoread",
1088
- "thoughts_*",
1089
- "reasoning_model_*",
1090
- "tools_*",
1091
- "pr_comments_*",
1092
- ])
1093
- },
1094
- })
1095
- })
1096
-
1097
- // MCP config merging tests
1098
-
1099
- test("project config can override MCP server enabled status", async () => {
1100
- await using tmp = await tmpdir({
1101
- init: async (dir) => {
1102
- // Simulates a base config (like from remote .well-known) with disabled MCP
1103
- await Bun.write(
1104
- path.join(dir, "jonsoc.jsonc"),
1105
- JSON.stringify({
1106
- $schema: "https://jonsoc.com/config.json",
1107
- mcp: {
1108
- jira: {
1109
- type: "remote",
1110
- url: "https://jira.example.com/mcp",
1111
- enabled: false,
1112
- },
1113
- wiki: {
1114
- type: "remote",
1115
- url: "https://wiki.example.com/mcp",
1116
- enabled: false,
1117
- },
1118
- },
1119
- }),
1120
- )
1121
- // Project config enables just jira
1122
- await Bun.write(
1123
- path.join(dir, "jonsoc.json"),
1124
- JSON.stringify({
1125
- $schema: "https://jonsoc.com/config.json",
1126
- mcp: {
1127
- jira: {
1128
- type: "remote",
1129
- url: "https://jira.example.com/mcp",
1130
- enabled: true,
1131
- },
1132
- },
1133
- }),
1134
- )
1135
- },
1136
- })
1137
- await Instance.provide({
1138
- directory: tmp.path,
1139
- fn: async () => {
1140
- const config = await Config.get()
1141
- // jira should be enabled (overridden by project config)
1142
- expect(config.mcp?.jira).toEqual({
1143
- type: "remote",
1144
- url: "https://jira.example.com/mcp",
1145
- enabled: true,
1146
- })
1147
- // wiki should still be disabled (not overridden)
1148
- expect(config.mcp?.wiki).toEqual({
1149
- type: "remote",
1150
- url: "https://wiki.example.com/mcp",
1151
- enabled: false,
1152
- })
1153
- },
1154
- })
1155
- })
1156
-
1157
- test("MCP config deep merges preserving base config properties", async () => {
1158
- await using tmp = await tmpdir({
1159
- init: async (dir) => {
1160
- // Base config with full MCP definition
1161
- await Bun.write(
1162
- path.join(dir, "jonsoc.jsonc"),
1163
- JSON.stringify({
1164
- $schema: "https://jonsoc.com/config.json",
1165
- mcp: {
1166
- myserver: {
1167
- type: "remote",
1168
- url: "https://myserver.example.com/mcp",
1169
- enabled: false,
1170
- headers: {
1171
- "X-Custom-Header": "value",
1172
- },
1173
- },
1174
- },
1175
- }),
1176
- )
1177
- // Override just enables it, should preserve other properties
1178
- await Bun.write(
1179
- path.join(dir, "jonsoc.json"),
1180
- JSON.stringify({
1181
- $schema: "https://jonsoc.com/config.json",
1182
- mcp: {
1183
- myserver: {
1184
- type: "remote",
1185
- url: "https://myserver.example.com/mcp",
1186
- enabled: true,
1187
- },
1188
- },
1189
- }),
1190
- )
1191
- },
1192
- })
1193
- await Instance.provide({
1194
- directory: tmp.path,
1195
- fn: async () => {
1196
- const config = await Config.get()
1197
- expect(config.mcp?.myserver).toEqual({
1198
- type: "remote",
1199
- url: "https://myserver.example.com/mcp",
1200
- enabled: true,
1201
- headers: {
1202
- "X-Custom-Header": "value",
1203
- },
1204
- })
1205
- },
1206
- })
1207
- })
1208
-
1209
- test("local .jonsoc config can override MCP from project config", async () => {
1210
- await using tmp = await tmpdir({
1211
- init: async (dir) => {
1212
- // Project config with disabled MCP
1213
- await Bun.write(
1214
- path.join(dir, "jonsoc.json"),
1215
- JSON.stringify({
1216
- $schema: "https://jonsoc.com/config.json",
1217
- mcp: {
1218
- docs: {
1219
- type: "remote",
1220
- url: "https://docs.example.com/mcp",
1221
- enabled: false,
1222
- },
1223
- },
1224
- }),
1225
- )
1226
- // Local .jonsoc directory config enables it
1227
- const jonsocDir = path.join(dir, ".jonsoc")
1228
- await fs.mkdir(jonsocDir, { recursive: true })
1229
- await Bun.write(
1230
- path.join(jonsocDir, "jonsoc.json"),
1231
- JSON.stringify({
1232
- $schema: "https://jonsoc.com/config.json",
1233
- mcp: {
1234
- docs: {
1235
- type: "remote",
1236
- url: "https://docs.example.com/mcp",
1237
- enabled: true,
1238
- },
1239
- },
1240
- }),
1241
- )
1242
- },
1243
- })
1244
- await Instance.provide({
1245
- directory: tmp.path,
1246
- fn: async () => {
1247
- const config = await Config.get()
1248
- expect(config.mcp?.docs?.enabled).toBe(true)
1249
- },
1250
- })
1251
- })
1252
-
1253
- test("project config overrides remote well-known config", async () => {
1254
- const originalFetch = globalThis.fetch
1255
- let fetchedUrl: string | undefined
1256
- const mockFetch = mock((url: string | URL | Request) => {
1257
- const urlStr = url.toString()
1258
- if (urlStr.includes(".well-known/jonsoc")) {
1259
- fetchedUrl = urlStr
1260
- return Promise.resolve(
1261
- new Response(
1262
- JSON.stringify({
1263
- config: {
1264
- mcp: {
1265
- jira: {
1266
- type: "remote",
1267
- url: "https://jira.example.com/mcp",
1268
- enabled: false,
1269
- },
1270
- },
1271
- },
1272
- }),
1273
- { status: 200 },
1274
- ),
1275
- )
1276
- }
1277
- return originalFetch(url)
1278
- })
1279
- globalThis.fetch = mockFetch as unknown as typeof fetch
1280
-
1281
- const originalAuthAll = Auth.all
1282
- Auth.all = mock(() =>
1283
- Promise.resolve({
1284
- "https://example.com": {
1285
- type: "wellknown" as const,
1286
- key: "TEST_TOKEN",
1287
- token: "test-token",
1288
- },
1289
- }),
1290
- )
1291
-
1292
- try {
1293
- await using tmp = await tmpdir({
1294
- git: true,
1295
- init: async (dir) => {
1296
- // Project config enables jira (overriding remote default)
1297
- await Bun.write(
1298
- path.join(dir, "jonsoc.json"),
1299
- JSON.stringify({
1300
- $schema: "https://jonsoc.com/config.json",
1301
- mcp: {
1302
- jira: {
1303
- type: "remote",
1304
- url: "https://jira.example.com/mcp",
1305
- enabled: true,
1306
- },
1307
- },
1308
- }),
1309
- )
1310
- },
1311
- })
1312
- await Instance.provide({
1313
- directory: tmp.path,
1314
- fn: async () => {
1315
- const config = await Config.get()
1316
- // Verify fetch was called for wellknown config
1317
- expect(fetchedUrl).toBe("https://example.com/.well-known/jonsoc")
1318
- // Project config (enabled: true) should override remote (enabled: false)
1319
- expect(config.mcp?.jira?.enabled).toBe(true)
1320
- },
1321
- })
1322
- } finally {
1323
- globalThis.fetch = originalFetch
1324
- Auth.all = originalAuthAll
1325
- }
1326
- })
1327
-
1328
- describe("getPluginName", () => {
1329
- test("extracts name from file:// URL", () => {
1330
- expect(Config.getPluginName("file:///path/to/plugin/foo.js")).toBe("foo")
1331
- expect(Config.getPluginName("file:///path/to/plugin/bar.ts")).toBe("bar")
1332
- expect(Config.getPluginName("file:///some/path/my-plugin.js")).toBe("my-plugin")
1333
- })
1334
-
1335
- test("extracts name from npm package with version", () => {
1336
- expect(Config.getPluginName("oh-my-jonsoc@2.4.3")).toBe("oh-my-jonsoc")
1337
- expect(Config.getPluginName("some-plugin@1.0.0")).toBe("some-plugin")
1338
- expect(Config.getPluginName("plugin@latest")).toBe("plugin")
1339
- })
1340
-
1341
- test("extracts name from scoped npm package", () => {
1342
- expect(Config.getPluginName("@scope/pkg@1.0.0")).toBe("@scope/pkg")
1343
- expect(Config.getPluginName("@jonsoc/plugin@2.0.0")).toBe("@jonsoc/plugin")
1344
- })
1345
-
1346
- test("returns full string for package without version", () => {
1347
- expect(Config.getPluginName("some-plugin")).toBe("some-plugin")
1348
- expect(Config.getPluginName("@scope/pkg")).toBe("@scope/pkg")
1349
- })
1350
- })
1351
-
1352
- describe("deduplicatePlugins", () => {
1353
- test("removes duplicates keeping higher priority (later entries)", () => {
1354
- const plugins = ["global-plugin@1.0.0", "shared-plugin@1.0.0", "local-plugin@2.0.0", "shared-plugin@2.0.0"]
1355
-
1356
- const result = Config.deduplicatePlugins(plugins)
1357
-
1358
- expect(result).toContain("global-plugin@1.0.0")
1359
- expect(result).toContain("local-plugin@2.0.0")
1360
- expect(result).toContain("shared-plugin@2.0.0")
1361
- expect(result).not.toContain("shared-plugin@1.0.0")
1362
- expect(result.length).toBe(3)
1363
- })
1364
-
1365
- test("prefers local file over npm package with same name", () => {
1366
- const plugins = ["oh-my-jonsoc@2.4.3", "file:///project/.jonsoc/plugin/oh-my-jonsoc.js"]
1367
-
1368
- const result = Config.deduplicatePlugins(plugins)
1369
-
1370
- expect(result.length).toBe(1)
1371
- expect(result[0]).toBe("file:///project/.jonsoc/plugin/oh-my-jonsoc.js")
1372
- })
1373
-
1374
- test("preserves order of remaining plugins", () => {
1375
- const plugins = ["a-plugin@1.0.0", "b-plugin@1.0.0", "c-plugin@1.0.0"]
1376
-
1377
- const result = Config.deduplicatePlugins(plugins)
1378
-
1379
- expect(result).toEqual(["a-plugin@1.0.0", "b-plugin@1.0.0", "c-plugin@1.0.0"])
1380
- })
1381
-
1382
- test("local plugin directory overrides global jonsoc.json plugin", async () => {
1383
- await using tmp = await tmpdir({
1384
- init: async (dir) => {
1385
- const projectDir = path.join(dir, "project")
1386
- const jonsocDir = path.join(projectDir, ".jonsoc")
1387
- const pluginDir = path.join(jonsocDir, "plugin")
1388
- await fs.mkdir(pluginDir, { recursive: true })
1389
-
1390
- await Bun.write(
1391
- path.join(dir, "jonsoc.json"),
1392
- JSON.stringify({
1393
- $schema: "https://jonsoc.com/config.json",
1394
- plugin: ["my-plugin@1.0.0"],
1395
- }),
1396
- )
1397
-
1398
- await Bun.write(path.join(pluginDir, "my-plugin.js"), "export default {}")
1399
- },
1400
- })
1401
-
1402
- await Instance.provide({
1403
- directory: path.join(tmp.path, "project"),
1404
- fn: async () => {
1405
- const config = await Config.get()
1406
- const plugins = config.plugin ?? []
1407
-
1408
- const myPlugins = plugins.filter((p) => Config.getPluginName(p) === "my-plugin")
1409
- expect(myPlugins.length).toBe(1)
1410
- expect(myPlugins[0].startsWith("file://")).toBe(true)
1411
- },
1412
- })
1413
- })
1414
- })
1415
-
1416
- describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
1417
- test("skips project config files when flag is set", async () => {
1418
- const originalEnv = process.env["OPENCODE_DISABLE_PROJECT_CONFIG"]
1419
- process.env["OPENCODE_DISABLE_PROJECT_CONFIG"] = "true"
1420
-
1421
- try {
1422
- await using tmp = await tmpdir({
1423
- init: async (dir) => {
1424
- // Create a project config that would normally be loaded
1425
- await Bun.write(
1426
- path.join(dir, "jonsoc.json"),
1427
- JSON.stringify({
1428
- $schema: "https://jonsoc.com/config.json",
1429
- model: "project/model",
1430
- username: "project-user",
1431
- }),
1432
- )
1433
- },
1434
- })
1435
- await Instance.provide({
1436
- directory: tmp.path,
1437
- fn: async () => {
1438
- const config = await Config.get()
1439
- // Project config should NOT be loaded - model should be default, not "project/model"
1440
- expect(config.model).not.toBe("project/model")
1441
- expect(config.username).not.toBe("project-user")
1442
- },
1443
- })
1444
- } finally {
1445
- if (originalEnv === undefined) {
1446
- delete process.env["OPENCODE_DISABLE_PROJECT_CONFIG"]
1447
- } else {
1448
- process.env["OPENCODE_DISABLE_PROJECT_CONFIG"] = originalEnv
1449
- }
1450
- }
1451
- })
1452
-
1453
- test("skips project .jonsoc/ directories when flag is set", async () => {
1454
- const originalEnv = process.env["OPENCODE_DISABLE_PROJECT_CONFIG"]
1455
- process.env["OPENCODE_DISABLE_PROJECT_CONFIG"] = "true"
1456
-
1457
- try {
1458
- await using tmp = await tmpdir({
1459
- init: async (dir) => {
1460
- // Create a .jonsoc directory with a command
1461
- const jonsocDir = path.join(dir, ".jonsoc", "command")
1462
- await fs.mkdir(jonsocDir, { recursive: true })
1463
- await Bun.write(path.join(jonsocDir, "test-cmd.md"), "# Test Command\nThis is a test command.")
1464
- },
1465
- })
1466
- await Instance.provide({
1467
- directory: tmp.path,
1468
- fn: async () => {
1469
- const directories = await Config.directories()
1470
- // Project .jonsoc should NOT be in directories list
1471
- const hasProjectOpencode = directories.some((d) => d.startsWith(tmp.path))
1472
- expect(hasProjectOpencode).toBe(false)
1473
- },
1474
- })
1475
- } finally {
1476
- if (originalEnv === undefined) {
1477
- delete process.env["OPENCODE_DISABLE_PROJECT_CONFIG"]
1478
- } else {
1479
- process.env["OPENCODE_DISABLE_PROJECT_CONFIG"] = originalEnv
1480
- }
1481
- }
1482
- })
1483
-
1484
- test("still loads global config when flag is set", async () => {
1485
- const originalEnv = process.env["OPENCODE_DISABLE_PROJECT_CONFIG"]
1486
- process.env["OPENCODE_DISABLE_PROJECT_CONFIG"] = "true"
1487
-
1488
- try {
1489
- await using tmp = await tmpdir()
1490
- await Instance.provide({
1491
- directory: tmp.path,
1492
- fn: async () => {
1493
- // Should still get default config (from global or defaults)
1494
- const config = await Config.get()
1495
- expect(config).toBeDefined()
1496
- expect(config.username).toBeDefined()
1497
- },
1498
- })
1499
- } finally {
1500
- if (originalEnv === undefined) {
1501
- delete process.env["OPENCODE_DISABLE_PROJECT_CONFIG"]
1502
- } else {
1503
- process.env["OPENCODE_DISABLE_PROJECT_CONFIG"] = originalEnv
1504
- }
1505
- }
1506
- })
1507
-
1508
- test("skips relative instructions with warning when flag is set but no config dir", async () => {
1509
- const originalDisable = process.env["OPENCODE_DISABLE_PROJECT_CONFIG"]
1510
- const originalConfigDir = process.env["OPENCODE_CONFIG_DIR"]
1511
-
1512
- try {
1513
- // Ensure no config dir is set
1514
- delete process.env["OPENCODE_CONFIG_DIR"]
1515
- process.env["OPENCODE_DISABLE_PROJECT_CONFIG"] = "true"
1516
-
1517
- await using tmp = await tmpdir({
1518
- init: async (dir) => {
1519
- // Create a config with relative instruction path
1520
- await Bun.write(
1521
- path.join(dir, "jonsoc.json"),
1522
- JSON.stringify({
1523
- $schema: "https://jonsoc.com/config.json",
1524
- instructions: ["./CUSTOM.md"],
1525
- }),
1526
- )
1527
- // Create the instruction file (should be skipped)
1528
- await Bun.write(path.join(dir, "CUSTOM.md"), "# Custom Instructions")
1529
- },
1530
- })
1531
-
1532
- await Instance.provide({
1533
- directory: tmp.path,
1534
- fn: async () => {
1535
- // The relative instruction should be skipped without error
1536
- // We're mainly verifying this doesn't throw and the config loads
1537
- const config = await Config.get()
1538
- expect(config).toBeDefined()
1539
- // The instruction should have been skipped (warning logged)
1540
- // We can't easily test the warning was logged, but we verify
1541
- // the relative path didn't cause an error
1542
- },
1543
- })
1544
- } finally {
1545
- if (originalDisable === undefined) {
1546
- delete process.env["OPENCODE_DISABLE_PROJECT_CONFIG"]
1547
- } else {
1548
- process.env["OPENCODE_DISABLE_PROJECT_CONFIG"] = originalDisable
1549
- }
1550
- if (originalConfigDir === undefined) {
1551
- delete process.env["OPENCODE_CONFIG_DIR"]
1552
- } else {
1553
- process.env["OPENCODE_CONFIG_DIR"] = originalConfigDir
1554
- }
1555
- }
1556
- })
1557
-
1558
- test("OPENCODE_CONFIG_DIR still works when flag is set", async () => {
1559
- const originalDisable = process.env["OPENCODE_DISABLE_PROJECT_CONFIG"]
1560
- const originalConfigDir = process.env["OPENCODE_CONFIG_DIR"]
1561
-
1562
- try {
1563
- await using configDirTmp = await tmpdir({
1564
- init: async (dir) => {
1565
- // Create config in the custom config dir
1566
- await Bun.write(
1567
- path.join(dir, "jonsoc.json"),
1568
- JSON.stringify({
1569
- $schema: "https://jonsoc.com/config.json",
1570
- model: "configdir/model",
1571
- }),
1572
- )
1573
- },
1574
- })
1575
-
1576
- await using projectTmp = await tmpdir({
1577
- init: async (dir) => {
1578
- // Create config in project (should be ignored)
1579
- await Bun.write(
1580
- path.join(dir, "jonsoc.json"),
1581
- JSON.stringify({
1582
- $schema: "https://jonsoc.com/config.json",
1583
- model: "project/model",
1584
- }),
1585
- )
1586
- },
1587
- })
1588
-
1589
- process.env["OPENCODE_DISABLE_PROJECT_CONFIG"] = "true"
1590
- process.env["OPENCODE_CONFIG_DIR"] = configDirTmp.path
1591
-
1592
- await Instance.provide({
1593
- directory: projectTmp.path,
1594
- fn: async () => {
1595
- const config = await Config.get()
1596
- // Should load from OPENCODE_CONFIG_DIR, not project
1597
- expect(config.model).toBe("configdir/model")
1598
- },
1599
- })
1600
- } finally {
1601
- if (originalDisable === undefined) {
1602
- delete process.env["OPENCODE_DISABLE_PROJECT_CONFIG"]
1603
- } else {
1604
- process.env["OPENCODE_DISABLE_PROJECT_CONFIG"] = originalDisable
1605
- }
1606
- if (originalConfigDir === undefined) {
1607
- delete process.env["OPENCODE_CONFIG_DIR"]
1608
- } else {
1609
- process.env["OPENCODE_CONFIG_DIR"] = originalConfigDir
1610
- }
1611
- }
1612
- })
1613
- })