@sean.holung/minicode 0.3.11 → 0.4.0

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 (191) 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/tests/agent.test.js +1 -1
  19. package/dist/tests/context-indicator.test.js +1 -1
  20. package/dist/tests/file-tools.test.js +2 -2
  21. package/dist/tests/focus-tracker.test.js +1 -1
  22. package/dist/tests/guardrails.test.js +1 -1
  23. package/dist/tests/indexer.test.js +59 -28
  24. package/dist/tests/model-client-openai.test.js +1 -1
  25. package/dist/tests/model-selection.test.js +1 -1
  26. package/dist/tests/python-plugin.test.js +3 -3
  27. package/dist/tests/read-symbol.test.js +84 -10
  28. package/dist/tests/reasoning-effort.test.js +1 -1
  29. package/dist/tests/search-code-map.test.js +132 -1
  30. package/dist/tests/serve.integration.test.js +1 -1
  31. package/dist/tests/session-store.test.js +1 -1
  32. package/dist/tests/session.test.js +1 -1
  33. package/dist/tests/symbol-resolution.test.js +57 -0
  34. package/dist/tests/system-prompt.test.js +1 -1
  35. package/dist/tests/tool-registry.test.js +1 -1
  36. package/node_modules/@sean.holung/minicode-sdk/LICENSE +201 -0
  37. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/README.md +43 -22
  38. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/agent.d.ts +10 -1
  39. package/node_modules/@sean.holung/minicode-sdk/dist/src/agent/agent.d.ts.map +1 -0
  40. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/agent.js +18 -9
  41. package/node_modules/@sean.holung/minicode-sdk/dist/src/agent/agent.js.map +1 -0
  42. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/index.d.ts +1 -1
  43. package/node_modules/@sean.holung/minicode-sdk/dist/src/index.d.ts.map +1 -0
  44. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/index.js +1 -1
  45. package/node_modules/@sean.holung/minicode-sdk/dist/src/index.js.map +1 -0
  46. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/mcp/client-registry.js +1 -1
  47. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/mcp/client-registry.js.map +1 -1
  48. package/node_modules/@sean.holung/minicode-sdk/dist/src/model/client.d.ts.map +1 -0
  49. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/model/client.js +71 -4
  50. package/node_modules/@sean.holung/minicode-sdk/dist/src/model/client.js.map +1 -0
  51. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/prompt/system-prompt.d.ts.map +1 -1
  52. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/prompt/system-prompt.js +1 -1
  53. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/prompt/system-prompt.js.map +1 -1
  54. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file-replacers.d.ts +59 -0
  55. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file-replacers.d.ts.map +1 -0
  56. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file-replacers.js +392 -0
  57. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file-replacers.js.map +1 -0
  58. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file.d.ts +19 -0
  59. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file.d.ts.map +1 -0
  60. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/edit-file.js +14 -25
  61. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file.js.map +1 -0
  62. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/read-file.d.ts.map +1 -1
  63. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/read-file.js +11 -5
  64. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/read-file.js.map +1 -1
  65. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/run-command.d.ts.map +1 -1
  66. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/run-command.js +3 -0
  67. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/run-command.js.map +1 -0
  68. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/search.d.ts.map +1 -1
  69. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/search.js +52 -25
  70. package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/search.js.map +1 -0
  71. package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/package.json +6 -2
  72. package/node_modules/minicode-plugin-python/dist/src/index.d.ts +1 -1
  73. package/node_modules/minicode-plugin-python/dist/src/index.d.ts.map +1 -1
  74. package/node_modules/minicode-plugin-python/dist/tsconfig.tsbuildinfo +1 -1
  75. package/node_modules/minicode-plugin-python/package.json +2 -2
  76. package/package.json +3 -3
  77. package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts.map +0 -1
  78. package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js.map +0 -1
  79. package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts.map +0 -1
  80. package/node_modules/@minicode/agent-sdk/dist/src/index.js.map +0 -1
  81. package/node_modules/@minicode/agent-sdk/dist/src/model/client.d.ts.map +0 -1
  82. package/node_modules/@minicode/agent-sdk/dist/src/model/client.js.map +0 -1
  83. package/node_modules/@minicode/agent-sdk/dist/src/tools/edit-file.d.ts +0 -13
  84. package/node_modules/@minicode/agent-sdk/dist/src/tools/edit-file.d.ts.map +0 -1
  85. package/node_modules/@minicode/agent-sdk/dist/src/tools/edit-file.js.map +0 -1
  86. package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.js.map +0 -1
  87. package/node_modules/@minicode/agent-sdk/dist/src/tools/search.js.map +0 -1
  88. package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.d.ts +0 -2
  89. package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.d.ts.map +0 -1
  90. package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.js +0 -569
  91. package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.js.map +0 -1
  92. package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.d.ts +0 -2
  93. package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.d.ts.map +0 -1
  94. package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.js +0 -131
  95. package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.js.map +0 -1
  96. package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.d.ts +0 -2
  97. package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.d.ts.map +0 -1
  98. package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.js +0 -54
  99. package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.js.map +0 -1
  100. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.integration.test.d.ts +0 -2
  101. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.integration.test.d.ts.map +0 -1
  102. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.integration.test.js +0 -64
  103. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.integration.test.js.map +0 -1
  104. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.test.d.ts +0 -2
  105. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.test.d.ts.map +0 -1
  106. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.test.js +0 -350
  107. package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.test.js.map +0 -1
  108. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-anthropic-structured-output.test.d.ts +0 -2
  109. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-anthropic-structured-output.test.d.ts.map +0 -1
  110. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-anthropic-structured-output.test.js +0 -211
  111. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-anthropic-structured-output.test.js.map +0 -1
  112. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.d.ts +0 -2
  113. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.d.ts.map +0 -1
  114. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.js +0 -330
  115. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.js.map +0 -1
  116. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-structured-output.test.d.ts +0 -2
  117. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-structured-output.test.d.ts.map +0 -1
  118. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-structured-output.test.js +0 -171
  119. package/node_modules/@minicode/agent-sdk/dist/tests/model-client-structured-output.test.js.map +0 -1
  120. package/node_modules/@minicode/agent-sdk/dist/tests/session.test.d.ts +0 -2
  121. package/node_modules/@minicode/agent-sdk/dist/tests/session.test.d.ts.map +0 -1
  122. package/node_modules/@minicode/agent-sdk/dist/tests/session.test.js +0 -226
  123. package/node_modules/@minicode/agent-sdk/dist/tests/session.test.js.map +0 -1
  124. package/node_modules/@minicode/agent-sdk/dist/tests/structured-output.test.d.ts +0 -2
  125. package/node_modules/@minicode/agent-sdk/dist/tests/structured-output.test.d.ts.map +0 -1
  126. package/node_modules/@minicode/agent-sdk/dist/tests/structured-output.test.js +0 -212
  127. package/node_modules/@minicode/agent-sdk/dist/tests/structured-output.test.js.map +0 -1
  128. package/node_modules/@minicode/agent-sdk/dist/tests/system-prompt.test.d.ts +0 -2
  129. package/node_modules/@minicode/agent-sdk/dist/tests/system-prompt.test.d.ts.map +0 -1
  130. package/node_modules/@minicode/agent-sdk/dist/tests/system-prompt.test.js +0 -76
  131. package/node_modules/@minicode/agent-sdk/dist/tests/system-prompt.test.js.map +0 -1
  132. package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.d.ts +0 -3
  133. package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.d.ts.map +0 -1
  134. package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.js +0 -20
  135. package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.js.map +0 -1
  136. package/node_modules/@minicode/agent-sdk/dist/tests/tool-factory-options.test.d.ts +0 -2
  137. package/node_modules/@minicode/agent-sdk/dist/tests/tool-factory-options.test.d.ts.map +0 -1
  138. package/node_modules/@minicode/agent-sdk/dist/tests/tool-factory-options.test.js +0 -72
  139. package/node_modules/@minicode/agent-sdk/dist/tests/tool-factory-options.test.js.map +0 -1
  140. package/node_modules/@minicode/agent-sdk/dist/tests/tool-registry.test.d.ts +0 -2
  141. package/node_modules/@minicode/agent-sdk/dist/tests/tool-registry.test.d.ts.map +0 -1
  142. package/node_modules/@minicode/agent-sdk/dist/tests/tool-registry.test.js +0 -69
  143. package/node_modules/@minicode/agent-sdk/dist/tests/tool-registry.test.js.map +0 -1
  144. package/node_modules/@minicode/agent-sdk/dist/tsconfig.tsbuildinfo +0 -1
  145. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/structured-output.d.ts +0 -0
  146. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/structured-output.d.ts.map +0 -0
  147. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/structured-output.js +0 -0
  148. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/structured-output.js.map +0 -0
  149. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/types.d.ts +0 -0
  150. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/types.d.ts.map +0 -0
  151. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/types.js +0 -0
  152. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/types.js.map +0 -0
  153. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/focus-tracker.d.ts +0 -0
  154. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/focus-tracker.d.ts.map +0 -0
  155. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/focus-tracker.js +0 -0
  156. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/focus-tracker.js.map +0 -0
  157. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/types.d.ts +0 -0
  158. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/types.d.ts.map +0 -0
  159. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/types.js +0 -0
  160. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/types.js.map +0 -0
  161. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/mcp/client-registry.d.ts +0 -0
  162. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/mcp/client-registry.d.ts.map +0 -0
  163. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/model/client.d.ts +0 -0
  164. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/prompt/system-prompt.d.ts +0 -0
  165. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/safety/guardrails.d.ts +0 -0
  166. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/safety/guardrails.d.ts.map +0 -0
  167. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/safety/guardrails.js +0 -0
  168. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/safety/guardrails.js.map +0 -0
  169. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/session/session.d.ts +0 -0
  170. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/session/session.d.ts.map +0 -0
  171. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/session/session.js +0 -0
  172. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/session/session.js.map +0 -0
  173. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/helpers.d.ts +0 -0
  174. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/helpers.d.ts.map +0 -0
  175. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/helpers.js +0 -0
  176. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/helpers.js.map +0 -0
  177. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/list-files.d.ts +0 -0
  178. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/list-files.d.ts.map +0 -0
  179. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/list-files.js +0 -0
  180. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/list-files.js.map +0 -0
  181. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/read-file.d.ts +0 -0
  182. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/registry.d.ts +0 -0
  183. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/registry.d.ts.map +0 -0
  184. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/registry.js +0 -0
  185. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/registry.js.map +0 -0
  186. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/run-command.d.ts +0 -0
  187. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/search.d.ts +0 -0
  188. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/write-file.d.ts +0 -0
  189. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/write-file.d.ts.map +0 -0
  190. /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/write-file.js +0 -0
  191. /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";
