@tyvm/knowhow 0.0.89 → 0.0.91

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 (344) hide show
  1. package/.depcheckrc +31 -0
  2. package/CONFIG.md +52 -0
  3. package/README.md +344 -29
  4. package/WORKER.md +169 -334
  5. package/autodoc/chat-guide.md +540 -0
  6. package/autodoc/cli-reference.md +765 -0
  7. package/autodoc/config-reference.md +541 -0
  8. package/autodoc/embeddings-guide.md +566 -0
  9. package/autodoc/generate-guide.md +477 -0
  10. package/autodoc/language-plugin-guide.md +443 -0
  11. package/autodoc/modules-guide.md +352 -0
  12. package/autodoc/plugins-guide.md +720 -0
  13. package/autodoc/quickstart-guide.md +129 -0
  14. package/autodoc/skills-guide.md +468 -0
  15. package/autodoc/worker-guide.md +526 -0
  16. package/bin/knowhow.js +1 -1
  17. package/package.json +4 -32
  18. package/src/agents/tools/executeScript/index.ts +5 -0
  19. package/src/agents/tools/googleSearch.ts +2 -2
  20. package/src/agents/tools/index.ts +0 -3
  21. package/src/agents/tools/list.ts +0 -147
  22. package/src/agents/tools/loadWebpage.ts +3 -113
  23. package/src/ai.ts +33 -2
  24. package/src/auth/browserLogin.ts +10 -13
  25. package/src/cli.ts +63 -3
  26. package/src/clients/gemini.ts +96 -25
  27. package/src/clients/http.ts +7 -11
  28. package/src/clients/pricing/google.ts +122 -26
  29. package/src/config.ts +28 -4
  30. package/src/conversion.ts +24 -54
  31. package/src/index.ts +30 -3
  32. package/src/login.ts +5 -6
  33. package/src/plugins/language.ts +0 -4
  34. package/src/plugins/plugins.ts +0 -14
  35. package/src/plugins/url.ts +31 -12
  36. package/src/processors/TokenCompressor.ts +2 -2
  37. package/src/processors/ToolResponseCache.ts +3 -3
  38. package/src/processors/tools/grepToolResponse.ts +9 -4
  39. package/src/processors/tools/jqToolResponse.ts +11 -6
  40. package/src/processors/tools/listStoredToolResponses.ts +1 -1
  41. package/src/processors/tools/tailToolResponse.ts +9 -4
  42. package/src/services/GitHub.ts +2 -2
  43. package/src/services/KnowhowClient.ts +34 -34
  44. package/src/{plugins/downloader/downloader.ts → services/MediaProcessorService.ts} +109 -267
  45. package/src/services/S3.ts +16 -16
  46. package/src/services/index.ts +4 -4
  47. package/src/services/modules/index.ts +10 -2
  48. package/src/services/modules/types.ts +5 -2
  49. package/src/services/script-execution/ScriptExecutor.ts +29 -10
  50. package/src/services/script-execution/ScriptPolicy.ts +6 -2
  51. package/src/types.ts +1 -0
  52. package/src/utils/http.ts +127 -0
  53. package/src/workers/auth/PasskeySetup.ts +7 -11
  54. package/tests/clients/AIClient.test.ts +24 -21
  55. package/tests/manual/file-edits/figma.test.ts +3 -70
  56. package/tests/plugins/language/languagePlugin-content-triggers.test.ts +2 -0
  57. package/tests/plugins/language/languagePlugin.test.ts +2 -0
  58. package/tests/processors/ToolResponseCache.test.ts +2 -2
  59. package/tests/test.spec.ts +0 -14
  60. package/tests/unit/modules/moduleLoading.test.ts +7 -4
  61. package/tests/unit/plugins/pluginLoading.test.ts +6 -6
  62. package/ts_build/package.json +4 -32
  63. package/ts_build/src/agents/tools/ast/astAppendNode.d.ts +1 -1
  64. package/ts_build/src/agents/tools/ast/astAppendNode.js +2 -90
  65. package/ts_build/src/agents/tools/ast/astAppendNode.js.map +1 -1
  66. package/ts_build/src/agents/tools/ast/astDeleteNode.d.ts +1 -1
  67. package/ts_build/src/agents/tools/ast/astDeleteNode.js +2 -88
  68. package/ts_build/src/agents/tools/ast/astDeleteNode.js.map +1 -1
  69. package/ts_build/src/agents/tools/ast/astEditNode.d.ts +1 -1
  70. package/ts_build/src/agents/tools/ast/astEditNode.js +2 -90
  71. package/ts_build/src/agents/tools/ast/astEditNode.js.map +1 -1
  72. package/ts_build/src/agents/tools/ast/astGetPathForLine.d.ts +1 -1
  73. package/ts_build/src/agents/tools/ast/astGetPathForLine.js +2 -72
  74. package/ts_build/src/agents/tools/ast/astGetPathForLine.js.map +1 -1
  75. package/ts_build/src/agents/tools/ast/astListPaths.d.ts +1 -1
  76. package/ts_build/src/agents/tools/ast/astListPaths.js +2 -72
  77. package/ts_build/src/agents/tools/ast/astListPaths.js.map +1 -1
  78. package/ts_build/src/agents/tools/executeScript/index.d.ts +3 -2
  79. package/ts_build/src/agents/tools/executeScript/index.js +4 -1
  80. package/ts_build/src/agents/tools/executeScript/index.js.map +1 -1
  81. package/ts_build/src/agents/tools/googleSearch.js +2 -2
  82. package/ts_build/src/agents/tools/googleSearch.js.map +1 -1
  83. package/ts_build/src/agents/tools/index.d.ts +0 -3
  84. package/ts_build/src/agents/tools/index.js +0 -3
  85. package/ts_build/src/agents/tools/index.js.map +1 -1
  86. package/ts_build/src/agents/tools/list.js +0 -138
  87. package/ts_build/src/agents/tools/list.js.map +1 -1
  88. package/ts_build/src/agents/tools/loadWebpage.js +1 -89
  89. package/ts_build/src/agents/tools/loadWebpage.js.map +1 -1
  90. package/ts_build/src/agents/tools/textSearch.d.ts +1 -1
  91. package/ts_build/src/ai.js +18 -1
  92. package/ts_build/src/ai.js.map +1 -1
  93. package/ts_build/src/auth/browserLogin.js +7 -7
  94. package/ts_build/src/auth/browserLogin.js.map +1 -1
  95. package/ts_build/src/cli.d.ts +1 -1
  96. package/ts_build/src/cli.js +47 -1
  97. package/ts_build/src/cli.js.map +1 -1
  98. package/ts_build/src/clients/gemini.d.ts +1 -73
  99. package/ts_build/src/clients/gemini.js +57 -19
  100. package/ts_build/src/clients/gemini.js.map +1 -1
  101. package/ts_build/src/clients/http.js +5 -9
  102. package/ts_build/src/clients/http.js.map +1 -1
  103. package/ts_build/src/clients/pricing/google.d.ts +17 -73
  104. package/ts_build/src/clients/pricing/google.js +47 -10
  105. package/ts_build/src/clients/pricing/google.js.map +1 -1
  106. package/ts_build/src/config.js +17 -2
  107. package/ts_build/src/config.js.map +1 -1
  108. package/ts_build/src/conversion.d.ts +1 -4
  109. package/ts_build/src/conversion.js +12 -27
  110. package/ts_build/src/conversion.js.map +1 -1
  111. package/ts_build/src/index.d.ts +4 -0
  112. package/ts_build/src/index.js +19 -3
  113. package/ts_build/src/index.js.map +1 -1
  114. package/ts_build/src/login.js +5 -4
  115. package/ts_build/src/login.js.map +1 -1
  116. package/ts_build/src/plugins/downloader/downloader.js +3 -3
  117. package/ts_build/src/plugins/downloader/downloader.js.map +1 -1
  118. package/ts_build/src/plugins/language.js.map +1 -1
  119. package/ts_build/src/plugins/plugins.js +0 -14
  120. package/ts_build/src/plugins/plugins.js.map +1 -1
  121. package/ts_build/src/plugins/tree-sitter/editor.d.ts +3 -32
  122. package/ts_build/src/plugins/tree-sitter/editor.js +6 -208
  123. package/ts_build/src/plugins/tree-sitter/editor.js.map +1 -1
  124. package/ts_build/src/plugins/tree-sitter/parser.d.ts +19 -54
  125. package/ts_build/src/plugins/tree-sitter/parser.js +19 -293
  126. package/ts_build/src/plugins/tree-sitter/parser.js.map +1 -1
  127. package/ts_build/src/plugins/tree-sitter/simple-paths.d.ts +2 -15
  128. package/ts_build/src/plugins/tree-sitter/simple-paths.js +2 -324
  129. package/ts_build/src/plugins/tree-sitter/simple-paths.js.map +1 -1
  130. package/ts_build/src/plugins/url.js +27 -8
  131. package/ts_build/src/plugins/url.js.map +1 -1
  132. package/ts_build/src/processors/TokenCompressor.js +2 -2
  133. package/ts_build/src/processors/TokenCompressor.js.map +1 -1
  134. package/ts_build/src/processors/ToolResponseCache.js +3 -3
  135. package/ts_build/src/processors/ToolResponseCache.js.map +1 -1
  136. package/ts_build/src/processors/tools/grepToolResponse.d.ts +3 -1
  137. package/ts_build/src/processors/tools/grepToolResponse.js +8 -2
  138. package/ts_build/src/processors/tools/grepToolResponse.js.map +1 -1
  139. package/ts_build/src/processors/tools/jqToolResponse.d.ts +3 -1
  140. package/ts_build/src/processors/tools/jqToolResponse.js +10 -4
  141. package/ts_build/src/processors/tools/jqToolResponse.js.map +1 -1
  142. package/ts_build/src/processors/tools/listStoredToolResponses.js +1 -1
  143. package/ts_build/src/processors/tools/listStoredToolResponses.js.map +1 -1
  144. package/ts_build/src/processors/tools/tailToolResponse.d.ts +3 -1
  145. package/ts_build/src/processors/tools/tailToolResponse.js +8 -2
  146. package/ts_build/src/processors/tools/tailToolResponse.js.map +1 -1
  147. package/ts_build/src/services/GitHub.js +2 -2
  148. package/ts_build/src/services/GitHub.js.map +1 -1
  149. package/ts_build/src/services/KnowhowClient.d.ts +29 -29
  150. package/ts_build/src/services/KnowhowClient.js +33 -33
  151. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  152. package/ts_build/src/services/MediaProcessorService.d.ts +22 -0
  153. package/ts_build/src/services/MediaProcessorService.js +215 -0
  154. package/ts_build/src/services/MediaProcessorService.js.map +1 -0
  155. package/ts_build/src/services/S3.js +12 -18
  156. package/ts_build/src/services/S3.js.map +1 -1
  157. package/ts_build/src/services/index.d.ts +3 -2
  158. package/ts_build/src/services/index.js +3 -3
  159. package/ts_build/src/services/index.js.map +1 -1
  160. package/ts_build/src/services/modules/index.js +10 -2
  161. package/ts_build/src/services/modules/index.js.map +1 -1
  162. package/ts_build/src/services/modules/types.d.ts +5 -2
  163. package/ts_build/src/services/script-execution/ScriptExecutor.js +22 -7
  164. package/ts_build/src/services/script-execution/ScriptExecutor.js.map +1 -1
  165. package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +1 -1
  166. package/ts_build/src/services/script-execution/ScriptPolicy.js +4 -2
  167. package/ts_build/src/services/script-execution/ScriptPolicy.js.map +1 -1
  168. package/ts_build/src/types.d.ts +1 -0
  169. package/ts_build/src/types.js +1 -0
  170. package/ts_build/src/types.js.map +1 -1
  171. package/ts_build/src/utils/http.d.ts +27 -0
  172. package/ts_build/src/utils/http.js +98 -0
  173. package/ts_build/src/utils/http.js.map +1 -0
  174. package/ts_build/src/workers/auth/PasskeySetup.js +6 -7
  175. package/ts_build/src/workers/auth/PasskeySetup.js.map +1 -1
  176. package/ts_build/tests/clients/AIClient.test.js +11 -14
  177. package/ts_build/tests/clients/AIClient.test.js.map +1 -1
  178. package/ts_build/tests/manual/file-edits/figma.test.d.ts +0 -1
  179. package/ts_build/tests/manual/file-edits/figma.test.js +1 -46
  180. package/ts_build/tests/manual/file-edits/figma.test.js.map +1 -1
  181. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +2 -0
  182. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
  183. package/ts_build/tests/plugins/language/languagePlugin.test.js +2 -0
  184. package/ts_build/tests/plugins/language/languagePlugin.test.js.map +1 -1
  185. package/ts_build/tests/processors/ToolResponseCache.test.js +2 -2
  186. package/ts_build/tests/processors/ToolResponseCache.test.js.map +1 -1
  187. package/ts_build/tests/test.spec.js +0 -14
  188. package/ts_build/tests/test.spec.js.map +1 -1
  189. package/ts_build/tests/tree-sitter/tree-sitter.test.d.ts +0 -1
  190. package/ts_build/tests/tree-sitter/tree-sitter.test.js +2 -183
  191. package/ts_build/tests/tree-sitter/tree-sitter.test.js.map +1 -1
  192. package/ts_build/tests/unit/modules/moduleLoading.test.js +6 -4
  193. package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
  194. package/ts_build/tests/unit/plugins/pluginLoading.test.js +4 -4
  195. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
  196. package/autodoc/chat.mdx +0 -20
  197. package/autodoc/cli.mdx +0 -11
  198. package/autodoc/plugins/asana.mdx +0 -47
  199. package/autodoc/plugins/downloader/downloader.mdx +0 -38
  200. package/autodoc/plugins/downloader/plugin.mdx +0 -37
  201. package/autodoc/plugins/downloader/types.mdx +0 -42
  202. package/autodoc/plugins/embedding.mdx +0 -41
  203. package/autodoc/plugins/figma.mdx +0 -45
  204. package/autodoc/plugins/github.mdx +0 -40
  205. package/autodoc/plugins/jira.mdx +0 -46
  206. package/autodoc/plugins/language.mdx +0 -37
  207. package/autodoc/plugins/linear.mdx +0 -35
  208. package/autodoc/plugins/notion.mdx +0 -38
  209. package/autodoc/plugins/plugins.mdx +0 -59
  210. package/autodoc/plugins/types.mdx +0 -51
  211. package/autodoc/plugins/vim.mdx +0 -39
  212. package/autodoc/tools/addInternalTools.mdx +0 -1
  213. package/autodoc/tools/agentCall.mdx +0 -1
  214. package/autodoc/tools/asana/definitions.mdx +0 -10
  215. package/autodoc/tools/asana/index.mdx +0 -12
  216. package/autodoc/tools/askHuman.mdx +0 -1
  217. package/autodoc/tools/callPlugin.mdx +0 -1
  218. package/autodoc/tools/embeddingSearch.mdx +0 -1
  219. package/autodoc/tools/execCommand.mdx +0 -1
  220. package/autodoc/tools/fileSearch.mdx +0 -1
  221. package/autodoc/tools/finalAnswer.mdx +0 -1
  222. package/autodoc/tools/github/definitions.mdx +0 -6
  223. package/autodoc/tools/github/index.mdx +0 -8
  224. package/autodoc/tools/index.mdx +0 -14
  225. package/autodoc/tools/lintFile.mdx +0 -7
  226. package/autodoc/tools/list.mdx +0 -16
  227. package/autodoc/tools/modifyFile.mdx +0 -7
  228. package/autodoc/tools/patch.mdx +0 -9
  229. package/autodoc/tools/readBlocks.mdx +0 -1
  230. package/autodoc/tools/readFile.mdx +0 -1
  231. package/autodoc/tools/scanFile.mdx +0 -1
  232. package/autodoc/tools/textSearch.mdx +0 -6
  233. package/autodoc/tools/types/fileblock.mdx +0 -1
  234. package/autodoc/tools/visionTool.mdx +0 -1
  235. package/autodoc/tools/writeFile.mdx +0 -1
  236. package/benchmarks/.dockerignore +0 -7
  237. package/benchmarks/README.md +0 -166
  238. package/benchmarks/docker/Dockerfile +0 -68
  239. package/benchmarks/example-config.yml +0 -27
  240. package/benchmarks/jest.config.js +0 -13
  241. package/benchmarks/package-lock.json +0 -4297
  242. package/benchmarks/package.json +0 -39
  243. package/benchmarks/results/27b0a06/2025-09-27/xai/xai-grok-code-fast-1.json +0 -2909
  244. package/benchmarks/results/4057aed/2025-08-14/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -1671
  245. package/benchmarks/results/4542435/2025-08-05/lms/lms-openai-gpt-oss-20b.json +0 -2814
  246. package/benchmarks/results/4542435/2025-08-05/lms/lms-qwen-qwen3-30b-a3b-2507.json +0 -2014
  247. package/benchmarks/results/4fb9125/2025-08-07/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -3121
  248. package/benchmarks/results/5766aee/2025-08-02/lms-qwen/qwen3-coder-30b.json +0 -98
  249. package/benchmarks/results/6d73808/2025-08-07/openai/openai-gpt-5.json +0 -3256
  250. package/benchmarks/results/77bf0a6/2025-08-02/lms-qwen/qwen3-30b-a3b-2507.json +0 -4298
  251. package/benchmarks/results/8c0d445/2025-08-03/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -3031
  252. package/benchmarks/results/8c0d445/2025-08-03/openai/openai-gpt-4.1-2025-04-14.json +0 -2990
  253. package/benchmarks/results/ac6b2ab/2025-08-03/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -3256
  254. package/benchmarks/results/ac6b2ab/2025-08-03/lms/lms-qwen-qwen3-coder-30b.json +0 -3007
  255. package/benchmarks/results/ac6b2ab/2025-08-03/openai/openai-gpt-4.1-2025-04-14.json +0 -3256
  256. package/benchmarks/results/ac6b2ab/2025-08-03/openai/openai-gpt-4.1-mini-2025-04-14.json +0 -3036
  257. package/benchmarks/results/ac6b2ab/2025-08-03/openai/openai-gpt-4.1-nano-2025-04-14.json +0 -3280
  258. package/benchmarks/results/adff675/2025-08-04/lms/lms-qwen-qwen3-30b-a3b-2507.json +0 -1920
  259. package/benchmarks/results/adff675/2025-08-04/lms/lms-qwen-qwen3-coder-30b.json +0 -3281
  260. package/benchmarks/results/b502ed9/2025-08-03/lms-qwen/qwen3-coder-30b.json +0 -2896
  261. package/benchmarks/results/d1a8129/2025-08-03/lms/lms-qwen-qwen3-coder-30b.json +0 -3011
  262. package/benchmarks/results/e60471c/2025-08-03/lms/qwen3-30b-a3b-2507.json +0 -3003
  263. package/benchmarks/scripts/build-and-run.sh +0 -47
  264. package/benchmarks/scripts/clone-exercism.sh +0 -92
  265. package/benchmarks/scripts/validate.sh +0 -48
  266. package/benchmarks/src/__tests__/runner.test.ts +0 -27
  267. package/benchmarks/src/cli.ts +0 -90
  268. package/benchmarks/src/evaluators/EvaluatorRegistry.ts +0 -64
  269. package/benchmarks/src/evaluators/JavaScriptEvaluator.ts +0 -183
  270. package/benchmarks/src/evaluators/index.ts +0 -3
  271. package/benchmarks/src/evaluators/types.ts +0 -22
  272. package/benchmarks/src/index.ts +0 -3
  273. package/benchmarks/src/providers.ts +0 -13
  274. package/benchmarks/src/runner.ts +0 -824
  275. package/benchmarks/src/types.ts +0 -63
  276. package/benchmarks/tsconfig.json +0 -19
  277. package/leaderboard/README.md +0 -148
  278. package/leaderboard/app/api/benchmark-data/route.ts +0 -131
  279. package/leaderboard/app/api/benchmark-detail/route.ts +0 -172
  280. package/leaderboard/app/details/[model]/[provider]/[language]/page.tsx +0 -501
  281. package/leaderboard/app/exercise/[model]/[provider]/[language]/[exercise]/page.tsx +0 -375
  282. package/leaderboard/app/globals.css +0 -27
  283. package/leaderboard/app/layout.tsx +0 -21
  284. package/leaderboard/app/page.tsx +0 -170
  285. package/leaderboard/components/LeaderboardTable.tsx +0 -168
  286. package/leaderboard/components/PerformanceChart.tsx +0 -109
  287. package/leaderboard/next-env.d.ts +0 -5
  288. package/leaderboard/next.config.js +0 -4
  289. package/leaderboard/package-lock.json +0 -6363
  290. package/leaderboard/package.json +0 -28
  291. package/leaderboard/postcss.config.js +0 -6
  292. package/leaderboard/tailwind.config.js +0 -17
  293. package/leaderboard/tsconfig.json +0 -28
  294. package/leaderboard/types/benchmark.ts +0 -67
  295. package/leaderboard/utils/dataProcessor.ts +0 -33
  296. package/src/agents/tools/asana/definitions.ts +0 -199
  297. package/src/agents/tools/asana/index.ts +0 -108
  298. package/src/agents/tools/ast/astAppendNode.ts +0 -90
  299. package/src/agents/tools/ast/astDeleteNode.ts +0 -88
  300. package/src/agents/tools/ast/astEditNode.ts +0 -95
  301. package/src/agents/tools/ast/astGetPathForLine.ts +0 -73
  302. package/src/agents/tools/ast/astListPaths.ts +0 -66
  303. package/src/agents/tools/ast/index.ts +0 -7
  304. package/src/agents/tools/github/definitions.ts +0 -89
  305. package/src/agents/tools/github/index.ts +0 -67
  306. package/src/chat-old.ts +0 -446
  307. package/src/plugins/asana.ts +0 -146
  308. package/src/plugins/downloader/plugin.ts +0 -103
  309. package/src/plugins/downloader/types.ts +0 -92
  310. package/src/plugins/figma.ts +0 -158
  311. package/src/plugins/github.ts +0 -219
  312. package/src/plugins/jira.ts +0 -115
  313. package/src/plugins/linear.ts +0 -230
  314. package/src/plugins/notion.ts +0 -179
  315. package/src/plugins/tree-sitter/editor.ts +0 -369
  316. package/src/plugins/tree-sitter/lang-packs/index.ts +0 -23
  317. package/src/plugins/tree-sitter/lang-packs/java.ts +0 -59
  318. package/src/plugins/tree-sitter/lang-packs/javascript.ts +0 -57
  319. package/src/plugins/tree-sitter/lang-packs/python.ts +0 -45
  320. package/src/plugins/tree-sitter/lang-packs/types.ts +0 -79
  321. package/src/plugins/tree-sitter/lang-packs/typescript.ts +0 -49
  322. package/src/plugins/tree-sitter/parser.ts +0 -470
  323. package/src/plugins/tree-sitter/simple-paths.ts +0 -467
  324. package/test-comprehensive.ts +0 -31
  325. package/tests/tree-sitter/editor.test.ts +0 -113
  326. package/tests/tree-sitter/invalid.test.ts +0 -299
  327. package/tests/tree-sitter/paths/common-edits.test.ts +0 -564
  328. package/tests/tree-sitter/paths/debug-exact-position.test.ts +0 -44
  329. package/tests/tree-sitter/paths/debug-line-indexing.test.ts +0 -49
  330. package/tests/tree-sitter/paths/debug-paths.test.ts +0 -90
  331. package/tests/tree-sitter/paths/paths.test.ts +0 -170
  332. package/tests/tree-sitter/paths/simple-paths.test.ts +0 -367
  333. package/tests/tree-sitter/sample-after.ts +0 -48
  334. package/tests/tree-sitter/sample-before.ts +0 -25
  335. package/tests/tree-sitter/test-files/completely-broken.ts +0 -7
  336. package/tests/tree-sitter/test-files/duplicate-braces.ts +0 -39
  337. package/tests/tree-sitter/test-files/invalid-nesting.ts +0 -39
  338. package/tests/tree-sitter/test-files/malformed-signature.ts +0 -39
  339. package/tests/tree-sitter/test-files/mismatched-parens.ts +0 -39
  340. package/tests/tree-sitter/test-files/missing-semicolon.ts +0 -39
  341. package/tests/tree-sitter/test-files/partially-broken.ts +0 -20
  342. package/tests/tree-sitter/test-files/specific-errors.ts +0 -14
  343. package/tests/tree-sitter/test-files/unclosed-string.ts +0 -39
  344. package/tests/tree-sitter/tree-sitter.test.ts +0 -251
