@sean.holung/minicode 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +44 -3
  2. package/dist/src/cli/args.js +65 -0
  3. package/dist/src/index.js +109 -26
  4. package/dist/src/session/session-store.js +82 -0
  5. package/dist/src/tools/find-references.js +1 -1
  6. package/dist/src/tools/get-dependencies.js +1 -1
  7. package/dist/src/tools/read-symbol.js +1 -2
  8. package/dist/src/tools/registry.js +26 -61
  9. package/dist/src/tools/search-code-map.js +1 -1
  10. package/dist/src/ui/cli-ink.js +91 -19
  11. package/dist/tests/agent.test.js +2 -3
  12. package/dist/tests/cli-args.test.js +73 -0
  13. package/dist/tests/cli-oneshot.integration.test.js +26 -0
  14. package/dist/tests/dependency-graph.test.js +12 -12
  15. package/dist/tests/file-tools.test.js +2 -3
  16. package/dist/tests/find-references.test.js +6 -6
  17. package/dist/tests/guardrails.test.js +1 -1
  18. package/dist/tests/indexer.test.js +9 -9
  19. package/dist/tests/model-client-openai.test.js +1 -1
  20. package/dist/tests/read-symbol.test.js +16 -17
  21. package/dist/tests/search-code-map.test.js +2 -2
  22. package/dist/tests/session-store.test.js +115 -0
  23. package/dist/tests/session.test.js +1 -1
  24. package/dist/tests/system-prompt.test.js +1 -1
  25. package/dist/tests/tool-registry.test.js +1 -1
  26. package/package.json +7 -2
  27. package/dist/src/agent/agent.js +0 -209
  28. package/dist/src/agent/types.js +0 -1
  29. package/dist/src/model/client.js +0 -374
  30. package/dist/src/prompt/system-prompt.js +0 -91
  31. package/dist/src/safety/guardrails.js +0 -55
  32. package/dist/src/session/session.js +0 -95
  33. package/dist/src/tools/edit-file.js +0 -73
  34. package/dist/src/tools/helpers.js +0 -42
  35. package/dist/src/tools/list-files.js +0 -63
  36. package/dist/src/tools/read-file.js +0 -79
  37. package/dist/src/tools/run-command.js +0 -92
  38. package/dist/src/tools/search.js +0 -153
  39. package/dist/src/tools/write-file.js +0 -44
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A lightweight CLI coding agent optimized for **local models** by providing AST-based intelligent context for smaller models running on consumer hardware.
4
4
 
5
+ > minicode gives local models a dependency-aware map of your codebase, so agents read less, reason better, and ship changes faster.
6
+
5
7
  Read operations dominate token usage in typical agent sessions; minicode addresses this by optimizing for **specific languages** — indexing your project at startup with language plugins (TypeScript/JavaScript built-in) and injecting a compact **code map** (signatures only) into the system prompt, plus symbol-level tools (`read_symbol`, `find_references`, `get_dependencies`) so the model reads only what it needs instead of entire files. This keeps prompts lean enough for smaller models in the 20B range, with faster inference and better attention over the relevant code.
6
8
 
7
9
  ## Quick Start (LM Studio)
@@ -21,7 +23,7 @@ OPENAI_BASE_URL=http://localhost:1234/v1
21
23
  OPENAI_API_KEY=
22
24
  MAX_STEPS=50
23
25
  MAX_TOKENS=4096
24
- MAX_CONTEXT_TOKENS=120000
26
+ MAX_CONTEXT_TOKENS=60000
25
27
  WORKSPACE_ROOT=.
26
28
  COMMAND_TIMEOUT_MS=30000
27
29
  MAX_FILE_SIZE_BYTES=1000000
@@ -46,6 +48,20 @@ or you can also pass it an intial prompt from the start:
46
48
  minicode "Add error handling to src/api.ts"
