jinzd-ai-cli 0.4.76 → 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
@@ -30,6 +30,7 @@
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
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`
33
34
  - **Streaming Tool Use** — Real-time streaming of AI reasoning and tool calls as they happen
34
35
  - **Sub-Agents** — Delegate complex subtasks to isolated child agents with independent tool loops
35
36
  - **Extended Thinking** — Claude deep reasoning mode with `/think` toggle
@@ -177,6 +178,7 @@ AI autonomously invokes these 27 tools during conversations:
177
178
  | `find_symbol` | safe | Locate symbol definitions via persistent tree-sitter index (TS/JS/TSX/Python) |
178
179
  | `get_outline` | safe | Enumerate all top-level declarations in one source file |
179
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" |
180
182
 
181
183
  **Safety levels**: `safe` = auto-execute, `write` = diff preview + confirmation, `destructive` = prominent warning + confirmation.
182
184
 
@@ -199,7 +201,7 @@ AI autonomously invokes these 27 tools during conversations:
199
201
  | `/checkpoint` | Save/restore conversation checkpoints |
200
202
  | `/fork` | Fork the current session into a new session file |
201
203
  | `/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) |
204
+ | `/index` | Manage symbol + semantic index (status/rebuild/clear/semantic-rebuild/semantic-clear) — powers `find_symbol` / `get_outline` / `find_references` / `search_code` (C1+C2) |
203
205
  | `/search <keyword>` | Full-text search across all sessions |
204
206
  | `/skill` | Manage agent skill packs |
205
207
  | `/mcp` | View MCP server status and tools |
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  schemaToJsonSchema,
4
4
  truncateForPersist
5
- } from "./chunk-KR4FTJWB.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-2Q77FT3F.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,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.76";
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";
@@ -41,8 +41,10 @@ var PLAN_MODE_READONLY_TOOLS = /* @__PURE__ */ new Set([
41
41
  // C1 symbol index (read-only)
42
42
  "get_outline",
43
43
  // C1 symbol index (read-only)
44
- "find_references"
44
+ "find_references",
45
45
  // C1 symbol index (read-only)
46
+ "search_code"
47
+ // C2 semantic search (read-only)
46
48
  ]);
47
49
  var PLAN_MODE_SYSTEM_ADDON = `# \u{1F50D} Plan Mode \u2014 Read-Only Planning Mode
48
50
 
@@ -85,7 +87,8 @@ var SUBAGENT_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
85
87
  "run_tests",
86
88
  "find_symbol",
87
89
  "get_outline",
88
- "find_references"
90
+ "find_references",
91
+ "search_code"
89
92
  ]);
90
93
  var TEST_TIMEOUT = 3e5;
91
94
  var AGENTIC_BEHAVIOR_GUIDELINE = `# Important Behavioral Guidelines
@@ -1,21 +1,25 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ EnvLoader,
4
+ NetworkError,
5
+ ToolError
6
+ } from "./chunk-2ZD3YTVM.js";
2
7
  import {
3
8
  fileCheckpoints
4
9
  } from "./chunk-4BKXL7SM.js";
5
10
  import {
6
11
  indexProject
7
12
  } from "./chunk-NHNWUBXB.js";
13
+ import {
14
+ hasSemanticIndex,
15
+ semanticSearch
16
+ } from "./chunk-HPDDAXFY.js";
8
17
  import {
9
18
  loadIndex
10
19
  } from "./chunk-6VRJGH25.js";
11
20
  import {
12
21
  runTestsTool
13
- } from "./chunk-K3JJX2Z5.js";
14
- import {
15
- EnvLoader,
16
- NetworkError,
17
- ToolError
18
- } from "./chunk-2ZD3YTVM.js";
22
+ } from "./chunk-QATT4NCL.js";
19
23
  import {
20
24
  CONFIG_DIR_NAME,
21
25
  DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
@@ -23,7 +27,7 @@ import {
23
27
  SUBAGENT_ALLOWED_TOOLS,
24
28
  SUBAGENT_DEFAULT_MAX_ROUNDS,
25
29
  SUBAGENT_MAX_ROUNDS_LIMIT
26
- } from "./chunk-2Q77FT3F.js";
30
+ } from "./chunk-S4WDPHKS.js";
27
31
 
28
32
  // src/tools/builtin/bash.ts
29
33
  import { execSync } from "child_process";
