@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,13 +1,99 @@
1
- import { expectNonEmptyString, expectOptionalNumber } from "@minicode/agent-sdk";
1
+ import { expectNonEmptyString, expectOptionalNumber } from "@sean.holung/minicode-sdk";
2
2
  import { getSymbolDisplayName, getSymbolLookupNames } from "../indexer/symbol-names.js";
3
3
  import { searchSymbols } from "../shared/symbol-search.js";
4
4
  const DEFAULT_LIMIT = 30;
5
+ const DOC_SUMMARY_MAX_CHARS = 100;
6
+ /**
7
+ * Strip JSDoc/block-comment markers and return the first non-empty line of
8
+ * the comment, truncated.
9
+ *
10
+ * Surfacing this beside each match in `search_code_map` output is what
11
+ * addresses the disambiguation failure case from issue #184: when a search
12
+ * returns several similar-named symbols (e.g. `ToolRegistry` class +
13
+ * `createToolRegistry` wrapper + `ToolRegistry.createDefault` method), the
14
+ * model has no way to pick the right one from kind+path alone. The doc
15
+ * summary tells it which is which.
16
+ */
17
+ export function summarizeDocComment(doc) {
18
+ if (!doc) {
19
+ return "";
20
+ }
21
+ // Strip leading /** and trailing */ — defense-in-depth; symbol indexing
22
+ // already cleans most of this.
23
+ const lines = doc
24
+ .replace(/^\s*\/\*\*?/, "")
25
+ .replace(/\*\/\s*$/, "")
26
+ // TS compiler can emit JSDoc with `\r` line separators (not `\n`),
27
+ // so we split on both. Without this, the entire comment becomes a
28
+ // single "line" and the truncation chops mid-paragraph rather than
29
+ // at the first description sentence.
30
+ .split(/\r\n|\r|\n/)
31
+ .map((line) => line.replace(/^\s*\*\s?/, "").trim())
32
+ .filter((line) => line.length > 0);
33
+ if (lines.length === 0) {
34
+ return "";
35
+ }
36
+ const first = lines[0];
37
+ if (first.length <= DOC_SUMMARY_MAX_CHARS) {
38
+ return first;
39
+ }
40
+ return `${first.slice(0, DOC_SUMMARY_MAX_CHARS - 3)}...`;
41
+ }
42
+ /**
43
+ * When a search returns matches of multiple symbol kinds (class, function,
44
+ * method, …) for what is likely the same conceptual name, the model can
45
+ * confidently pick the wrong one — particularly for queries like
46
+ * `ToolRegistry` that resolve to a class, several class methods, and a
47
+ * standalone wrapper function. This is exactly the failure shape from
48
+ * issue #184. We surface the ambiguity so the model knows to refine
49
+ * rather than guess.
50
+ *
51
+ * The hint fires only on cross-kind ambiguity (class/interface/type AND
52
+ * function/method) — the typical "noun vs. verb" shape (`Foo` class vs.
53
+ * `createFoo()` factory). Methods alone alongside their class are
54
+ * structurally expected and not flagged.
55
+ */
56
+ export function buildAmbiguityHint(matches) {
57
+ const kinds = new Set(matches.map((m) => m.kind));
58
+ if (kinds.size <= 1) {
59
+ return "";
60
+ }
61
+ const hasNoun = kinds.has("class") || kinds.has("interface") || kinds.has("type");
62
+ // Standalone functions only — class methods alongside their class
63
+ // (e.g. `Widget` + `Widget.init`) are a structurally expected pairing,
64
+ // not a disambiguation problem. The model already understands that
65
+ // `Foo.method` is a member of `Foo`. The real confusion is between a
66
+ // type-name and a same-base-name standalone function (`Widget` class
67
+ // vs. `createWidget()` factory).
68
+ //
69
+ // Trade-off: this deliberately under-flags the rarer cross-class case
70
+ // (`class Widget` in one file plus an unrelated `widget()` method on
71
+ // some other class). Adding `method` to the verb set would close that
72
+ // gap but would also make the hint fire on every class-with-its-own-
73
+ // methods search, which is by far the more common shape — false
74
+ // positives there would be louder than the missed cross-class case.
75
+ const hasStandaloneVerb = kinds.has("function");
76
+ if (!hasNoun || !hasStandaloneVerb) {
77
+ return "";
78
+ }
79
+ return [
80
+ "Note: results span multiple symbol kinds. A class/interface/type and a function/method with similar",
81
+ "names usually have different responsibilities (e.g. `Foo` class vs. `createFoo()` factory). Read the",
82
+ "doc summaries below or call `read_symbol` on a specific candidate before assuming which one the",
83
+ "question is about.",
84
+ ].join("\n");
85
+ }
86
+ function formatMatchLine(s) {
87
+ const main = `- ${getSymbolDisplayName(s)} (${s.kind}) — ${s.filePath}:${s.startLine} — qualified: ${s.qualifiedName}`;
88
+ const summary = summarizeDocComment(s.docComment);
89
+ return summary ? `${main}\n └─ ${summary}` : main;
90
+ }
5
91
  export function createSearchCodeMapTool(projectIndex) {
6
92
  return {
7
93
  name: "search_code_map",
8
94
  description: "Search the full project index for symbols by name or substring. " +
9
95
  "Use when the code map is truncated and you need to find a symbol not listed. " +
10
- "Returns disambiguated display names, qualified names, and file paths; use read_symbol with the result.",
96
+ "Returns disambiguated display names, qualified names, file paths, and a one-line doc summary; use read_symbol with the result.",
11
97
  inputSchema: {
12
98
  type: "object",
13
99
  properties: {
@@ -52,10 +138,10 @@ export function createSearchCodeMapTool(projectIndex) {
52
138
  lookupNames: getSymbolLookupNames(sym),
53
139
  })), pattern, { kind, limit, skip });
54
140
  const shown = result.matches;
55
- const lines = shown.map((s) => `- ${getSymbolDisplayName(s)} (${s.kind}) — ${s.filePath}:${s.startLine} — qualified: ${s.qualifiedName}`);
141
+ const lines = shown.map(formatMatchLine);
56
142
  const remaining = result.total - skip - shown.length;
57
143
  const footer = remaining > 0
58
- ? `\n... and ${remaining} more (use skip: ${skip + limit}, limit: ${limit} for next page)`
144
+ ? `\n... and ${remaining} more. Use skip: ${skip + limit}, limit: ${limit} for the next page, or refine the pattern to narrow.`
59
145
  : "";
60
146
  if (result.total === 0) {
61
147
  return `No symbols matching "${pattern}"${kind ? ` (kind: ${kind})` : ""}. Try a shorter or different pattern.`;
@@ -70,12 +156,18 @@ export function createSearchCodeMapTool(projectIndex) {
70
156
  footer,
71
157
  ].join("\n");
72
158
  }
73
- return [
159
+ const ambiguityHint = buildAmbiguityHint(shown);
160
+ const sections = [
74
161
  `# Symbols matching "${pattern}" (${result.total} total)`,
75
- "",
76
- ...lines,
77
- footer,
78
- ].join("\n");
162
+ ];
163
+ if (ambiguityHint) {
164
+ sections.push("", ambiguityHint);
165
+ }
166
+ sections.push("", ...lines);
167
+ if (footer) {
168
+ sections.push(footer);
169
+ }
170
+ return sections.join("\n");
79
171
  },