47
49
  ```
48
50
 
51
+ Run a single task and exit (useful for scripts/CI/orchestration):
52
+
53
+ ```bash
54
+ minicode --oneshot "Find TODOs and summarize action items"
55
+ # short flag
56
+ minicode -1 "Refactor parseArgs and run tests"
57
+
58
+ # JSON output (for pipeline parsing)
59
+ minicode --oneshot --json "Summarize recent changes"
60
+
61
+ # Write final output to a file (suppresses terminal response output)
62
+ minicode --oneshot --out result.txt "Generate release notes"
63
+ ```
64
+
49
65
  **Requirements:** Node.js 22+, LM Studio (or any OpenAI-compatible local server), `rg` in PATH (recommended). Set `MODEL` to match the model name in LM Studio.
50
66
 
51
67
  ### Install from source
@@ -81,6 +97,8 @@ For a deep technical walkthrough of AST parsing, dependency graph construction,
81
97
 
82
98
  For agent-loop internals (session lifecycle, tool execution, streaming, loop detection, and model client behavior), see [docs/AGENT_RUNTIME.md](docs/AGENT_RUNTIME.md).
83
99
 
100
+ For the proposed reusable package architecture and public interfaces for a standalone runtime SDK, see [docs/SDK_SPEC.md](docs/SDK_SPEC.md).
101
+
84
102
  minicode reduces token usage by indexing your project and providing targeted tools:
85
103
 
86
104
  - **Code map** — A compact project skeleton (signatures only) is injected into the system prompt so the model can orient itself without reading full files.
@@ -110,6 +128,17 @@ The graph powers:
110
128
  - **`find_references`** — Returns symbols that call or reference a given symbol.
111
129
  - **`read_symbol`** — Shows "Used by", "Calls", and "Referenced Types" derived from the graph.
112
130
 
131
+ ### Why this differs from a tree-sitter-first approach
132
+
133
+ Tree-sitter-focused agents are excellent for fast, generic syntax parsing across many languages. minicode takes a different path for TypeScript/JavaScript by using the TypeScript compiler AST to build a project symbol graph and drive graph-aware tools.
134
+
135
+ Advantages of this approach in minicode:
136
+
137
+ - **Dependency-aware navigation** — tools can follow call/type/inheritance edges (`calls`, `references`, `extends`, `implements`) instead of relying on text-only search.
138
+ - **Higher-signal context under tight budgets** — code-map ranking prioritizes exported and highly referenced symbols so key APIs survive truncation.
139
+ - **Targeted reads for local models** — symbol-level tools (`read_symbol`, `find_references`, `get_dependencies`) reduce unnecessary file reads and improve attention on relevant code.
140
+ - **Fast iterative indexing** — syntax-only AST parsing (without full type-checking) keeps startup and reindexing lightweight while preserving structural code intelligence.
141
+
113
142
  ## Plugin System
114
143
 
115
144
  ### Supported Languages
@@ -228,6 +257,20 @@ npm run dev -- --verbose "Fix the bug"
228
257
  npm run dev -- -v
229
258
  ```
230
259
 
260
+ One-shot mode in development:
261
+
262
+ ```bash
263
+ npm run dev -- --oneshot "Fix lint errors and explain changes"
264
+ npm run dev -- --oneshot --json "Summarize TODOs"
265
+ npm run dev -- --oneshot --out result.txt "Draft changelog"
266
+ ```
267
+
268
+ ### Exit codes
269
+
270
+ - `0`: Success
271
+ - `1`: Runtime failure
272
+ - `2`: CLI usage/validation error (for example, `--oneshot` without a prompt)
273
+
231
274
  ## Scripts
232
275
 
233
276
  - `npm run dev` - start the CLI in TypeScript mode
@@ -237,5 +280,3 @@ npm run dev -- -v
237
280
  - `npm run lint` - run ESLint on TypeScript source and tests
238
281
  - `npm test` - run Node test suite
239
282
 
