jinzd-ai-cli 0.4.75 → 0.4.76
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 +6 -1
- package/dist/{chunk-5P4QTZBI.js → chunk-2Q77FT3F.js} +12 -3
- package/dist/{chunk-E6RP5DBU.js → chunk-34NJTPWZ.js} +12 -3
- package/dist/chunk-6VRJGH25.js +115 -0
- package/dist/chunk-BJAT4GNC.js +113 -0
- package/dist/{chunk-ASNDBI5R.js → chunk-H4DQNZZ6.js} +1 -1
- package/dist/{chunk-3BHGEPIT.js → chunk-K3JJX2Z5.js} +1 -1
- package/dist/{chunk-MPIUYP6Q.js → chunk-KR4FTJWB.js} +234 -4
- package/dist/chunk-NHNWUBXB.js +431 -0
- package/dist/chunk-RFQVUMDB.js +430 -0
- package/dist/{chunk-C4MGON2N.js → chunk-XTH7S3AM.js} +2 -2
- package/dist/{hub-W3BF22UV.js → hub-YPNEYO3Z.js} +1 -1
- package/dist/index.js +93 -14
- package/dist/{run-tests-LEYTZHPU.js → run-tests-2I5S24IH.js} +1 -1
- package/dist/{run-tests-V2JJADIU.js → run-tests-MKKCDUUV.js} +2 -2
- package/dist/{server-2XO72FRP.js → server-6MPBAH4K.js} +52 -11
- package/dist/{task-orchestrator-277NWVSE.js → task-orchestrator-I5HPXTJY.js} +5 -3
- package/dist/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/wasm/tree-sitter-python.wasm +0 -0
- package/dist/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/wasm/web-tree-sitter.wasm +0 -0
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
- **Anthropic Batches API** *(v0.4.73+)* — `aicli batch submit/list/status/results/cancel` for 50%-off, 24-hour async processing — ideal for offline analysis and bulk evals
|
|
30
30
|
- **Web UI Session Replay** *(v0.4.71+)* — 🎬 button on every saved session opens a timeline replay: every message, tool call, reasoning, and cache-aware token usage at a glance
|
|
31
31
|
- **Conversation Branching** *(v0.4.74+)* — `/branch list/new/switch/delete/rename` inside the REPL, plus a 🌿 "fork here" button on every replay step — explore alternate directions without losing the original thread
|
|
32
|
+
- **Symbol Index** *(v0.4.76+)* — persistent tree-sitter index for TS/JS/TSX/Python powers three new AI tools: `find_symbol`, `get_outline`, `find_references`. Orders of magnitude faster than grep for definition lookups; background refresh on REPL startup, `/index status|rebuild|clear` to manage
|
|
32
33
|
- **Streaming Tool Use** — Real-time streaming of AI reasoning and tool calls as they happen
|
|
33
34
|
- **Sub-Agents** — Delegate complex subtasks to isolated child agents with independent tool loops
|
|
34
35
|
- **Extended Thinking** — Claude deep reasoning mode with `/think` toggle
|
|
@@ -145,7 +146,7 @@ aicli
|
|
|
145
146
|
|
|
146
147
|
## Built-in Tools (Agentic)
|
|
147
148
|
|
|
148
|
-
AI autonomously invokes these
|
|
149
|
+
AI autonomously invokes these 27 tools during conversations:
|
|
149
150
|
|
|
150
151
|
| Tool | Safety | Description |
|
|
151
152
|
|------|--------|-------------|
|
|
@@ -173,6 +174,9 @@ AI autonomously invokes these 24 tools during conversations:
|
|
|
173
174
|
| `git_log` | safe | Show commit history (oneline/full, filter by file/author) |
|
|
174
175
|
| `git_commit` | write | Create a git commit (stage files, message) |
|
|
175
176
|
| `notebook_edit` | write | Edit Jupyter notebook cells (add/edit/delete/move) |
|
|
177
|
+
| `find_symbol` | safe | Locate symbol definitions via persistent tree-sitter index (TS/JS/TSX/Python) |
|
|
178
|
+
| `get_outline` | safe | Enumerate all top-level declarations in one source file |
|
|
179
|
+
| `find_references` | safe | Search indexed files for references to a symbol name |
|
|
176
180
|
|
|
177
181
|
**Safety levels**: `safe` = auto-execute, `write` = diff preview + confirmation, `destructive` = prominent warning + confirmation.
|
|
178
182
|
|
|
@@ -195,6 +199,7 @@ AI autonomously invokes these 24 tools during conversations:
|
|
|
195
199
|
| `/checkpoint` | Save/restore conversation checkpoints |
|
|
196
200
|
| `/fork` | Fork the current session into a new session file |
|
|
197
201
|
| `/branch` | Create/switch/delete branches *within* the current session (B2) |
|
|
202
|
+
| `/index` | Manage tree-sitter symbol index (status/rebuild/clear) — powers `find_symbol` / `get_outline` / `find_references` (C1) |
|
|
198
203
|
| `/search <keyword>` | Full-text search across all sessions |
|
|
199
204
|
| `/skill` | Manage agent skill packs |
|
|
200
205
|
| `/mcp` | View MCP server status and tools |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/core/constants.ts
|
|
4
|
-
var VERSION = "0.4.
|
|
4
|
+
var VERSION = "0.4.76";
|
|
5
5
|
var APP_NAME = "ai-cli";
|
|
6
6
|
var CONFIG_DIR_NAME = ".aicli";
|
|
7
7
|
var CONFIG_FILE_NAME = "config.json";
|
|
@@ -30,8 +30,14 @@ var PLAN_MODE_READONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
|
30
30
|
"google_search",
|
|
31
31
|
"ask_user",
|
|
32
32
|
// 允许:可向用户澄清需求
|
|
33
|
-
"write_todos"
|
|
33
|
+
"write_todos",
|
|
34
34
|
// 允许:可输出任务列表作为实施计划
|
|
35
|
+
"find_symbol",
|
|
36
|
+
// C1 symbol index (read-only)
|
|
37
|
+
"get_outline",
|
|
38
|
+
// C1 symbol index (read-only)
|
|
39
|
+
"find_references"
|
|
40
|
+
// C1 symbol index (read-only)
|
|
35
41
|
]);
|
|
36
42
|
var PLAN_MODE_SYSTEM_ADDON = `# \u{1F50D} Plan Mode \u2014 Read-Only Planning Mode
|
|
37
43
|
|
|
@@ -71,7 +77,10 @@ var SUBAGENT_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
|
|
|
71
77
|
"web_fetch",
|
|
72
78
|
"google_search",
|
|
73
79
|
"write_todos",
|
|
74
|
-
"run_tests"
|
|
80
|
+
"run_tests",
|
|
81
|
+
"find_symbol",
|
|
82
|
+
"get_outline",
|
|
83
|
+
"find_references"
|
|
75
84
|
]);
|
|
76
85
|
var CONTEXT_PRESSURE_THRESHOLD = 0.8;
|
|
77
86
|
var TEST_TIMEOUT = 3e5;
|
|
@@ -6,7 +6,7 @@ import { platform } from "os";
|
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
|
|
8
8
|
// src/core/constants.ts
|
|
9
|
-
var VERSION = "0.4.
|
|
9
|
+
var VERSION = "0.4.76";
|
|
10
10
|
var APP_NAME = "ai-cli";
|
|
11
11
|
var CONFIG_DIR_NAME = ".aicli";
|
|
12
12
|
var CONFIG_FILE_NAME = "config.json";
|
|
@@ -35,8 +35,14 @@ var PLAN_MODE_READONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
|
35
35
|
"google_search",
|
|
36
36
|
"ask_user",
|
|
37
37
|
// 允许:可向用户澄清需求
|
|
38
|
-
"write_todos"
|
|
38
|
+
"write_todos",
|
|
39
39
|
// 允许:可输出任务列表作为实施计划
|
|
40
|
+
"find_symbol",
|
|
41
|
+
// C1 symbol index (read-only)
|
|
42
|
+
"get_outline",
|
|
43
|
+
// C1 symbol index (read-only)
|
|
44
|
+
"find_references"
|
|
45
|
+
// C1 symbol index (read-only)
|
|
40
46
|
]);
|
|
41
47
|
var PLAN_MODE_SYSTEM_ADDON = `# \u{1F50D} Plan Mode \u2014 Read-Only Planning Mode
|
|
42
48
|
|
|
@@ -76,7 +82,10 @@ var SUBAGENT_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
|
|
|
76
82
|
"web_fetch",
|
|
77
83
|
"google_search",
|
|
78
84
|
"write_todos",
|
|
79
|
-
"run_tests"
|
|
85
|
+
"run_tests",
|
|
86
|
+
"find_symbol",
|
|
87
|
+
"get_outline",
|
|
88
|
+
"find_references"
|
|
80
89
|
]);
|
|
81
90
|
var TEST_TIMEOUT = 3e5;
|
|
82
91
|
var AGENTIC_BEHAVIOR_GUIDELINE = `# Important Behavioral Guidelines
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/symbols/store.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import os from "os";
|
|
7
|
+
import crypto from "crypto";
|
|
8
|
+
|
|
9
|
+
// src/symbols/types.ts
|
|
10
|
+
var SYMBOL_INDEX_VERSION = 1;
|
|
11
|
+
function detectLanguage(ext) {
|
|
12
|
+
switch (ext.toLowerCase()) {
|
|
13
|
+
case "ts":
|
|
14
|
+
case "mts":
|
|
15
|
+
case "cts":
|
|
16
|
+
return "typescript";
|
|
17
|
+
case "tsx":
|
|
18
|
+
return "tsx";
|
|
19
|
+
case "js":
|
|
20
|
+
case "mjs":
|
|
21
|
+
case "cjs":
|
|
22
|
+
case "jsx":
|
|
23
|
+
return "javascript";
|
|
24
|
+
case "py":
|
|
25
|
+
case "pyi":
|
|
26
|
+
return "python";
|
|
27
|
+
default:
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/symbols/store.ts
|
|
33
|
+
var INDEX_DIR_NAME = "index";
|
|
34
|
+
function indexDir() {
|
|
35
|
+
return path.join(os.homedir(), ".aicli", INDEX_DIR_NAME);
|
|
36
|
+
}
|
|
37
|
+
function projectHash(root) {
|
|
38
|
+
const normalized = path.resolve(root).toLowerCase();
|
|
39
|
+
return crypto.createHash("sha1").update(normalized).digest("hex").slice(0, 16);
|
|
40
|
+
}
|
|
41
|
+
function indexPath(root) {
|
|
42
|
+
return path.join(indexDir(), `${projectHash(root)}.json`);
|
|
43
|
+
}
|
|
44
|
+
function loadIndex(root) {
|
|
45
|
+
const p = indexPath(root);
|
|
46
|
+
try {
|
|
47
|
+
if (!fs.existsSync(p)) return null;
|
|
48
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
if (parsed.version !== SYMBOL_INDEX_VERSION) return null;
|
|
51
|
+
if (parsed.root && path.resolve(parsed.root) !== path.resolve(root)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return parsed;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function saveIndex(index) {
|
|
60
|
+
const dir = indexDir();
|
|
61
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
62
|
+
const target = indexPath(index.root);
|
|
63
|
+
const tmp = `${target}.tmp`;
|
|
64
|
+
fs.writeFileSync(tmp, JSON.stringify(index), "utf-8");
|
|
65
|
+
fs.renameSync(tmp, target);
|
|
66
|
+
}
|
|
67
|
+
function clearIndex(root) {
|
|
68
|
+
const p = indexPath(root);
|
|
69
|
+
try {
|
|
70
|
+
if (fs.existsSync(p)) fs.unlinkSync(p);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function emptyIndex(root) {
|
|
75
|
+
return {
|
|
76
|
+
version: SYMBOL_INDEX_VERSION,
|
|
77
|
+
root: path.resolve(root),
|
|
78
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
79
|
+
fileCount: 0,
|
|
80
|
+
symbolCount: 0,
|
|
81
|
+
files: {},
|
|
82
|
+
symbols: []
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function upsertFileSymbols(index, file, mtime, symbols) {
|
|
86
|
+
const absFile = path.resolve(file);
|
|
87
|
+
const had = absFile in index.files;
|
|
88
|
+
index.symbols = index.symbols.filter((s) => s.location.file !== absFile);
|
|
89
|
+
index.symbols.push(...symbols);
|
|
90
|
+
index.files[absFile] = mtime;
|
|
91
|
+
index.symbolCount = index.symbols.length;
|
|
92
|
+
if (!had) index.fileCount = Object.keys(index.files).length;
|
|
93
|
+
index.generated = (/* @__PURE__ */ new Date()).toISOString();
|
|
94
|
+
return index;
|
|
95
|
+
}
|
|
96
|
+
function removeFile(index, file) {
|
|
97
|
+
const absFile = path.resolve(file);
|
|
98
|
+
if (!(absFile in index.files)) return index;
|
|
99
|
+
index.symbols = index.symbols.filter((s) => s.location.file !== absFile);
|
|
100
|
+
delete index.files[absFile];
|
|
101
|
+
index.symbolCount = index.symbols.length;
|
|
102
|
+
index.fileCount = Object.keys(index.files).length;
|
|
103
|
+
index.generated = (/* @__PURE__ */ new Date()).toISOString();
|
|
104
|
+
return index;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
detectLanguage,
|
|
109
|
+
loadIndex,
|
|
110
|
+
saveIndex,
|
|
111
|
+
clearIndex,
|
|
112
|
+
emptyIndex,
|
|
113
|
+
upsertFileSymbols,
|
|
114
|
+
removeFile
|
|
115
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/symbols/store.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import crypto from "crypto";
|
|
6
|
+
|
|
7
|
+
// src/symbols/types.ts
|
|
8
|
+
var SYMBOL_INDEX_VERSION = 1;
|
|
9
|
+
function detectLanguage(ext) {
|
|
10
|
+
switch (ext.toLowerCase()) {
|
|
11
|
+
case "ts":
|
|
12
|
+
case "mts":
|
|
13
|
+
case "cts":
|
|
14
|
+
return "typescript";
|
|
15
|
+
case "tsx":
|
|
16
|
+
return "tsx";
|
|
17
|
+
case "js":
|
|
18
|
+
case "mjs":
|
|
19
|
+
case "cjs":
|
|
20
|
+
case "jsx":
|
|
21
|
+
return "javascript";
|
|
22
|
+
case "py":
|
|
23
|
+
case "pyi":
|
|
24
|
+
return "python";
|
|
25
|
+
default:
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/symbols/store.ts
|
|
31
|
+
var INDEX_DIR_NAME = "index";
|
|
32
|
+
function indexDir() {
|
|
33
|
+
return path.join(os.homedir(), ".aicli", INDEX_DIR_NAME);
|
|
34
|
+
}
|
|
35
|
+
function projectHash(root) {
|
|
36
|
+
const normalized = path.resolve(root).toLowerCase();
|
|
37
|
+
return crypto.createHash("sha1").update(normalized).digest("hex").slice(0, 16);
|
|
38
|
+
}
|
|
39
|
+
function indexPath(root) {
|
|
40
|
+
return path.join(indexDir(), `${projectHash(root)}.json`);
|
|
41
|
+
}
|
|
42
|
+
function loadIndex(root) {
|
|
43
|
+
const p = indexPath(root);
|
|
44
|
+
try {
|
|
45
|
+
if (!fs.existsSync(p)) return null;
|
|
46
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
47
|
+
const parsed = JSON.parse(raw);
|
|
48
|
+
if (parsed.version !== SYMBOL_INDEX_VERSION) return null;
|
|
49
|
+
if (parsed.root && path.resolve(parsed.root) !== path.resolve(root)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return parsed;
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function saveIndex(index) {
|
|
58
|
+
const dir = indexDir();
|
|
59
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
60
|
+
const target = indexPath(index.root);
|
|
61
|
+
const tmp = `${target}.tmp`;
|
|
62
|
+
fs.writeFileSync(tmp, JSON.stringify(index), "utf-8");
|
|
63
|
+
fs.renameSync(tmp, target);
|
|
64
|
+
}
|
|
65
|
+
function clearIndex(root) {
|
|
66
|
+
const p = indexPath(root);
|
|
67
|
+
try {
|
|
68
|
+
if (fs.existsSync(p)) fs.unlinkSync(p);
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function emptyIndex(root) {
|
|
73
|
+
return {
|
|
74
|
+
version: SYMBOL_INDEX_VERSION,
|
|
75
|
+
root: path.resolve(root),
|
|
76
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
77
|
+
fileCount: 0,
|
|
78
|
+
symbolCount: 0,
|
|
79
|
+
files: {},
|
|
80
|
+
symbols: []
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function upsertFileSymbols(index, file, mtime, symbols) {
|
|
84
|
+
const absFile = path.resolve(file);
|
|
85
|
+
const had = absFile in index.files;
|
|
86
|
+
index.symbols = index.symbols.filter((s) => s.location.file !== absFile);
|
|
87
|
+
index.symbols.push(...symbols);
|
|
88
|
+
index.files[absFile] = mtime;
|
|
89
|
+
index.symbolCount = index.symbols.length;
|
|
90
|
+
if (!had) index.fileCount = Object.keys(index.files).length;
|
|
91
|
+
index.generated = (/* @__PURE__ */ new Date()).toISOString();
|
|
92
|
+
return index;
|
|
93
|
+
}
|
|
94
|
+
function removeFile(index, file) {
|
|
95
|
+
const absFile = path.resolve(file);
|
|
96
|
+
if (!(absFile in index.files)) return index;
|
|
97
|
+
index.symbols = index.symbols.filter((s) => s.location.file !== absFile);
|
|
98
|
+
delete index.files[absFile];
|
|
99
|
+
index.symbolCount = index.symbols.length;
|
|
100
|
+
index.fileCount = Object.keys(index.files).length;
|
|
101
|
+
index.generated = (/* @__PURE__ */ new Date()).toISOString();
|
|
102
|
+
return index;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
detectLanguage,
|
|
107
|
+
loadIndex,
|
|
108
|
+
saveIndex,
|
|
109
|
+
clearIndex,
|
|
110
|
+
emptyIndex,
|
|
111
|
+
upsertFileSymbols,
|
|
112
|
+
removeFile
|
|
113
|
+
};
|
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
import {
|
|
3
3
|
fileCheckpoints
|
|
4
4
|
} from "./chunk-4BKXL7SM.js";
|
|
5
|
+
import {
|
|
6
|
+
indexProject
|
|
7
|
+
} from "./chunk-NHNWUBXB.js";
|
|
8
|
+
import {
|
|
9
|
+
loadIndex
|
|
10
|
+
} from "./chunk-6VRJGH25.js";
|
|
5
11
|
import {
|
|
6
12
|
runTestsTool
|
|
7
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-K3JJX2Z5.js";
|
|
8
14
|
import {
|
|
9
15
|
EnvLoader,
|
|
10
16
|
NetworkError,
|
|
@@ -17,7 +23,7 @@ import {
|
|
|
17
23
|
SUBAGENT_ALLOWED_TOOLS,
|
|
18
24
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
19
25
|
SUBAGENT_MAX_ROUNDS_LIMIT
|
|
20
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-2Q77FT3F.js";
|
|
21
27
|
|
|
22
28
|
// src/tools/builtin/bash.ts
|
|
23
29
|
import { execSync } from "child_process";
|
|
@@ -1028,8 +1034,8 @@ function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "co
|
|
|
1028
1034
|
if (rule.when) {
|
|
1029
1035
|
if (rule.when.dangerLevel && rule.when.dangerLevel !== dangerLevel) continue;
|
|
1030
1036
|
if (rule.when.pathPattern) {
|
|
1031
|
-
const
|
|
1032
|
-
if (!
|
|
1037
|
+
const path3 = String(args["path"] ?? args["command"] ?? "");
|
|
1038
|
+
if (!path3.includes(rule.when.pathPattern)) continue;
|
|
1033
1039
|
}
|
|
1034
1040
|
}
|
|
1035
1041
|
return rule.action;
|
|
@@ -1729,6 +1735,13 @@ Important: For long content (over 500 lines or 3000 chars), you MUST split into
|
|
|
1729
1735
|
}
|
|
1730
1736
|
const lines = content.split("\n").length;
|
|
1731
1737
|
const mode = appendMode ? "appended" : "written";
|
|
1738
|
+
void (async () => {
|
|
1739
|
+
try {
|
|
1740
|
+
const { updateFile } = await import("./indexer-C7QYYHSZ.js");
|
|
1741
|
+
await updateFile(process.cwd(), filePath);
|
|
1742
|
+
} catch {
|
|
1743
|
+
}
|
|
1744
|
+
})();
|
|
1732
1745
|
return `File ${mode}: ${filePath} (${lines} lines, ${content.length} bytes)`;
|
|
1733
1746
|
}
|
|
1734
1747
|
};
|
|
@@ -4286,6 +4299,220 @@ var notebookEditTool = {
|
|
|
4286
4299
|
}
|
|
4287
4300
|
};
|
|
4288
4301
|
|
|
4302
|
+
// src/tools/builtin/symbol-tools.ts
|
|
4303
|
+
import path2 from "path";
|
|
4304
|
+
|
|
4305
|
+
// src/symbols/queries.ts
|
|
4306
|
+
import path from "path";
|
|
4307
|
+
import fs from "fs";
|
|
4308
|
+
function findSymbol(index, opts) {
|
|
4309
|
+
const name = opts.name.toLowerCase();
|
|
4310
|
+
const exact = opts.exact !== false;
|
|
4311
|
+
const kinds = opts.kind ? new Set(Array.isArray(opts.kind) ? opts.kind : [opts.kind]) : null;
|
|
4312
|
+
const limit = opts.limit ?? 50;
|
|
4313
|
+
const out = [];
|
|
4314
|
+
for (const s of index.symbols) {
|
|
4315
|
+
if (kinds && !kinds.has(s.kind)) continue;
|
|
4316
|
+
const lower = s.name.toLowerCase();
|
|
4317
|
+
if (exact ? lower === name : lower.includes(name)) {
|
|
4318
|
+
out.push(s);
|
|
4319
|
+
if (out.length >= limit) break;
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
return out;
|
|
4323
|
+
}
|
|
4324
|
+
function getOutline(index, file) {
|
|
4325
|
+
const abs = path.resolve(file);
|
|
4326
|
+
return index.symbols.filter((s) => s.location.file === abs).sort((a, b) => a.location.line - b.location.line).map((s) => ({
|
|
4327
|
+
name: s.name,
|
|
4328
|
+
kind: s.kind,
|
|
4329
|
+
line: s.location.line,
|
|
4330
|
+
signature: s.signature,
|
|
4331
|
+
container: s.container,
|
|
4332
|
+
exported: s.exported
|
|
4333
|
+
}));
|
|
4334
|
+
}
|
|
4335
|
+
function findReferences(index, name, opts = {}) {
|
|
4336
|
+
const maxFiles = opts.maxFiles ?? 2e3;
|
|
4337
|
+
const maxHits = opts.maxHits ?? 500;
|
|
4338
|
+
const re = new RegExp(`\\b${name.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\\b`);
|
|
4339
|
+
const hits = [];
|
|
4340
|
+
const files = Object.keys(index.files).slice(0, maxFiles);
|
|
4341
|
+
for (const file of files) {
|
|
4342
|
+
let content;
|
|
4343
|
+
try {
|
|
4344
|
+
content = fs.readFileSync(file, "utf-8");
|
|
4345
|
+
} catch {
|
|
4346
|
+
continue;
|
|
4347
|
+
}
|
|
4348
|
+
if (!re.test(content)) continue;
|
|
4349
|
+
const lines = content.split(/\r?\n/);
|
|
4350
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4351
|
+
const m = re.exec(lines[i]);
|
|
4352
|
+
if (m) {
|
|
4353
|
+
hits.push({
|
|
4354
|
+
file,
|
|
4355
|
+
line: i + 1,
|
|
4356
|
+
column: m.index,
|
|
4357
|
+
text: lines[i].trim().slice(0, 200)
|
|
4358
|
+
});
|
|
4359
|
+
if (hits.length >= maxHits) return hits;
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
return hits;
|
|
4364
|
+
}
|
|
4365
|
+
|
|
4366
|
+
// src/tools/builtin/symbol-tools.ts
|
|
4367
|
+
var NO_INDEX_HINT = "No symbol index exists for this project yet. Ask the user to run `/index rebuild`, or call indexProject() first. Alternatively, use grep_files for a slower fallback.";
|
|
4368
|
+
async function ensureIndex(root, autoBuild) {
|
|
4369
|
+
let index = loadIndex(root);
|
|
4370
|
+
if (!index && autoBuild) {
|
|
4371
|
+
const result = await indexProject(root);
|
|
4372
|
+
index = result.index;
|
|
4373
|
+
}
|
|
4374
|
+
return index;
|
|
4375
|
+
}
|
|
4376
|
+
var findSymbolTool = {
|
|
4377
|
+
definition: {
|
|
4378
|
+
name: "find_symbol",
|
|
4379
|
+
description: "Locate symbol definitions (functions, classes, methods, interfaces, types, variables) across the project using the pre-built symbol index. Orders of magnitude faster than grep_files for definition lookups. Returns file:line locations plus a one-line signature.",
|
|
4380
|
+
parameters: {
|
|
4381
|
+
name: {
|
|
4382
|
+
type: "string",
|
|
4383
|
+
description: 'Symbol name to find (e.g. "handleChat", "ProviderRegistry")',
|
|
4384
|
+
required: true
|
|
4385
|
+
},
|
|
4386
|
+
exact: {
|
|
4387
|
+
type: "boolean",
|
|
4388
|
+
description: "Exact match (default true). When false, substring match (case-insensitive).",
|
|
4389
|
+
required: false
|
|
4390
|
+
},
|
|
4391
|
+
kind: {
|
|
4392
|
+
type: "string",
|
|
4393
|
+
description: "Restrict to one kind: function, method, class, interface, type, enum, variable, property. Omit to search all.",
|
|
4394
|
+
required: false
|
|
4395
|
+
},
|
|
4396
|
+
path: {
|
|
4397
|
+
type: "string",
|
|
4398
|
+
description: "Project root (defaults to current working directory).",
|
|
4399
|
+
required: false
|
|
4400
|
+
},
|
|
4401
|
+
limit: {
|
|
4402
|
+
type: "number",
|
|
4403
|
+
description: "Max results (default 50).",
|
|
4404
|
+
required: false
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
},
|
|
4408
|
+
async execute(args) {
|
|
4409
|
+
const name = String(args.name ?? "").trim();
|
|
4410
|
+
if (!name) return "Error: `name` is required.";
|
|
4411
|
+
const root = path2.resolve(String(args.path ?? process.cwd()));
|
|
4412
|
+
const index = await ensureIndex(root, true);
|
|
4413
|
+
if (!index) return NO_INDEX_HINT;
|
|
4414
|
+
const kindArg = args.kind ? String(args.kind) : void 0;
|
|
4415
|
+
const hits = findSymbol(index, {
|
|
4416
|
+
name,
|
|
4417
|
+
exact: args.exact !== false,
|
|
4418
|
+
kind: kindArg,
|
|
4419
|
+
limit: typeof args.limit === "number" ? args.limit : 50
|
|
4420
|
+
});
|
|
4421
|
+
if (hits.length === 0) {
|
|
4422
|
+
return `No symbol matching "${name}" found (index has ${index.symbolCount} symbols across ${index.fileCount} files).`;
|
|
4423
|
+
}
|
|
4424
|
+
const lines = hits.map((s) => {
|
|
4425
|
+
const rel = path2.relative(index.root, s.location.file) || s.location.file;
|
|
4426
|
+
const container = s.container ? ` (in ${s.container})` : "";
|
|
4427
|
+
const exp = s.exported ? " [exported]" : "";
|
|
4428
|
+
const sig = s.signature ? `
|
|
4429
|
+
${s.signature}` : "";
|
|
4430
|
+
return `${s.kind} ${s.name}${container}${exp} \u2014 ${rel}:${s.location.line}${sig}`;
|
|
4431
|
+
});
|
|
4432
|
+
return `Found ${hits.length} symbol(s):
|
|
4433
|
+
${lines.join("\n")}`;
|
|
4434
|
+
}
|
|
4435
|
+
};
|
|
4436
|
+
var getOutlineTool = {
|
|
4437
|
+
definition: {
|
|
4438
|
+
name: "get_outline",
|
|
4439
|
+
description: "Return the complete list of top-level declarations in a single source file (classes, functions, methods, types), sorted by line number. Use this to understand file structure without reading its full contents.",
|
|
4440
|
+
parameters: {
|
|
4441
|
+
file: {
|
|
4442
|
+
type: "string",
|
|
4443
|
+
description: "Path to the source file (absolute, or relative to project root).",
|
|
4444
|
+
required: true
|
|
4445
|
+
},
|
|
4446
|
+
path: {
|
|
4447
|
+
type: "string",
|
|
4448
|
+
description: "Project root (defaults to current working directory).",
|
|
4449
|
+
required: false
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
},
|
|
4453
|
+
async execute(args) {
|
|
4454
|
+
const file = String(args.file ?? "").trim();
|
|
4455
|
+
if (!file) return "Error: `file` is required.";
|
|
4456
|
+
const root = path2.resolve(String(args.path ?? process.cwd()));
|
|
4457
|
+
const absFile = path2.isAbsolute(file) ? file : path2.resolve(root, file);
|
|
4458
|
+
const index = await ensureIndex(root, true);
|
|
4459
|
+
if (!index) return NO_INDEX_HINT;
|
|
4460
|
+
const outline = getOutline(index, absFile);
|
|
4461
|
+
if (outline.length === 0) {
|
|
4462
|
+
return `No indexed symbols found for ${path2.relative(root, absFile) || absFile}. The file may be of an unsupported language (supported: TS/JS/TSX/JSX/Python), unreadable, or not yet re-indexed.`;
|
|
4463
|
+
}
|
|
4464
|
+
const rel = path2.relative(root, absFile) || absFile;
|
|
4465
|
+
const lines = outline.map((o) => {
|
|
4466
|
+
const container = o.container ? ` (in ${o.container})` : "";
|
|
4467
|
+
const exp = o.exported ? " [exported]" : "";
|
|
4468
|
+
return ` ${String(o.line).padStart(5)}: ${o.kind} ${o.name}${container}${exp}`;
|
|
4469
|
+
});
|
|
4470
|
+
return `Outline of ${rel} (${outline.length} symbols):
|
|
4471
|
+
${lines.join("\n")}`;
|
|
4472
|
+
}
|
|
4473
|
+
};
|
|
4474
|
+
var findReferencesTool = {
|
|
4475
|
+
definition: {
|
|
4476
|
+
name: "find_references",
|
|
4477
|
+
description: "Find all textual references to a symbol name across indexed files (whole-word match). MVP: regex-based \u2014 may include unrelated identifiers with the same name. Pair with find_symbol to locate the definition.",
|
|
4478
|
+
parameters: {
|
|
4479
|
+
name: {
|
|
4480
|
+
type: "string",
|
|
4481
|
+
description: "Symbol name to find references for.",
|
|
4482
|
+
required: true
|
|
4483
|
+
},
|
|
4484
|
+
path: {
|
|
4485
|
+
type: "string",
|
|
4486
|
+
description: "Project root (defaults to current working directory).",
|
|
4487
|
+
required: false
|
|
4488
|
+
},
|
|
4489
|
+
max_hits: {
|
|
4490
|
+
type: "number",
|
|
4491
|
+
description: "Max reference hits to return (default 200).",
|
|
4492
|
+
required: false
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
},
|
|
4496
|
+
async execute(args) {
|
|
4497
|
+
const name = String(args.name ?? "").trim();
|
|
4498
|
+
if (!name) return "Error: `name` is required.";
|
|
4499
|
+
const root = path2.resolve(String(args.path ?? process.cwd()));
|
|
4500
|
+
const index = await ensureIndex(root, true);
|
|
4501
|
+
if (!index) return NO_INDEX_HINT;
|
|
4502
|
+
const maxHits = typeof args.max_hits === "number" ? args.max_hits : 200;
|
|
4503
|
+
const hits = findReferences(index, name, { maxHits });
|
|
4504
|
+
if (hits.length === 0) {
|
|
4505
|
+
return `No references to "${name}" found in ${index.fileCount} indexed files.`;
|
|
4506
|
+
}
|
|
4507
|
+
const lines = hits.map((h) => {
|
|
4508
|
+
const rel = path2.relative(index.root, h.file) || h.file;
|
|
4509
|
+
return `${rel}:${h.line}:${h.column} ${h.text}`;
|
|
4510
|
+
});
|
|
4511
|
+
return `Found ${hits.length} reference(s) to "${name}":
|
|
4512
|
+
${lines.join("\n")}`;
|
|
4513
|
+
}
|
|
4514
|
+
};
|
|
4515
|
+
|
|
4289
4516
|
// src/core/token-estimator.ts
|
|
4290
4517
|
var CJK_REGEX = /[\u2E80-\u9FFF\uA000-\uA4FF\uAC00-\uD7FF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF]/g;
|
|
4291
4518
|
function estimateTokens(text) {
|
|
@@ -4341,6 +4568,9 @@ var ToolRegistry = class {
|
|
|
4341
4568
|
this.register(gitLogTool);
|
|
4342
4569
|
this.register(gitCommitTool);
|
|
4343
4570
|
this.register(notebookEditTool);
|
|
4571
|
+
this.register(findSymbolTool);
|
|
4572
|
+
this.register(getOutlineTool);
|
|
4573
|
+
this.register(findReferencesTool);
|
|
4344
4574
|
}
|
|
4345
4575
|
register(tool) {
|
|
4346
4576
|
this.tools.set(tool.definition.name, tool);
|