80
172
  };
81
173
  }
@@ -1,5 +1,5 @@
1
1
  import process from "node:process";
2
- import { CodingAgent, createModelClient } from "@minicode/agent-sdk";
2
+ import { CodingAgent, createModelClient } from "@sean.holung/minicode-sdk";
3
3
  import { loadAgentConfig, getConfigSetupMessage } from "../agent/config.js";
4
4
  import { handleConfigSlashCommand } from "../cli/config-slash-command.js";
5
5
  import { computeFileHashes, getWorkspaceCacheDir, loadIndex, saveIndex, } from "../indexer/cache.js";
@@ -2580,7 +2580,7 @@ async function showDetail(node, detailEl) {
2580
2580
  html += '<div class="detail-section" id="detail-refs"><div class="detail-section-title">References</div><div class="detail-section-list">Loading...</div></div>';
2581
2581
  detailEl.innerHTML = '<div class="resize-handle"></div>' + html;
2582
2582
  detailEl.classList.remove("hidden");
2583
- syncDetailPanelLayout({ fit: true });
2583
+ syncDetailPanelLayout();
2584
2584
  const handle = detailEl.querySelector(".resize-handle");
2585
2585
  handle.addEventListener("mousedown", (e) => {
2586
2586
  e.preventDefault();
Binary file
@@ -0,0 +1,9 @@
1
+ <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="64" height="64" rx="16" fill="#0f172a"/>
3
+ <path d="M18 19L44 22M18 19L20 45M44 22L46 46M20 45L46 46M18 19L46 46" stroke="#38bdf8" stroke-width="3" stroke-linecap="round" opacity="0.65"/>
4
+ <circle cx="18" cy="19" r="7" fill="#e2e8f0"/>
5
+ <circle cx="44" cy="22" r="6" fill="#22d3ee"/>
6
+ <circle cx="20" cy="45" r="6" fill="#22d3ee"/>
7
+ <circle cx="46" cy="46" r="7" fill="#e2e8f0"/>
8
+ <path d="M18 19L46 46" stroke="#f8fafc" stroke-width="2" stroke-linecap="round" opacity="0.35"/>
9
+ </svg>
@@ -4,6 +4,8 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>minicode</title>
7
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
8
+ <link rel="icon" sizes="any" href="/favicon.ico">
7
9
  <link rel="stylesheet" href="style.css">
8
10
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
11
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
2
  import path from "node:path";
3
3
  import { test } from "node:test";
4
- import { CodingAgent, ToolRegistry, } from "@minicode/agent-sdk";
4
+ import { CodingAgent, ToolRegistry, } from "@sean.holung/minicode-sdk";
5
5
  import { buildProjectIndex } from "../src/indexer/project-index.js";
6
6
  import { createTestAgentConfig } from "./test-utils.js";
7
7
  class SequenceModelClient {
@@ -5,7 +5,7 @@ import { readFileSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { createRequestHandler } from "../src/serve/server.js";
7
7
  import { AgentBridge } from "../src/serve/agent-bridge.js";
8
- import { CodingAgent, ToolRegistry, } from "@minicode/agent-sdk";
8
+ import { CodingAgent, ToolRegistry, } from "@sean.holung/minicode-sdk";
9
9
  import { UiStore } from "../src/ui/state/ui-store.js";
10
10
  import { createTestAgentConfig } from "./test-utils.js";
11
11
  const distWeb = join(import.meta.dirname, "..", "dist", "src", "web");
@@ -3,7 +3,7 @@ import { mkdtemp, readFile, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import path from "node:path";
5
5
  import { test } from "node:test";
6
- import { createEditFileTool, createReadFileTool, createRunCommandTool, createWriteFileTool } from "@minicode/agent-sdk";
6
+ import { createEditFileTool, createReadFileTool, createRunCommandTool, createWriteFileTool } from "@sean.holung/minicode-sdk";
7
7
  import { buildProjectIndex } from "../src/indexer/project-index.js";
8
8
  import { createToolRegistry } from "../src/tools/registry.js";
9
9
  import { createTestAgentConfig } from "./test-utils.js";
@@ -33,7 +33,7 @@ test("edit_file fails when old_string is not unique", async () => {
33
33
  path: "sample.txt",
34
34
  old_string: "repeat",
35
35
  new_string: "once",
36
- }), /matched 3 times/);
36
+ }), /multiple matches/i);
37
37
  const unchanged = await readFile(filePath, "utf8");
38
38
  assert.equal(unchanged, "repeat repeat repeat");
39
39
  });