240
-
241
-
@@ -0,0 +1,65 @@
1
+ export class CliUsageError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "CliUsageError";
5
+ }
6
+ }
7
+ export function parseCliArgs(argv) {
8
+ const args = argv.slice(2);
9
+ let verbose = false;
10
+ let oneshot = false;
11
+ let json = false;
12
+ let outFile;
13
+ const taskParts = [];
14
+ for (let i = 0; i < args.length; i += 1) {
15
+ const arg = args[i];
16
+ if (arg === undefined) {
17
+ continue;
18
+ }
19
+ if (arg === "--verbose" || arg === "-v") {
20
+ verbose = true;
21
+ continue;
22
+ }
23
+ if (arg === "--oneshot" || arg === "-1") {
24
+ oneshot = true;
25
+ continue;
26
+ }
27
+ if (arg === "--json") {
28
+ json = true;
29
+ continue;
30
+ }
31
+ if (arg === "--out") {
32
+ const value = args[i + 1];
33
+ if (!value || value.startsWith("-")) {
34
+ throw new CliUsageError("--out requires a file path. Example: --out result.txt");
35
+ }
36
+ outFile = value;
37
+ i += 1;
38
+ continue;
39
+ }
40
+ if (arg.startsWith("--out=")) {
41
+ const value = arg.slice("--out=".length).trim();
42
+ if (value.length === 0) {
43
+ throw new CliUsageError("--out requires a non-empty file path. Example: --out=result.txt");
44
+ }
45
+ outFile = value;
46
+ continue;
47
+ }
48
+ taskParts.push(arg);
49
+ }
50
+ return {
51
+ verbose,
52
+ oneshot,
53
+ json,
54
+ ...(outFile ? { outFile } : {}),
55
+ task: taskParts.join(" ").trim(),
56
+ };
57
+ }
58
+ export function validateCliArgs(args) {
59
+ if (args.oneshot && args.task.length === 0) {
60
+ throw new CliUsageError("--oneshot requires a task prompt. Example: minicode --oneshot \"Fix lint errors\"");
61
+ }
62
+ if (!args.oneshot && (args.json || args.outFile)) {
63
+ throw new CliUsageError("--json and --out are only supported with --oneshot.");
64
+ }
65
+ }
package/dist/src/index.js CHANGED
@@ -1,24 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import process from "node:process";
3
+ import { writeFile } from "node:fs/promises";
3
4
  import { createInterface } from "node:readline/promises";
4
- import { CodingAgent } from "./agent/agent.js";
5
+ import { CodingAgent, createModelClient } from "@minicode/agent-sdk";
5
6
  import { formatConfigForDisplay, loadAgentConfig } from "./agent/config.js";
7
+ import { listSessions, loadSession, loadSessionByLabel, saveSession, } from "./session/session-store.js";
6
8
  import { computeFileHashes, getWorkspaceCacheDir, loadIndex, saveIndex, } from "./indexer/cache.js";
7
9
  import { buildProjectIndex } from "./indexer/project-index.js";
8
- import { createModelClient } from "./model/client.js";
9
- import { ToolRegistry } from "./tools/registry.js";
10
- function parseArgs(argv) {
11
- const args = argv.slice(2);
12
- const verbose = args.includes("--verbose") || args.includes("-v");
13
- const filtered = args.filter((a) => a !== "--verbose" && a !== "-v");
14
- const task = filtered.join(" ").trim();
15
- return { verbose, task };
16
- }
10
+ import { createToolRegistry } from "./tools/registry.js";
11
+ import { CliUsageError, parseCliArgs, validateCliArgs, } from "./cli/args.js";
12
+ const EXIT_CODE_SUCCESS = 0;
13
+ const EXIT_CODE_RUNTIME_ERROR = 1;
14
+ const EXIT_CODE_USAGE_ERROR = 2;
17
15
  function printBanner() {
18
16
  console.log("minicode");
19
17
  console.log('Type your request, or "/exit" to quit.');
20
18
  }