@@ -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");
@@ -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 = "";
@@ -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";
@@ -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
  // ---------------------------------------------------------------------------
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { tmpdir } from "node:os";
5
5
  import { test } from "node:test";
6
6
  import { buildProjectIndex } from "../src/indexer/project-index.js";
7
- import { createSearchCodeMapTool } from "../src/tools/search-code-map.js";
7
+ import { buildAmbiguityHint, createSearchCodeMapTool, summarizeDocComment, } from "../src/tools/search-code-map.js";
8
8
  test("search_code_map finds symbols by substring", async () => {
9
9
  const root = path.resolve(import.meta.dirname, "..");
10
10
  const projectIndex = await buildProjectIndex(root);
@@ -57,3 +57,134 @@ export class Employee {
57
57
  assert.ok(result.includes("qualified: Employee#interface"));
58
58
  assert.ok(result.includes("qualified: Employee#class"));
59
59
  });
60
+ // ─── Issue #184: disambiguation hints + doc summaries ──────────────────
61
+ test("search_code_map surfaces JSDoc summaries beside each match (issue #184)", async () => {
62
+ const workspaceRoot = await mkdtemp(path.join(tmpdir(), "minicode-doc-summaries-"));
63
+ await writeFile(path.join(workspaceRoot, "sample.ts"), `/**
64
+ * Create a default ToolWidget with all builtin gears.
65
+ * Wraps the SDK class for application-level setup.
66
+ */
67
+ export function createToolWidget(): ToolWidget {
68
+ return new ToolWidget();
69
+ }
70
+
71
+ export class ToolWidget {
72
+ constructor() {}
73
+ }
74
+ `, "utf8");
75
+ const projectIndex = await buildProjectIndex(workspaceRoot);
76
+ const tool = createSearchCodeMapTool(projectIndex);
77
+ const result = await tool.execute({ pattern: "ToolWidget" });
78
+ // The doc summary's first line should appear under the createToolWidget
79
+ // entry — without it, the model can't tell `ToolWidget` (the class) apart
80
+ // from `createToolWidget` (the factory) by kind+path alone.
81
+ assert.match(result, /Create a default ToolWidget with all builtin gears\./);
82
+ // Each shown match should still expose its qualified name, intact.
83
+ assert.match(result, /qualified: ToolWidget/);
84
+ assert.match(result, /qualified: createToolWidget/);
85
+ });
86
+ test("search_code_map prepends an ambiguity hint when matches span class + function (issue #184)", async () => {
87
+ const workspaceRoot = await mkdtemp(path.join(tmpdir(), "minicode-ambiguity-"));
88
+ await writeFile(path.join(workspaceRoot, "sample.ts"), `export class Widget {
89
+ constructor() {}
90
+ }
91
+
92
+ export function createWidget(): Widget {
93
+ return new Widget();
94
+ }
95
+ `, "utf8");
96
+ const projectIndex = await buildProjectIndex(workspaceRoot);
97
+ const tool = createSearchCodeMapTool(projectIndex);
98
+ const result = await tool.execute({ pattern: "Widget" });
99
+ // The hint specifically calls out the noun-vs-verb-form shape that
100
+ // led to the find-tool-registration regression.
101
+ assert.match(result, /Note: results span multiple symbol kinds/);
102
+ assert.match(result, /class\/interface\/type and a function\/method/);
103
+ assert.match(result, /read_symbol/);
104
+ });
105
+ test("search_code_map does NOT prepend the ambiguity hint when matches are all the same kind", async () => {
106
+ const workspaceRoot = await mkdtemp(path.join(tmpdir(), "minicode-no-ambiguity-"));
107
+ await writeFile(path.join(workspaceRoot, "sample.ts"), `export function widgetA(): void {}
108
+ export function widgetB(): void {}
109
+ export function widgetC(): void {}
110
+ `, "utf8");
111
+ const projectIndex = await buildProjectIndex(workspaceRoot);
112
+ const tool = createSearchCodeMapTool(projectIndex);
113
+ const result = await tool.execute({ pattern: "widget" });
114
+ // All three are functions — no ambiguity to flag.
115
+ assert.doesNotMatch(result, /results span multiple symbol kinds/);
116
+ });
117
+ test("search_code_map does NOT prepend the ambiguity hint for class-with-its-methods", async () => {
118
+ const workspaceRoot = await mkdtemp(path.join(tmpdir(), "minicode-class-methods-"));
119
+ await writeFile(path.join(workspaceRoot, "sample.ts"), `export class Widget {
120
+ init(): void {}
121
+ destroy(): void {}
122
+ }
123
+ `, "utf8");
124
+ const projectIndex = await buildProjectIndex(workspaceRoot);
125
+ const tool = createSearchCodeMapTool(projectIndex);
126
+ const result = await tool.execute({ pattern: "Widget" });
127
+ // class + methods is a structurally expected pairing, not a noun/verb
128
+ // disambiguation problem. The hint should stay quiet here.
129
+ assert.doesNotMatch(result, /results span multiple symbol kinds/);
130
+ });
131
+ // ─── Direct helper unit tests ──────────────────────────────────────────
132
+ test("summarizeDocComment returns empty string for missing input", () => {
133
+ assert.equal(summarizeDocComment(undefined), "");
134
+ assert.equal(summarizeDocComment(""), "");
135
+ assert.equal(summarizeDocComment(" \n \n"), "");
136
+ });
137
+ test("summarizeDocComment strips JSDoc markers and returns first non-empty line", () => {
138
+ const input = "/**\n * First sentence here.\n * More detail.\n */";
139
+ assert.equal(summarizeDocComment(input), "First sentence here.");
140
+ });
141
+ test("summarizeDocComment splits on \\r-only line separators (TS compiler shape)", () => {
142
+ // The TS compiler stores docComment with `\r` line separators on some
143
+ // platforms — without explicit `\r` handling the whole comment becomes
144
+ // a single line and the truncation chops mid-paragraph rather than at
145
+ // the first description sentence.
146
+ const input = "First sentence.\rSecond sentence.\rThird sentence.";
147
+ assert.equal(summarizeDocComment(input), "First sentence.");
148
+ });
149
+ test("summarizeDocComment truncates long first lines with ellipsis", () => {
150
+ const longLine = "a".repeat(150);
151
+ const result = summarizeDocComment(longLine);
152
+ assert.equal(result.length, 100);
153
+ assert.ok(result.endsWith("..."));
154
+ });
155
+ function makeSymbol(kind, name = "X") {
156
+ return {
157
+ name,
158
+ qualifiedName: name,
159
+ kind,
160
+ filePath: "x.ts",
161
+ startLine: 1,
162
+ endLine: 1,
163
+ exported: true,
164
+ };
165
+ }
166
+ test("buildAmbiguityHint stays quiet when all matches share one kind", () => {
167
+ const matches = [makeSymbol("function"), makeSymbol("function")];
168
+ assert.equal(buildAmbiguityHint(matches), "");
169
+ });
170
+ test("buildAmbiguityHint stays quiet for class-with-its-methods", () => {
171
+ const matches = [makeSymbol("class"), makeSymbol("method"), makeSymbol("method")];
172
+ assert.equal(buildAmbiguityHint(matches), "");
173
+ });
174
+ test("buildAmbiguityHint fires for class + standalone function (noun-vs-verb)", () => {
175
+ const matches = [makeSymbol("class"), makeSymbol("function")];
176
+ const hint = buildAmbiguityHint(matches);
177
+ assert.match(hint, /results span multiple symbol kinds/);
178
+ assert.match(hint, /read_symbol/);
179
+ });
180
+ test("buildAmbiguityHint fires for interface + standalone function", () => {
181
+ const matches = [makeSymbol("interface"), makeSymbol("function")];
182
+ assert.match(buildAmbiguityHint(matches), /results span multiple symbol kinds/);
183
+ });
184
+ test("buildAmbiguityHint stays quiet when only methods accompany the noun (no standalone function)", () => {
185
+ // Documents the deliberate trade-off: cross-class method collisions
186
+ // are under-flagged to avoid noisy false positives on the more
187
+ // common class-with-its-own-methods shape.
188
+ const matches = [makeSymbol("class"), makeSymbol("method")];
189
+ assert.equal(buildAmbiguityHint(matches), "");
190
+ });
@@ -6,7 +6,7 @@ import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { createRequestHandler, shutdownServe } from "../src/serve/server.js";
8
8
  import { AgentBridge } from "../src/serve/agent-bridge.js";
9
- import { Session } from "@minicode/agent-sdk";
9
+ import { Session } from "@sean.holung/minicode-sdk";
10
10
  import { DuplicateSessionLabelError } from "../src/session/session-store.js";
11
11
  import { createTestAgentConfig } from "./test-utils.js";
12
12
  /**