@@ -4512,6 +4516,67 @@ var findReferencesTool = {
4512
4516
  ${lines.join("\n")}`;
4513
4517
  }
4514
4518
  };
4519
+ var searchCodeTool = {
4520
+ definition: {
4521
+ name: "search_code",
4522
+ description: 'Semantic (meaning-based) search across indexed code symbols using local sentence embeddings. Use this when you do NOT know the exact symbol name \u2014 it finds code by purpose, e.g. "rate limiting logic", "where users are authenticated", "error recovery retry loops". Returns ranked matches with similarity scores. Requires a prior `/index semantic-rebuild` to build embeddings (one-time 117 MB model download + a few seconds per 1K symbols). For exact name lookups use find_symbol instead.',
4523
+ parameters: {
4524
+ query: {
4525
+ type: "string",
4526
+ description: "Natural-language description of the code you're looking for. English or Chinese both work.",
4527
+ required: true
4528
+ },
4529
+ k: {
4530
+ type: "number",
4531
+ description: "Max results to return (default 10, max 50).",
4532
+ required: false
4533
+ },
4534
+ kind: {
4535
+ type: "string",
4536
+ description: "Restrict to one symbol kind: function, method, class, interface, type, enum, variable, property. Omit to search all.",
4537
+ required: false
4538
+ },
4539
+ path: {
4540
+ type: "string",
4541
+ description: "Project root (defaults to current working directory).",
4542
+ required: false
4543
+ }
4544
+ }
4545
+ },
4546
+ async execute(args) {
4547
+ const query = String(args.query ?? "").trim();
4548
+ if (!query) return "Error: `query` is required.";
4549
+ const root = path2.resolve(String(args.path ?? process.cwd()));
4550
+ const k = Math.min(50, Math.max(1, typeof args.k === "number" ? args.k : 10));
4551
+ const kindFilter = args.kind ? String(args.kind) : void 0;
4552
+ if (!hasSemanticIndex(root)) {
4553
+ return "No semantic index exists for this project yet. Ask the user to run `/index semantic-rebuild` (first run downloads a ~117 MB embedding model; subsequent rebuilds are fast). As a fallback, use grep_files or find_symbol for literal-name lookups.";
4554
+ }
4555
+ let hits;
4556
+ try {
4557
+ const oversampled = await semanticSearch(root, query, kindFilter ? k * 4 : k);
4558
+ hits = kindFilter ? oversampled.filter((h) => h.symbol.kind === kindFilter).slice(0, k) : oversampled;
4559
+ } catch (err) {
4560
+ const msg = err instanceof Error ? err.message : String(err);
4561
+ return `Semantic search failed: ${msg}`;
4562
+ }
4563
+ if (hits.length === 0) {
4564
+ return `No semantic matches found for "${query}". Try a different phrasing, or run \`/index semantic-rebuild\` if the codebase has changed significantly.`;
4565
+ }
4566
+ const lines = hits.map((h) => {
4567
+ const s = h.symbol;
4568
+ const rel = path2.relative(root, s.location.file) || s.location.file;
4569
+ const container = s.container ? ` (in ${s.container})` : "";
4570
+ const exp = s.exported ? " [exported]" : "";
4571
+ const score = h.score.toFixed(3);
4572
+ const sig = s.signature ? `
4573
+ ${s.signature}` : "";
4574
+ return `[score=${score}] ${s.kind} ${s.name}${container}${exp} \u2014 ${rel}:${s.location.line}${sig}`;
4575
+ });
4576
+ return `Top ${hits.length} semantic match(es) for "${query}":
4577
+ ${lines.join("\n")}`;
4578
+ }
4579
+ };
4515
4580
 
4516
4581
  // src/core/token-estimator.ts
4517
4582
  var CJK_REGEX = /[\u2E80-\u9FFF\uA000-\uA4FF\uAC00-\uD7FF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF]/g;
@@ -4571,6 +4636,7 @@ var ToolRegistry = class {
4571
4636
  this.register(findSymbolTool);
4572
4637
  this.register(getOutlineTool);
4573
4638
  this.register(findReferencesTool);
4639
+ this.register(searchCodeTool);
4574
4640
  }
4575
4641
  register(tool) {
4576
4642
  this.tools.set(tool.definition.name, tool);
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-2Q77FT3F.js";
11
+ } from "./chunk-S4WDPHKS.js";
12
12
 
