@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
package/README.md
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
A graph-native coding agent and code exploration environment built around structural context optimization that leverages symbol-aware retrieval, dependency graphs, and targeted context. It started as a way to make local models viable under tighter context budgets, and it now also works well with hosted frontier models through the same runtime, web UI, and OpenAI-compatible serve mode.
|
|
6
6
|
|
|
7
|
-
minicode is built on a simple bet: models perform better when you give them
|
|
7
|
+
minicode is built on a simple bet: models perform better when you give them more useful context, not less raw context. Bloated context dilutes attention; targeted context lets the model build a structural picture of the codebase before answering.
|
|
8
8
|
|
|
9
|
-
Read operations dominate token usage in typical agent sessions; minicode addresses this by optimizing for **specific languages**. It indexes your project at startup with language plugins, injects a compact **code map** (signatures only) into the system prompt, and exposes symbol-level tools (`read_symbol`, `find_references`, `get_dependencies`) so the model
|
|
9
|
+
Read operations dominate token usage in typical agent sessions; minicode addresses this by optimizing for **specific languages**. It indexes your project at startup with language plugins, injects a compact **code map** (signatures only) into the system prompt, and exposes symbol-level tools (`read_symbol`, `find_references`, `get_dependencies`) so the model can walk the code structurally instead of grepping and reading entire files. TypeScript and JavaScript support come built-in, with custom language plugins leaving room for broader language support over time.
|
|
10
|
+
|
|
11
|
+
In our own ablation on a 25-task local benchmark suite (see [`benchmarks/RESULTS-GEMMA-4.md`](benchmarks/RESULTS-GEMMA-4.md)), turning the structural tools on raises pass rate from 47% to 61% on Gemma 4 26B-A4B — a +14.7 pp lift driven mostly by **comprehension-heavy tasks** (planning and refactor work where the agent has to trace relationships across files). The tradeoff is honest: graph tools cost about 30% more tokens per task when actually used, but they buy correctness on the kinds of questions where reading whole files just runs out the model's reasoning budget without building a structural picture.
|
|
10
12
|
|
|
11
13
|
_Run `minicode serve` to get the web UI on localhost: chat, tool activity, session controls, model switching, symbol focus, annotations, and a live dependency graph._
|
|
12
14
|
|
|
@@ -118,7 +120,7 @@ For agent-loop internals (session lifecycle, tool execution, streaming, loop det
|
|
|
118
120
|
|
|
119
121
|
For the proposed reusable package architecture and public interfaces for a standalone runtime SDK, see [docs/SDK_SPEC.md](docs/SDK_SPEC.md).
|
|
120
122
|
|
|
121
|
-
minicode
|
|
123
|
+
minicode replaces grep-and-read-whole-file with symbol-level navigation:
|
|
122
124
|
|
|
123
125
|
- **Code map** — A compact project skeleton (signatures only) is injected into the system prompt so the model can orient itself without reading full files.
|
|
124
126
|
- `read_symbol` — Read a specific function or class by name, with referenced types.
|
|
@@ -19,7 +19,7 @@ import { readFileSync, existsSync } from "node:fs";
|
|
|
19
19
|
import path from "node:path";
|
|
20
20
|
import { homedir } from "node:os";
|
|
21
21
|
import { writeFile } from "node:fs/promises";
|
|
22
|
-
import { createModelClient, } from "@minicode
|
|
22
|
+
import { createModelClient, } from "@sean.holung/minicode-sdk";
|
|
23
23
|
import { parse as parseDotenv } from "dotenv";
|
|
24
24
|
import { loadBenchmarkTasks, loadBenchmarkTask } from "../src/benchmark/task-loader.js";
|
|
25
25
|
import { runBenchmarkSuite } from "../src/benchmark/runner.js";
|
|
@@ -111,6 +111,12 @@ export function buildConfig(options = {}) {
|
|
|
111
111
|
maxToolOutputChars: getNumberSetting(getShellOverride("MAX_TOOL_OUTPUT_CHARS"), fileConfig.maxToolOutputChars, 8000),
|
|
112
112
|
openAiBaseUrl,
|
|
113
113
|
...(openAiApiKey ? { openAiApiKey } : {}),
|
|
114
|
+
// Explicit match to the loadAgentConfig default so benchmark runs see
|
|
115
|
+
// the same static-system-prompt behavior that real CLI/serve users
|
|
116
|
+
// get. Omitting this routes through the SDK fallback in agent.ts and,
|
|
117
|
+
// depending on its semantics, may flip behavior unintentionally — see
|
|
118
|
+
// PR #138 follow-up for the original issue.
|
|
119
|
+
enableDynamicPrompt: false,
|
|
114
120
|
};
|
|
115
121
|
}
|
|
116
122
|
/* ------------------------------------------------------------------ */
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* over tool-call instrumentation and trace capture.
|
|
6
6
|
*/
|
|
7
7
|
import { execSync } from "node:child_process";
|
|
8
|
-
import { cp, mkdtemp, rm } from "node:fs/promises";
|
|
8
|
+
import { cp, mkdir, mkdtemp, rm, symlink, stat } from "node:fs/promises";
|
|
9
9
|
import { tmpdir } from "node:os";
|
|
10
10
|
import path from "node:path";
|
|
11
|
-
import { CodingAgent, Session, ToolRegistry, } from "@minicode
|
|
11
|
+
import { CodingAgent, Session, ToolRegistry, } from "@sean.holung/minicode-sdk";
|
|
12
12
|
const COPY_SKIP_NAMES = new Set([
|
|
13
13
|
".git",
|
|
14
14
|
"node_modules",
|
|
@@ -16,6 +16,24 @@ const COPY_SKIP_NAMES = new Set([
|
|
|
16
16
|
"build",
|
|
17
17
|
"coverage",
|
|
18
18
|
]);
|
|
19
|
+
/**
|
|
20
|
+
* Narrow symlinks (relative to the source workspace) injected into the
|
|
21
|
+
* isolated workspace after the bulk copy. Goal: make `tsc --noEmit`
|
|
22
|
+
* resolve types for the post-edit diagnostic, *without* enabling the
|
|
23
|
+
* full test runner.
|
|
24
|
+
*
|
|
25
|
+
* - `node_modules/@types` lets tsc resolve `@types/node` etc. (the
|
|
26
|
+
* typescript binary itself runs from the source workspace and finds
|
|
27
|
+
* its lib files relative to its own install location, so we don't
|
|
28
|
+
* need to symlink it into the temp tree.)
|
|
29
|
+
*
|
|
30
|
+
* We intentionally do NOT symlink `node_modules/.bin` or general deps,
|
|
31
|
+
* so commands like `npm test` fail fast with "tsc: not found" rather
|
|
32
|
+
* than running the full pretest+test suite and burning per-task
|
|
33
|
+
* tool-call budget on infrastructure noise. See benchmarks/RESULTS for
|
|
34
|
+
* the symlink scope conversation.
|
|
35
|
+
*/
|
|
36
|
+
const SYMLINK_FROM_SOURCE = ["node_modules/@types"];
|
|
19
37
|
function getGitCommitSha() {
|
|
20
38
|
try {
|
|
21
39
|
return execSync("git rev-parse HEAD", { encoding: "utf8" }).trim();
|
|
@@ -45,6 +63,31 @@ function shouldCopyPath(src) {
|
|
|
45
63
|
const name = path.basename(src);
|
|
46
64
|
return !COPY_SKIP_NAMES.has(name);
|
|
47
65
|
}
|
|
66
|
+
async function linkSharedDirs(sourceRoot, workspaceRoot) {
|
|
67
|
+
for (const rel of SYMLINK_FROM_SOURCE) {
|
|
68
|
+
const sourcePath = path.join(sourceRoot, rel);
|
|
69
|
+
try {
|
|
70
|
+
const s = await stat(sourcePath);
|
|
71
|
+
if (!s.isDirectory())
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const linkPath = path.join(workspaceRoot, rel);
|
|
78
|
+
// Ensure parent dir exists — e.g. `node_modules/@types` requires
|
|
79
|
+
// `node_modules/` to be created first (we don't bulk-copy it).
|
|
80
|
+
await mkdir(path.dirname(linkPath), { recursive: true });
|
|
81
|
+
try {
|
|
82
|
+
await symlink(sourcePath, linkPath, "dir");
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Best-effort: a parent path may not exist or the link target
|
|
86
|
+
// already does. Skip silently — tools that need these resolve
|
|
87
|
+
// to a useful error if missing.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
48
91
|
async function prepareTaskWorkspace(task, options) {
|
|
49
92
|
const sourceWorkspaceRoot = resolveSourceWorkspaceRoot(task, options);
|
|
50
93
|
if (options.isolateWorkspace === false) {
|
|
@@ -60,6 +103,7 @@ async function prepareTaskWorkspace(task, options) {
|
|
|
60
103
|
recursive: true,
|
|
61
104
|
filter: shouldCopyPath,
|
|
62
105
|
});
|
|
106
|
+
await linkSharedDirs(sourceWorkspaceRoot, isolatedWorkspaceRoot);
|
|
63
107
|
return {
|
|
64
108
|
sourceWorkspaceRoot,
|
|
65
109
|
workspaceRoot: isolatedWorkspaceRoot,
|
|
@@ -74,7 +118,26 @@ function getTrackedSymbolNames(toolName, input) {
|
|
|
74
118
|
.filter((value) => typeof value === "string" && value.length > 0);
|
|
75
119
|
return [...new Set(names)];
|
|
76
120
|
}
|
|
77
|
-
|
|
121
|
+
// search_code_map uses `pattern`, the other graph tools use one of
|
|
122
|
+
// `name`/`symbol`/`symbolName`/`query`. Without `pattern` in this
|
|
123
|
+
// fallback chain, a search_code_map call would fail to register a
|
|
124
|
+
// queried symbol even when the pattern is the literal symbol name —
|
|
125
|
+
// and any rubric using `expectedSymbols` would then incorrectly fail
|
|
126
|
+
// tasks that were answered correctly via the code-map search.
|
|
127
|
+
//
|
|
128
|
+
// We register the pattern verbatim without resolving it against the
|
|
129
|
+
// project index. The downstream `expectedSymbols` matcher in
|
|
130
|
+
// `evaluator.ts` uses `queried.includes(expected)`, so a too-narrow
|
|
131
|
+
// query (e.g. `"Tool"` against `expectedSymbols: ["ToolRegistry"]`)
|
|
132
|
+
// correctly fails to satisfy the rubric. A too-broad query
|
|
133
|
+
// (`"ToolRegistryFactory"` against `["ToolRegistry"]`) does satisfy
|
|
134
|
+
// it — that asymmetry is intentional and matches how a user-facing
|
|
135
|
+
// search-by-substring is normally interpreted.
|
|
136
|
+
const name = input.symbol ??
|
|
137
|
+
input.symbolName ??
|
|
138
|
+
input.name ??
|
|
139
|
+
input.query ??
|
|
140
|
+
input.pattern;
|
|
78
141
|
return typeof name === "string" && name.length > 0 ? [name] : [];
|
|
79
142
|
}
|
|
80
143
|
function trackStructuralFileReads(toolName, projectIndex, input, filesRead) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
-
import { CodingAgent, createModelClient, } from "@minicode
|
|
4
|
+
import { CodingAgent, createModelClient, } from "@sean.holung/minicode-sdk";
|
|
5
5
|
import { getConfigSetupMessage } from "../agent/config.js";
|
|
6
6
|
import { buildBenchmarkAgentConfig, resolveBenchmarkEnv, } from "../benchmark/config.js";
|
|
7
7
|
import { collectWorkspaceChanges, writeWorkspaceDiff, } from "../benchmark/workspace-changes.js";
|
package/dist/src/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { writeFile } from "node:fs/promises";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
|
-
import { CodingAgent, createModelClient } from "@minicode
|
|
5
|
+
import { CodingAgent, createModelClient } from "@sean.holung/minicode-sdk";
|
|
6
6
|
import { loadAgentConfig, getConfigSetupMessage } from "./agent/config.js";
|
|
7
7
|
import { listSessions, loadSession, loadSessionByLabel, saveSession, } from "./session/session-store.js";
|
|
8
8
|
import { computeFileHashes, getWorkspaceCacheDir, loadIndex, saveIndex, } from "./indexer/cache.js";
|
|
@@ -10,6 +10,20 @@ function formatSymbol(symbol, indent, isMethod) {
|
|
|
10
10
|
}
|
|
11
11
|
return `${indent}${symbol.kind} ${getSymbolDisplayName(symbol)}\n${indent} ${symbol.signature}`;
|
|
12
12
|
}
|
|
13
|
+
// Terser per-line format: "{indent}- {displayName} ({kind})". Roughly halves
|
|
14
|
+
// the chars/symbol cost vs the legacy full-signature format, doubling
|
|
15
|
+
// coverage at the same token budget. Default since Experiment 17 (May 2026)
|
|
16
|
+
// — pooled n=6 benchmark showed +3.4pp overall and +16.7pp planning vs the
|
|
17
|
+
// full format, matching the dynamic-prompt baseline with zero cache cost.
|
|
18
|
+
// Opt out with MINICODE_CODE_MAP_FORMAT=full.
|
|
19
|
+
function formatSymbolNamed(symbol, indent) {
|
|
20
|
+
return `${indent}- ${getSymbolDisplayName(symbol)} (${symbol.kind})`;
|
|
21
|
+
}
|
|
22
|
+
function selectFormatter() {
|
|
23
|
+
return process.env.MINICODE_CODE_MAP_FORMAT === "full"
|
|
24
|
+
? formatSymbol
|
|
25
|
+
: formatSymbolNamed;
|
|
26
|
+
}
|
|
13
27
|
function isEntryPointFile(filePath) {
|
|
14
28
|
const name = filePath.replace(/\\/g, "/");
|
|
15
29
|
return /(?:^|\/)index\.[jt]sx?$/.test(name);
|
|
@@ -97,6 +111,7 @@ function createSymbolRanker(adjacency, focusSymbols) {
|
|
|
97
111
|
export function generateCodeMap(symbolsByFile, tokenBudget = DEFAULT_TOKEN_BUDGET, dependencyEdges, focusSymbols) {
|
|
98
112
|
const totalCount = [...symbolsByFile.values()].reduce((sum, syms) => sum + syms.length, 0);
|
|
99
113
|
const lines = ["# Project Code Map", ""];
|
|
114
|
+
const formatter = selectFormatter();
|
|
100
115
|
const adjacency = dependencyEdges
|
|
101
116
|
? buildAdjacencyMaps(dependencyEdges)
|
|
102
117
|
: { byFrom: new Map(), byTo: new Map() };
|
|
@@ -137,7 +152,7 @@ export function generateCodeMap(symbolsByFile, tokenBudget = DEFAULT_TOKEN_BUDGE
|
|
|
137
152
|
else if (!isMethod) {
|
|
138
153
|
currentClass = null;
|
|
139
154
|
}
|
|
140
|
-
const block =
|
|
155
|
+
const block = formatter(symbol, indent, isMethod);
|
|
141
156
|
const blockTokens = estimateTokens(block);
|
|
142
157
|
if (totalTokens + blockTokens > tokenBudget) {
|
|
143
158
|
truncatedSymbols += 1;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CodingAgent, createModelClient } from "@minicode
|
|
1
|
+
import { CodingAgent, createModelClient } from "@sean.holung/minicode-sdk";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { isGatedTool, shouldAutoAllow, } from "../auto-allow.js";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { Session } from "@minicode
|
|
4
|
+
import { Session } from "@sean.holung/minicode-sdk";
|
|
5
5
|
let sessionsDir = path.join(os.homedir(), ".minicode", "sessions");
|
|
6
6
|
export class DuplicateSessionLabelError extends Error {
|
|
7
7
|
label;
|
|
@@ -12,13 +12,34 @@ export function resolveSymbolInput(projectIndex, name) {
|
|
|
12
12
|
export function formatSymbolMatch(match) {
|
|
13
13
|
return `${getSymbolDisplayName(match)} (${match.kind}) — ${match.filePath}:${match.startLine} — qualified: ${match.qualifiedName}`;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Cap on how many disambiguation entries we render. The output flows
|
|
17
|
+
* through the agent's generic tool-output truncator, which clips by
|
|
18
|
+
* character count — including in the middle of a qualified name. That
|
|
19
|
+
* leaves the model with an unparseable lookup key, which it then
|
|
20
|
+
* guesses at, producing a "not found" loop. Bounding by entry count
|
|
21
|
+
* here means each shown match has its full qualified name intact, and
|
|
22
|
+
* generic char-truncation never has occasion to fire on the result.
|
|
23
|
+
*
|
|
24
|
+
* 12 covers virtually every real ambiguity (a name that genuinely
|
|
25
|
+
* matches more is too generic to be useful — the agent should refine
|
|
26
|
+
* regardless). Each entry is ~150-300 chars, so 12 entries fit
|
|
27
|
+
* comfortably under typical maxToolOutputChars caps.
|
|
28
|
+
*/
|
|
29
|
+
const MAX_AMBIGUOUS_MATCHES = 12;
|
|
15
30
|
export function formatAmbiguousSymbolMatches(toolName, name, matches) {
|
|
16
|
-
|
|
31
|
+
const shown = matches.slice(0, MAX_AMBIGUOUS_MATCHES);
|
|
32
|
+
const elided = matches.length - shown.length;
|
|
33
|
+
const lines = [
|
|
17
34
|
`Symbol "${name}" is ambiguous; ${matches.length} matches were found.`,
|
|
18
35
|
`Re-run ${toolName} with one of these qualified or disambiguated names:`,
|
|
19
36
|
"",
|
|
20
|
-
...
|
|
21
|
-
]
|
|
37
|
+
...shown.map((match) => `- ${formatSymbolMatch(match)}`),
|
|
38
|
+
];
|
|
39
|
+
if (elided > 0) {
|
|
40
|
+
lines.push("", `[... and ${elided} more match(es) not shown. Refine the name (e.g. include the file path or use the qualified form like "Foo#class@path/to/file.ts") to narrow further.]`);
|
|
41
|
+
}
|
|
42
|
+
return lines.join("\n");
|
|
22
43
|
}
|
|
23
44
|
export function serializeSymbolMatch(match) {
|
|
24
45
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expectNonEmptyString, expectOptionalNumber } from "@minicode
|
|
1
|
+
import { expectNonEmptyString, expectOptionalNumber } from "@sean.holung/minicode-sdk";
|
|
2
2
|
import { getSymbolDisplayName } from "../indexer/symbol-names.js";
|
|
3
3
|
import { formatAmbiguousSymbolMatches, resolveSymbolInput, } from "../shared/symbol-resolution.js";
|
|
4
4
|
export function createFindPathTool(projectIndex) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expectNonEmptyString, expectOptionalNumber } from "@minicode
|
|
1
|
+
import { expectNonEmptyString, expectOptionalNumber } from "@sean.holung/minicode-sdk";
|
|
2
2
|
import { getSymbolDisplayName } from "../indexer/symbol-names.js";
|
|
3
3
|
import { formatAmbiguousSymbolMatches, resolveSymbolInput, } from "../shared/symbol-resolution.js";
|
|
4
4
|
const DEFAULT_LIMIT = 50;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expectNonEmptyString, expectOptionalNumber } from "@minicode
|
|
1
|
+
import { expectNonEmptyString, expectOptionalNumber } from "@sean.holung/minicode-sdk";
|
|
2
2
|
import { getSymbolDisplayName } from "../indexer/symbol-names.js";
|
|
3
3
|
import { formatAmbiguousSymbolMatches, resolveSymbolInput, } from "../shared/symbol-resolution.js";
|
|
4
4
|
export function createGetDependenciesTool(projectIndex) {
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdir, stat } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { getWorkspaceCacheDir } from "../indexer/cache.js";
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
let cachedTscPath;
|
|
8
|
+
function resolveTscPath() {
|
|
9
|
+
if (cachedTscPath !== undefined)
|
|
10
|
+
return cachedTscPath;
|
|
11
|
+
try {
|
|
12
|
+
cachedTscPath = require.resolve("typescript/bin/tsc");
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
cachedTscPath = null;
|
|
16
|
+
}
|
|
17
|
+
return cachedTscPath;
|
|
18
|
+
}
|
|
19
|
+
const TS_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"]);
|
|
20
|
+
const MAX_PER_FILE = 20;
|
|
21
|
+
const TSC_TIMEOUT_MS = 15_000;
|
|
22
|
+
const DISABLE_ENV = "MINICODE_DISABLE_POST_EDIT_DIAGNOSTICS";
|
|
23
|
+
/**
|
|
24
|
+
* Walk up from `startDir` looking for the nearest tsconfig.json. Stops at
|
|
25
|
+
* `workspaceRoot` (inclusive). Returns null if none found.
|
|
26
|
+
*/
|
|
27
|
+
async function findNearestTsconfig(startDir, workspaceRoot) {
|
|
28
|
+
const root = path.resolve(workspaceRoot);
|
|
29
|
+
let dir = path.resolve(startDir);
|
|
30
|
+
while (true) {
|
|
31
|
+
const candidate = path.join(dir, "tsconfig.json");
|
|
32
|
+
try {
|
|
33
|
+
const s = await stat(candidate);
|
|
34
|
+
if (s.isFile())
|
|
35
|
+
return candidate;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// not present, keep walking
|
|
39
|
+
}
|
|
40
|
+
if (dir === root)
|
|
41
|
+
return null;
|
|
42
|
+
const parent = path.dirname(dir);
|
|
43
|
+
if (parent === dir)
|
|
44
|
+
return null;
|
|
45
|
+
dir = parent;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const TSC_LINE_RE = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s*(.*)$/;
|
|
49
|
+
function parseTscOutput(output, projectDir) {
|
|
50
|
+
const lines = output.split(/\r?\n/);
|
|
51
|
+
const out = [];
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
const m = line.match(TSC_LINE_RE);
|
|
54
|
+
if (!m)
|
|
55
|
+
continue;
|
|
56
|
+
const [, rawFile, ln, col, sev, code, msg] = m;
|
|
57
|
+
if (!rawFile || !ln || !col || !sev || !code || msg === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
const absFile = path.isAbsolute(rawFile)
|
|
60
|
+
? rawFile
|
|
61
|
+
: path.resolve(projectDir, rawFile);
|
|
62
|
+
out.push({
|
|
63
|
+
file: absFile,
|
|
64
|
+
line: Number(ln),
|
|
65
|
+
col: Number(col),
|
|
66
|
+
severity: sev === "error" ? "error" : "warning",
|
|
67
|
+
code,
|
|
68
|
+
message: msg.trim(),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
async function runTsc(tsconfigPath, workspaceRoot) {
|
|
74
|
+
const cacheDir = path.join(getWorkspaceCacheDir(workspaceRoot), "diagnostics");
|
|
75
|
+
await mkdir(cacheDir, { recursive: true });
|
|
76
|
+
const tsbuildinfo = path.join(cacheDir, `${path.basename(path.dirname(tsconfigPath))}.tsbuildinfo`);
|
|
77
|
+
const tscPath = resolveTscPath();
|
|
78
|
+
if (!tscPath)
|
|
79
|
+
return null;
|
|
80
|
+
return await new Promise((resolve) => {
|
|
81
|
+
const child = spawn(process.execPath, [
|
|
82
|
+
tscPath,
|
|
83
|
+
"--noEmit",
|
|
84
|
+
"--pretty",
|
|
85
|
+
"false",
|
|
86
|
+
"--incremental",
|
|
87
|
+
"--tsBuildInfoFile",
|
|
88
|
+
tsbuildinfo,
|
|
89
|
+
"-p",
|
|
90
|
+
tsconfigPath,
|
|
91
|
+
], {
|
|
92
|
+
cwd: workspaceRoot,
|
|
93
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
94
|
+
env: process.env,
|
|
95
|
+
});
|
|
96
|
+
let stdout = "";
|
|
97
|
+
let stderr = "";
|
|
98
|
+
let settled = false;
|
|
99
|
+
const timer = setTimeout(() => {
|
|
100
|
+
if (settled)
|
|
101
|
+
return;
|
|
102
|
+
settled = true;
|
|
103
|
+
child.kill("SIGKILL");
|
|
104
|
+
resolve(null);
|
|
105
|
+
}, TSC_TIMEOUT_MS);
|
|
106
|
+
child.stdout?.on("data", (chunk) => {
|
|
107
|
+
stdout += chunk.toString("utf8");
|
|
108
|
+
});
|
|
109
|
+
child.stderr?.on("data", (chunk) => {
|
|
110
|
+
stderr += chunk.toString("utf8");
|
|
111
|
+
});
|
|
112
|
+
child.on("error", () => {
|
|
113
|
+
if (settled)
|
|
114
|
+
return;
|
|
115
|
+
settled = true;
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
resolve(null);
|
|
118
|
+
});
|
|
119
|
+
child.on("close", () => {
|
|
120
|
+
if (settled)
|
|
121
|
+
return;
|
|
122
|
+
settled = true;
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
const combined = stdout + (stderr ? `\n${stderr}` : "");
|
|
125
|
+
resolve({
|
|
126
|
+
diagnostics: parseTscOutput(combined, path.dirname(tsconfigPath)),
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function formatDiagnostics(filePath, diags) {
|
|
132
|
+
const errors = diags.filter((d) => d.severity === "error");
|
|
133
|
+
if (errors.length === 0)
|
|
134
|
+
return undefined;
|
|
135
|
+
const limited = errors.slice(0, MAX_PER_FILE);
|
|
136
|
+
const more = errors.length - MAX_PER_FILE;
|
|
137
|
+
const suffix = more > 0 ? `\n... and ${more} more` : "";
|
|
138
|
+
const body = limited
|
|
139
|
+
.map((d) => `ERROR [${d.line}:${d.col}] ${d.message}`)
|
|
140
|
+
.join("\n");
|
|
141
|
+
return `<diagnostics file="${filePath}">\n${body}${suffix}\n</diagnostics>`;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Serialize diagnostic runs per workspace+tsconfig so concurrent edits don't
|
|
145
|
+
* trigger overlapping tsc processes that fight over the same .tsbuildinfo.
|
|
146
|
+
*/
|
|
147
|
+
const inflightByKey = new Map();
|
|
148
|
+
function serializedRun(tsconfigPath, workspaceRoot) {
|
|
149
|
+
const key = `${workspaceRoot}::${tsconfigPath}`;
|
|
150
|
+
const prev = inflightByKey.get(key) ?? Promise.resolve(null);
|
|
151
|
+
const next = prev.then(() => runTsc(tsconfigPath, workspaceRoot));
|
|
152
|
+
inflightByKey.set(key, next.finally(() => {
|
|
153
|
+
if (inflightByKey.get(key) === next)
|
|
154
|
+
inflightByKey.delete(key);
|
|
155
|
+
}));
|
|
156
|
+
return next;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Run tsc against the nearest tsconfig and return an opencode-style
|
|
160
|
+
* `<diagnostics file="...">` block listing errors in the touched file. Returns
|
|
161
|
+
* undefined when there are no errors, when the file is not a TypeScript-family
|
|
162
|
+
* file, when no tsconfig is found, or when the run fails. Never throws.
|
|
163
|
+
*/
|
|
164
|
+
export async function buildPostEditDiagnostic(filePath, workspaceRoot) {
|
|
165
|
+
if (process.env[DISABLE_ENV] === "1")
|
|
166
|
+
return undefined;
|
|
167
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
168
|
+
if (!TS_EXTENSIONS.has(ext))
|
|
169
|
+
return undefined;
|
|
170
|
+
try {
|
|
171
|
+
const tsconfig = await findNearestTsconfig(path.dirname(filePath), workspaceRoot);
|
|
172
|
+
if (!tsconfig)
|
|
173
|
+
return undefined;
|
|
174
|
+
const result = await serializedRun(tsconfig, workspaceRoot);
|
|
175
|
+
if (!result)
|
|
176
|
+
return undefined;
|
|
177
|
+
const absTouched = path.resolve(filePath);
|
|
178
|
+
const forFile = result.diagnostics.filter((d) => path.resolve(d.file) === absTouched);
|
|
179
|
+
const displayPath = path.relative(workspaceRoot, absTouched) || filePath;
|
|
180
|
+
return formatDiagnostics(displayPath, forFile);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
|
-
import { resolveWorkspacePath, validateFileReadSize, expectNonEmptyString, expectOptionalBoolean, } from "@minicode
|
|
2
|
+
import { resolveWorkspacePath, validateFileReadSize, expectNonEmptyString, expectOptionalBoolean, } from "@sean.holung/minicode-sdk";
|
|
3
3
|
import { getSymbolDisplayName } from "../indexer/symbol-names.js";
|
|
4
4
|
import { formatAmbiguousSymbolMatches, resolveSymbolInput, } from "../shared/symbol-resolution.js";
|
|
5
5
|
const LEADING_CONTEXT_LINES = 3;
|
|
@@ -30,7 +30,7 @@ export function createReadSymbolTool(config, projectIndex) {
|
|
|
30
30
|
const includeBody = expectOptionalBoolean(input, "includeBody") ?? true;
|
|
31
31
|
const resolution = resolveSymbolInput(projectIndex, name);
|
|
32
32
|
if (resolution.status === "missing") {
|
|
33
|
-
return `Symbol "${name}" not found in the project index. Try
|
|
33
|
+
return `Symbol "${name}" not found in the project index. Try search_code_map first (grep over symbol signatures), then fall back to search (text/regex) or read_file (whole file) only if symbol-based retrieval can't find what you need.`;
|
|
34
34
|
}
|
|
35
35
|
if (resolution.status === "ambiguous") {
|
|
36
36
|
return formatAmbiguousSymbolMatches("read_symbol", name, resolution.matches);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { ToolRegistry, createReadFileTool, createWriteFileTool, createEditFileTool, createSearchTool, createListFilesTool, createRunCommandTool, } from "@sean.holung/minicode-sdk";
|
|
2
3
|
import { createFindReferencesTool } from "./find-references.js";
|
|
3
4
|
import { createGetDependenciesTool } from "./get-dependencies.js";
|
|
4
5
|
import { createReadSymbolTool } from "./read-symbol.js";
|
|
5
6
|
import { createFindPathTool } from "./find-path.js";
|
|
6
7
|
import { createSearchCodeMapTool } from "./search-code-map.js";
|
|
8
|
+
import { buildPostEditDiagnostic } from "./post-edit-diagnostics.js";
|
|
7
9
|
export { ToolRegistry };
|
|
8
10
|
async function reindexBestEffort(projectIndex, relPath, content) {
|
|
9
11
|
try {
|
|
@@ -16,12 +18,23 @@ async function reindexBestEffort(projectIndex, relPath, content) {
|
|
|
16
18
|
/**
|
|
17
19
|
* Create a ToolRegistry with the SDK's core tools plus indexer-specific tools
|
|
18
20
|
* when a ProjectIndex is available.
|
|
21
|
+
*
|
|
22
|
+
* Set `MINICODE_TOOL_PROFILE=file-search-only` (or pass it via the env to a
|
|
23
|
+
* benchmark run) to omit the five graph-aware tools (`read_symbol`,
|
|
24
|
+
* `find_references`, `get_dependencies`, `find_path`, `search_code_map`).
|
|
25
|
+
* The code map remains in the system prompt — only the interactive tools
|
|
26
|
+
* are gated. This is the right shape for an `all-tools` vs.
|
|
27
|
+
* `file-search-only` ablation per `benchmarks/STRATEGY.md`.
|
|
19
28
|
*/
|
|
20
29
|
export function createToolRegistry(config, projectIndex) {
|
|
21
30
|
const hooks = projectIndex
|
|
22
31
|
? {
|
|
23
32
|
afterWrite: (relPath, content) => reindexBestEffort(projectIndex, relPath, content),
|
|
24
|
-
afterEdit: (
|
|
33
|
+
afterEdit: async (absPath, content) => {
|
|
34
|
+
const relPath = path.relative(config.workspaceRoot, absPath);
|
|
35
|
+
await reindexBestEffort(projectIndex, relPath, content);
|
|
36
|
+
return await buildPostEditDiagnostic(absPath, config.workspaceRoot);
|
|
37
|
+
},
|
|
25
38
|
afterCommand: async () => {
|
|
26
39
|
await projectIndex.refreshFromWorkspace();
|
|
27
40
|
},
|
|
@@ -35,7 +48,9 @@ export function createToolRegistry(config, projectIndex) {
|
|
|
35
48
|
createListFilesTool(config),
|
|
36
49
|
createRunCommandTool(config, hooks ? { afterCommand: hooks.afterCommand } : undefined),
|
|
37
50
|
];
|
|
38
|
-
|
|
51
|
+
const includeGraphTools = projectIndex !== undefined &&
|
|
52
|
+
process.env.MINICODE_TOOL_PROFILE !== "file-search-only";
|
|
53
|
+
if (includeGraphTools && projectIndex) {
|
|
39
54
|
tools.splice(1, 0, createReadSymbolTool(config, projectIndex));
|
|
40
55
|
tools.splice(2, 0, createFindReferencesTool(projectIndex));
|
|
41
56
|
tools.splice(3, 0, createGetDependenciesTool(projectIndex));
|