@@ -1,4 +1,4 @@
1
- import { Plugin } from "../../plugins/types";
1
+ import { Plugin, PluginContext } from "../../plugins/types";
2
2
  import { IAgent } from "../../agents/interface";
3
3
  import { Tool } from "../../clients/types";
4
4
  import { Config } from "../../types";
@@ -7,6 +7,7 @@ import { AgentService } from "../AgentService";
7
7
  import { PluginService } from "../../plugins/plugins";
8
8
  import { AIClient } from "../../clients";
9
9
  import { ToolsService } from "../Tools";
10
+ import { MediaProcessorService } from "../MediaProcessorService";
10
11
 
11
12
  /*
12
13
  *
@@ -29,7 +30,8 @@ export interface ModuleTool {
29
30
 
30
31
  export type ModuleAgent = IAgent;
31
32
 
32
- export type ModulePlugin = { name: string; plugin: Plugin };
33
+ export type PluginConstructor = new (context: PluginContext) => Plugin;
34
+ export type ModulePlugin = { name: string; plugin: PluginConstructor };
33
35
 
34
36
  export type ModuleClient = {
35
37
  client: GenericClient;
@@ -47,6 +49,7 @@ export interface ModuleContext {
47
49
  Plugins: PluginService;
48
50
  Clients: AIClient;
49
51
  Tools: ToolsService;
52
+ MediaProcessor?: MediaProcessorService;
50
53
  }
51
54
 
52
55
  export interface KnowhowModule {
@@ -96,7 +96,7 @@ export class ScriptExecutor {
96
96
 
97
97
  try {
98
98
  // Validate script
99
- const validation = policyEnforcer.validateScript(request.script);
99
+ const validation = policyEnforcer.validateScript(request.script, policy.allowNetworkAccess);
100
100
  if (!validation.valid) {
101
101
  tracer.emitEvent("script_validation_failed", {
102
102
  issues: validation.issues,
@@ -240,8 +240,9 @@ export class ScriptExecutor {
240
240
  tracer.emitEvent("script_execution_start", {});
241
241
 
242
242
  // Execute the script and get the result
243
+ // Note: do NOT set timeout here — it kills the isolate while awaiting host async promises.
244
+ // The outer executeWithTimeout wrapper handles wall-clock timeout instead.
243
245
  const result = await compiledScript.run(vmContext, {
244
- timeout: policyEnforcer.getQuotas().maxExecutionTimeMs,
245
246
  promise: true,
246
247
  copy: true,
247
248
  });
@@ -280,13 +281,29 @@ export class ScriptExecutor {
280
281
  `__host_${name}`,
281
282
  new ivm.Reference(async (...args: any[]) => {
282
283
  const result = await fn(...args);
283
- return new ivm.ExternalCopy(result).copyInto();
284
+ const safeResult = result !== undefined ? result : null;
285
+ const plainResult =
286
+ safeResult !== null && typeof safeResult === 'object'
287
+ ? JSON.parse(JSON.stringify(safeResult))
288
+ : safeResult;
289
+ // copyInto() transfers the value into the isolate heap so it's directly usable
290
+ return new ivm.ExternalCopy(plainResult).copyInto();
284
291
  })
285
292
  );
293
+ // Use applySyncPromise so the script isolate suspends and yields the Node.js event loop
294
+ // back to the host while waiting for async host operations (MCP stdio calls etc.).
295
+ // Without this, the ivm isolate blocks the event loop and stdio-based MCP transports
296
+ // can never deliver their responses → deadlock.
286
297
  await vmContext.eval(`
287
298
  globalThis.${name} = (...a) =>
288
- __host_${name}.apply(undefined, a,
289
- { arguments: { copy: true }, result: { promise: true, copy: true } });
299
+ new Promise((resolve, reject) => {
300
+ try {
301
+ // applySyncPromise does not support result options — the Reference fn returns ExternalCopy
302
+ const result = __host_${name}.applySyncPromise(undefined, a,
303
+ { arguments: { copy: true } });
304
+ resolve(result);
305
+ } catch(e) { reject(e); }
306
+ });
290
307
  `);
291
308
  };
292
309
 
@@ -308,11 +325,13 @@ export class ScriptExecutor {
308
325
 
309
326
  // Expose async sandbox functions
310
327
  await exposeAsync("callTool", async (tool, params) => {
311
- const { functionResp } = await sandboxContext.callTool(
312
- tool as string,
313
- params
314
- );
315
- return functionResp;
328
+ try {
329
+ const result = await sandboxContext.callTool(tool as string, params);
330
+ const { functionResp } = result;
331
+ return functionResp !== undefined ? functionResp : null;
332
+ } catch (err) {
333
+ throw err;
334
+ }
316
335
  });
317
336
  await exposeAsync("llm", (messages, options) =>
318
337
  sandboxContext.llm(messages, options || {})
@@ -163,7 +163,7 @@ export class ScriptPolicyEnforcer {
163
163
  /**
164
164
  * Validate script content for security issues
165
165
  */