13
13
  // src/config/config-manager.ts
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/symbols/vector-store.ts
4
+ import fs2 from "fs";
5
+ import path2 from "path";
6
+ import os2 from "os";
7
+ import crypto from "crypto";
8
+
9
+ // src/symbols/embedder.ts
10
+ import path from "path";
11
+ import os from "os";
12
+ import fs from "fs";
13
+ var EMBEDDING_MODEL_ID = "Xenova/paraphrase-multilingual-MiniLM-L12-v2";
14
+ var EMBEDDING_DIM = 384;
15
+ var pipelinePromise = null;
16
+ function cacheDir() {
17
+ return path.join(os.homedir(), ".aicli", "models");
18
+ }
19
+ async function getEmbedder() {
20
+ if (pipelinePromise) return pipelinePromise;
21
+ pipelinePromise = (async () => {
22
+ const mod = await import("@huggingface/transformers");
23
+ const dir = cacheDir();
24
+ fs.mkdirSync(dir, { recursive: true });
25
+ mod.env.cacheDir = dir;
26
+ mod.env.allowRemoteModels = true;
27
+ mod.env.allowLocalModels = true;
28
+ const pipe = await mod.pipeline("feature-extraction", EMBEDDING_MODEL_ID, {
29
+ // Keep the ONNX session in float32; int8 quantization exists but the
30
+ // quality drop on short code identifiers is noticeable.
31
+ dtype: "fp32"
32
+ });
33
+ return pipe;
34
+ })();
35
+ return pipelinePromise;
36
+ }
37
+ async function embed(texts) {
38
+ if (texts.length === 0) return [];
39
+ const pipe = await getEmbedder();
40
+ const out = await pipe(texts, { pooling: "mean", normalize: true });
41
+ const batch = texts.length;
42
+ const dim = EMBEDDING_DIM;
43
+ const rows = new Array(batch);
44
+ for (let i = 0; i < batch; i++) {
45
+ rows[i] = new Float32Array(out.data.buffer, out.data.byteOffset + i * dim * 4, dim).slice();
46
+ }
47
+ return rows;
48
+ }
49
+ async function embedOne(text) {
50
+ const [vec] = await embed([text]);
51
+ return vec;
52
+ }
53
+
54
+ // src/symbols/vector-store.ts
55
+ var MAGIC = 1094927190;
56
+ var VERSION = 1;
57
+ var HEADER_BYTES = 16;
58
+ function indexDir() {
59
+ return path2.join(os2.homedir(), ".aicli", "index");
60
+ }
61
+ function projectHash(root) {
62
+ return crypto.createHash("sha1").update(path2.resolve(root).toLowerCase()).digest("hex").slice(0, 16);
63
+ }
64
+ function vecPath(root) {
65
+ return path2.join(indexDir(), `${projectHash(root)}.vec`);
66
+ }
67
+ function emptyVectorStore(root) {
68
+ return {
69
+ root: path2.resolve(root),
70
+ count: 0,
71
+ dim: EMBEDDING_DIM,
72
+ vectors: new Float32Array(0),
73
+ symbolIdx: new Uint32Array(0)
74
+ };
75
+ }
76
+ function saveVectorStore(root, indices, vectors) {
77
+ if (indices.length * EMBEDDING_DIM !== vectors.length) {
78
+ throw new Error(
79
+ `saveVectorStore: length mismatch \u2014 ${indices.length} indices vs ${vectors.length / EMBEDDING_DIM} vectors`
80
+ );
81
+ }
82
+ const count = indices.length;
83
+ const dir = indexDir();
84
+ fs2.mkdirSync(dir, { recursive: true });
85
+ const totalBytes = HEADER_BYTES + count * 4 + count * EMBEDDING_DIM * 4;
86
+ const buf = Buffer.alloc(totalBytes);
87
+ buf.writeUInt32LE(MAGIC, 0);
88
+ buf.writeUInt32LE(VERSION, 4);
89
+ buf.writeUInt32LE(count, 8);
90
+ buf.writeUInt32LE(EMBEDDING_DIM, 12);
91
+ Buffer.from(indices.buffer, indices.byteOffset, indices.byteLength).copy(buf, HEADER_BYTES);
92
+ Buffer.from(vectors.buffer, vectors.byteOffset, vectors.byteLength).copy(buf, HEADER_BYTES + count * 4);
93
+ const target = vecPath(root);
94
+ const tmp = `${target}.tmp`;
95
+ fs2.writeFileSync(tmp, buf);
96
+ fs2.renameSync(tmp, target);
97
+ }
98
+ function loadVectorStore(root) {
99
+ const p = vecPath(root);
100
+ if (!fs2.existsSync(p)) return null;
101
+ let buf;
102
+ try {
103
+ buf = fs2.readFileSync(p);
104
+ } catch {
105
+ return null;
106
+ }
107
+ if (buf.length < HEADER_BYTES) return null;
108
+ const magic = buf.readUInt32LE(0);
109
+ const version = buf.readUInt32LE(4);
110
+ const count = buf.readUInt32LE(8);
111
+ const dim = buf.readUInt32LE(12);
112
+ if (magic !== MAGIC || version !== VERSION || dim !== EMBEDDING_DIM) return null;
113
+ const expected = HEADER_BYTES + count * 4 + count * dim * 4;
114
+ if (buf.length !== expected) return null;
115
+ const symbolIdx = new Uint32Array(
116
+ buf.buffer.slice(buf.byteOffset + HEADER_BYTES, buf.byteOffset + HEADER_BYTES + count * 4)
117
+ );
118
+ const vectors = new Float32Array(
119
+ buf.buffer.slice(
120
+ buf.byteOffset + HEADER_BYTES + count * 4,
121
+ buf.byteOffset + HEADER_BYTES + count * 4 + count * dim * 4
122
+ )
123
+ );
124
+ return { root: path2.resolve(root), count, dim, vectors, symbolIdx };
125
+ }
126
+ function clearVectorStore(root) {
127
+ const p = vecPath(root);
128
+ try {
129
+ if (fs2.existsSync(p)) fs2.unlinkSync(p);
130
+ } catch {
131
+ }
132
+ }
133
+ function searchVectorStore(store, queryVec, k) {
134
+ if (store.count === 0) return [];
135
+ if (queryVec.length !== store.dim) {
136
+ throw new Error(`searchVectorStore: dim mismatch (query=${queryVec.length}, store=${store.dim})`);
137
+ }
138
+ const { count, dim, vectors, symbolIdx } = store;
139
+ const heap = [];
140
+ const push = (hit) => {
141
+ if (heap.length < k) {
142
+ heap.push(hit);
143
+ heap.sort((a, b) => a.score - b.score);
144
+ } else if (hit.score > heap[0].score) {
145
+ heap[0] = hit;
146
+ heap.sort((a, b) => a.score - b.score);
147
+ }
148
+ };
149
+ for (let row = 0; row < count; row++) {
150
+ const base = row * dim;
151
+ let score = 0;
152
+ for (let d = 0; d < dim; d++) {
153
+ score += vectors[base + d] * queryVec[d];
154
+ }
155
+ push({ row, symbolIdx: symbolIdx[row], score });
156
+ }
157
+ return heap.sort((a, b) => b.score - a.score);
158
+ }
159
+
160
+ export {
161
+ EMBEDDING_DIM,
162
+ embed,
163
+ embedOne,
164
+ emptyVectorStore,
165
+ saveVectorStore,
166
+ loadVectorStore,
167
+ clearVectorStore,
168
+ searchVectorStore
169
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-2Q77FT3F.js";
4
+ } from "./chunk-S4WDPHKS.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.76";
4
+ var VERSION = "0.4.77";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -36,8 +36,10 @@ var PLAN_MODE_READONLY_TOOLS = /* @__PURE__ */ new Set([
36
36
  // C1 symbol index (read-only)
37
37
  "get_outline",
38
38
  // C1 symbol index (read-only)
39
- "find_references"
39
+ "find_references",
40
40
  // C1 symbol index (read-only)
41
+ "search_code"
42
+ // C2 semantic search (read-only)
41
43
  ]);
