@sean.holung/minicode 0.3.11 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/README.md +5 -3
  2. package/dist/scripts/run-benchmarks.js +7 -1
  3. package/dist/src/benchmark/runner.js +66 -3
  4. package/dist/src/cli/benchmark-run.js +1 -1
  5. package/dist/src/index.js +1 -1
  6. package/dist/src/indexer/code-map.js +16 -1
  7. package/dist/src/serve/agent-bridge.js +1 -1
  8. package/dist/src/session/session-store.js +1 -1
  9. package/dist/src/shared/symbol-resolution.js +24 -3
  10. package/dist/src/tools/find-path.js +1 -1
  11. package/dist/src/tools/find-references.js +1 -1
  12. package/dist/src/tools/get-dependencies.js +1 -1
  13. package/dist/src/tools/post-edit-diagnostics.js +185 -0
  14. package/dist/src/tools/read-symbol.js +2 -2
  15. package/dist/src/tools/registry.js +18 -3
  16. package/dist/src/tools/search-code-map.js +101 -9
  17. package/dist/src/ui/cli-ink.js +1 -1
  18. package/dist/src/web/app.js +1 -1
  19. package/dist/src/web/favicon.ico +0 -0
  20. package/dist/src/web/favicon.svg +9 -0
  21. package/dist/src/web/index.html +2 -0
  22. package/dist/tests/agent.test.js +1 -1
  23. package/dist/tests/context-indicator.test.js +1 -1
  24. package/dist/tests/file-tools.test.js +2 -2
  25. package/dist/tests/focus-tracker.test.js +1 -1
  26. package/dist/tests/graph-onboarding.test.js +20 -0
  27. package/dist/tests/guardrails.test.js +1 -1
  28. package/dist/tests/indexer.test.js +59 -28
  29. package/dist/tests/model-client-openai.test.js +4 -3
  30. package/dist/tests/model-selection.test.js +1 -1
  31. package/dist/tests/package-metadata.test.js +9 -0
  32. package/dist/tests/python-plugin.test.js +3 -3
  33. package/dist/tests/read-symbol.test.js +84 -10
  34. package/dist/tests/reasoning-effort.test.js +1 -1
  35. package/dist/tests/search-code-map.test.js +132 -1
  36. package/dist/tests/serve.integration.test.js +1 -1
  37. package/dist/tests/session-store.test.js +1 -1
  38. package/dist/tests/session.test.js +1 -1
  39. package/dist/tests/symbol-resolution.test.js +57 -0
  40. package/dist/tests/system-prompt.test.js +1 -1
  41. package/dist/tests/tool-registry.test.js +1 -1
  42. package/node_modules/@sean.holung/minicode-sdk/LICENSE +201 -0
  43. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/README.md +43 -22
  44. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/agent.d.ts +10 -1
  45. package/node_modules/@sean.holung/minicode-sdk/dist/src/agent/agent.d.ts.map +1 -0
  46. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/agent.js +33 -10
  47. package/node_modules/@sean.holung/minicode-sdk/dist/src/agent/agent.js.map +1 -0
  48. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/index.d.ts +1 -1
  49. package/node_modules/@sean.holung/minicode-sdk/dist/src/index.d.ts.map +1 -0
  50. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/index.js +1 -1
  51. package/node_modules/@sean.holung/minicode-sdk/dist/src/index.js.map +1 -0
  52. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/mcp/client-registry.js +1 -1
  53. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/mcp/client-registry.js.map +1 -1
  54. package/node_modules/@sean.holung/minicode-sdk/dist/src/model/client.d.ts.map +1 -0
  55. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/model/client.js +73 -5
  56. package/node_modules/@sean.holung/minicode-sdk/dist/src/model/client.js.map +1 -0
  57. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/prompt/system-prompt.d.ts.map +1 -1
  58. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/prompt/system-prompt.js +1 -1
  59. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/prompt/system-prompt.js.map +1 -1
  60. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file-replacers.d.ts +59 -0
  61. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file-replacers.d.ts.map +1 -0
  62. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file-replacers.js +392 -0
  63. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file-replacers.js.map +1 -0
  64. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file.d.ts +19 -0
  65. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file.d.ts.map +1 -0
  66. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/edit-file.js +14 -25
  67. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file.js.map +1 -0
  68. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/read-file.d.ts.map +1 -1
  69. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/read-file.js +11 -5
  70. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/read-file.js.map +1 -1
  71. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/run-command.d.ts.map +1 -1
  72. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/run-command.js +3 -0
  73. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/run-command.js.map +1 -0
  74. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/search.d.ts.map +1 -1
  75. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/search.js +52 -25
  76. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/search.js.map +1 -0
  77. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/package.json +6 -2
  78. package/node_modules/minicode-plugin-python/dist/src/index.d.ts +1 -1
  79. package/node_modules/minicode-plugin-python/dist/src/index.d.ts.map +1 -1
  80. package/node_modules/minicode-plugin-python/dist/tsconfig.tsbuildinfo +1 -1
  81. package/node_modules/minicode-plugin-python/package.json +2 -2
  82. package/package.json +4 -3
  83. package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts.map +0 -1
  84. package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js.map +0 -1
  85. package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts.map +0 -1
  86. package/node_modules/@minicode/agent-sdk/dist/src/index.js.map +0 -1
  87. package/node_modules/@minicode/agent-sdk/dist/src/model/client.d.ts.map +0 -1
  88. package/node_modules/@minicode/agent-sdk/dist/src/model/client.js.map +0 -1
  89. package/node_modules/@minicode/agent-sdk/dist/src/tools/edit-file.d.ts +0 -13
  90. package/node_modules/@minicode/agent-sdk/dist/src/tools/edit-file.d.ts.map +0 -1
  91. package/node_modules/@minicode/agent-sdk/dist/src/tools/edit-file.js.map +0 -1
  92. package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.js.map +0 -1
  93. package/node_modules/@minicode/agent-sdk/dist/src/tools/search.js.map +0 -1
  94. package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.d.ts +0 -2
  95. package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.d.ts.map +0 -1
  96. package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.js +0 -569
  97. package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.js.map +0 -1
  98. package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.d.ts +0 -2
  99. package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.d.ts.map +0 -1
  100. package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.js +0 -131
  101. package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.js.map +0 -1
  102. package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.d.ts +0 -2
  103. package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.d.ts.map +0 -1
  104. package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.js +0 -54
  105. package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.js.map +0 -1
  106. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.integration.test.d.ts +0 -2
  107. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.integration.test.d.ts.map +0 -1
  108. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.integration.test.js +0 -64
  109. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.integration.test.js.map +0 -1
  110. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.test.d.ts +0 -2
  111. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.test.d.ts.map +0 -1
  112. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.test.js +0 -350
  113. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.test.js.map +0 -1
  114. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-anthropic-structured-output.test.d.ts +0 -2
  115. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-anthropic-structured-output.test.d.ts.map +0 -1
  116. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-anthropic-structured-output.test.js +0 -211
  117. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-anthropic-structured-output.test.js.map +0 -1
  118. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.d.ts +0 -2
  119. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.d.ts.map +0 -1
  120. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.js +0 -330
  121. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.js.map +0 -1
  122. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-structured-output.test.d.ts +0 -2
  123. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-structured-output.test.d.ts.map +0 -1
  124. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-structured-output.test.js +0 -171
  125. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-structured-output.test.js.map +0 -1
  126. package/node_modules/@minicode/agent-sdk/dist/tests/session.test.d.ts +0 -2
  127. package/node_modules/@minicode/agent-sdk/dist/tests/session.test.d.ts.map +0 -1
  128. package/node_modules/@minicode/agent-sdk/dist/tests/session.test.js +0 -226
  129. package/node_modules/@minicode/agent-sdk/dist/tests/session.test.js.map +0 -1
  130. package/node_modules/@minicode/agent-sdk/dist/tests/structured-output.test.d.ts +0 -2
  131. package/node_modules/@minicode/agent-sdk/dist/tests/structured-output.test.d.ts.map +0 -1
  132. package/node_modules/@minicode/agent-sdk/dist/tests/structured-output.test.js +0 -212
  133. package/node_modules/@minicode/agent-sdk/dist/tests/structured-output.test.js.map +0 -1
  134. package/node_modules/@minicode/agent-sdk/dist/tests/system-prompt.test.d.ts +0 -2
  135. package/node_modules/@minicode/agent-sdk/dist/tests/system-prompt.test.d.ts.map +0 -1
  136. package/node_modules/@minicode/agent-sdk/dist/tests/system-prompt.test.js +0 -76
  137. package/node_modules/@minicode/agent-sdk/dist/tests/system-prompt.test.js.map +0 -1
  138. package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.d.ts +0 -3
  139. package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.d.ts.map +0 -1
  140. package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.js +0 -20
  141. package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.js.map +0 -1
  142. package/node_modules/@minicode/agent-sdk/dist/tests/tool-factory-options.test.d.ts +0 -2
  143. package/node_modules/@minicode/agent-sdk/dist/tests/tool-factory-options.test.d.ts.map +0 -1
  144. package/node_modules/@minicode/agent-sdk/dist/tests/tool-factory-options.test.js +0 -72
  145. package/node_modules/@minicode/agent-sdk/dist/tests/tool-factory-options.test.js.map +0 -1
  146. package/node_modules/@minicode/agent-sdk/dist/tests/tool-registry.test.d.ts +0 -2
  147. package/node_modules/@minicode/agent-sdk/dist/tests/tool-registry.test.d.ts.map +0 -1
  148. package/node_modules/@minicode/agent-sdk/dist/tests/tool-registry.test.js +0 -69
  149. package/node_modules/@minicode/agent-sdk/dist/tests/tool-registry.test.js.map +0 -1
  150. package/node_modules/@minicode/agent-sdk/dist/tsconfig.tsbuildinfo +0 -1
  151. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/structured-output.d.ts +0 -0
  152. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/structured-output.d.ts.map +0 -0
  153. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/structured-output.js +0 -0
  154. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/structured-output.js.map +0 -0
  155. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/types.d.ts +0 -0
  156. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/types.d.ts.map +0 -0
  157. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/types.js +0 -0
  158. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/types.js.map +0 -0
  159. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/focus-tracker.d.ts +0 -0
  160. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/focus-tracker.d.ts.map +0 -0
  161. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/focus-tracker.js +0 -0
  162. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/focus-tracker.js.map +0 -0
  163. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/types.d.ts +0 -0
  164. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/types.d.ts.map +0 -0
  165. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/types.js +0 -0
  166. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/types.js.map +0 -0
  167. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/mcp/client-registry.d.ts +0 -0
  168. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/mcp/client-registry.d.ts.map +0 -0
  169. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/model/client.d.ts +0 -0
  170. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/prompt/system-prompt.d.ts +0 -0
  171. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/safety/guardrails.d.ts +0 -0
  172. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/safety/guardrails.d.ts.map +0 -0
  173. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/safety/guardrails.js +0 -0
  174. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/safety/guardrails.js.map +0 -0
  175. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/session/session.d.ts +0 -0
  176. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/session/session.d.ts.map +0 -0
  177. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/session/session.js +0 -0
  178. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/session/session.js.map +0 -0
  179. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/helpers.d.ts +0 -0
  180. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/helpers.d.ts.map +0 -0
  181. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/helpers.js +0 -0
  182. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/helpers.js.map +0 -0
  183. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/list-files.d.ts +0 -0
  184. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/list-files.d.ts.map +0 -0
  185. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/list-files.js +0 -0
  186. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/list-files.js.map +0 -0
  187. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/read-file.d.ts +0 -0
  188. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/registry.d.ts +0 -0
  189. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/registry.d.ts.map +0 -0
  190. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/registry.js +0 -0
  191. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/registry.js.map +0 -0
  192. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/run-command.d.ts +0 -0
  193. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/search.d.ts +0 -0
  194. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/write-file.d.ts +0 -0
  195. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/write-file.d.ts.map +0 -0
  196. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/write-file.js +0 -0
  197. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/write-file.js.map +0 -0
