@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.
- package/README.md +5 -3
- package/dist/scripts/run-benchmarks.js +7 -1
- package/dist/src/benchmark/runner.js +66 -3
- package/dist/src/cli/benchmark-run.js +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/indexer/code-map.js +16 -1
- package/dist/src/serve/agent-bridge.js +1 -1
- package/dist/src/session/session-store.js +1 -1
- package/dist/src/shared/symbol-resolution.js +24 -3
- package/dist/src/tools/find-path.js +1 -1
- package/dist/src/tools/find-references.js +1 -1
- package/dist/src/tools/get-dependencies.js +1 -1
- package/dist/src/tools/post-edit-diagnostics.js +185 -0
- package/dist/src/tools/read-symbol.js +2 -2
- package/dist/src/tools/registry.js +18 -3
- package/dist/src/tools/search-code-map.js +101 -9
- package/dist/src/ui/cli-ink.js +1 -1
- package/dist/tests/agent.test.js +1 -1
- package/dist/tests/context-indicator.test.js +1 -1
- package/dist/tests/file-tools.test.js +2 -2
- package/dist/tests/focus-tracker.test.js +1 -1
- package/dist/tests/guardrails.test.js +1 -1
- package/dist/tests/indexer.test.js +59 -28
- package/dist/tests/model-client-openai.test.js +1 -1
- package/dist/tests/model-selection.test.js +1 -1
- package/dist/tests/python-plugin.test.js +3 -3
- package/dist/tests/read-symbol.test.js +84 -10
- package/dist/tests/reasoning-effort.test.js +1 -1
- package/dist/tests/search-code-map.test.js +132 -1
- package/dist/tests/serve.integration.test.js +1 -1
- package/dist/tests/session-store.test.js +1 -1
- package/dist/tests/session.test.js +1 -1
- package/dist/tests/symbol-resolution.test.js +57 -0
- package/dist/tests/system-prompt.test.js +1 -1
- package/dist/tests/tool-registry.test.js +1 -1
- package/node_modules/@sean.holung/minicode-sdk/LICENSE +201 -0
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/README.md +43 -22
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/agent.d.ts +10 -1
- package/node_modules/@sean.holung/minicode-sdk/dist/src/agent/agent.d.ts.map +1 -0
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/agent.js +18 -9
- package/node_modules/@sean.holung/minicode-sdk/dist/src/agent/agent.js.map +1 -0
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/index.d.ts +1 -1
- package/node_modules/@sean.holung/minicode-sdk/dist/src/index.d.ts.map +1 -0
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/index.js +1 -1
- package/node_modules/@sean.holung/minicode-sdk/dist/src/index.js.map +1 -0
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/mcp/client-registry.js +1 -1
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/mcp/client-registry.js.map +1 -1
- package/node_modules/@sean.holung/minicode-sdk/dist/src/model/client.d.ts.map +1 -0
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/model/client.js +71 -4
- package/node_modules/@sean.holung/minicode-sdk/dist/src/model/client.js.map +1 -0
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/prompt/system-prompt.d.ts.map +1 -1
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/prompt/system-prompt.js +1 -1
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/prompt/system-prompt.js.map +1 -1
- package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file-replacers.d.ts +59 -0
- package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file-replacers.d.ts.map +1 -0
- package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file-replacers.js +392 -0
- package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file-replacers.js.map +1 -0
- package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file.d.ts +19 -0
- package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file.d.ts.map +1 -0
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/edit-file.js +14 -25
- package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/edit-file.js.map +1 -0
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/read-file.d.ts.map +1 -1
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/read-file.js +11 -5
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/read-file.js.map +1 -1
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/run-command.d.ts.map +1 -1
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/run-command.js +3 -0
- package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/run-command.js.map +1 -0
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/search.d.ts.map +1 -1
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/search.js +52 -25
- package/node_modules/@sean.holung/minicode-sdk/dist/src/tools/search.js.map +1 -0
- package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/package.json +6 -2
- package/node_modules/minicode-plugin-python/dist/src/index.d.ts +1 -1
- package/node_modules/minicode-plugin-python/dist/src/index.d.ts.map +1 -1
- package/node_modules/minicode-plugin-python/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/minicode-plugin-python/package.json +2 -2
- package/package.json +3 -3
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/edit-file.d.ts +0 -13
- package/node_modules/@minicode/agent-sdk/dist/src/tools/edit-file.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/edit-file.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/search.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.js +0 -569
- package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.js +0 -131
- package/node_modules/@minicode/agent-sdk/dist/tests/file-tools.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.js +0 -54
- package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.integration.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.integration.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.integration.test.js +0 -64
- package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.integration.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.test.js +0 -350
- package/node_modules/@minicode/agent-sdk/dist/tests/mcp-client.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-anthropic-structured-output.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-anthropic-structured-output.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-anthropic-structured-output.test.js +0 -211
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-anthropic-structured-output.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.js +0 -330
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-structured-output.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-structured-output.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-structured-output.test.js +0 -171
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-structured-output.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/session.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/session.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/session.test.js +0 -226
- package/node_modules/@minicode/agent-sdk/dist/tests/session.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/structured-output.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/structured-output.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/structured-output.test.js +0 -212
- package/node_modules/@minicode/agent-sdk/dist/tests/structured-output.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/system-prompt.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/system-prompt.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/system-prompt.test.js +0 -76
- package/node_modules/@minicode/agent-sdk/dist/tests/system-prompt.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.d.ts +0 -3
- package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.js +0 -20
- package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/tool-factory-options.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/tool-factory-options.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/tool-factory-options.test.js +0 -72
- package/node_modules/@minicode/agent-sdk/dist/tests/tool-factory-options.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/tool-registry.test.d.ts +0 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/tool-registry.test.d.ts.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/tool-registry.test.js +0 -69
- package/node_modules/@minicode/agent-sdk/dist/tests/tool-registry.test.js.map +0 -1
- package/node_modules/@minicode/agent-sdk/dist/tsconfig.tsbuildinfo +0 -1
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/structured-output.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/structured-output.d.ts.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/structured-output.js +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/structured-output.js.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/types.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/types.d.ts.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/types.js +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/agent/types.js.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/focus-tracker.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/focus-tracker.d.ts.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/focus-tracker.js +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/focus-tracker.js.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/types.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/types.d.ts.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/types.js +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/indexer/types.js.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/mcp/client-registry.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/mcp/client-registry.d.ts.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/model/client.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/prompt/system-prompt.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/safety/guardrails.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/safety/guardrails.d.ts.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/safety/guardrails.js +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/safety/guardrails.js.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/session/session.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/session/session.d.ts.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/session/session.js +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/session/session.js.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/helpers.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/helpers.d.ts.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/helpers.js +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/helpers.js.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/list-files.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/list-files.d.ts.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/list-files.js +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/list-files.js.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/read-file.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/registry.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/registry.d.ts.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/registry.js +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/registry.js.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/run-command.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/search.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/write-file.d.ts +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/write-file.d.ts.map +0 -0
- /package/node_modules/{@minicode/agent-sdk → @sean.holung/minicode-sdk}/dist/src/tools/write-file.js +0 -0
- /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
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
159
|
+
const ambiguityHint = buildAmbiguityHint(shown);
|
|
160
|
+
const sections = [
|
|
74
161
|
`# Symbols matching "${pattern}" (${result.total} total)`,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
}
|
package/dist/src/ui/cli-ink.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
-
import { CodingAgent, createModelClient } from "@minicode
|
|
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";
|
package/dist/tests/agent.test.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
}), /
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
201
|
-
|
|
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("
|
|
231
|
-
assert.ok(codeMap.text.includes("
|
|
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
|
|
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
|
|
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
|
|
41
|
-
assert.ok(codeMap.text.includes("
|
|
42
|
-
assert.ok(codeMap.text.includes("
|
|
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
|
|
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
|
|
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
|
/**
|