42
44
  var PLAN_MODE_SYSTEM_ADDON = `# \u{1F50D} Plan Mode \u2014 Read-Only Planning Mode
43
45
 
@@ -80,7 +82,8 @@ var SUBAGENT_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
80
82
  "run_tests",
81
83
  "find_symbol",
82
84
  "get_outline",
83
- "find_references"
85
+ "find_references",
86
+ "search_code"
84
87
  ]);
85
88
  var CONTEXT_PRESSURE_THRESHOLD = 0.8;
86
89
  var TEST_TIMEOUT = 3e5;
@@ -0,0 +1,83 @@
1
+ import {
2
+ loadIndex
3
+ } from "./chunk-BJAT4GNC.js";
4
+ import {
5
+ EMBEDDING_DIM,
6
+ embed,
7
+ embedOne,
8
+ loadVectorStore,
9
+ saveVectorStore,
10
+ searchVectorStore
11
+ } from "./chunk-XMA222FQ.js";
12
+
13
+ // src/symbols/semantic.ts
14
+ function buildEmbeddingText(s) {
15
+ const parts = [s.kind, s.name];
16
+ if (s.container) parts.push(`in ${s.container}`);
17
+ if (s.signature) parts.push(s.signature);
18
+ if (s.doc) parts.push(s.doc);
19
+ return parts.join(" ").slice(0, 512);
20
+ }
21
+ async function rebuildSemanticIndex(root, opts = {}) {
22
+ const start = Date.now();
23
+ const index = loadIndex(root);
24
+ if (!index) {
25
+ throw new Error(
26
+ `No symbol index for ${root}. Run /index rebuild first so find_symbol has data to embed.`
27
+ );
28
+ }
29
+ const maxSymbols = opts.maxSymbols ?? 2e4;
30
+ const batchSize = opts.batchSize ?? 32;
31
+ const total = Math.min(index.symbols.length, maxSymbols);
32
+ if (total === 0) {
33
+ saveVectorStore(root, new Uint32Array(0), new Float32Array(0));
34
+ return { symbolsEmbedded: 0, durationMs: Date.now() - start };
35
+ }
36
+ const symbolIdx = new Uint32Array(total);
37
+ const vectors = new Float32Array(total * EMBEDDING_DIM);
38
+ let modelFirstLoadMs;
39
+ for (let i = 0; i < total; i += batchSize) {
40
+ const end = Math.min(i + batchSize, total);
41
+ const batch = [];
42
+ for (let j = i; j < end; j++) {
43
+ symbolIdx[j] = j;
44
+ batch.push(buildEmbeddingText(index.symbols[j]));
45
+ }
46
+ const batchStart = Date.now();
47
+ const rows = await embed(batch);
48
+ if (i === 0) modelFirstLoadMs = Date.now() - batchStart;
49
+ for (let r = 0; r < rows.length; r++) {
50
+ vectors.set(rows[r], (i + r) * EMBEDDING_DIM);
51
+ }
52
+ if (opts.onProgress) opts.onProgress(end, total);
53
+ }
54
+ saveVectorStore(root, symbolIdx, vectors);
55
+ return {
56
+ symbolsEmbedded: total,
57
+ durationMs: Date.now() - start,
58
+ modelFirstLoadMs
59
+ };
60
+ }
61
+ async function semanticSearch(root, query, k = 10) {
62
+ const index = loadIndex(root);
63
+ if (!index) return [];
64
+ const store = loadVectorStore(root);
65
+ if (!store || store.count === 0) return [];
66
+ const queryVec = await embedOne(query);
67
+ const hits = searchVectorStore(store, queryVec, k);
68
+ return hits.map((h) => ({
69
+ ...h,
70
+ symbol: index.symbols[h.symbolIdx]
71
+ })).filter((h) => h.symbol !== void 0);
72
+ }
73
+ function hasSemanticIndex(root) {
74
+ const s = loadVectorStore(root);
75
+ return s !== null && s.count > 0;
76
+ }
77
+
78
+ export {
79
+ buildEmbeddingText,
80
+ rebuildSemanticIndex,
81
+ semanticSearch,
82
+ hasSemanticIndex
83
+ };
@@ -0,0 +1,167 @@
1
+ // src/symbols/vector-store.ts
2
+ import fs2 from "fs";
3
+ import path2 from "path";
4
+ import os2 from "os";
5
+ import crypto from "crypto";
6
+
7
+ // src/symbols/embedder.ts
8
+ import path from "path";
9
+ import os from "os";
10
+ import fs from "fs";
11
+ var EMBEDDING_MODEL_ID = "Xenova/paraphrase-multilingual-MiniLM-L12-v2";
12
+ var EMBEDDING_DIM = 384;
13
+ var pipelinePromise = null;
14
+ function cacheDir() {
15
+ return path.join(os.homedir(), ".aicli", "models");
16
+ }
17
+ async function getEmbedder() {
18
+ if (pipelinePromise) return pipelinePromise;
19
+ pipelinePromise = (async () => {
20
+ const mod = await import("@huggingface/transformers");
21
+ const dir = cacheDir();
22
+ fs.mkdirSync(dir, { recursive: true });
23
+ mod.env.cacheDir = dir;
24
+ mod.env.allowRemoteModels = true;
25
+ mod.env.allowLocalModels = true;
26
+ const pipe = await mod.pipeline("feature-extraction", EMBEDDING_MODEL_ID, {
27
+ // Keep the ONNX session in float32; int8 quantization exists but the
28
+ // quality drop on short code identifiers is noticeable.
29
+ dtype: "fp32"
30
+ });
31
+ return pipe;
32
+ })();
33
+ return pipelinePromise;
34
+ }
35
+ async function embed(texts) {
36
+ if (texts.length === 0) return [];
37
+ const pipe = await getEmbedder();
38
+ const out = await pipe(texts, { pooling: "mean", normalize: true });
39
+ const batch = texts.length;
40
+ const dim = EMBEDDING_DIM;
41
+ const rows = new Array(batch);
42
+ for (let i = 0; i < batch; i++) {
43
+ rows[i] = new Float32Array(out.data.buffer, out.data.byteOffset + i * dim * 4, dim).slice();
44
+ }
45
+ return rows;
46
+ }
47
+ async function embedOne(text) {
48
+ const [vec] = await embed([text]);
49
+ return vec;
50
+ }
51
+
52
+ // src/symbols/vector-store.ts
53
+ var MAGIC = 1094927190;
54
+ var VERSION = 1;
55
+ var HEADER_BYTES = 16;
56
+ function indexDir() {
57
+ return path2.join(os2.homedir(), ".aicli", "index");
58
+ }
59
+ function projectHash(root) {
60
+ return crypto.createHash("sha1").update(path2.resolve(root).toLowerCase()).digest("hex").slice(0, 16);
61
+ }
62
+ function vecPath(root) {
63
+ return path2.join(indexDir(), `${projectHash(root)}.vec`);
64
+ }
65
+ function emptyVectorStore(root) {
66
+ return {
67
+ root: path2.resolve(root),
68
+ count: 0,
69
+ dim: EMBEDDING_DIM,
70
+ vectors: new Float32Array(0),
71
+ symbolIdx: new Uint32Array(0)
72
+ };
73
+ }
74
+ function saveVectorStore(root, indices, vectors) {
75
+ if (indices.length * EMBEDDING_DIM !== vectors.length) {
76
+ throw new Error(
77
+ `saveVectorStore: length mismatch \u2014 ${indices.length} indices vs ${vectors.length / EMBEDDING_DIM} vectors`
78
+ );
79
+ }
80
+ const count = indices.length;
81
+ const dir = indexDir();
82
+ fs2.mkdirSync(dir, { recursive: true });
83
+ const totalBytes = HEADER_BYTES + count * 4 + count * EMBEDDING_DIM * 4;
84
+ const buf = Buffer.alloc(totalBytes);
85
+ buf.writeUInt32LE(MAGIC, 0);
86
+ buf.writeUInt32LE(VERSION, 4);
87
+ buf.writeUInt32LE(count, 8);
88
+ buf.writeUInt32LE(EMBEDDING_DIM, 12);
89
+ Buffer.from(indices.buffer, indices.byteOffset, indices.byteLength).copy(buf, HEADER_BYTES);
90
+ Buffer.from(vectors.buffer, vectors.byteOffset, vectors.byteLength).copy(buf, HEADER_BYTES + count * 4);
91
+ const target = vecPath(root);
92
+ const tmp = `${target}.tmp`;
93
+ fs2.writeFileSync(tmp, buf);
94
+ fs2.renameSync(tmp, target);
95
+ }
96
+ function loadVectorStore(root) {
97
+ const p = vecPath(root);
98
+ if (!fs2.existsSync(p)) return null;
99
+ let buf;
100
+ try {
101
+ buf = fs2.readFileSync(p);
102
+ } catch {
103
+ return null;
104
+ }
105
+ if (buf.length < HEADER_BYTES) return null;
106
+ const magic = buf.readUInt32LE(0);
107
+ const version = buf.readUInt32LE(4);
108
+ const count = buf.readUInt32LE(8);
109
+ const dim = buf.readUInt32LE(12);
110
+ if (magic !== MAGIC || version !== VERSION || dim !== EMBEDDING_DIM) return null;
111
+ const expected = HEADER_BYTES + count * 4 + count * dim * 4;
112
+ if (buf.length !== expected) return null;
113
+ const symbolIdx = new Uint32Array(
114
+ buf.buffer.slice(buf.byteOffset + HEADER_BYTES, buf.byteOffset + HEADER_BYTES + count * 4)
115
+ );
116
+ const vectors = new Float32Array(
117
+ buf.buffer.slice(
118
+ buf.byteOffset + HEADER_BYTES + count * 4,
119
+ buf.byteOffset + HEADER_BYTES + count * 4 + count * dim * 4
120
+ )
121
+ );
122
+ return { root: path2.resolve(root), count, dim, vectors, symbolIdx };
123
+ }
124
+ function clearVectorStore(root) {
125
+ const p = vecPath(root);
126
+ try {
127
+ if (fs2.existsSync(p)) fs2.unlinkSync(p);
128
+ } catch {
129
+ }
130
+ }
131
+ function searchVectorStore(store, queryVec, k) {
132
+ if (store.count === 0) return [];
133
+ if (queryVec.length !== store.dim) {
134
+ throw new Error(`searchVectorStore: dim mismatch (query=${queryVec.length}, store=${store.dim})`);
135
+ }
136
+ const { count, dim, vectors, symbolIdx } = store;
137
+ const heap = [];
138
+ const push = (hit) => {
139
+ if (heap.length < k) {
140
+ heap.push(hit);
141
+ heap.sort((a, b) => a.score - b.score);
142
+ } else if (hit.score > heap[0].score) {
143
+ heap[0] = hit;
144
+ heap.sort((a, b) => a.score - b.score);
145
+ }
146
+ };
147
+ for (let row = 0; row < count; row++) {
148
+ const base = row * dim;
149
+ let score = 0;
150
+ for (let d = 0; d < dim; d++) {
151
+ score += vectors[base + d] * queryVec[d];
152
+ }
153
+ push({ row, symbolIdx: symbolIdx[row], score });
154
+ }
155
+ return heap.sort((a, b) => b.score - a.score);
156
+ }
157
+
158
+ export {
159
+ EMBEDDING_DIM,
160
+ embed,
161
+ embedOne,
162
+ emptyVectorStore,
163
+ saveVectorStore,
164
+ loadVectorStore,
165
+ clearVectorStore,
166
+ searchVectorStore
167
+ };
@@ -385,7 +385,7 @@ ${content}`);
385
385
  }