@@ -1,131 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtemp, mkdir, readFile, writeFile } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import path from "node:path";
5
- import { test } from "node:test";
6
- import { createEditFileTool } from "../src/tools/edit-file.js";
7
- import { createReadFileTool } from "../src/tools/read-file.js";
8
- import { createSearchTool } from "../src/tools/search.js";
9
- import { createWriteFileTool } from "../src/tools/write-file.js";
10
- import { createTestAgentConfig } from "./test-utils.js";
11
- async function createTempWorkspace() {
12
- return mkdtemp(path.join(tmpdir(), "sdk-tests-"));
13
- }
14
- test("read_file reads file with line numbers", async () => {
15
- const workspaceRoot = await createTempWorkspace();
16
- const filePath = path.join(workspaceRoot, "test.txt");
17
- await writeFile(filePath, "line1\nline2\nline3", "utf8");
18
- const tool = createReadFileTool(createTestAgentConfig(workspaceRoot));
19
- const result = await tool.execute({ path: "test.txt" });
20
- assert.ok(result.includes("1|line1"));
21
- assert.ok(result.includes("2|line2"));
22
- assert.ok(result.includes("3|line3"));
23
- });
24
- test("read_file supports negative offset", async () => {
25
- const workspaceRoot = await createTempWorkspace();
26
- const filePath = path.join(workspaceRoot, "lines.txt");
27
- await writeFile(filePath, "a\nb\nc\nd", "utf8");
28
- const readTool = createReadFileTool(createTestAgentConfig(workspaceRoot));
29
- const output = await readTool.execute({
30
- path: "lines.txt",
31
- offset: -2,
32
- limit: 2,
33
- });
34
- assert.equal(output, "3|c\n4|d");
35
- });
36
- test("read_file rejects paths outside workspace", async () => {
37
- const workspaceRoot = await createTempWorkspace();
38
- const tool = createReadFileTool(createTestAgentConfig(workspaceRoot));
39
- await assert.rejects(() => tool.execute({ path: "../../../etc/passwd" }), /outside workspace root/);
40
- });
41
- test("edit_file replaces exactly one match", async () => {
42
- const workspaceRoot = await createTempWorkspace();
43
- const filePath = path.join(workspaceRoot, "sample.txt");
44
- await writeFile(filePath, "hello world", "utf8");
45
- const editTool = createEditFileTool(createTestAgentConfig(workspaceRoot));
46
- const result = await editTool.execute({
47
- path: "sample.txt",
48
- old_string: "world",
49
- new_string: "there",
50
- });
51
- assert.match(result, /Updated "sample\.txt" successfully\./);
52
- const updated = await readFile(filePath, "utf8");
53
- assert.equal(updated, "hello there");
54
- });
55
- test("edit_file fails when old_string is not unique", async () => {
56
- const workspaceRoot = await createTempWorkspace();
57
- const filePath = path.join(workspaceRoot, "sample.txt");
58
- await writeFile(filePath, "repeat repeat repeat", "utf8");
59
- const editTool = createEditFileTool(createTestAgentConfig(workspaceRoot));
60
- await assert.rejects(() => editTool.execute({
61
- path: "sample.txt",
62
- old_string: "repeat",
63
- new_string: "once",
64
- }), /matched 3 times/);
65
- const unchanged = await readFile(filePath, "utf8");
66
- assert.equal(unchanged, "repeat repeat repeat");
67
- });
68
- test("edit_file calls afterEdit hook", async () => {
69
- const workspaceRoot = await createTempWorkspace();
70
- const filePath = path.join(workspaceRoot, "hook-test.txt");
71
- await writeFile(filePath, "before edit", "utf8");
72
- let hookCalled = false;
73
- let hookPath = "";
74
- const editTool = createEditFileTool(createTestAgentConfig(workspaceRoot), {
75
- afterEdit: (path) => {
76
- hookCalled = true;
77
- hookPath = path;
78
- },
79
- });
80
- await editTool.execute({
81
- path: "hook-test.txt",
82
- old_string: "before",
83
- new_string: "after",
84
- });
85
- assert.ok(hookCalled);
86
- assert.ok(hookPath.endsWith("hook-test.txt"));
87
- });
88
- test("write_file creates file and calls afterWrite hook", async () => {
89
- const workspaceRoot = await createTempWorkspace();
90
- let hookCalled = false;
91
- const writeTool = createWriteFileTool(createTestAgentConfig(workspaceRoot), {
92
- afterWrite: () => {
93
- hookCalled = true;
94
- },
95
- });
96
- const result = await writeTool.execute({
97
- path: "new-file.txt",
98
- content: "hello world",
99
- });
100
- assert.match(result, /Wrote 11 characters/);
101
- const content = await readFile(path.join(workspaceRoot, "new-file.txt"), "utf8");
102
- assert.equal(content, "hello world");
103
- assert.ok(hookCalled);
104
- });
105
- test("write_file creates nested directories", async () => {
106
- const workspaceRoot = await createTempWorkspace();
107
- const writeTool = createWriteFileTool(createTestAgentConfig(workspaceRoot));
108
- await writeTool.execute({
109
- path: "deep/nested/dir/file.txt",
110
- content: "nested content",
111
- });
112
- const content = await readFile(path.join(workspaceRoot, "deep/nested/dir/file.txt"), "utf8");
113
- assert.equal(content, "nested content");
114
- });
115
- test("search finds matches inside hidden files", async () => {
116
- const workspaceRoot = await createTempWorkspace();
117
- await mkdir(path.join(workspaceRoot, ".github", "workflows"), { recursive: true });
118
- await writeFile(path.join(workspaceRoot, ".github", "workflows", "ci.yml"), "name: CI\nsteps:\n - run: echo hello\n", "utf8");
119
- const searchTool = createSearchTool(createTestAgentConfig(workspaceRoot));
120
- const result = await searchTool.execute({ pattern: "echo hello" });
121
- assert.ok(result.includes(".github/workflows/ci.yml"));
122
- });
123
- test("search finds matches inside gitignored files", async () => {
124
- const workspaceRoot = await createTempWorkspace();
125
- await writeFile(path.join(workspaceRoot, ".gitignore"), "generated.txt\n", "utf8");
126
- await writeFile(path.join(workspaceRoot, "generated.txt"), "needle in ignored file\n", "utf8");
127
- const searchTool = createSearchTool(createTestAgentConfig(workspaceRoot));
128
- const result = await searchTool.execute({ pattern: "needle in ignored file" });
129
- assert.ok(result.includes("generated.txt"));
130
- });
131
- //# sourceMappingURL=file-tools.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"file-tools.test.js","sourceRoot":"","sources":["../../tests/file-tools.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=guardrails.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"guardrails.test.d.ts","sourceRoot":"","sources":["../../tests/guardrails.test.ts"],"names":[],"mappings":""}
@@ -1,54 +0,0 @@
1
- import { test } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { ensureStepWithinLimit, formatStepLimitMessage, isDestructiveCommand, resolveWorkspacePath, validateCommand, validateFileReadSize, validatePath, } from "../src/safety/guardrails.js";
4
- test("validatePath allows files within workspace", () => {
5
- const workspaceRoot = "/tmp/workspace";
6
- assert.equal(validatePath("src/index.ts", workspaceRoot), true);
7
- });
8
- test("validatePath rejects escape attempts", () => {
9
- const workspaceRoot = "/tmp/workspace";
10
- assert.equal(validatePath("../etc/passwd", workspaceRoot), false);
11
- });
12
- test("resolveWorkspacePath rejects absolute escape paths", () => {
13
- const workspaceRoot = "/tmp/workspace";
14
- assert.throws(() => resolveWorkspacePath("/etc/passwd", workspaceRoot), /outside workspace root/);
15
- });
16
- test("resolveWorkspacePath resolves relative paths", () => {
17
- const workspaceRoot = "/tmp/workspace";
18
- const result = resolveWorkspacePath("src/index.ts", workspaceRoot);
19
- assert.equal(result, "/tmp/workspace/src/index.ts");
20
- });
21
- test("validateCommand blocks denylisted commands", () => {
22
- assert.throws(() => validateCommand("rm -rf /", [/\brm\s+-rf\s+\//i]), /blocked by safety denylist/);
23
- });
24
- test("validateCommand allows non-denylisted commands", () => {
25
- assert.doesNotThrow(() => validateCommand("ls -la", [/\brm\s+-rf\s+\//i]));
26
- });
27
- test("isDestructiveCommand detects rm -rf", () => {
28
- assert.equal(isDestructiveCommand("rm -rf ."), true);
29
- });
30
- test("isDestructiveCommand detects git reset --hard", () => {
31
- assert.equal(isDestructiveCommand("git reset --hard HEAD~1"), true);
32
- });
33
- test("isDestructiveCommand returns false for safe commands", () => {
34
- assert.equal(isDestructiveCommand("npm test"), false);
35
- });
36
- test("ensureStepWithinLimit throws at limit", () => {
37
- assert.throws(() => ensureStepWithinLimit(10, 10), /turn call limit/);
38
- });
39
- test("ensureStepWithinLimit allows steps below limit", () => {
40
- assert.doesNotThrow(() => ensureStepWithinLimit(5, 10));
41
- });
42
- test("formatStepLimitMessage explains how to continue and adjust the limit", () => {
43
- const message = formatStepLimitMessage(10);
44
- assert.match(message, /Type "continue"/);
45
- assert.match(message, /Settings/);
46
- assert.match(message, /maxSteps/);
47
- });
48
- test("validateFileReadSize throws for oversized files", () => {
49
- assert.throws(() => validateFileReadSize(2_000_000, 1_000_000), /File too large/);
50
- });
51
- test("validateFileReadSize allows files within limit", () => {
52
- assert.doesNotThrow(() => validateFileReadSize(500_000, 1_000_000));
53
- });
54
- //# sourceMappingURL=guardrails.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"guardrails.test.js","sourceRoot":"","sources":["../../tests/guardrails.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=mcp-client.integration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp-client.integration.test.d.ts","sourceRoot":"","sources":["../../tests/mcp-client.integration.test.ts"],"names":[],"mappings":""}
@@ -1,64 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { test } from "node:test";
5
- import { createMcpTools } from "../src/mcp/client-registry.js";
6
- const FIXTURE_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "fixtures", "mcp-stdio-fixture.mjs");
7
- test("createMcpTools roundtrips end-to-end against a stdio MCP server", async () => {
8
- const bundle = await createMcpTools({
9
- servers: [
10
- {
11
- name: "math",
12
- transport: "stdio",
13
- command: process.execPath,
14
- args: [FIXTURE_PATH],
15
- },
16
- ],
17
- });
18
- try {
19
- const names = bundle.tools.map((t) => t.name).sort();
20
- assert.deepEqual(names, ["math__add", "math__echo"]);
21
- const addTool = bundle.tools.find((t) => t.name === "math__add");
22
- assert.ok(addTool, "expected math__add to be present");
23
- const addOut = await addTool.execute({ a: 2, b: 40 });
24
- assert.equal(addOut, "42");
25
- const echoTool = bundle.tools.find((t) => t.name === "math__echo");
26
- assert.ok(echoTool, "expected math__echo to be present");
27
- const echoOut = await echoTool.execute({ message: "hi mom" });
28
- assert.equal(echoOut, "hi mom");
29
- }
30
- finally {
31
- await bundle.close();
32
- }
33
- });
34
- test("createMcpTools merges tools from multiple stdio servers", async () => {
35
- const bundle = await createMcpTools({
36
- servers: [
37
- {
38
- name: "alpha",
39
- transport: "stdio",
40
- command: process.execPath,
41
- args: [FIXTURE_PATH],
42
- },
43
- {
44
- name: "beta",
45
- transport: "stdio",
46
- command: process.execPath,
47
- args: [FIXTURE_PATH],
48
- },
49
- ],
50
- });
51
- try {
52
- const names = bundle.tools.map((t) => t.name).sort();
53
- assert.deepEqual(names, [
54
- "alpha__add",
55
- "alpha__echo",
56
- "beta__add",
57
- "beta__echo",
58
- ]);
59
- }
60
- finally {
61
- await bundle.close();
62
- }
63
- });
64
- //# sourceMappingURL=mcp-client.integration.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp-client.integration.test.js","sourceRoot":"","sources":["../../tests/mcp-client.integration.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=mcp-client.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp-client.test.d.ts","sourceRoot":"","sources":["../../tests/mcp-client.test.ts"],"names":[],"mappings":""}
@@ -1,350 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { test } from "node:test";
3
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
5
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
- import { z } from "zod";
7
- import { createMcpTools, formatMcpResult, wrapMcpClients, } from "../src/mcp/client-registry.js";
8
- /**
9
- * Spin up an in-process MCP server, link it to a client via
10
- * InMemoryTransport, and return the connected client.
11
- */
12
- async function spawnInMemoryServer(spec) {
13
- const server = new McpServer({ name: spec.name, version: spec.version ?? "1.0.0" }, { capabilities: { tools: {} } });
14
- for (const t of spec.tools) {
15
- server.tool(t.name, t.description, t.schema, async (args) => {
16
- const result = await t.handler(args);
17
- return result;
18
- });
19
- }
20
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
21
- await server.connect(serverTransport);
22
- const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} });
23
- await client.connect(clientTransport);
24
- return client;
25
- }
26
- test("formatMcpResult concatenates text blocks", () => {
27
- const out = formatMcpResult([
28
- { type: "text", text: "hello" },
29
- { type: "text", text: "world" },
30
- ]);
31
- assert.equal(out, "hello\nworld");
32
- });
33
- test("formatMcpResult substitutes placeholders for non-text blocks", () => {
34
- const out = formatMcpResult([
35
- { type: "text", text: "before" },
36
- { type: "image", mimeType: "image/png", data: "..." },
37
- { type: "audio", mimeType: "audio/wav", data: "..." },
38
- { type: "text", text: "after" },
39
- ]);
40
- assert.equal(out, "before\n[image content omitted (image/png)]\n[audio content omitted (audio/wav)]\nafter");
41
- });
42
- test("formatMcpResult inlines text from resource blocks", () => {
43
- const out = formatMcpResult([
44
- {
45
- type: "resource",
46
- resource: { uri: "test://file", text: "resource body" },
47
- },
48
- ]);
49
- assert.equal(out, "resource body");
50
- });
51
- test("formatMcpResult labels binary resource blocks with uri and mime type", () => {
52
- const out = formatMcpResult([
53
- {
54
- type: "resource",
55
- resource: {
56
- uri: "test://blob",
57
- blob: "base64...",
58
- mimeType: "application/octet-stream",
59
- },
60
- },
61
- ]);
62
- assert.equal(out, "[resource test://blob (application/octet-stream)]");
63
- });
64
- test("formatMcpResult renders resource_link blocks", () => {
65
- const out = formatMcpResult([
66
- { type: "resource_link", uri: "test://link", name: "linked-thing" },
67
- ]);
68
- assert.equal(out, "[resource_link linked-thing]");
69
- });
70
- test("formatMcpResult labels unsupported content types", () => {
71
- const out = formatMcpResult([
72
- { type: "video", data: "..." },
73
- ]);
74
- assert.equal(out, "[unsupported content type: video]");
75
- });
76
- test("formatMcpResult returns sentinel on empty content", () => {
77
- assert.equal(formatMcpResult([]), "(empty result)");
78
- });
79
- test("wrapMcpClients discovers tools and namespaces them", async () => {
80
- const client = await spawnInMemoryServer({
81
- name: "fs-mock",
82
- tools: [
83
- {
84
- name: "read",
85
- description: "read a file",
86
- schema: { path: z.string() },
87
- handler: async ({ path }) => ({
88
- content: [{ type: "text", text: `read ${String(path)}` }],
89
- }),
90
- },
91
- ],
92
- });
93
- const bundle = await wrapMcpClients([{ name: "fs", client }]);
94
- assert.equal(bundle.tools.length, 1);
95
- assert.equal(bundle.tools[0].name, "fs__read");
96
- assert.equal(bundle.tools[0].description, "read a file");
97
- const out = await bundle.tools[0].execute({ path: "/tmp/x" });
98
- assert.equal(out, "read /tmp/x");
99
- await bundle.close();
100
- });
101
- test("wrapMcpClients omits namespacing when namespace is false", async () => {
102
- const client = await spawnInMemoryServer({
103
- name: "fs-mock",
104
- tools: [
105
- {
106
- name: "list",
107
- description: "list files",
108
- schema: {},
109
- handler: async () => ({
110
- content: [{ type: "text", text: "a\nb\nc" }],
111
- }),
112
- },
113
- ],
114
- });
115
- const bundle = await wrapMcpClients([{ name: "fs", client }], { namespace: false });
116
- assert.equal(bundle.tools.length, 1);
117
- assert.equal(bundle.tools[0].name, "list");
118
- await bundle.close();
119
- });
120
- test("wrapMcpClients sanitizes invalid characters in server and tool names", async () => {
121
- // Anthropic / OpenAI restrict tool names to [a-zA-Z0-9_-]{1,64}.
122
- // A server / tool with dots or spaces would otherwise crash the API call.
123
- const seenArgs = [];
124
- const client = await spawnInMemoryServer({
125
- name: "fixture",
126
- tools: [
127
- {
128
- name: "repo.create_issue",
129
- description: "namespaced tool",
130
- schema: { title: z.string() },
131
- handler: async (args) => {
132
- seenArgs.push(args);
133
- return { content: [{ type: "text", text: "ok" }] };
134
- },
135
- },
136
- ],
137
- });
138
- const bundle = await wrapMcpClients([
139
- { name: "github mcp", client },
140
- ]);
141
- assert.equal(bundle.tools.length, 1);
142
- assert.equal(bundle.tools[0].name, "github_mcp__repo_create_issue");
143
- // Original MCP tool name must be preserved for the actual callTool dispatch.
144
- await bundle.tools[0].execute({ title: "hello" });
145
- assert.equal(seenArgs.length, 1);
146
- assert.equal(seenArgs[0].title, "hello");
147
- await bundle.close();
148
- });
149
- test("wrapMcpClients truncates exposed names that exceed 64 chars", async () => {
150
- const longTool = "x".repeat(80);
151
- const client = await spawnInMemoryServer({
152
- name: "fixture",
153
- tools: [
154
- {
155
- name: longTool,
156
- description: "very long",
157
- schema: {},
158
- handler: async () => ({
159
- content: [{ type: "text", text: "ok" }],
160
- }),
161
- },
162
- ],
163
- });
164
- const bundle = await wrapMcpClients([{ name: "s", client }], { namespace: false });
165
- assert.equal(bundle.tools.length, 1);
166
- assert.ok(bundle.tools[0].name.length <= 64, `expected <= 64 chars, got ${bundle.tools[0].name.length}`);
167
- await bundle.close();
168
- });
169
- test("wrapMcpClients reports name collisions via onError and skips duplicates", async () => {
170
- const a = await spawnInMemoryServer({
171
- name: "a",
172
- tools: [
173
- {
174
- name: "echo",
175
- description: "echo",
176
- schema: { msg: z.string() },
177
- handler: async ({ msg }) => ({
178
- content: [{ type: "text", text: `a:${String(msg)}` }],
179
- }),
180
- },
181
- ],
182
- });
183
- const b = await spawnInMemoryServer({
184
- name: "b",
185
- tools: [
186
- {
187
- name: "echo",
188
- description: "echo",
189
- schema: { msg: z.string() },
190
- handler: async ({ msg }) => ({
191
- content: [{ type: "text", text: `b:${String(msg)}` }],
192
- }),
193
- },
194
- ],
195
- });
196
- const errors = [];
197
- const bundle = await wrapMcpClients([
198
- { name: "shared", client: a },
199
- { name: "shared", client: b },
200
- ], {
201
- namespace: true,
202
- onError: (server, error) => {
203
- errors.push({
204
- server,
205
- message: error instanceof Error ? error.message : String(error),
206
- });
207
- },
208
- });
209
- assert.equal(bundle.tools.length, 1);
210
- assert.equal(bundle.tools[0].name, "shared__echo");
211
- assert.equal(errors.length, 1);
212
- assert.match(errors[0].message, /Tool name collision/);
213
- await bundle.close();
214
- });
215
- test("wrapMcpClients forwards isError through the wrapped result", async () => {
216
- const client = await spawnInMemoryServer({
217
- name: "errorful",
218
- tools: [
219
- {
220
- name: "boom",
221
- description: "always fails",
222
- schema: {},
223
- handler: async () => ({
224
- content: [{ type: "text", text: "kaboom" }],
225
- isError: true,
226
- }),
227
- },
228
- ],
229
- });
230
- const bundle = await wrapMcpClients([{ name: "x", client }]);
231
- const out = await bundle.tools[0].execute({});
232
- assert.match(out, /MCP tool error \(x__boom\): kaboom/);
233
- await bundle.close();
234
- });
235
- test("wrapMcpClients closes clients whose listTools throws (no transport leak)", async () => {
236
- // Build a real connected client, then monkey-patch listTools to throw.
237
- // We'd otherwise leak its in-memory transport — verify close() ran.
238
- const client = await spawnInMemoryServer({
239
- name: "leaky",
240
- tools: [
241
- {
242
- name: "noop",
243
- description: "noop",
244
- schema: {},
245
- handler: async () => ({ content: [{ type: "text", text: "" }] }),
246
- },
247
- ],
248
- });
249
- let closed = false;
250
- const originalClose = client.close.bind(client);
251
- client.close = async () => {
252
- closed = true;
253
- await originalClose();
254
- };
255
- client.listTools = async () => {
256
- throw new Error("listTools failed");
257
- };
258
- const errors = [];
259
- const bundle = await wrapMcpClients([{ name: "leaky", client }], {
260
- onError: (name) => {
261
- errors.push(name);
262
- },
263
- });
264
- assert.equal(bundle.tools.length, 0);
265
- assert.deepEqual(errors, ["leaky"]);
266
- assert.equal(closed, true, "client.close() should have been called when listTools threw");
267
- await bundle.close();
268
- });
269
- test("wrapMcpClients uses onError when listTools rejects, skips that server", async () => {
270
- const good = await spawnInMemoryServer({
271
- name: "good",
272
- tools: [
273
- {
274
- name: "ping",
275
- description: "ping",
276
- schema: {},
277
- handler: async () => ({
278
- content: [{ type: "text", text: "pong" }],
279
- }),
280
- },
281
- ],
282
- });
283
- // A client that's never been connected — listTools throws.
284
- const broken = new Client({ name: "broken", version: "1.0.0" }, { capabilities: {} });
285
- const errors = [];
286
- const bundle = await wrapMcpClients([
287
- { name: "broken", client: broken },
288
- { name: "good", client: good },
289
- ], {
290
- onError: (name) => {
291
- errors.push(name);
292
- },
293
- });
294
- assert.equal(errors.length, 1);
295
- assert.equal(errors[0], "broken");
296
- assert.equal(bundle.tools.length, 1);
297
- assert.equal(bundle.tools[0].name, "good__ping");
298
- await bundle.close();
299
- });
300
- test("createMcpTools surfaces transport-level connect failures via onError", async () => {
301
- // Use a stdio command that doesn't exist so the spawn fails.
302
- const errors = [];
303
- const bundle = await createMcpTools({
304
- servers: [
305
- {
306
- name: "missing",
307
- transport: "stdio",
308
- command: "/nonexistent/path/that/should/not/exist",
309
- args: [],
310
- },
311
- ],
312
- onError: (name, error) => {
313
- errors.push({
314
- name,
315
- message: error instanceof Error ? error.message : String(error),
316
- });
317
- },
318
- });
319
- assert.equal(bundle.tools.length, 0);
320
- assert.equal(errors.length, 1);
321
- assert.equal(errors[0].name, "missing");
322
- await bundle.close();
323
- });
324
- test("wrapped tool roundtrips arguments to client.callTool", async () => {
325
- const seenArgs = [];
326
- const client = await spawnInMemoryServer({
327
- name: "arg-spy",
328
- tools: [
329
- {
330
- name: "spy",
331
- description: "captures its args",
332
- schema: { a: z.number(), b: z.string() },
333
- handler: async (args) => {
334
- seenArgs.push(args);
335
- return {
336
- content: [{ type: "text", text: JSON.stringify(args) }],
337
- };
338
- },
339
- },
340
- ],
341
- });
342
- const bundle = await wrapMcpClients([{ name: "s", client }]);
343
- const out = await bundle.tools[0].execute({ a: 7, b: "hi" });
344
- assert.equal(out, '{"a":7,"b":"hi"}');
345
- assert.equal(seenArgs.length, 1);
346
- assert.equal(seenArgs[0].a, 7);
347
- assert.equal(seenArgs[0].b, "hi");
348
- await bundle.close();
349
- });
350
- //# sourceMappingURL=mcp-client.test.js.map