166
- validateScript(scriptContent: string): { valid: boolean; issues: string[] } {
166
+ validateScript(scriptContent: string, allowNetworkAccess?: boolean): { valid: boolean; issues: string[] } {
167
167
  const issues: string[] = [];
168
168
 
169
169
  // Check for dangerous patterns
@@ -176,11 +176,15 @@ export class ScriptPolicyEnforcer {
176
176
  /Function\s*\(/gi, // Function constructor
177
177
  /setTimeout/gi, // setTimeout
178
178
  /setInterval/gi, // setInterval
179
- /fetch\s*\(/gi, // Direct fetch calls
180
179
  /XMLHttpRequest/gi, // XHR
181
180
  /WebSocket/gi, // WebSocket
182
181
  ];
183
182
 
183
+ // Block direct fetch calls when network access is not explicitly allowed
184
+ if (!allowNetworkAccess) {
185
+ dangerousPatterns.push(/fetch\s*\(/gi);
186
+ }
187
+
184
188
  for (const pattern of dangerousPatterns) {
185
189
  if (pattern.test(scriptContent)) {
186
190
  issues.push(`Potentially dangerous pattern detected: ${pattern.source}`);
package/src/types.ts CHANGED
@@ -265,6 +265,7 @@ export const Models = {
265
265
  Gemini_31_Pro_Preview: "gemini-3.1-pro-preview",
266
266
  Gemini_31_Flash_Image_Preview: "gemini-3.1-flash-image-preview",
267
267
  Gemini_31_Flash_Lite_Preview: "gemini-3.1-flash-lite-preview",
268
+ Gemini_31_Flash_Live_Preview: "gemini-3.1-flash-live-preview",
268
269
  Gemini_3_Flash_Preview: "gemini-3-flash-preview",
269
270
  Gemini_3_Pro_Image_Preview: "gemini-3-pro-image-preview",
270
271
  // Gemini 2.5
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Thin native-fetch wrapper that returns { data: T } for backward compatibility.
3
+ * Replaces axios throughout the codebase.
4
+ */
5
+
6
+ export interface HttpResponse<T = any> {
7
+ data: T;
8
+ status: number;
9
+ headers: Headers;
10
+ }
11
+
12
+ export class HttpError extends Error {
13
+ constructor(
14
+ public status: number,
15
+ public response: Response,
16
+ message: string
17
+ ) {
18
+ super(message);
19
+ this.name = "HttpError";
20
+ }
21
+ }
22
+
23
+ async function parseBody<T>(response: Response, responseType?: string): Promise<T> {
24
+ if (responseType === "arraybuffer") {
25
+ return (await response.arrayBuffer()) as unknown as T;
26
+ }
27
+ if (responseType === "stream") {
28
+ return response.body as unknown as T;
29
+ }
30
+ const text = await response.text();
31
+ if (!text) return undefined as unknown as T;
32
+ try {
33
+ return JSON.parse(text) as T;
34
+ } catch {
35
+ return text as unknown as T;
36
+ }
37
+ }
38
+
39
+ async function request<T = any>(
40
+ url: string,
41
+ options: {
42
+ method?: string;
43
+ headers?: Record<string, string>;
44
+ body?: any;
45
+ responseType?: "json" | "arraybuffer" | "stream" | "text";
46
+ params?: Record<string, any>;
47
+ /** Timeout in milliseconds. Default: 30000 (30s). Use 0 to disable. */
48
+ timeout?: number;
49
+ } = {}
50
+ ): Promise<HttpResponse<T>> {
51
+ const { method = "GET", headers = {}, body, responseType, params, timeout = 30000 } = options;
52
+
53
+ let fullUrl = url;
54
+ if (params && Object.keys(params).length > 0) {
55
+ const searchParams = new URLSearchParams();
56
+ for (const [k, v] of Object.entries(params)) {
57
+ if (v !== undefined && v !== null) {
58
+ searchParams.set(k, String(v));
59
+ }
60
+ }
61
+ fullUrl = `${url}?${searchParams.toString()}`;
62
+ }
63
+
64
+ const fetchOptions: RequestInit = {
65
+ method,
66
+ headers: { ...headers },
67
+ };
68
+
69
+ if (body !== undefined && body !== null) {
70
+ if (body instanceof Buffer || body instanceof Uint8Array || typeof (body as any).pipe === "function") {
71
+ // Stream or buffer — pass directly, Node fetch supports ReadableStream
72
+ fetchOptions.body = body;
73
+ } else if (typeof body === "object" && !(body instanceof FormData)) {
74
+ fetchOptions.body = JSON.stringify(body);
75
+ (fetchOptions.headers as Record<string, string>)["Content-Type"] = "application/json";
76
+ } else {
77
+ fetchOptions.body = body;
78
+ }
79
+ }
80
+
81
+ // Apply timeout via AbortController
82
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
83
+ if (timeout > 0) {
84
+ const controller = new AbortController();
85
+ fetchOptions.signal = controller.signal;
86
+ timeoutId = setTimeout(() => {
87
+ controller.abort();
88
+ }, timeout);
89
+ }
90
+
91
+ let response: Response;
92
+ try {
93
+ response = await fetch(fullUrl, fetchOptions);
94
+ } catch (e: any) {
95
+ if (e?.name === "AbortError") {
96
+ throw new HttpError(408, undefined as any, `Request timeout after ${timeout}ms: ${url}`);
97
+ }
98
+ throw e;
99
+ } finally {
100
+ if (timeoutId !== undefined) clearTimeout(timeoutId);
101
+ }
102
+
103
+ if (!response.ok) {
104
+ const text = await response.text().catch(() => "");
105
+ throw new HttpError(response.status, response, `HTTP ${response.status}: ${text}`);
106
+ }
107
+
108
+ const data = await parseBody<T>(response, responseType);
109
+
110
+ return { data, status: response.status, headers: response.headers };
111
+ }
112
+
113
+ export const http = {
114
+ get: <T = any>(url: string, options?: Omit<Parameters<typeof request>[1], "method">) =>
115
+ request<T>(url, { ...options, method: "GET" }),
116
+ post: <T = any>(url: string, body?: any, options?: Omit<Parameters<typeof request>[1], "method" | "body">) =>
117
+ request<T>(url, { ...options, method: "POST", body }),
118
+ put: <T = any>(url: string, body?: any, options?: Omit<Parameters<typeof request>[1], "method" | "body">) =>
119
+ request<T>(url, { ...options, method: "PUT", body }),
120
+ patch: <T = any>(url: string, body?: any, options?: Omit<Parameters<typeof request>[1], "method" | "body">) =>
121
+ request<T>(url, { ...options, method: "PATCH", body }),
122
+ delete: <T = any>(url: string, options?: Omit<Parameters<typeof request>[1], "method">) =>
123
+ request<T>(url, { ...options, method: "DELETE" }),
124
+ isHttpError: (e: any): e is HttpError => e instanceof HttpError,
125
+ };
126
+
127
+ export default http;
@@ -1,4 +1,4 @@
1
- import axios from "axios";
1
+ import http from "../../utils/http";
2
2
  import { KNOWHOW_API_URL } from "../../services/KnowhowClient";
3
3
  import { openBrowser } from "../../auth/browserLogin";
4
4
  import { Spinner } from "../../auth/spinner";
@@ -93,21 +93,18 @@ export class PasskeySetupService {
93
93
 
94
94
  private async createSetupSession(jwt: string): Promise<PasskeySetupSession> {
95
95
  try {
96
- const response = await axios.post<PasskeySetupSession>(
96
+ const response = await http.post<PasskeySetupSession>(
97
97
  `${this.baseUrl}/api/worker/passkey/setup/session`,
98
98
  {},
99
99
  {
100
100
  headers: { Authorization: `Bearer ${jwt}` },
101
- timeout: 10000,
102
101
  }
103
102
  );
104
103
  return response.data;
105
104
  } catch (error) {
106
- if (axios.isAxiosError(error)) {
105
+ if (http.isHttpError(error)) {
107
106
  throw new Error(
108
- `Failed to create passkey setup session: ${
109
- error.response?.data?.message || error.message
110
- }`
107
+ `Failed to create passkey setup session: ${error.message}`
111
108
  );
112
109
  }
113
110
  throw error;
@@ -125,9 +122,8 @@ export class PasskeySetupService {
125
122
  attempt++;
126
123
 
127
124
  try {
128
- const response = await axios.get<PasskeySetupStatus>(
129
- `${this.baseUrl}/api/worker/passkey/setup/status/${sessionId}`,
130
- { timeout: 10000 }
125
+ const response = await http.get<PasskeySetupStatus>(
126
+ `${this.baseUrl}/api/worker/passkey/setup/status/${sessionId}`
131
127
  );
132
128
 
133
129
  const { status, credential } = response.data;
@@ -140,7 +136,7 @@ export class PasskeySetupService {
140
136
  );
141
137
  }
142
138
  } catch (error) {
143
- if (axios.isAxiosError(error) && error.code === "ECONNABORTED") {
139
+ if (http.isHttpError(error) && error.status === 408) {
144
140
  // Timeout — keep polling
145
141
  } else if (!(error instanceof Error && error.message.includes("expired"))) {
146
142
  // Re-throw non-timeout, non-expected errors
@@ -154,12 +154,18 @@ describe("AIClient", () => {
154
154
  });
155
155
 
156
156
  it("should find model by detection in registered providers", () => {
157
- aiClient.registerClient("test", new FakeClient());
158
- aiClient.registerModels("test", ["gpt-4-turbo", "gpt-4-vision"]);
157
+ // Register an explicit myopenai provider with a gpt-4 prefix model
158
+ aiClient.registerClient("myopenai", new FakeClient());
159
+ aiClient.registerModels("myopenai", ["gpt-4-turbo", "gpt-4-vision"]);
159
160
 
161
+ // "another" provider (registered in beforeEach) has exact "gpt-4" match
162
+ // "myopenai" has prefix-matches "gpt-4-turbo" and "gpt-4-vision"
163
+ // findModel finds the first provider with a model matching the prefix "gpt-4"
160
164
  const result = aiClient.detectProviderModel("", "gpt-4");
161
- expect(result.provider).toBe("openai"); // Real openai provider takes precedence
162
- expect(result.model).toBe("gpt-4.1-2025-04-14"); // Actual model found by prefix match
165
+ // Some provider should be found that has a gpt-4 model
166
+ expect(result.provider).toBeTruthy();
167
+ // The found model should start with "gpt-4"
168
+ expect(result.model).toMatch(/^gpt-4/);
163
169
  });
164
170
 
165
171
  it("should handle model with provider prefix when provider is empty", () => {
@@ -175,15 +181,16 @@ describe("AIClient", () => {
175
181
  });
176
182
 
177
183
  it("should detect real provider when model exists", () => {
178
- aiClient.registerClient("test", new FakeClient());
179
- aiClient.registerModels("test", ["claude-3-opus"]);
184
+ // Register an anthropic provider explicitly so we don't rely on env vars
185
+ aiClient.registerClient("anthropic", new FakeClient());
186
+ aiClient.registerModels("anthropic", ["claude-3-opus-20240229"]);
180
187
 
181
188
  // Test with provider prefix that gets stripped
182
189
  const result = aiClient.detectProviderModel(
183
190
  "",
184
191
  "anthropic/claude-3-opus-20240229"
185
192
  );
186
- expect(result.provider).toBe("anthropic"); // Real anthropic provider found
193
+ expect(result.provider).toBe("anthropic"); // anthropic provider found
187
194
  expect(result.model).toBe("claude-3-opus-20240229");
188
195
  });
189
196
  });
@@ -208,15 +215,13 @@ describe("AIClient", () => {
208
215
  it("should return all registered models from all providers", () => {
209
216
  const allModels = aiClient.listAllModels();
210
217
  expect(typeof allModels).toBe("object");
211
- // listAllModels() only returns models from real providers that have API keys
212
- // Our test clients are not included in the listAllModels() output
213
- // But we can verify real providers are present
218
+ // Verify our registered test providers are present
214
219
  expect(Object.keys(allModels).length).toBeGreaterThan(0);
215
- // Real providers like openai, anthropic should be present
220
+ // The test providers registered in beforeEach should be present
216
221
  const providers = Object.keys(allModels);
217
222
  expect(
218
223
  providers.some((p) =>
219
- ["openai", "anthropic", "google", "xai"].includes(p)
224
+ ["fake", "another"].includes(p)
220
225
  )
221
226
  ).toBe(true);
222
227
  });
@@ -470,8 +475,10 @@ describe("AIClient", () => {
470
475
  });
471
476
 
472
477
  it("should handle provider stripping with complex model names", () => {
473
- // Test detection with real providers that exist in AIClient
474
- // AIClient should find the real anthropic provider for claude models
478
+ // Register providers explicitly so we don't rely on env vars
479
+ aiClient.registerClient("anthropic", new FakeClient());
480
+ aiClient.registerModels("anthropic", ["claude-3-opus-20240229"]);
481
+
475
482
  const detection1 = aiClient.detectProviderModel(
476
483
  "",
477
484
  "anthropic/claude-3-opus-20240229"
@@ -484,14 +491,10 @@ describe("AIClient", () => {
484
491
  "",
485
492
  "openai/non-existent-model"
486
493
  );
487
- // Should either return empty strings or fallback to defaults
494
+ // Should return original values when no match found
488
495
  expect(detection2).toBeDefined();
489
- if (detection2?.provider === "") {
490
- expect(detection2?.model).toBe("openai/non-existent-model");
491
- } else {
492
- expect(detection2?.provider).toBe("openai");
493
- expect(detection2?.model).toBe("gpt-5");
494
- }
496
+ expect(detection2?.provider).toBe("");
497
+ expect(detection2?.model).toBe("openai/non-existent-model");
495
498
  });
496
499
 
497
500
  it("should handle model prefix matching edge cases", () => {
@@ -1,70 +1,3 @@
1
- import { FigmaPlugin } from "../../../src/plugins/figma";
2
-
3
- const figmaToken = "test_token"; // This should be the actual testing token
4
- const figmaPlugin = new FigmaPlugin();
5
-
6
- const exampleFigmaFileUrl =
7
- "https://www.figma.com/file/iK2ggDLxl94Q4q0FKUdlmM/Guilds";
8
- const exampleNodeId = "52-717";
9
- const exampleNodeIds = [exampleNodeId];
10
-
11
- const expectedApiResponse = {
12
- name: "Guilds",
13
- nodes: { "52-717": { document: {} } },
14
- };
15
-
16
- describe("FigmaPlugin", () => {
17
- describe("extractUrls", () => {
18
- it("should extract Figma file URLs from a given text", () => {
19
- const text = `Design can be found here: ${exampleFigmaFileUrl}`;
20
- expect(figmaPlugin.extractUrls(text)).toEqual([exampleFigmaFileUrl]);
21
- });
22
- });
23
-
24
- describe("extractFileIdFromUrl", () => {
25
- it("should extract file ID from Figma URL", () => {
26
- expect(figmaPlugin.extractFileIdFromUrl(exampleFigmaFileUrl)).toBe(
27
- "iK2ggDLxl94Q4q0FKUdlmM"
28
- );
29
- });
30
- });
31
-
32
- describe("parseNodeIdsFromUrl", () => {
33
- it("should extract node IDs from Figma URL", () => {
34
- const urlWithNode = exampleFigmaFileUrl + "?node-id=" + exampleNodeId;
35
- expect(figmaPlugin.parseNodeIdsFromUrl(urlWithNode)).toEqual(
36
- exampleNodeIds
37
- );
38
- });
39
- });
40
-
41
- describe("loadFigmaData", () => {
42
- it("should make an API call and return Figma data", async () => {
43
- const data = await figmaPlugin.loadFigmaData(
44
- exampleFigmaFileUrl + "?node-id=" + exampleNodeId
45
- );
46
- expect(data).toBeDefined;
47
- });
48
- });
49
-
50
- describe("embed", () => {
51
- it("should return a minimal embedding array", async () => {
52
- const embeddings = await figmaPlugin.embed(
53
- `Here's the design file: ${exampleFigmaFileUrl}?node-id=${exampleNodeId}`
54
- );
55
- expect(embeddings).toBeInstanceOf(Array);
56
- expect(embeddings).toHaveLength(1);
57
- const nodeId = exampleNodeId.replace("-", ":");
58
- console.log(JSON.parse(embeddings[0].text));
59
- /*
60
- *expect(embeddings[0].text).toEqual(
61
- * JSON.stringify({
62
- * id: expectedApiResponse.name,
63
- * metadata: {},
64
- * text: JSON.stringify(expectedApiResponse),
65
- * })
66
- *);
67
- */
68
- });
69
- });
70
- });
1
+ // FigmaPlugin tests moved to @tyvm/knowhow-plugin-figma package
2
+ // Install and configure @tyvm/knowhow-plugin-figma to use Figma integration
3
+ describe.skip("FigmaPlugin (moved to @tyvm/knowhow-plugin-figma)", () => {});
@@ -18,6 +18,7 @@ jest.mock("../../../src/services/EventService", () => ({
18
18
  EventService: jest.fn().mockImplementation(() => ({
19
19
  on: jest.fn(),
20
20
  emit: jest.fn(),
21
+ log: jest.fn(),
21
22
  })),
22
23
  }));
23
24
 
@@ -54,6 +55,7 @@ describe("LanguagePlugin - Content-Based Triggering", () => {
54
55
  eventHandlers.set(event, handler);
55
56
  }),
56
57
  emit: jest.fn(),
58
+ log: jest.fn(),
57
59
  };
58
60
 
59
61
  mockPluginService = {
@@ -73,6 +73,7 @@ describe("LanguagePlugin", () => {
73
73
  const mockEventService = {
74
74
  on: jest.fn(),
75
75
  emit: jest.fn(),
76
+ log: jest.fn(),
76
77
  };
77
78
  const mockListPlugins = jest.fn().mockReturnValue(["github", "asana"]);
78
79
  const mockCall = jest.fn().mockResolvedValue(["mocked plugin response"]);
@@ -362,6 +363,7 @@ describe("LanguagePlugin", () => {
362
363
  const mockEventService = {
363
364
  on: jest.fn(),
364
365
  emit: jest.fn(),
366
+ log: jest.fn(),
365
367
  };
366
368
  const mockPluginService = new MockedPluginService({} as any);
367
369
  const mockListPlugins = jest.fn().mockReturnValue(["github"]);
@@ -371,7 +371,7 @@ describe("ToolResponseCache", () => {
371
371
  const result = await cache.queryToolResponse("missing_id", ".test");
372
372
  expect(result).toContain("Error: No tool response found");
373
373
  expect(result).toContain("missing_id");
374
- expect(result).toContain("Available IDs:");
374
+ expect(result).toContain("Available toolCallIds:");
375
375
  });
376
376
 
377
377
  it("should handle invalid JQ queries with error message", async () => {
@@ -698,7 +698,7 @@ describe("ToolResponseCache", () => {
698
698
  const result = await cache.tailToolResponse("missing_id");
699
699
  expect(result).toContain("Error: No tool response found");
700
700
  expect(result).toContain("missing_id");
701
- expect(result).toContain("Available IDs:");
701
+ expect(result).toContain("Available toolCallIds:");
702
702
  });
703
703
 
704
704
  it("should handle lines option of 0", async () => {
@@ -1,18 +1,4 @@
1
1
  // Global mocks that need to be hoisted before any imports
2
- jest.mock("tree-sitter", () => {
3
- return jest.fn().mockImplementation(() => ({
4
- setLanguage: jest.fn(),
5
- parse: jest.fn(() => ({ rootNode: { toString: () => "mock tree" } })),
6
- }));
7
- });
8
- jest.mock("tree-sitter-typescript", () => ({ typescript: jest.fn() }));
9
- jest.mock("tree-sitter-javascript", () => ({ javascript: jest.fn() }));
10
- jest.mock("../src/plugins/tree-sitter/parser", () => ({
11
- LanguageAgnosticParser: jest.fn(),
12
- }));
13
- jest.mock("../src/plugins/tree-sitter/editor", () => ({
14
- TreeEditor: jest.fn(),
15
- }));
16
2
  jest.mock("../src/services/S3", () => ({
17
3
  S3Service: jest.fn().mockImplementation(() => ({
18
4
  uploadFile: jest.fn(),
@@ -132,7 +132,7 @@ describe("ModulesService.loadModulesFromConfig", () => {
132
132
  });
133
133
 
134
134
  it("should load plugins from a module", async () => {
135
- const mockPlugin = {
135
+ const mockPluginInstance = {
136
136
  meta: { key: "test-plugin", name: "Test Plugin" },
137
137
  isEnabled: () => true,
138
138
  enable: () => {},
@@ -141,8 +141,10 @@ describe("ModulesService.loadModulesFromConfig", () => {
141
141
  callMany: () => Promise.resolve(""),
142
142
  embed: () => Promise.resolve([]),
143
143
  };
144
+ // ModulePlugin expects a constructor (class), not an instance
145
+ const MockPluginClass = jest.fn().mockImplementation(() => mockPluginInstance);
144
146
  const mockModule = makeModule({
145
- plugins: [{ name: "test-plugin", plugin: mockPlugin as any }],
147
+ plugins: [{ name: "test-plugin", plugin: MockPluginClass as any }],
146
148
  });
147
149
 
148
150
  const service = new ModulesService();
@@ -153,13 +155,14 @@ describe("ModulesService.loadModulesFromConfig", () => {
153
155
  const resolvedCtx = ctx || context;
154
156
  await mockModule.init({ config: {} as Config, cwd: process.cwd() });
155
157
  for (const plugin of mockModule.plugins) {
156
- resolvedCtx.Plugins.registerPlugin(plugin.name, plugin.plugin);
158
+ const instance = new (plugin.plugin as any)(resolvedCtx);
159
+ resolvedCtx.Plugins.registerPlugin(plugin.name, instance);
157
160
  }
158
161
  });
159
162
 
160
163
  await service.loadModulesFromConfig(context);
161
164
 
162
- expect(context.Plugins.registerPlugin).toHaveBeenCalledWith("test-plugin", mockPlugin);
165
+ expect(context.Plugins.registerPlugin).toHaveBeenCalledWith("test-plugin", mockPluginInstance);
163
166
  spy.mockRestore();
164
167
  });
165
168
 
@@ -11,6 +11,7 @@ function makeContext(): PluginContext {
11
11
  emit: jest.fn(),
12
12
  emitBlocking: jest.fn(),
13
13
  emitNonBlocking: jest.fn(),
14
+ log: jest.fn(),
14
15
  } as any,
15
16
  } as PluginContext;
16
17
  }
@@ -110,8 +111,8 @@ describe("PluginService.loadPluginsFromConfig", () => {
110
111
  });
111
112
 
112
113
  it("should log a warning and not crash when a plugin fails to load", async () => {
113
- const service = new PluginService(makeContext());
114
- const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
114
+ const context = makeContext();
115
+ const service = new PluginService(context);
115
116
 
116
117
  service.loadPlugin = jest.fn().mockRejectedValue(new Error("Module not found"));
117
118
 
@@ -122,12 +123,11 @@ describe("PluginService.loadPluginsFromConfig", () => {
122
123
  } as unknown as Config;
123
124
 
124
125
  await expect(service.loadPluginsFromConfig(config)).resolves.toBeUndefined();
125
- expect(warnSpy).toHaveBeenCalledWith(
126
+ expect(context.Events.log).toHaveBeenCalledWith(
127
+ "PluginService",
126
128
  expect.stringContaining("broken"),
127
- expect.any(String)
129
+ "warn"
128
130
  );
129
-
130
- warnSpy.mockRestore();
131
131
  });
132
132
 
133
133
  it("should load each plugin with the correct spec string", async () => {