21
- async function runInteractive(verbose, initialTask) {
19
+ async function createAgentRuntime(verbose, onProgress) {
22
20
  const config = await loadAgentConfig();
23
21
  const modelClient = createModelClient(config);
24
22
  let projectIndex;
@@ -37,15 +35,26 @@ async function runInteractive(verbose, initialTask) {
37
35
  catch {
38
36
  projectIndex = undefined;
39
37
  }
40
- const toolRegistry = ToolRegistry.createDefault(config, projectIndex);
41
- const agent = new CodingAgent({
42
- config,
43
- modelClient,
44
- toolRegistry,
45
- verbose,
46
- ...(projectIndex !== undefined ? { projectIndex } : {}),
47
- onProgress: (msg) => console.error(` ${msg}`),
48
- });
38
+ const toolRegistry = createToolRegistry(config, projectIndex);
39
+ function buildAgent(session) {
40
+ return new CodingAgent({
41
+ config,
42
+ modelClient,
43
+ toolRegistry,
44
+ verbose,
45
+ ...(session ? { session } : {}),
46
+ ...(projectIndex !== undefined
47
+ ? { getCodeMap: () => projectIndex.getCodeMap() }
48
+ : {}),
49
+ ...(onProgress ? { onProgress } : {}),
50
+ });
51
+ }
52
+ return { agent: buildAgent(), config, toolRegistry, projectIndex, buildAgent };
53
+ }
54
+ async function runInteractive(verbose, initialTask) {
55
+ const runtime = await createAgentRuntime(verbose, (msg) => console.error(` ${msg}`));
56
+ let { agent } = runtime;
57
+ const { config, buildAgent } = runtime;
49
58
  printBanner();
50
59
  console.log(`Workspace: ${config.workspaceRoot}`);
51
60
  console.log(`Provider: ${config.modelProvider}`);
@@ -64,7 +73,7 @@ async function runInteractive(verbose, initialTask) {
64
73
  turnAbortController.abort();
65
74
  }
66
75
  else if (shuttingDown) {
67
- process.exit(1);
76
+ process.exit(EXIT_CODE_RUNTIME_ERROR);
68
77
  }
69
78
  else {
70
79
  shuttingDown = true;
@@ -89,7 +98,7 @@ async function runInteractive(verbose, initialTask) {
89
98
  break;
90
99
  }
91
100
  if (trimmed === "/help") {
92
- console.log('Commands: "/help", "/config", "/exit"');
101
+ console.log('Commands: "/help", "/config", "/save [label]", "/load [label]", "/sessions", "/exit"');
93
102
  console.log("Start with --verbose or -v to log prompts, responses, and tool calls.");
94
103
  continue;
95
104
  }
@@ -97,6 +106,57 @@ async function runInteractive(verbose, initialTask) {
97
106
  console.log("\n" + formatConfigForDisplay(config) + "\n");
98
107
  continue;
99
108
  }
109
+ if (trimmed === "/save" || trimmed.startsWith("/save ")) {
110
+ const label = trimmed.slice("/save".length).trim() || undefined;
111
+ try {
112
+ const meta = await saveSession(agent.getSession(), label);
113
+ console.log(`Session saved as "${meta.label}" (${meta.messageCount} messages)`);
114
+ }
115
+ catch (error) {
116
+ const msg = error instanceof Error ? error.message : "Unknown error";
117
+ console.error(`Failed to save session: ${msg}`);
118
+ }
119
+ continue;
120
+ }
121
+ if (trimmed === "/sessions") {
122
+ const sessions = await listSessions();
123
+ if (sessions.length === 0) {
124
+ console.log("No saved sessions found.");
125
+ }
126
+ else {
127
+ console.log("Saved sessions:");
128
+ for (const s of sessions) {
129
+ console.log(` ${s.label} (${s.messageCount} msgs, saved ${s.savedAt})`);
130
+ }
131
+ }
132
+ continue;
133
+ }
134
+ if (trimmed === "/load" || trimmed.startsWith("/load ")) {
135
+ const arg = trimmed.slice("/load".length).trim();
136
+ if (arg.length === 0) {
137
+ const sessions = await listSessions();
138
+ if (sessions.length === 0) {
139
+ console.log("No saved sessions found.");
140
+ }
141
+ else {
142
+ console.log("Saved sessions:");
143
+ for (const s of sessions) {
144
+ console.log(` ${s.label} (${s.messageCount} msgs, saved ${s.savedAt})`);
145
+ }
146
+ console.log('\nUse "/load <label>" to restore a session.');
147
+ }
148
+ continue;
149
+ }
150
+ const result = (await loadSessionByLabel(arg)) ??
151
+ (await loadSession(arg));
152
+ if (!result) {
153
+ console.log(`No session found matching "${arg}".`);
154
+ continue;
155
+ }
156
+ agent = buildAgent(result.session);
157
+ console.log(`Session "${result.label}" restored (${result.session.getMessages().length} messages).`);
158
+ continue;
159
+ }
100
160
  turnAbortController = new AbortController();
101
161
  try {
102
162
  const { text, usage } = await agent.runTurn(trimmed, {
@@ -121,18 +181,41 @@ async function runInteractive(verbose, initialTask) {
121
181
  }
122
182
  rl.close();
123
183
  }
184
+ async function runOneshot(params) {
185
+ const { agent } = await createAgentRuntime(params.verbose);
186
+ const { text, usage } = await agent.runTurn(params.task);
187
+ const payload = params.json
188
+ ? JSON.stringify({ text, usage: usage ?? null }, null, 2)
189
+ : text;
190
+ if (params.outFile) {
191
+ await writeFile(params.outFile, payload + "\n", "utf8");
192
+ return;
193
+ }
194
+ console.log(payload);
195
+ }
124
196
  async function main() {
125
- const { verbose, task } = parseArgs(process.argv);
197
+ const cliArgs = parseCliArgs(process.argv);
198
+ validateCliArgs(cliArgs);
199
+ if (cliArgs.oneshot) {
200
+ await runOneshot(cliArgs);
201
+ process.exitCode = EXIT_CODE_SUCCESS;
202
+ return;
203
+ }
126
204
  const uiMode = process.env.CLI_UI_MODE ?? "ink";
127
205
  if (uiMode !== "legacy" && process.stdin.isTTY) {
128
206
  const { runInkCli } = await import("./ui/cli-ink.js");
129
- await runInkCli(verbose, task.length > 0 ? task : undefined);
207
+ await runInkCli(cliArgs.verbose, cliArgs.task.length > 0 ? cliArgs.task : undefined);
208
+ process.exitCode = EXIT_CODE_SUCCESS;
130
209
  return;
131
210
  }
132
- await runInteractive(verbose, task.length > 0 ? task : undefined);
211
+ await runInteractive(cliArgs.verbose, cliArgs.task.length > 0 ? cliArgs.task : undefined);
212
+ process.exitCode = EXIT_CODE_SUCCESS;
133
213
  }
134
214
  main().catch((error) => {
135
215
  const message = error instanceof Error ? error.message : String(error);
136
216
  console.error(`Fatal error: ${message}`);
137
- process.exit(1);
217
+ if (error instanceof CliUsageError) {
218
+ process.exit(EXIT_CODE_USAGE_ERROR);
219
+ }
220
+ process.exit(EXIT_CODE_RUNTIME_ERROR);
138
221
  });
@@ -0,0 +1,82 @@
1
+ import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { Session } from "@minicode/agent-sdk";
5
+ let sessionsDir = path.join(os.homedir(), ".minicode", "sessions");
6
+ /** Override sessions directory (for testing). */
7
+ export function setSessionsDir(dir) {
8
+ sessionsDir = dir;
9
+ }
10
+ export async function saveSession(session, label) {
11
+ await mkdir(sessionsDir, { recursive: true });
12
+ const savedAt = new Date().toISOString();
13
+ const snapshot = session.toJSON();
14
+ const resolvedLabel = label && label.trim().length > 0
15
+ ? label.trim()
16
+ : new Date().toLocaleString();
17
+ const data = {
18
+ label: resolvedLabel,
19
+ savedAt,
20
+ session: snapshot,
21
+ };
22
+ const filePath = path.join(sessionsDir, `${snapshot.id}.json`);
23
+ await writeFile(filePath, JSON.stringify(data, null, 2), "utf8");
24
+ return {
25
+ id: snapshot.id,
26
+ label: resolvedLabel,
27
+ createdAt: snapshot.createdAt,
28
+ savedAt,
29
+ messageCount: snapshot.messages.length,
30
+ };
31
+ }
32
+ export async function listSessions() {
33
+ let files;
34
+ try {
35
+ files = await readdir(sessionsDir);
36
+ }
37
+ catch {
38
+ return [];
39
+ }
40
+ const results = [];
41
+ for (const file of files) {
42
+ if (!file.endsWith(".json"))
43
+ continue;
44
+ try {
45
+ const raw = await readFile(path.join(sessionsDir, file), "utf8");
46
+ const data = JSON.parse(raw);
47
+ results.push({
48
+ id: data.session.id,
49
+ label: data.label,
50
+ createdAt: data.session.createdAt,
51
+ savedAt: data.savedAt,
52
+ messageCount: data.session.messages.length,
53
+ });
54
+ }
55
+ catch {
56
+ // skip corrupt files
57
+ }
58
+ }
59
+ results.sort((a, b) => b.savedAt.localeCompare(a.savedAt));
60
+ return results;
61
+ }
62
+ export async function loadSession(sessionId) {
63
+ const filePath = path.join(sessionsDir, `${sessionId}.json`);
64
+ try {
65
+ const raw = await readFile(filePath, "utf8");
66
+ const data = JSON.parse(raw);
67
+ return {
68
+ session: Session.fromJSON(data.session),
69
+ label: data.label,
70
+ };
71
+ }
72
+ catch {
73
+ return undefined;
74
+ }
75
+ }
76
+ export async function loadSessionByLabel(label) {
77
+ const sessions = await listSessions();
78
+ const match = sessions.find((s) => s.label.toLowerCase() === label.toLowerCase());
79
+ if (!match)
80
+ return undefined;
81
+ return loadSession(match.id);
82
+ }
@@ -1,4 +1,4 @@
1
- import { expectNonEmptyString, expectOptionalNumber, } from "./helpers.js";
1
+ import { expectNonEmptyString, expectOptionalNumber } from "@minicode/agent-sdk";
2
2
  const DEFAULT_LIMIT = 50;
3
3
  export function createFindReferencesTool(projectIndex) {
4
4
  return {
@@ -1,4 +1,4 @@
1
- import { expectNonEmptyString, expectOptionalNumber } from "./helpers.js";
1
+ import { expectNonEmptyString, expectOptionalNumber } from "@minicode/agent-sdk";
2
2
  export function createGetDependenciesTool(projectIndex) {
3
3
  return {
4
4
  name: "get_dependencies",
@@ -1,6 +1,5 @@
1
1
  import { readFile, stat } from "node:fs/promises";
2
- import { resolveWorkspacePath, validateFileReadSize, } from "../safety/guardrails.js";
3
- import { expectNonEmptyString, expectOptionalBoolean } from "./helpers.js";
2
+ import { resolveWorkspacePath, validateFileReadSize, expectNonEmptyString, expectOptionalBoolean, } from "@minicode/agent-sdk";
4
3
  const LEADING_CONTEXT_LINES = 3;
5
4
  export function createReadSymbolTool(config, projectIndex) {
6
5
  return {
@@ -1,68 +1,33 @@
1
- import { createEditFileTool } from "./edit-file.js";
1
+ import { ToolRegistry, createReadFileTool, createWriteFileTool, createEditFileTool, createSearchTool, createListFilesTool, createRunCommandTool, } from "@minicode/agent-sdk";
2
2
  import { createFindReferencesTool } from "./find-references.js";
3
3
  import { createGetDependenciesTool } from "./get-dependencies.js";
4
- import { createListFilesTool } from "./list-files.js";
5
- import { createReadFileTool } from "./read-file.js";
6
4
  import { createReadSymbolTool } from "./read-symbol.js";
7
- import { createRunCommandTool } from "./run-command.js";
8
5
  import { createSearchCodeMapTool } from "./search-code-map.js";
9
- import { createSearchTool } from "./search.js";
10
- import { createWriteFileTool } from "./write-file.js";
11
- function ensureInputObject(input) {
12
- if (typeof input !== "object" || input === null || Array.isArray(input)) {
13
- throw new Error("Tool input must be a JSON object.");
14
- }
15
- return input;
16
- }
17
- function toToolSchema(tool) {
18
- return {
19
- name: tool.name,
20
- description: tool.description,
21
- input_schema: tool.inputSchema,
22
- };
23
- }
24
- export class ToolRegistry {
25
- toolsByName = new Map();
26
- constructor(tools) {
27
- for (const tool of tools) {
28
- if (this.toolsByName.has(tool.name)) {
29
- throw new Error(`Duplicate tool registration for "${tool.name}".`);
30
- }
31
- this.toolsByName.set(tool.name, tool);
32
- }
33
- }
34
- static createDefault(config, projectIndex) {
35
- const tools = [
36
- createReadFileTool(config),
37
- createWriteFileTool(config, projectIndex),
38
- createEditFileTool(config, projectIndex),
39
- createSearchTool(config),
40
- createListFilesTool(config),
41
- createRunCommandTool(config),
42
- ];
43
- if (projectIndex) {
44
- tools.splice(1, 0, createReadSymbolTool(config, projectIndex));
45
- tools.splice(2, 0, createFindReferencesTool(projectIndex));
46
- tools.splice(3, 0, createGetDependenciesTool(projectIndex));
47
- tools.splice(4, 0, createSearchCodeMapTool(projectIndex));
48
- }
49
- return new ToolRegistry(tools);
50
- }
51
- getToolSchemas() {
52
- return [...this.toolsByName.values()].map(toToolSchema);
53
- }
54
- async execute(name, input) {
55
- const tool = this.toolsByName.get(name);
56
- if (!tool) {
57
- return `Tool error: Unknown tool "${name}".`;
58
- }
59
- try {
60
- const inputObject = ensureInputObject(input);
61
- return await tool.execute(inputObject);
62
- }
63
- catch (error) {
64
- const message = error instanceof Error ? error.message : "Unknown tool failure";
65
- return `Tool error (${name}): ${message}`;
6
+ export { ToolRegistry };
7
+ /**
8
+ * Create a ToolRegistry with the SDK's core tools plus indexer-specific tools
9
+ * when a ProjectIndex is available.
10
+ */
11
+ export function createToolRegistry(config, projectIndex) {
12
+ const hooks = projectIndex
13
+ ? {
14
+ afterWrite: (relPath, content) => projectIndex.reindexFile(relPath, content),
15
+ afterEdit: (relPath, content) => projectIndex.reindexFile(relPath, content),
66
16
  }
17
+ : undefined;
18
+ const tools = [
19
+ createReadFileTool(config),
20
+ createWriteFileTool(config, hooks ? { afterWrite: hooks.afterWrite } : undefined),
21
+ createEditFileTool(config, hooks ? { afterEdit: hooks.afterEdit } : undefined),
22
+ createSearchTool(config),
23
+ createListFilesTool(config),
24
+ createRunCommandTool(config),
25
+ ];
26
+ if (projectIndex) {
27
+ tools.splice(1, 0, createReadSymbolTool(config, projectIndex));
28
+ tools.splice(2, 0, createFindReferencesTool(projectIndex));
29
+ tools.splice(3, 0, createGetDependenciesTool(projectIndex));
30
+ tools.splice(4, 0, createSearchCodeMapTool(projectIndex));
67
31
  }
32
+ return new ToolRegistry(tools);
68
33
  }
@@ -1,4 +1,4 @@
1
- import { expectNonEmptyString, expectOptionalNumber, } from "./helpers.js";
1
+ import { expectNonEmptyString, expectOptionalNumber } from "@minicode/agent-sdk";
2
2
  const DEFAULT_LIMIT = 30;
3
3
  function matchesPattern(text, pattern) {
4
4
  const lowerText = text.toLowerCase();