386
386
  }
387
387
  async function runTaskMode(config, providers, configManager, topic) {
388
- const { TaskOrchestrator } = await import("./task-orchestrator-I5HPXTJY.js");
388
+ const { TaskOrchestrator } = await import("./task-orchestrator-VC7LCN5J.js");
389
389
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
390
390
  let interrupted = false;
391
391
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -30,7 +30,10 @@ import {
30
30
  saveDevState,
31
31
  sessionHasMeaningfulContent,
32
32
  setupProxy
33
- } from "./chunk-XTH7S3AM.js";
33
+ } from "./chunk-5PZMIBEN.js";
34
+ import {
35
+ ConfigManager
36
+ } from "./chunk-LQ76WLB6.js";
34
37
  import {
35
38
  ToolExecutor,
36
39
  ToolRegistry,
@@ -46,17 +49,16 @@ import {
46
49
  spawnAgentContext,
47
50
  theme,
48
51
  undoStack
49
- } from "./chunk-KR4FTJWB.js";
52
+ } from "./chunk-LKDX2GOW.js";
53
+ import "./chunk-2ZD3YTVM.js";
50
54
  import {
51
55
  fileCheckpoints
52
56
  } from "./chunk-4BKXL7SM.js";
53
57
  import "./chunk-NHNWUBXB.js";
58
+ import "./chunk-HPDDAXFY.js";
54
59
  import "./chunk-6VRJGH25.js";
55
- import "./chunk-K3JJX2Z5.js";
56
- import {
57
- ConfigManager
58
- } from "./chunk-H4DQNZZ6.js";
59
- import "./chunk-2ZD3YTVM.js";
60
+ import "./chunk-PFYAAX2S.js";
61
+ import "./chunk-QATT4NCL.js";
60
62
  import {
61
63
  AGENTIC_BEHAVIOR_GUIDELINE,
62
64
  AUTHOR,
@@ -78,7 +80,7 @@ import {
78
80
  SKILLS_DIR_NAME,
79
81
  VERSION,
80
82
  buildUserIdentityPrompt
81
- } from "./chunk-2Q77FT3F.js";
83
+ } from "./chunk-S4WDPHKS.js";
82
84
 
