jinzd-ai-cli 0.4.75 → 0.4.77

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 CHANGED
@@ -29,6 +29,8 @@
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
33
+ - **Semantic Code Search** *(v0.4.77+)* — `search_code` tool finds code by meaning, not name. Local sentence embeddings (multilingual MiniLM, 117 MB one-time download) score symbols by cosine similarity against natural-language queries in English or Chinese ("where are users authenticated", "哪里做了速率限制"). No API key, runs on CPU. Manage with `/index semantic-rebuild|semantic-clear`
32
34
  - **Streaming Tool Use** — Real-time streaming of AI reasoning and tool calls as they happen
33
35
  - **Sub-Agents** — Delegate complex subtasks to isolated child agents with independent tool loops
34
36
  - **Extended Thinking** — Claude deep reasoning mode with `/think` toggle
@@ -145,7 +147,7 @@ aicli
145
147
 
146
148
  ## Built-in Tools (Agentic)
147
149
 
148
- AI autonomously invokes these 24 tools during conversations:
150
+ AI autonomously invokes these 27 tools during conversations:
149
151
 
150
152
  | Tool | Safety | Description |
151
153
  |------|--------|-------------|
@@ -173,6 +175,10 @@ AI autonomously invokes these 24 tools during conversations:
173
175
  | `git_log` | safe | Show commit history (oneline/full, filter by file/author) |
174
176
  | `git_commit` | write | Create a git commit (stage files, message) |
175
177
  | `notebook_edit` | write | Edit Jupyter notebook cells (add/edit/delete/move) |
178
+ | `find_symbol` | safe | Locate symbol definitions via persistent tree-sitter index (TS/JS/TSX/Python) |
179
+ | `get_outline` | safe | Enumerate all top-level declarations in one source file |
180
+ | `find_references` | safe | Search indexed files for references to a symbol name |
181
+ | `search_code` | safe | Semantic (meaning-based) code search via local sentence embeddings — bilingual, "grep by meaning" |
176
182
 
177
183
  **Safety levels**: `safe` = auto-execute, `write` = diff preview + confirmation, `destructive` = prominent warning + confirmation.
178
184
 
@@ -195,6 +201,7 @@ AI autonomously invokes these 24 tools during conversations:
195
201
  | `/checkpoint` | Save/restore conversation checkpoints |
196
202
  | `/fork` | Fork the current session into a new session file |
197
203
  | `/branch` | Create/switch/delete branches *within* the current session (B2) |
204
+ | `/index` | Manage symbol + semantic index (status/rebuild/clear/semantic-rebuild/semantic-clear) — powers `find_symbol` / `get_outline` / `find_references` / `search_code` (C1+C2) |
198
205
  | `/search <keyword>` | Full-text search across all sessions |
199
206
  | `/skill` | Manage agent skill packs |
200
207
  | `/mcp` | View MCP server status and tools |
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  schemaToJsonSchema,
4
4
  truncateForPersist
5
- } from "./chunk-MPIUYP6Q.js";
5
+ } from "./chunk-LKDX2GOW.js";
6
6
  import {
7
7
  AuthError,
8
8
  ProviderError,
@@ -18,7 +18,7 @@ import {
18
18
  MCP_PROTOCOL_VERSION,
19
19
  MCP_TOOL_PREFIX,
20
20
  VERSION
21
- } from "./chunk-5P4QTZBI.js";
21
+ } from "./chunk-S4WDPHKS.js";
22
22
 
23
23
  // src/providers/claude.ts
24
24
  import Anthropic from "@anthropic-ai/sdk";