@@ -1,6 +1,6 @@
1
1
  import { test } from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { FocusTracker } from "@minicode/agent-sdk";
3
+ import { FocusTracker } from "@sean.holung/minicode-sdk";
4
4
  test("FocusTracker tracks added symbols", () => {
5
5
  const tracker = new FocusTracker();
6
6
  tracker.addSymbol("Foo");
@@ -3,6 +3,7 @@ import { test } from 'node:test';
3
3
  import { readFileSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
5
  const distWeb = join(import.meta.dirname, '..', 'dist', 'src', 'web');
6
+ const graphSource = join(import.meta.dirname, '..', 'src', 'web', 'graph.ts');
6
7
  test('built CSS contains graph-onboarding styles', () => {
7
8
  const css = readFileSync(join(distWeb, 'style.css'), 'utf-8');
8
9
  assert.ok(css.includes('.graph-onboarding'), 'CSS should contain .graph-onboarding class');
@@ -23,11 +24,20 @@ test('built HTML contains #cy graph container', () => {
23
24
  const html = readFileSync(join(distWeb, 'index.html'), 'utf-8');
24
25
  assert.ok(html.includes('id="cy"'), 'HTML should contain the #cy graph container');
25
26
  assert.ok(html.includes('id="graph-pane"'), 'HTML should contain the #graph-pane wrapper');
27
+ assert.ok(html.includes('href="/favicon.svg"'), 'HTML should link the app favicon');
28
+ assert.ok(html.includes('href="/favicon.ico"'), 'HTML should link the fallback ICO favicon');
26
29
  assert.ok(html.includes('Search symbols or files...'), 'HTML should expose mixed symbol/file search');
27
30
  assert.ok(html.includes('id="graph-refresh"'), 'HTML should expose a graph refresh button');
28
31
  assert.ok(html.includes('id="file-preview-modal"'), 'HTML should contain the file preview modal shell');
29
32
  assert.ok(html.includes('id="file-preview-code"'), 'HTML should contain the file preview code surface');
30
33
  });
34
+ test('web build copies the favicon asset', () => {
35
+ const favicon = readFileSync(join(distWeb, 'favicon.svg'), 'utf-8');
36
+ const fallbackFavicon = readFileSync(join(distWeb, 'favicon.ico'));
37
+ assert.ok(favicon.includes('<svg'), 'favicon should be an SVG asset');
38
+ assert.ok(favicon.includes('#38bdf8'), 'favicon should use the minicode graph accent');
39
+ assert.ok(fallbackFavicon.length > 0, 'fallback favicon should be copied');
40
+ });
31
41
  test('onboarding hint includes user-facing guidance text in built JS', () => {
32
42
  const js = readFileSync(join(distWeb, 'app.js'), 'utf-8');
33
43
  assert.ok(js.includes('Code dependency graph'), 'onboarding title should mention the code dependency graph');
@@ -58,6 +68,16 @@ test('built JS auto-opens symbol details for agent activity and graph search sel
58
68
  assert.ok(js.includes('openDetail: true'), 'JS should request the detail panel when focusing symbols from agent activity or search');
59
69
  assert.ok(js.includes('await showDetail(node, detailEl)'), 'JS should populate the symbol detail panel when focus requests it');
60
70
  });
71
+ test('symbol selection keeps the current graph viewport instead of fitting the canvas', () => {
72
+ const source = readFileSync(graphSource, 'utf-8');
73
+ const showDetailStart = source.indexOf('async function showDetail');
74
+ const loadSourceStart = source.indexOf('async function loadSource');
75
+ assert.ok(showDetailStart >= 0, 'graph source should define showDetail');
76
+ assert.ok(loadSourceStart > showDetailStart, 'graph source should define loadSource after showDetail');
77
+ const showDetailBody = source.slice(showDetailStart, loadSourceStart);
78
+ assert.ok(showDetailBody.includes('syncDetailPanelLayout();'), 'showDetail should resize Cytoscape without changing pan/zoom');
79
+ assert.ok(!showDetailBody.includes('syncDetailPanelLayout({ fit: true })'), 'showDetail should not fit the graph when a symbol is selected');
80
+ });
61
81
  test('built JS supports file search results and file-centered neighborhood rendering', () => {
62
82
  const js = readFileSync(join(distWeb, 'app.js'), 'utf-8');
63
83
  assert.ok(js.includes('focusFileInGraph'), 'JS should define a file-focused graph seeding helper');
@@ -1,6 +1,6 @@
1
1
  import { test } from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { resolveWorkspacePath, validateCommand, validatePath, } from "@minicode/agent-sdk";
3
+ import { resolveWorkspacePath, validateCommand, validatePath, } from "@sean.holung/minicode-sdk";
4
4
  test("validatePath allows files within workspace", () => {
5
5
  const workspaceRoot = "/tmp/workspace";
6
6
  assert.equal(validatePath("src/index.ts", workspaceRoot), true);
@@ -168,11 +168,29 @@ test("Code map handles empty symbols map", () => {
168
168
  assert.ok(result.text.includes("# Project Code Map"));
169
169
  assert.ok(result.text.length < 100);
170
170
  });
171
+ test("Code map honors MINICODE_CODE_MAP_FORMAT=full opt-out", () => {
172
+ const symbols = typescriptPlugin.indexFile("sample.ts", SAMPLE_TS);
173
+ const byFile = new Map([["sample.ts", symbols]]);
174
+ const prev = process.env.MINICODE_CODE_MAP_FORMAT;
175
+ process.env.MINICODE_CODE_MAP_FORMAT = "full";
176
+ try {
177
+ const result = generateCodeMap(byFile);
178
+ // Full format prefixes each non-method symbol with the bare kind keyword
179
+ // (e.g. "class CodingAgent") and emits a separate signature line.
180
+ assert.ok(result.text.includes("class CodingAgent"), "full format prefixes class keyword");
181
+ }
182
+ finally {
183
+ if (prev === undefined)
184
+ delete process.env.MINICODE_CODE_MAP_FORMAT;
185
+ else
186
+ process.env.MINICODE_CODE_MAP_FORMAT = prev;
187
+ }
188
+ });
171
189
  test("Code map nests methods under class", () => {
172
190
  const symbols = typescriptPlugin.indexFile("sample.ts", SAMPLE_TS);
173
191
  const byFile = new Map([["sample.ts", symbols]]);
174
192
  const result = generateCodeMap(byFile);
175
- assert.ok(result.text.includes("class CodingAgent"));
193
+ assert.ok(result.text.includes("CodingAgent (class)"));
176
194
  assert.ok(result.text.includes("runTurn"));
177
195
  const runTurnLine = result.text.split("\n").find((l) => l.includes("runTurn"));
178
196
  assert.ok(runTurnLine?.startsWith(" "), "method should be indented under class");
@@ -197,19 +215,32 @@ test("reindexFile updates symbols and code map after file change", async () => {
197
215
  const updatedSym = index.getSymbol("greet");
198
216
  assert.ok(updatedSym, "should still find greet");
199
217
  assert.ok(updatedSym.signature.includes("title?: string"), "signature should reflect updated params");
200
- const codeMap = index.getCodeMap();
201
- assert.ok(codeMap.text.includes("title?: string"), "code map should reflect new signature");
218
+ // Verify the code map is regenerated after reindex. The default named
219
+ // format doesn't include signatures, so we opt into the full format
220
+ // here to assert the signature change flows through.
221
+ const prev = process.env.MINICODE_CODE_MAP_FORMAT;
222
+ process.env.MINICODE_CODE_MAP_FORMAT = "full";
223
+ try {
224
+ const codeMap = index.getCodeMap();
225
+ assert.ok(codeMap.text.includes("title?: string"), "code map should reflect new signature");
226
+ }
227
+ finally {
228
+ if (prev === undefined)
229
+ delete process.env.MINICODE_CODE_MAP_FORMAT;
230
+ else
231
+ process.env.MINICODE_CODE_MAP_FORMAT = prev;
232
+ }
202
233
  });
203
234
  test("buildProjectIndex preserves colliding top-level symbols with distinct display names", async () => {
204
235
  const workspaceRoot = await mkdtemp(path.join(tmpdir(), "minicode-colliding-symbols-"));
205
236
  const samplePath = path.join(workspaceRoot, "sample.ts");
206
- const content = `export interface Employee {
207
- id: string;
208
- }
209
-
210
- export class Employee {
211
- constructor(public id: string) {}
212
- }
237
+ const content = `export interface Employee {
238
+ id: string;
239
+ }
240
+
241
+ export class Employee {
242
+ constructor(public id: string) {}
243
+ }
213
244
  `;
214
245
  await writeFile(samplePath, content, "utf8");
215
246
  const index = await buildProjectIndex(workspaceRoot);
@@ -227,8 +258,8 @@ export class Employee {
227
258
  assert.equal(employeeClass.kind, "class");
228
259
  assert.equal(employeeInterface.kind, "interface");
229
260
  const codeMap = index.getCodeMap();
230
- assert.ok(codeMap.text.includes("interface Employee (interface)"));
231
- assert.ok(codeMap.text.includes("class Employee (class)"));
261
+ assert.ok(codeMap.text.includes("Employee (interface)"));
262
+ assert.ok(codeMap.text.includes("Employee (class)"));
232
263
  const resolved = index.getSymbol("Employee");
233
264
  assert.ok(resolved, "bare lookup should still resolve one of the colliding symbols");
234
265
  assert.ok(resolved.displayName === "Employee (class)" || resolved.displayName === "Employee (interface)", "resolved symbol should use a disambiguated display name");
@@ -359,23 +390,23 @@ function beta() {}
359
390
  assert.ok(callEdge, "should resolve dependencies even without cached AST");
360
391
  });
361
392
  test("resolveDependencies prefers same-file symbols over same-named helpers in other files", () => {
362
- const projectIndexCode = `
363
- async function collectSourceFiles(dir: string): Promise<void> {
364
- await collectSourceFiles(dir + "/nested");
365
- }
366
-
367
- export async function buildProjectIndex(): Promise<void> {
368
- await collectSourceFiles("src");
369
- }
393
+ const projectIndexCode = `
394
+ async function collectSourceFiles(dir: string): Promise<void> {
395
+ await collectSourceFiles(dir + "/nested");
396
+ }
397
+
398
+ export async function buildProjectIndex(): Promise<void> {
399
+ await collectSourceFiles("src");
400
+ }
370
401
  `;
371
- const cacheCode = `
372
- async function collectSourceFiles(dir: string): Promise<void> {
373
- await collectSourceFiles(dir + "/cached");
374
- }
375
-
376
- export async function computeFileHashes(): Promise<void> {
377
- await collectSourceFiles("src");
378
- }
402
+ const cacheCode = `
403
+ async function collectSourceFiles(dir: string): Promise<void> {
404
+ await collectSourceFiles(dir + "/cached");
405
+ }
406
+
407
+ export async function computeFileHashes(): Promise<void> {
408
+ await collectSourceFiles("src");
409
+ }
379
410
  `;
380
411
  const byFile = new Map([
381
412
  ["src/indexer/project-index.ts", typescriptPlugin.indexFile("src/indexer/project-index.ts", projectIndexCode)],
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { test } from "node:test";
3
- import { OpenAICompatibleModelClient, createModelClient, } from "@minicode/agent-sdk";
3
+ import { OpenAICompatibleModelClient, createModelClient, } from "@sean.holung/minicode-sdk";
4
4
  import { createTestAgentConfig } from "./test-utils.js";
5
5
  test("openai-compatible client sends tool schemas and parses tool calls", async () => {
6
6
  let capturedUrl = "";
@@ -73,7 +73,7 @@ test("openai-compatible client sends tool schemas and parses tool calls", async
73
73
  const tools = parsedBody.tools;
74
74
  assert.equal(tools[0]?.type, "function");
75
75
  });
76
- test("openai-compatible client sends correct app URL in HTTP-Referer header", async () => {
76
+ test("openai-compatible client sends OpenRouter app attribution headers", async () => {
77
77
  let capturedHeaders = {};
78
78
  const fetchImpl = async (_input, init) => {
79
79
  const rawHeaders = init?.headers;
@@ -100,7 +100,8 @@ test("openai-compatible client sends correct app URL in HTTP-Referer header", as
100
100
  maxTokens: 64,
101
101
  });
102
102
  assert.equal(capturedHeaders["HTTP-Referer"], "https://minicode.seanholung.com", "HTTP-Referer should point to minicode.seanholung.com");
103
- assert.equal(capturedHeaders["X-Title"], "minicode");
103
+ assert.equal(capturedHeaders["X-OpenRouter-Title"], "minicode");
104
+ assert.equal(capturedHeaders["X-OpenRouter-Categories"], "cli-agent,programming-app");
104
105
  });
105
106
  test("openai-compatible client repairs missing tool results before sending", async () => {
106
107
  let capturedBody = "";
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { test } from "node:test";
3
3
  import { createServer } from "node:http";
4
- import { OpenAICompatibleModelClient } from "@minicode/agent-sdk";
4
+ import { OpenAICompatibleModelClient } from "@sean.holung/minicode-sdk";
5
5
  import { createRequestHandler } from "../src/serve/server.js";
6
6
  import { AgentBridge } from "../src/serve/agent-bridge.js";
7
7
  import { createTestAgentConfig } from "./test-utils.js";
@@ -0,0 +1,9 @@
1
+ import assert from 'node:assert/strict';
2
+ import { test } from 'node:test';
3
+ import { readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ const packageJsonPath = join(import.meta.dirname, '..', 'package.json');
6
+ test('package metadata includes the minicode website for app attribution', () => {
7
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
8
+ assert.equal(pkg.homepage, 'https://minicode.seanholung.com');
9
+ });
@@ -37,9 +37,9 @@ test("buildProjectIndex indexes a Python workspace", async () => {
37
37
  assert.ok(index.getSymbol("Foo.bar"));
38
38
  assert.ok(index.getSymbol("baz"));
39
39
  const codeMap = index.getCodeMap();
40
- assert.ok(codeMap.text.includes("class Foo"));
41
- assert.ok(codeMap.text.includes("def bar"));
42
- assert.ok(codeMap.text.includes("def baz"));
40
+ assert.ok(codeMap.text.includes("Foo (class)"));
41
+ assert.ok(codeMap.text.includes("bar"));
42
+ assert.ok(codeMap.text.includes("baz"));
43
43
  });
44
44
  test("verify-index-python fixture exercises the indexing pipeline", async () => {
45
45
  const root = path.resolve(import.meta.dirname, "..");
@@ -27,6 +27,11 @@ test("read_symbol returns error for unknown symbol name", async () => {
27
27
  const result = await tool.execute({ name: "NonExistentSymbol123" });
28
28
  assert.ok(result.includes("not found"));
29
29
  assert.ok(result.includes("search") || result.includes("read_file"));
30
+ // Miss path should preferentially nudge toward search_code_map (the
31
+ // graph-aware retry) before suggesting full-file reads — otherwise
32
+ // the agent abandons minicode's symbol-aware path the moment it
33
+ // hits a single miss.
34
+ assert.ok(result.includes("search_code_map"), `expected miss message to suggest search_code_map; got: ${result}`);
30
35
  });
31
36
  test("read_symbol with includeBody: false returns signature only", async () => {
32
37
  const root = path.resolve(import.meta.dirname, "..");
@@ -66,6 +71,75 @@ test("read_symbol appears in tool registry schemas when projectIndex provided",
66
71
  const props = readSymbol.input_schema.properties;
67
72
  assert.ok(props && "name" in props);
68
73
  });
74
+ test("MINICODE_TOOL_PROFILE=file-search-only omits the five graph-aware tools", async () => {
75
+ const root = path.resolve(import.meta.dirname, "..");
76
+ const config = createTestAgentConfig(root);
77
+ const projectIndex = await buildProjectIndex(root);
78
+ const original = process.env.MINICODE_TOOL_PROFILE;
79
+ process.env.MINICODE_TOOL_PROFILE = "file-search-only";
80
+ try {
81
+ const registry = createToolRegistry(config, projectIndex);
82
+ const schemas = registry.getToolSchemas();
83
+ const names = new Set(schemas.map((s) => s.name));
84
+ // Graph-aware tools must be absent in file-search-only mode.
85
+ for (const omitted of [
86
+ "read_symbol",
87
+ "find_references",
88
+ "get_dependencies",
89
+ "find_path",
90
+ "search_code_map",
91
+ ]) {
92
+ assert.ok(!names.has(omitted), `expected ${omitted} to be absent`);
93
+ }
94
+ // Core file/search/run tools must still be present — that's the
95
+ // "search-only" half of the profile name.
96
+ for (const required of [
97
+ "read_file",
98
+ "write_file",
99
+ "edit_file",
100
+ "search",
101
+ "list_files",
102
+ "run_command",
103
+ ]) {
104
+ assert.ok(names.has(required), `expected ${required} to be present`);
105
+ }
106
+ }
107
+ finally {
108
+ if (original === undefined) {
109
+ delete process.env.MINICODE_TOOL_PROFILE;
110
+ }
111
+ else {
112
+ process.env.MINICODE_TOOL_PROFILE = original;
113
+ }
114
+ }
115
+ });
116
+ test("createToolRegistry includes graph-aware tools by default when projectIndex is provided", async () => {
117
+ const root = path.resolve(import.meta.dirname, "..");
118
+ const config = createTestAgentConfig(root);
119
+ const projectIndex = await buildProjectIndex(root);
120
+ // Must run with no MINICODE_TOOL_PROFILE set, otherwise the env from a
121
+ // host shell would mask the default behavior.
122
+ const original = process.env.MINICODE_TOOL_PROFILE;
123
+ delete process.env.MINICODE_TOOL_PROFILE;
124
+ try {
125
+ const registry = createToolRegistry(config, projectIndex);
126
+ const names = new Set(registry.getToolSchemas().map((s) => s.name));
127
+ for (const required of [
128
+ "read_symbol",
129
+ "find_references",
130
+ "get_dependencies",
131
+ "find_path",
132
+ "search_code_map",
133
+ ]) {
134
+ assert.ok(names.has(required), `expected ${required} to be present by default`);
135
+ }
136
+ }
137
+ finally {
138
+ if (original !== undefined) {
139
+ process.env.MINICODE_TOOL_PROFILE = original;
140
+ }
141
+ }
142
+ });
69
143
  test("read_symbol includes Referenced Types section for createToolRegistry", async () => {
70
144
  const root = path.resolve(import.meta.dirname, "..");
71
145
  const config = createTestAgentConfig(root);
@@ -77,11 +151,11 @@ test("read_symbol includes Referenced Types section for createToolRegistry", asy
77
151
  });
78
152
  test("read_symbol returns disambiguation list for ambiguous bare symbol names", async () => {
79
153
  const workspaceRoot = await mkdtemp(path.join(tmpdir(), "minicode-read-symbol-collisions-"));
80
- await writeFile(path.join(workspaceRoot, "sample.ts"), `export type Review = { id: string };
81
-
82
- export class Review {
83
- constructor(public id: string) {}
84
- }
154
+ await writeFile(path.join(workspaceRoot, "sample.ts"), `export type Review = { id: string };
155
+
156
+ export class Review {
157
+ constructor(public id: string) {}
158
+ }
85
159
  `, "utf8");
86
160
  const config = createTestAgentConfig(workspaceRoot);
87
161
  const projectIndex = await buildProjectIndex(workspaceRoot);
@@ -95,11 +169,11 @@ export class Review {
95
169
  });
96
170
  test("read_symbol accepts qualified names for colliding symbols", async () => {
97
171
  const workspaceRoot = await mkdtemp(path.join(tmpdir(), "minicode-read-symbol-qualified-"));
98
- await writeFile(path.join(workspaceRoot, "sample.ts"), `export type Review = { id: string };
99
-
100
- export class Review {
101
- constructor(public id: string) {}
102
- }
172
+ await writeFile(path.join(workspaceRoot, "sample.ts"), `export type Review = { id: string };
173
+
174
+ export class Review {
175
+ constructor(public id: string) {}
176
+ }
103
177
  `, "utf8");
104
178
  const config = createTestAgentConfig(workspaceRoot);
105
179
  const projectIndex = await buildProjectIndex(workspaceRoot);
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { test } from "node:test";
3
- import { OpenAICompatibleModelClient, CodingAgent, ToolRegistry, } from "@minicode/agent-sdk";
3
+ import { OpenAICompatibleModelClient, CodingAgent, ToolRegistry, } from "@sean.holung/minicode-sdk";
4
4
  import { createTestAgentConfig } from "./test-utils.js";
5
5
  import { loadAgentConfig } from "../src/agent/config.js";
6
6
  // ---------------------------------------------------------------------------