83
85
  // src/index.ts
84
86
  import { program } from "commander";
@@ -216,7 +218,7 @@ var Renderer = class {
216
218
  console.log(theme.dim(" Gemini (Google) \xB7 Zhipu (GLM) \xB7 OpenRouter \xB7 Ollama (Local, no API key)"));
217
219
  console.log(HR);
218
220
  const mcpToolCount = mcpInfo?.tools ?? 0;
219
- const toolTotal = 27 + pluginCount + mcpToolCount;
221
+ const toolTotal = 28 + pluginCount + mcpToolCount;
220
222
  const extras = [];
221
223
  if (pluginCount > 0) extras.push(`${pluginCount} plugin(s)`);
222
224
  if (mcpToolCount > 0) extras.push(`${mcpToolCount} MCP`);
@@ -249,6 +251,7 @@ var Renderer = class {
249
251
  console.log(tool("find_symbol", "Locate symbol definitions via persistent tree-sitter index (TS/JS/TSX/Python)"));
250
252
  console.log(tool("get_outline", "Enumerate all top-level declarations in one source file"));
251
253
  console.log(tool("find_references", "Search indexed files for references to a symbol name"));
254
+ console.log(tool("search_code", 'Semantic (meaning-based) code search via local embeddings \u2014 "grep by meaning", bilingual'));
252
255
  console.log(HR);
253
256
  console.log(theme.dim(" REPL Commands (42):"));
254
257
  console.log(theme.dim(" /help /about /provider /model /clear /compact /plan /session"));
@@ -976,7 +979,7 @@ function createDefaultCommands() {
976
979
  " /diff [--stats] - Show all file modifications in this session",
977
980
  " /fork [checkpoint] - Fork session from checkpoint or current position",
978
981
  " /branch [list|new|switch|delete|rename] - Manage conversation branches (fork tree)",
979
- " /index [status|rebuild|clear] - Symbol index for find_symbol / get_outline / find_references",
982
+ " /index [status|rebuild|clear|semantic-rebuild|semantic-clear] - Symbol + semantic index (find_symbol / search_code)",
980
983
  " /yolo [on|off] - Toggle session auto-approve (skip confirmations)",
981
984
  " /exit - Exit"
982
985
  ] : [];
@@ -2366,15 +2369,17 @@ ${hint}` : "")
2366
2369
  // ── /index ────────────────────────────────────────────────────
2367
2370
  {
2368
2371
  name: "index",
2369
- description: "Manage symbol index (status/rebuild/clear) \u2014 used by find_symbol / get_outline / find_references",
2370
- usage: "/index [status | rebuild | clear]",
2372
+ description: "Manage symbol + semantic index (status/rebuild/clear/semantic-rebuild/semantic-clear)",
2373
+ usage: "/index [status | rebuild | clear | semantic-rebuild | semantic-clear]",
2371
2374
  async execute(args, ctx) {
2372
2375
  const sub = (args[0] ?? "status").toLowerCase();
2373
2376
  const root = process.cwd();
2374
2377
  const { loadIndex, clearIndex } = await import("./store-S24SPPDZ.js");
2375
2378
  const { indexProject } = await import("./indexer-C7QYYHSZ.js");
2379
+ const { loadVectorStore, clearVectorStore } = await import("./vector-store-YTVHACBV.js");
2376
2380
  if (sub === "status") {
2377
2381
  const idx = loadIndex(root);
2382
+ const vec = loadVectorStore(root);
2378
2383
  if (!idx) {
2379
2384
  console.log(theme.dim(` No index for ${root}.`));
2380
2385
  console.log(theme.dim(" Run `/index rebuild` to build one."));
@@ -2385,12 +2390,16 @@ ${hint}` : "")
2385
2390
  console.log(` Generated: ${idx.generated}`);
2386
2391
  console.log(` Files: ${idx.fileCount}`);
2387
2392
  console.log(` Symbols: ${idx.symbolCount}`);
2393
+ if (vec) {
2394
+ console.log(` Semantic: ${vec.count} vectors \xD7 ${vec.dim}-dim (run /index semantic-rebuild after major changes)`);
2395
+ } else {
2396
+ console.log(` Semantic: ${theme.dim("not built")} \u2014 run /index semantic-rebuild to enable search_code`);
2397
+ }
2388
2398
  console.log();
2389
2399
  return;
2390
2400
  }