@@ -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
+ };
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ loadIndex
4
+ } from "./chunk-6VRJGH25.js";
5
+ import {
6
+ EMBEDDING_DIM,
7
+ embed,
8
+ embedOne,
9
+ loadVectorStore,
10
+ saveVectorStore,
11
+ searchVectorStore
12
+ } from "./chunk-PFYAAX2S.js";
13
+
14
+ // src/symbols/semantic.ts
15
+ function buildEmbeddingText(s) {
16
+ const parts = [s.kind, s.name];
17
+ if (s.container) parts.push(`in ${s.container}`);
18
+ if (s.signature) parts.push(s.signature);
19
+ if (s.doc) parts.push(s.doc);
20
+ return parts.join(" ").slice(0, 512);
21
+ }
22
+ async function rebuildSemanticIndex(root, opts = {}) {
23
+ const start = Date.now();
24
+ const index = loadIndex(root);
25
+ if (!index) {
26
+ throw new Error(
27
+ `No symbol index for ${root}. Run /index rebuild first so find_symbol has data to embed.`
28
+ );
29
+ }
30
+ const maxSymbols = opts.maxSymbols ?? 2e4;
31
+ const batchSize = opts.batchSize ?? 32;
32
+ const total = Math.min(index.symbols.length, maxSymbols);
33
+ if (total === 0) {
34
+ saveVectorStore(root, new Uint32Array(0), new Float32Array(0));
35
+ return { symbolsEmbedded: 0, durationMs: Date.now() - start };
36
+ }
37
+ const symbolIdx = new Uint32Array(total);
38
+ const vectors = new Float32Array(total * EMBEDDING_DIM);
39
+ let modelFirstLoadMs;
40
+ for (let i = 0; i < total; i += batchSize) {
41
+ const end = Math.min(i + batchSize, total);
42
+ const batch = [];
43
+ for (let j = i; j < end; j++) {
44
+ symbolIdx[j] = j;
45
+ batch.push(buildEmbeddingText(index.symbols[j]));
46
+ }
47
+ const batchStart = Date.now();
48
+ const rows = await embed(batch);
49
+ if (i === 0) modelFirstLoadMs = Date.now() - batchStart;
50
+ for (let r = 0; r < rows.length; r++) {
51
+ vectors.set(rows[r], (i + r) * EMBEDDING_DIM);
52
+ }
53
+ if (opts.onProgress) opts.onProgress(end, total);
54
+ }
55
+ saveVectorStore(root, symbolIdx, vectors);
56
+ return {
57
+ symbolsEmbedded: total,
58
+ durationMs: Date.now() - start,
59
+ modelFirstLoadMs
60
+ };
61
+ }
62
+ async function semanticSearch(root, query, k = 10) {
63
+ const index = loadIndex(root);
64
+ if (!index) return [];
65
+ const store = loadVectorStore(root);
66
+ if (!store || store.count === 0) return [];
67
+ const queryVec = await embedOne(query);
68
+ const hits = searchVectorStore(store, queryVec, k);
69
+ return hits.map((h) => ({
70
+ ...h,
71
+ symbol: index.symbols[h.symbolIdx]
72
+ })).filter((h) => h.symbol !== void 0);
73
+ }
74
+ function hasSemanticIndex(root) {
75
+ const s = loadVectorStore(root);
76
+ return s !== null && s.count > 0;
77
+ }
78
+
79
+ export {
80
+ buildEmbeddingText,
81
+ rebuildSemanticIndex,
82
+ semanticSearch,
83
+ hasSemanticIndex
84
+ };
@@ -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.75";
9
+ var VERSION = "0.4.77";
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,16 @@ 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)
46
+ "search_code"
47
+ // C2 semantic search (read-only)
40
48
  ]);
41
49
  var PLAN_MODE_SYSTEM_ADDON = `# \u{1F50D} Plan Mode \u2014 Read-Only Planning Mode
42
50
 
@@ -76,7 +84,11 @@ var SUBAGENT_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
76
84
  "web_fetch",
77
85
  "google_search",
78
86
  "write_todos",
79
- "run_tests"
87
+ "run_tests",
88
+ "find_symbol",
89
+ "get_outline",
90
+ "find_references",
91
+ "search_code"
80
92
  ]);
81
93
  var TEST_TIMEOUT = 3e5;
82
94
  var AGENTIC_BEHAVIOR_GUIDELINE = `# Important Behavioral Guidelines