jinzd-ai-cli 0.4.76 → 0.4.78
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 +3 -1
- package/dist/batch-CDCTUTIQ.js +218 -0
- package/dist/{chunk-KR4FTJWB.js → chunk-EUE6VMGO.js} +73 -7
- package/dist/{chunk-H4DQNZZ6.js → chunk-FCVQ6OP5.js} +1 -1
- package/dist/{chunk-XTH7S3AM.js → chunk-GKDETNDH.js} +2 -2
- package/dist/{chunk-2Q77FT3F.js → chunk-HELGL4RH.js} +6 -3
- package/dist/chunk-HPDDAXFY.js +84 -0
- package/dist/{chunk-K3JJX2Z5.js → chunk-KHYHG2SO.js} +1 -1
- package/dist/chunk-PFYAAX2S.js +169 -0
- package/dist/{chunk-34NJTPWZ.js → chunk-TNAPORYF.js} +6 -3
- package/dist/chunk-UTCC3UMT.js +83 -0
- package/dist/chunk-XMA222FQ.js +167 -0
- package/dist/electron-server.js +12465 -0
- package/dist/file-checkpoint-CGH6OJVI.js +6 -0
- package/dist/file-checkpoint-NKBHGC7L.js +7 -0
- package/dist/{hub-YPNEYO3Z.js → hub-BIFM6NOM.js} +1 -1
- package/dist/index.js +64 -19
- package/dist/indexer-C7QYYHSZ.js +10 -0
- package/dist/indexer-O5FCGFBJ.js +9 -0
- package/dist/{run-tests-MKKCDUUV.js → run-tests-JBM4K5FO.js} +2 -2
- package/dist/{run-tests-2I5S24IH.js → run-tests-MONKXJBT.js} +1 -1
- package/dist/semantic-MD7HYPWZ.js +14 -0
- package/dist/semantic-RBWU76MD.js +15 -0
- package/dist/{server-6MPBAH4K.js → server-GCE5V4SL.js} +51 -16
- package/dist/store-247B3TAU.js +16 -0
- package/dist/store-S24SPPDZ.js +17 -0
- package/dist/{task-orchestrator-I5HPXTJY.js → task-orchestrator-YAWEIZE7.js} +6 -4
- package/dist/vector-store-UR7IARXB.js +14 -0
- package/dist/vector-store-YTVHACBV.js +15 -0
- package/package.json +3 -10
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
|
|
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 |
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ConfigManager
|
|
4
|
+
} from "./chunk-FCVQ6OP5.js";
|
|
5
|
+
import "./chunk-2ZD3YTVM.js";
|
|
6
|
+
import "./chunk-HELGL4RH.js";
|
|
7
|
+
|
|
8
|
+
// src/cli/batch.ts
|
|
9
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
10
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
function parseBatchInput(jsonl) {
|
|
13
|
+
const lines = jsonl.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
14
|
+
if (lines.length === 0) throw new Error("Input file is empty");
|
|
15
|
+
if (lines.length > 1e5) throw new Error(`Too many requests: ${lines.length} (Anthropic limit: 100000)`);
|
|
16
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17
|
+
const out = [];
|
|
18
|
+
for (let i = 0; i < lines.length; i++) {
|
|
19
|
+
const line = lines[i];
|
|
20
|
+
let obj;
|
|
21
|
+
try {
|
|
22
|
+
obj = JSON.parse(line);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
throw new Error(`Line ${i + 1}: invalid JSON (${err instanceof Error ? err.message : String(err)})`);
|
|
25
|
+
}
|
|
26
|
+
if (!obj || typeof obj !== "object") {
|
|
27
|
+
throw new Error(`Line ${i + 1}: expected JSON object`);
|
|
28
|
+
}
|
|
29
|
+
const rec = obj;
|
|
30
|
+
const customId = String(rec.customId ?? rec.custom_id ?? "");
|
|
31
|
+
if (!customId) throw new Error(`Line ${i + 1}: missing customId`);
|
|
32
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(customId)) {
|
|
33
|
+
throw new Error(`Line ${i + 1}: customId "${customId}" must be 1-64 chars of [A-Za-z0-9_-]`);
|
|
34
|
+
}
|
|
35
|
+
if (seen.has(customId)) throw new Error(`Line ${i + 1}: duplicate customId "${customId}"`);
|
|
36
|
+
seen.add(customId);
|
|
37
|
+
const messages = rec.messages;
|
|
38
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
39
|
+
throw new Error(`Line ${i + 1}: messages must be a non-empty array`);
|
|
40
|
+
}
|
|
41
|
+
for (let j = 0; j < messages.length; j++) {
|
|
42
|
+
const m = messages[j];
|
|
43
|
+
if (!m || m.role !== "user" && m.role !== "assistant") {
|
|
44
|
+
throw new Error(`Line ${i + 1}: messages[${j}].role must be 'user' or 'assistant'`);
|
|
45
|
+
}
|
|
46
|
+
if (typeof m.content !== "string") {
|
|
47
|
+
throw new Error(`Line ${i + 1}: messages[${j}].content must be a string`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
out.push({
|
|
51
|
+
customId,
|
|
52
|
+
messages,
|
|
53
|
+
model: typeof rec.model === "string" ? rec.model : void 0,
|
|
54
|
+
system: typeof rec.system === "string" ? rec.system : void 0,
|
|
55
|
+
maxTokens: typeof rec.maxTokens === "number" ? rec.maxTokens : typeof rec.max_tokens === "number" ? rec.max_tokens : void 0
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
function getBatchesPath(config) {
|
|
61
|
+
return join(config.getConfigDir(), "batches.json");
|
|
62
|
+
}
|
|
63
|
+
function loadTrackedBatches(config) {
|
|
64
|
+
const p = getBatchesPath(config);
|
|
65
|
+
if (!existsSync(p)) return [];
|
|
66
|
+
try {
|
|
67
|
+
const data = JSON.parse(readFileSync(p, "utf-8"));
|
|
68
|
+
return Array.isArray(data.batches) ? data.batches : [];
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function saveTrackedBatches(config, batches) {
|
|
74
|
+
mkdirSync(config.getConfigDir(), { recursive: true });
|
|
75
|
+
writeFileSync(getBatchesPath(config), JSON.stringify({ batches }, null, 2), "utf-8");
|
|
76
|
+
}
|
|
77
|
+
function getClaudeClient(config) {
|
|
78
|
+
const apiKey = config.getApiKey("claude");
|
|
79
|
+
if (!apiKey) {
|
|
80
|
+
throw new Error("No Claude API key configured. Run `aicli config` or set AICLI_API_KEY_CLAUDE.");
|
|
81
|
+
}
|
|
82
|
+
return new Anthropic({ apiKey });
|
|
83
|
+
}
|
|
84
|
+
function getDefaultModel(config) {
|
|
85
|
+
const models = config.get("defaultModels");
|
|
86
|
+
return models?.claude ?? "claude-sonnet-4-5-20250929";
|
|
87
|
+
}
|
|
88
|
+
async function cmdSubmit(inputFile, options = {}) {
|
|
89
|
+
const config = new ConfigManager();
|
|
90
|
+
if (!existsSync(inputFile)) throw new Error(`Input file not found: ${inputFile}`);
|
|
91
|
+
const raw = readFileSync(inputFile, "utf-8");
|
|
92
|
+
const requests = parseBatchInput(raw);
|
|
93
|
+
const defaultModel = getDefaultModel(config);
|
|
94
|
+
console.log(`Parsed ${requests.length} request(s) from ${inputFile}`);
|
|
95
|
+
console.log(`Default model: ${defaultModel}`);
|
|
96
|
+
if (options.dryRun) {
|
|
97
|
+
console.log("[dry-run] Not submitting. Sample request:");
|
|
98
|
+
console.log(JSON.stringify(requests[0], null, 2));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const client = getClaudeClient(config);
|
|
102
|
+
const apiRequests = requests.map((r) => ({
|
|
103
|
+
custom_id: r.customId,
|
|
104
|
+
params: {
|
|
105
|
+
model: r.model ?? defaultModel,
|
|
106
|
+
max_tokens: r.maxTokens ?? 4096,
|
|
107
|
+
messages: r.messages,
|
|
108
|
+
...r.system ? { system: r.system } : {}
|
|
109
|
+
}
|
|
110
|
+
}));
|
|
111
|
+
console.log(`Submitting ${apiRequests.length} request(s) to Anthropic Batches API\u2026`);
|
|
112
|
+
const batch = await client.messages.batches.create({ requests: apiRequests });
|
|
113
|
+
const tracked = loadTrackedBatches(config);
|
|
114
|
+
tracked.unshift({
|
|
115
|
+
id: batch.id,
|
|
116
|
+
provider: "claude",
|
|
117
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
118
|
+
inputFile,
|
|
119
|
+
requestCount: requests.length,
|
|
120
|
+
model: defaultModel
|
|
121
|
+
});
|
|
122
|
+
saveTrackedBatches(config, tracked.slice(0, 200));
|
|
123
|
+
console.log(`
|
|
124
|
+
Batch submitted:`);
|
|
125
|
+
console.log(` ID: ${batch.id}`);
|
|
126
|
+
console.log(` Processing: ${batch.processing_status}`);
|
|
127
|
+
console.log(` Expires: ${batch.expires_at}`);
|
|
128
|
+
console.log(`
|
|
129
|
+
Check status: aicli batch status ${batch.id}`);
|
|
130
|
+
console.log(`Get results: aicli batch results ${batch.id}`);
|
|
131
|
+
}
|
|
132
|
+
async function cmdList() {
|
|
133
|
+
const config = new ConfigManager();
|
|
134
|
+
const tracked = loadTrackedBatches(config);
|
|
135
|
+
if (tracked.length === 0) {
|
|
136
|
+
console.log("No tracked batches. Submit one with `aicli batch submit <input.jsonl>`.");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const client = getClaudeClient(config);
|
|
140
|
+
console.log(`
|
|
141
|
+
${tracked.length} tracked batch(es):
|
|
142
|
+
`);
|
|
143
|
+
console.log(" ID Requests Status Created");
|
|
144
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
145
|
+
for (const b of tracked.slice(0, 20)) {
|
|
146
|
+
let status = "(unknown)";
|
|
147
|
+
try {
|
|
148
|
+
const fresh = await client.messages.batches.retrieve(b.id);
|
|
149
|
+
status = fresh.processing_status;
|
|
150
|
+
} catch {
|
|
151
|
+
status = "(fetch-failed)";
|
|
152
|
+
}
|
|
153
|
+
const created = new Date(b.createdAt).toLocaleString();
|
|
154
|
+
console.log(` ${b.id.padEnd(42)} ${String(b.requestCount).padStart(8)} ${status.padEnd(18)} ${created}`);
|
|
155
|
+
}
|
|
156
|
+
console.log("");
|
|
157
|
+
}
|
|
158
|
+
async function cmdStatus(batchId) {
|
|
159
|
+
const config = new ConfigManager();
|
|
160
|
+
const client = getClaudeClient(config);
|
|
161
|
+
const batch = await client.messages.batches.retrieve(batchId);
|
|
162
|
+
console.log(`
|
|
163
|
+
Batch ${batch.id}`);
|
|
164
|
+
console.log(` Processing: ${batch.processing_status}`);
|
|
165
|
+
console.log(` Created: ${batch.created_at}`);
|
|
166
|
+
console.log(` Expires: ${batch.expires_at}`);
|
|
167
|
+
if (batch.ended_at) console.log(` Ended: ${batch.ended_at}`);
|
|
168
|
+
const counts = batch.request_counts;
|
|
169
|
+
if (counts) {
|
|
170
|
+
console.log(` Counts: processing=${counts.processing} succeeded=${counts.succeeded} errored=${counts.errored} canceled=${counts.canceled} expired=${counts.expired}`);
|
|
171
|
+
}
|
|
172
|
+
if (batch.results_url) console.log(` Results URL: ${batch.results_url}`);
|
|
173
|
+
console.log("");
|
|
174
|
+
}
|
|
175
|
+
async function cmdResults(batchId, outPath) {
|
|
176
|
+
const config = new ConfigManager();
|
|
177
|
+
const client = getClaudeClient(config);
|
|
178
|
+
const batch = await client.messages.batches.retrieve(batchId);
|
|
179
|
+
if (batch.processing_status !== "ended") {
|
|
180
|
+
console.log(`Batch is not ready yet (status: ${batch.processing_status}). Try again later.`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const stream = await client.messages.batches.results(batchId);
|
|
184
|
+
const lines = [];
|
|
185
|
+
let ok = 0, err = 0;
|
|
186
|
+
for await (const result of stream) {
|
|
187
|
+
lines.push(JSON.stringify(result));
|
|
188
|
+
const type = result.result?.type;
|
|
189
|
+
if (type === "succeeded") ok++;
|
|
190
|
+
else err++;
|
|
191
|
+
}
|
|
192
|
+
const body = lines.join("\n") + "\n";
|
|
193
|
+
if (outPath) {
|
|
194
|
+
writeFileSync(outPath, body, "utf-8");
|
|
195
|
+
console.log(`Saved ${lines.length} result(s) to ${outPath} (${ok} succeeded, ${err} failed/errored).`);
|
|
196
|
+
} else {
|
|
197
|
+
process.stdout.write(body);
|
|
198
|
+
process.stderr.write(`
|
|
199
|
+
[Summary] ${lines.length} results: ${ok} succeeded, ${err} failed/errored.
|
|
200
|
+
`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function cmdCancel(batchId) {
|
|
204
|
+
const config = new ConfigManager();
|
|
205
|
+
const client = getClaudeClient(config);
|
|
206
|
+
const batch = await client.messages.batches.cancel(batchId);
|
|
207
|
+
console.log(`Canceled batch ${batch.id} (status: ${batch.processing_status}).`);
|
|
208
|
+
}
|
|
209
|
+
export {
|
|
210
|
+
cmdCancel,
|
|
211
|
+
cmdList,
|
|
212
|
+
cmdResults,
|
|
213
|
+
cmdStatus,
|
|
214
|
+
cmdSubmit,
|
|
215
|
+
loadTrackedBatches,
|
|
216
|
+
parseBatchInput,
|
|
217
|
+
saveTrackedBatches
|
|
218
|
+
};
|
|
@@ -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-
|
|
14
|
-
import {
|
|
15
|
-
EnvLoader,
|
|
16
|
-
NetworkError,
|
|
17
|
-
ToolError
|
|
18
|
-
} from "./chunk-2ZD3YTVM.js";
|
|
22
|
+
} from "./chunk-KHYHG2SO.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-
|
|
30
|
+
} from "./chunk-HELGL4RH.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);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
schemaToJsonSchema,
|
|
4
4
|
truncateForPersist
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-EUE6VMGO.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-
|
|
21
|
+
} from "./chunk-HELGL4RH.js";
|
|
22
22
|
|
|
23
23
|
// src/providers/claude.ts
|
|
24
24
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -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.78";
|
|
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,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
|
+
};
|
|
@@ -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
|
+
};
|