2391
2401
  if (sub === "rebuild") {
2392
2402
  console.log(theme.dim(` Indexing ${root}\u2026`));
2393
- const start = Date.now();
2394
2403
  const { stats } = await indexProject(root, {
2395
2404
  force: true,
2396
2405
  onProgress: (done, total) => {
@@ -2408,10 +2417,46 @@ ${hint}` : "")
2408
2417
  }
2409
2418
  if (sub === "clear") {
2410
2419
  clearIndex(root);
2411
- console.log(theme.success(` \u2713 Cleared symbol index for ${root}`));
2420
+ clearVectorStore(root);
2421
+ console.log(theme.success(` \u2713 Cleared symbol + semantic index for ${root}`));
2422
+ return;
2423
+ }
2424
+ if (sub === "semantic-rebuild") {
2425
+ const idx = loadIndex(root);
2426
+ if (!idx) {
2427
+ ctx.renderer.renderError("No symbol index yet. Run `/index rebuild` first, then semantic-rebuild.");
2428
+ return;
2429
+ }
2430
+ console.log(theme.dim(` Building semantic index for ${idx.symbolCount} symbols\u2026`));
2431
+ console.log(theme.dim(" (First run downloads ~117 MB embedding model to ~/.aicli/models/)"));
2432
+ const { rebuildSemanticIndex } = await import("./semantic-RBWU76MD.js");
2433
+ try {
2434
+ const stats = await rebuildSemanticIndex(root, {
2435
+ onProgress: (done, total) => {
2436
+ const pct = total > 0 ? Math.floor(done / total * 100) : 0;
2437
+ process.stdout.write(theme.dim(`\r ${done}/${total} (${pct}%)\u2026`));
2438
+ }
2439
+ });
2440
+ process.stdout.write("\r\x1B[K");
2441
+ const first = stats.modelFirstLoadMs ? ` (model load+first batch ${stats.modelFirstLoadMs}ms)` : "";
2442
+ console.log(theme.success(
2443
+ ` \u2713 Embedded ${stats.symbolsEmbedded} symbols in ${stats.durationMs}ms${first}`
2444
+ ));
2445
+ } catch (err) {
2446
+ process.stdout.write("\r\x1B[K");
2447
+ const msg = err instanceof Error ? err.message : String(err);
2448
+ ctx.renderer.renderError(`Semantic rebuild failed: ${msg}`);
2449
+ }
2412
2450
  return;
2413
2451
  }
2414
- ctx.renderer.renderError(`Unknown subcommand: ${sub}. Use status/rebuild/clear.`);
2452
+ if (sub === "semantic-clear") {
2453
+ clearVectorStore(root);
2454
+ console.log(theme.success(` \u2713 Cleared semantic index (symbol index preserved)`));
2455
+ return;
2456
+ }
2457
+ ctx.renderer.renderError(
2458
+ `Unknown subcommand: ${sub}. Use status/rebuild/clear/semantic-rebuild/semantic-clear.`
2459
+ );
2415
2460
  }
2416
2461
  },
2417
2462
  // ── /commands ─────────────────────────────────────────────────
@@ -2450,7 +2495,7 @@ ${hint}` : "")
2450
2495
  usage: "/test [command|filter]",
2451
2496
  async execute(args, ctx) {
2452
2497
  try {
2453
- const { executeTests } = await import("./run-tests-MKKCDUUV.js");
2498
+ const { executeTests } = await import("./run-tests-GEZ4NPWJ.js");
2454
2499
  const argStr = args.join(" ").trim();
2455
2500
  let testArgs = {};
2456
2501
  if (argStr) {
@@ -6343,7 +6388,7 @@ program.command("web").description("Start Web UI server with browser-based chat
6343
6388
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
6344
6389
  process.exit(1);
6345
6390
  }
6346
- const { startWebServer } = await import("./server-6MPBAH4K.js");
6391
+ const { startWebServer } = await import("./server-CMSF65WV.js");
6347
6392
  await startWebServer({ port, host: options.host });
6348
6393
  });
6349
6394
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -6466,7 +6511,7 @@ program.command("sessions").description("List recent conversation sessions").act
6466
6511
  });
6467
6512
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
6468
6513
  try {
6469
- const batch = await import("./batch-5HJGW6NA.js");
6514
+ const batch = await import("./batch-RRC4RXZF.js");
6470
6515
  switch (action) {
6471
6516
  case "submit":
6472
6517
  if (!arg) {
@@ -6626,7 +6671,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
6626
6671
  }),
6627
6672
  config.get("customProviders")
6628
6673
  );
6629
- const { startHub } = await import("./hub-YPNEYO3Z.js");
6674
+ const { startHub } = await import("./hub-CZSXSOIH.js");
6630
6675
  await startHub(
6631
6676
  {
6632
6677
  topic: topic ?? "",
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-K3JJX2Z5.js";
6
- import "./chunk-2Q77FT3F.js";
5
+ } from "./chunk-QATT4NCL.js";
6
+ import "./chunk-S4WDPHKS.js";
7
7
  export {
8
8
  executeTests,
9
9
  runTestsTool
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-34NJTPWZ.js";
4
+ } from "./chunk-IR5EW57V.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -20,7 +20,10 @@ import {
20
20
  persistToolRound,
21
21
  rebuildExtraMessages,
22
22
  setupProxy
23
- } from "./chunk-XTH7S3AM.js";
23
+ } from "./chunk-5PZMIBEN.js";
24
+ import {
25
+ ConfigManager
26
+ } from "./chunk-LQ76WLB6.js";
24
27
  import {
25
28
  ToolExecutor,
26
29
  ToolRegistry,
@@ -38,18 +41,14 @@ import {
38
41
  spawnAgentContext,
39
42
  truncateOutput,
40
43
  undoStack
41
- } from "./chunk-KR4FTJWB.js";
44
+ } from "./chunk-LKDX2GOW.js";
45
+ import "./chunk-2ZD3YTVM.js";
42
46
  import "./chunk-4BKXL7SM.js";
43
47
  import "./chunk-NHNWUBXB.js";
48
+ import "./chunk-HPDDAXFY.js";
44
49
  import "./chunk-6VRJGH25.js";
45
- import "./chunk-K3JJX2Z5.js";
46
- import {
47
- AuthManager
48
- } from "./chunk-BYNY5JPB.js";
49
- import {
50
- ConfigManager
51
- } from "./chunk-H4DQNZZ6.js";
52
- import "./chunk-2ZD3YTVM.js";
50
+ import "./chunk-PFYAAX2S.js";
51
+ import "./chunk-QATT4NCL.js";
53
52
  import {
54
53
  AGENTIC_BEHAVIOR_GUIDELINE,
55
54
  AUTHOR,
@@ -68,7 +67,10 @@ import {
68
67
  SKILLS_DIR_NAME,
69
68
  VERSION,
70
69
  buildUserIdentityPrompt
71
- } from "./chunk-2Q77FT3F.js";
70
+ } from "./chunk-S4WDPHKS.js";
71
+ import {
72
+ AuthManager
73
+ } from "./chunk-BYNY5JPB.js";
72
74
 
73
75
  // src/web/server.ts
74
76
  import express from "express";
@@ -1481,7 +1483,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
1481
1483
  " /checkpoint [save|restore|delete] <name> \u2014 Session checkpoints",
1482
1484
  " /fork [checkpoint] \u2014 Fork session from checkpoint or current",
1483
1485
  " /branch [list|new|switch|delete|rename] \u2014 Manage conversation branches",
1484
- " /index [status|rebuild|clear] \u2014 Symbol index for find_symbol / get_outline / find_references",
1486
+ " /index [status|rebuild|clear|semantic-rebuild|semantic-clear] \u2014 Symbol + semantic index (find_symbol / search_code)",
1485
1487
  " /review [--staged] \u2014 AI code review from git diff",
1486
1488
  " /security-review \u2014 Security vulnerability scan on git diff",
1487
1489
  " /rewind [list|<n>] \u2014 Rewind conversation & restore files to checkpoint",
@@ -1907,11 +1909,14 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1907
1909
  const root = process.cwd();
1908
1910
  const { loadIndex, clearIndex } = await import("./store-S24SPPDZ.js");
1909
1911
  const { indexProject } = await import("./indexer-C7QYYHSZ.js");
1912
+ const { loadVectorStore, clearVectorStore } = await import("./vector-store-YTVHACBV.js");
1910
1913
  if (sub === "status") {
1911
1914
  const idx = loadIndex(root);
1915
+ const vec = loadVectorStore(root);
1912
1916
  if (!idx) {
1913
1917
  this.send({ type: "info", message: `No symbol index for ${root}. Run /index rebuild.` });
1914
1918
  } else {
1919
+ const semantic = vec ? `${vec.count} vectors \xD7 ${vec.dim}-dim` : "not built \u2014 run /index semantic-rebuild to enable search_code";
1915
1920
  this.send({
1916
1921
  type: "info",
1917
1922
  message: [
@@ -1919,7 +1924,8 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1919
1924
  ` Root: ${idx.root}`,
1920
1925
  ` Generated: ${idx.generated}`,
1921
1926
  ` Files: ${idx.fileCount}`,
1922
- ` Symbols: ${idx.symbolCount}`
1927
+ ` Symbols: ${idx.symbolCount}`,
1928
+ ` Semantic: ${semantic}`
1923
1929
  ].join("\n")
1924
1930
  });
1925
1931
  }
@@ -1932,9 +1938,38 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1932
1938
  });
1933
1939
  } else if (sub === "clear") {
1934
1940
  clearIndex(root);
1935
- this.send({ type: "info", message: `\u2713 Cleared symbol index for ${root}` });
1941
+ clearVectorStore(root);
1942
+ this.send({ type: "info", message: `\u2713 Cleared symbol + semantic index for ${root}` });
1943
+ } else if (sub === "semantic-rebuild") {
1944
+ const idx = loadIndex(root);
1945
+ if (!idx) {
1946
+ this.send({ type: "error", message: "No symbol index yet. Run /index rebuild first." });
1947
+ break;
1948
+ }
1949
+ this.send({
1950
+ type: "info",
1951
+ message: `Building semantic index for ${idx.symbolCount} symbols\u2026 (first run downloads ~117 MB model)`
1952
+ });
1953
+ try {
1954
+ const { rebuildSemanticIndex } = await import("./semantic-RBWU76MD.js");
1955
+ const stats = await rebuildSemanticIndex(root);
1956
+ const first = stats.modelFirstLoadMs ? ` (model load+first batch ${stats.modelFirstLoadMs}ms)` : "";
1957
+ this.send({
1958
+ type: "info",
1959
+ message: `\u2713 Embedded ${stats.symbolsEmbedded} symbols in ${stats.durationMs}ms${first}`
1960
+ });
1961
+ } catch (err) {
1962
+ const msg = err instanceof Error ? err.message : String(err);
1963
+ this.send({ type: "error", message: `Semantic rebuild failed: ${msg}` });
1964
+ }
1965
+ } else if (sub === "semantic-clear") {
1966
+ clearVectorStore(root);
1967
+ this.send({ type: "info", message: "\u2713 Cleared semantic index (symbol index preserved)" });
1936
1968
  } else {
1937
- this.send({ type: "error", message: `Unknown subcommand: ${sub}. Use status/rebuild/clear.` });
1969
+ this.send({
1970
+ type: "error",
1971
+ message: `Unknown subcommand: ${sub}. Use status/rebuild/clear/semantic-rebuild/semantic-clear.`
1972
+ });
1938
1973
  }
1939
1974
  break;
1940
1975
  }
@@ -2103,7 +2138,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2103
2138
  case "test": {
2104
2139
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2105
2140
  try {
2106
- const { executeTests } = await import("./run-tests-MKKCDUUV.js");
2141
+ const { executeTests } = await import("./run-tests-GEZ4NPWJ.js");
2107
2142
  const argStr = args.join(" ").trim();
2108
2143
  let testArgs = {};
2109
2144
  if (argStr) {
@@ -4,15 +4,17 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-KR4FTJWB.js";
7
+ } from "./chunk-LKDX2GOW.js";
8
+ import "./chunk-2ZD3YTVM.js";
8
9
  import "./chunk-4BKXL7SM.js";
9
10
  import "./chunk-NHNWUBXB.js";
11
+ import "./chunk-HPDDAXFY.js";
10
12
  import "./chunk-6VRJGH25.js";
11
- import "./chunk-K3JJX2Z5.js";
12
- import "./chunk-2ZD3YTVM.js";
13
+ import "./chunk-PFYAAX2S.js";
14
+ import "./chunk-QATT4NCL.js";
13
15
  import {
14
16
  SUBAGENT_ALLOWED_TOOLS
15
- } from "./chunk-2Q77FT3F.js";
17
+ } from "./chunk-S4WDPHKS.js";
16
18
 
17
19
  // src/hub/task-orchestrator.ts
18
20
  import { createInterface } from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.76",
3
+ "version": "0.4.77",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -88,6 +88,7 @@
88
88
  "dependencies": {
89
89
  "@anthropic-ai/sdk": "^0.39.0",
90
90
  "@google/generative-ai": "^0.24.0",
91
+ "@huggingface/transformers": "^4.1.0",
91
92
  "@inquirer/prompts": "^7.0.0",
92
93
  "chalk": "^5.4.1",
93
94
  "commander": "^13.0.0",