@huicai/cli 2026.711.0 → 2026.711.24616
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/LICENSE +43 -21
- package/package.json +4 -3
- package/src/advisor.js +1 -46
- package/src/agent.js +1 -518
- package/src/api.js +1 -359
- package/src/autofix.js +1 -90
- package/src/banner.js +1 -83
- package/src/checkpoints.js +1 -99
- package/src/clipboard.js +1 -34
- package/src/codeindex.js +1 -229
- package/src/colors.js +1 -47
- package/src/commands.js +1 -1294
- package/src/compaction.js +1 -144
- package/src/config.js +1 -86
- package/src/consult.js +1 -65
- package/src/context-report.js +1 -119
- package/src/context.js +1 -152
- package/src/cron.js +1 -64
- package/src/daemon.js +1 -62
- package/src/device.js +1 -88
- package/src/diffview.js +1 -115
- package/src/doctor.js +1 -72
- package/src/executors.js +1 -391
- package/src/forks.js +1 -59
- package/src/gitflow.js +1 -140
- package/src/hooks.js +1 -207
- package/src/ide.js +1 -53
- package/src/input.js +1 -182
- package/src/insights.js +1 -102
- package/src/kbsync.js +1 -141
- package/src/markdown.js +0 -0
- package/src/markup-filter.js +1 -85
- package/src/mcp.js +1 -207
- package/src/memory.js +1 -161
- package/src/permissions.js +1 -120
- package/src/render.js +1 -138
- package/src/repl.js +1 -271
- package/src/scan.js +1 -84
- package/src/schedule.js +1 -48
- package/src/session.js +1 -140
- package/src/settings.js +0 -0
- package/src/skills.js +1 -248
- package/src/subagent.js +1 -183
- package/src/todo.js +1 -75
- package/src/tools.js +1 -147
- package/src/transcript.js +1 -94
- package/src/tui/app.js +1 -666
- package/src/usage.js +1 -43
- package/src/watch.js +1 -74
package/src/clipboard.js
CHANGED
|
@@ -1,34 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// 零依赖,best-effort:命令缺失或失败时返回 {ok:false},由调用方回退(如直接打印内容)。
|
|
3
|
-
|
|
4
|
-
import { spawn } from 'node:child_process';
|
|
5
|
-
|
|
6
|
-
/** 选取本平台的剪贴板写入命令。 */
|
|
7
|
-
function clipboardCmd() {
|
|
8
|
-
if (process.platform === 'darwin') return { cmd: 'pbcopy', args: [] };
|
|
9
|
-
if (process.platform === 'win32') return { cmd: 'clip', args: [] };
|
|
10
|
-
if (process.env.WAYLAND_DISPLAY) return { cmd: 'wl-copy', args: [] };
|
|
11
|
-
return { cmd: 'xclip', args: ['-selection', 'clipboard'] };
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** 把文本写入系统剪贴板。返回 {ok, tool}。 */
|
|
15
|
-
export function copyToClipboard(text) {
|
|
16
|
-
const { cmd, args } = clipboardCmd();
|
|
17
|
-
return new Promise((resolve) => {
|
|
18
|
-
let child;
|
|
19
|
-
try {
|
|
20
|
-
child = spawn(cmd, args);
|
|
21
|
-
} catch {
|
|
22
|
-
resolve({ ok: false, tool: cmd });
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
child.on('error', () => resolve({ ok: false, tool: cmd }));
|
|
26
|
-
child.on('close', (code) => resolve({ ok: code === 0, tool: cmd }));
|
|
27
|
-
child.stdin.on('error', () => {}); // 命令不存在时 stdin 写入会报错,已由 error/close 处理
|
|
28
|
-
try {
|
|
29
|
-
child.stdin.end(String(text));
|
|
30
|
-
} catch {
|
|
31
|
-
resolve({ ok: false, tool: cmd });
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
}
|
|
1
|
+
import{spawn as o}from"node:child_process";export function copyToClipboard(r){const{cmd:c,args:t}="darwin"===process.platform?{cmd:"pbcopy",args:[]}:"win32"===process.platform?{cmd:"clip",args:[]}:process.env.WAYLAND_DISPLAY?{cmd:"wl-copy",args:[]}:{cmd:"xclip",args:["-selection","clipboard"]};return new Promise(s=>{let e;try{e=o(c,t)}catch{return void s({ok:!1,tool:c})}e.on("error",()=>s({ok:!1,tool:c})),e.on("close",o=>s({ok:0===o,tool:c})),e.stdin.on("error",()=>{});try{e.stdin.end(String(r))}catch{s({ok:!1,tool:c})}})}
|
package/src/codeindex.js
CHANGED
|
@@ -1,229 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// 按「概念/多词查询」检索最相关的代码块——比 grep(纯词法逐行)更懂「相关性」,且零依赖。
|
|
3
|
-
// 可选:若平台提供 /v1/embeddings,则对候选块做向量重排,逼近真语义(api.embed 失败自动退化为纯 BM25)。
|
|
4
|
-
// 索引按 cwd 缓存于内存,过期或首次使用时惰性重建。
|
|
5
|
-
|
|
6
|
-
import fs from 'node:fs';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import { embed } from './api.js';
|
|
9
|
-
|
|
10
|
-
export const IGNORE = new Set([
|
|
11
|
-
'.git', 'node_modules', 'dist', 'build', 'target', 'out', '.next', '.nuxt',
|
|
12
|
-
'.venv', 'venv', '__pycache__', '.idea', '.gradle', '.mvn', 'coverage', '.cache',
|
|
13
|
-
]);
|
|
14
|
-
const CODE_EXT = new Set([
|
|
15
|
-
'.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.java', '.kt', '.py', '.go', '.rs',
|
|
16
|
-
'.rb', '.php', '.c', '.h', '.cpp', '.hpp', '.cs', '.swift', '.scala', '.sql',
|
|
17
|
-
'.vue', '.svelte', '.sh', '.css', '.scss', '.html', '.md', '.yaml', '.yml', '.json', '.toml',
|
|
18
|
-
]);
|
|
19
|
-
const WINDOW = 50; // 每块行数
|
|
20
|
-
const STEP = 40; // 步长(10 行重叠,避免边界切断)
|
|
21
|
-
const MAX_CHUNKS = 6000;
|
|
22
|
-
export const MAX_FILE_BYTES = 512 * 1024;
|
|
23
|
-
const CACHE_TTL = 5 * 60 * 1000;
|
|
24
|
-
|
|
25
|
-
const cache = new Map(); // cwd -> { index, builtAt }
|
|
26
|
-
|
|
27
|
-
/** 代码分词:驼峰拆分 + 非字母数字切分 + 小写(拉丁);CJK 出单字(召回)+相邻二元(精度)。
|
|
28
|
-
* 索引与查询共用同一分词,故中文注释/中文查询也能命中(代码常是中文注释 + 英文标识符混排)。 */
|
|
29
|
-
export function tokenize(s) {
|
|
30
|
-
const str = String(s);
|
|
31
|
-
const tokens = str
|
|
32
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
33
|
-
.toLowerCase()
|
|
34
|
-
.split(/[^a-z0-9_]+/)
|
|
35
|
-
.filter((t) => t.length >= 2 && t.length <= 40);
|
|
36
|
-
const singles = str.match(/[一-鿿]/g);
|
|
37
|
-
if (singles) for (const ch of singles) tokens.push(ch);
|
|
38
|
-
const runs = str.match(/[一-鿿]{2,}/g);
|
|
39
|
-
if (runs) for (const run of runs) for (let i = 0; i < run.length - 1; i++) tokens.push(run.slice(i, i + 2));
|
|
40
|
-
return tokens;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function* walk(dir, depth = 0, maxDepth = 14) {
|
|
44
|
-
let entries;
|
|
45
|
-
try {
|
|
46
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
47
|
-
} catch {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
for (const e of entries) {
|
|
51
|
-
if (IGNORE.has(e.name) || e.name.startsWith('.')) continue;
|
|
52
|
-
const full = path.join(dir, e.name);
|
|
53
|
-
if (e.isDirectory()) {
|
|
54
|
-
if (depth < maxDepth) yield* walk(full, depth + 1, maxDepth);
|
|
55
|
-
} else if (e.isFile() && CODE_EXT.has(path.extname(e.name).toLowerCase())) {
|
|
56
|
-
yield full;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** 构建 BM25 索引(惰性、可缓存)。 */
|
|
62
|
-
export function buildIndex(cwd) {
|
|
63
|
-
const chunks = [];
|
|
64
|
-
const df = new Map();
|
|
65
|
-
let totalLen = 0;
|
|
66
|
-
outer: for (const abs of walk(cwd)) {
|
|
67
|
-
let text;
|
|
68
|
-
try {
|
|
69
|
-
const st = fs.statSync(abs);
|
|
70
|
-
if (st.size > MAX_FILE_BYTES) continue;
|
|
71
|
-
const buf = fs.readFileSync(abs);
|
|
72
|
-
if (buf.includes(0)) continue; // 二进制
|
|
73
|
-
text = buf.toString('utf8');
|
|
74
|
-
} catch {
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
const rel = path.relative(cwd, abs).split(path.sep).join('/');
|
|
78
|
-
const lines = text.split('\n');
|
|
79
|
-
for (let start = 0; start < lines.length; start += STEP) {
|
|
80
|
-
const end = Math.min(lines.length, start + WINDOW);
|
|
81
|
-
const slice = lines.slice(start, end);
|
|
82
|
-
const tokens = tokenize(slice.join('\n'));
|
|
83
|
-
if (!tokens.length) continue;
|
|
84
|
-
const tf = new Map();
|
|
85
|
-
for (const t of tokens) tf.set(t, (tf.get(t) || 0) + 1);
|
|
86
|
-
for (const t of tf.keys()) df.set(t, (df.get(t) || 0) + 1);
|
|
87
|
-
chunks.push({ file: rel, start: start + 1, end, tf, len: tokens.length, text: slice.join('\n') });
|
|
88
|
-
totalLen += tokens.length;
|
|
89
|
-
if (chunks.length >= MAX_CHUNKS) break outer;
|
|
90
|
-
if (end >= lines.length) break;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return { chunks, df, N: chunks.length, avgLen: chunks.length ? totalLen / chunks.length : 1 };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** 取(缓存的)索引。 */
|
|
97
|
-
export function getIndex(cwd) {
|
|
98
|
-
const hit = cache.get(cwd);
|
|
99
|
-
if (hit && Date.now() - hit.builtAt < CACHE_TTL) return hit.index;
|
|
100
|
-
const index = buildIndex(cwd);
|
|
101
|
-
cache.set(cwd, { index, builtAt: Date.now() });
|
|
102
|
-
return index;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** 使某目录索引失效(写文件后可调用;此处保守,仅供扩展)。 */
|
|
106
|
-
export function invalidate(cwd) {
|
|
107
|
-
cache.delete(cwd);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** BM25 检索,返回前 k 个相关代码块。 */
|
|
111
|
-
export function search(index, query, k = 8) {
|
|
112
|
-
const qtokens = [...new Set(tokenize(query))];
|
|
113
|
-
if (!qtokens.length || !index.N) return [];
|
|
114
|
-
const { chunks, df, N, avgLen } = index;
|
|
115
|
-
const k1 = 1.5;
|
|
116
|
-
const b = 0.75;
|
|
117
|
-
const scored = [];
|
|
118
|
-
for (const c of chunks) {
|
|
119
|
-
let score = 0;
|
|
120
|
-
for (const qt of qtokens) {
|
|
121
|
-
const f = c.tf.get(qt);
|
|
122
|
-
if (!f) continue;
|
|
123
|
-
const n = df.get(qt) || 1;
|
|
124
|
-
const idf = Math.log(1 + (N - n + 0.5) / (n + 0.5));
|
|
125
|
-
score += idf * ((f * (k1 + 1)) / (f + k1 * (1 - b + b * (c.len / avgLen))));
|
|
126
|
-
}
|
|
127
|
-
if (score > 0) scored.push({ file: c.file, start: c.start, end: c.end, text: c.text, score });
|
|
128
|
-
}
|
|
129
|
-
scored.sort((a, b2) => b2.score - a.score);
|
|
130
|
-
return scored.slice(0, k);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** 余弦相似度。 */
|
|
134
|
-
export function cosine(a, b) {
|
|
135
|
-
if (!a || !b) return 0;
|
|
136
|
-
let dot = 0;
|
|
137
|
-
let na = 0;
|
|
138
|
-
let nb = 0;
|
|
139
|
-
const n = Math.min(a.length, b.length);
|
|
140
|
-
for (let i = 0; i < n; i++) {
|
|
141
|
-
dot += a[i] * b[i];
|
|
142
|
-
na += a[i] * a[i];
|
|
143
|
-
nb += b[i] * b[i];
|
|
144
|
-
}
|
|
145
|
-
return na && nb ? dot / (Math.sqrt(na) * Math.sqrt(nb)) : 0;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* search_code 工具执行器:BM25 召回 + (配置了嵌入模型时)向量语义重排,格式化为「文件:行范围 + 代码」。
|
|
150
|
-
* 配了 config.embeddingModel 才走 /v1/embeddings 做重排(真语义);未配或向量化失败则纯 BM25(离线可用)。
|
|
151
|
-
*/
|
|
152
|
-
export async function searchCode(cwd, args, config) {
|
|
153
|
-
const query = String(args?.query || '').trim();
|
|
154
|
-
if (!query) return '缺少 query(要检索的概念/功能描述)';
|
|
155
|
-
const k = Number.isFinite(args?.k) && args.k > 0 ? Math.min(args.k, 20) : 8;
|
|
156
|
-
const index = getIndex(cwd);
|
|
157
|
-
if (!index.N) return '(当前目录未索引到代码文件)';
|
|
158
|
-
const rerank = !!(config && config.embeddingModel && config.baseUrl && config.apiKey);
|
|
159
|
-
const recall = rerank ? Math.min(30, Math.max(k * 3, 15)) : k; // 重排时多召回候选
|
|
160
|
-
let hits = search(index, query, recall);
|
|
161
|
-
if (!hits.length) return `(未找到与「${query}」明显相关的代码;可换关键词,或用 grep 精确匹配)`;
|
|
162
|
-
let reranked = false;
|
|
163
|
-
if (rerank && hits.length > 1) {
|
|
164
|
-
try {
|
|
165
|
-
const texts = hits.map((h) => h.text.slice(0, 2000));
|
|
166
|
-
const vecs = await embed(config.baseUrl, config.apiKey, config.embeddingModel, [query, ...texts]);
|
|
167
|
-
if (vecs && vecs.length === texts.length + 1 && vecs[0]) {
|
|
168
|
-
const qv = vecs[0];
|
|
169
|
-
hits = hits.map((h, i) => ({ ...h, score: cosine(qv, vecs[i + 1]) })).sort((a, b) => b.score - a.score);
|
|
170
|
-
reranked = true;
|
|
171
|
-
}
|
|
172
|
-
} catch {
|
|
173
|
-
/* 向量化失败 → 退化为 BM25 排序,不影响可用性 */
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
const head = reranked ? '(向量语义重排)\n' : '';
|
|
177
|
-
return head + hits.slice(0, k).map((h) => {
|
|
178
|
-
const body = h.text.length > 1200 ? h.text.slice(0, 1200) + '\n…(本块过长已截断)' : h.text;
|
|
179
|
-
return `${h.file}:${h.start}-${h.end}\n${body}`;
|
|
180
|
-
}).join('\n\n─────\n\n');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** 提取一段代码里的关键符号名(函数/类/def/导出/顶层箭头常量),供项目地图用。 */
|
|
184
|
-
function extractSymbols(text) {
|
|
185
|
-
const syms = new Set();
|
|
186
|
-
const res = [
|
|
187
|
-
/(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_]\w*)/g,
|
|
188
|
-
/\bclass\s+([A-Za-z_]\w*)/g,
|
|
189
|
-
/\bdef\s+([A-Za-z_]\w*)/g,
|
|
190
|
-
/\b(?:export\s+)?const\s+([A-Za-z_]\w*)\s*=\s*(?:async\s*)?(?:function|\([^)]*\)\s*=>)/g,
|
|
191
|
-
/@(?:Get|Post|Put|Delete|Request)Mapping\(\s*"([^"]+)"/g,
|
|
192
|
-
/\bpublic\s+[\w<>[\]]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
193
|
-
];
|
|
194
|
-
for (const re of res) {
|
|
195
|
-
let m;
|
|
196
|
-
while ((m = re.exec(text)) && syms.size < 12) syms.add(m[1]);
|
|
197
|
-
}
|
|
198
|
-
return [...syms];
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* 项目地图:关键源文件 + 其主要符号的紧凑概览(注入系统提示词,帮智能体快速建立全局观)。
|
|
203
|
-
* 按文件大小近似「重要性」取 top N,控制总字符数。
|
|
204
|
-
*/
|
|
205
|
-
export function buildRepoMap(cwd, { maxFiles = 40, maxChars = 2500 } = {}) {
|
|
206
|
-
const files = [];
|
|
207
|
-
for (const abs of walk(cwd)) {
|
|
208
|
-
try {
|
|
209
|
-
const st = fs.statSync(abs);
|
|
210
|
-
if (st.size > MAX_FILE_BYTES) continue;
|
|
211
|
-
files.push({ abs, size: st.size, rel: path.relative(cwd, abs).split(path.sep).join('/') });
|
|
212
|
-
} catch { /* skip */ }
|
|
213
|
-
}
|
|
214
|
-
if (!files.length) return '';
|
|
215
|
-
files.sort((a, b) => b.size - a.size);
|
|
216
|
-
const lines = [];
|
|
217
|
-
let used = 0;
|
|
218
|
-
for (const f of files.slice(0, maxFiles)) {
|
|
219
|
-
let syms = [];
|
|
220
|
-
try {
|
|
221
|
-
syms = extractSymbols(fs.readFileSync(f.abs, 'utf8').slice(0, 40000));
|
|
222
|
-
} catch { /* skip */ }
|
|
223
|
-
const line = `- ${f.rel}${syms.length ? ':' + syms.slice(0, 8).join(', ') : ''}`;
|
|
224
|
-
if (used + line.length > maxChars) break;
|
|
225
|
-
lines.push(line);
|
|
226
|
-
used += line.length + 1;
|
|
227
|
-
}
|
|
228
|
-
return lines.join('\n');
|
|
229
|
-
}
|
|
1
|
+
import t from"node:fs";import e from"node:path";import{embed as n}from"./api.js";export const IGNORE=new Set([".git","node_modules","dist","build","target","out",".next",".nuxt",".venv","venv","__pycache__",".idea",".gradle",".mvn","coverage",".cache"]);const s=new Set([".js",".mjs",".cjs",".ts",".tsx",".jsx",".java",".kt",".py",".go",".rs",".rb",".php",".c",".h",".cpp",".hpp",".cs",".swift",".scala",".sql",".vue",".svelte",".sh",".css",".scss",".html",".md",".yaml",".yml",".json",".toml"]);export const MAX_FILE_BYTES=524288;const o=new Map;export function tokenize(t){const e=String(t),n=e.replace(/([a-z0-9])([A-Z])/g,"$1 $2").toLowerCase().split(/[^a-z0-9_]+/).filter(t=>t.length>=2&&t.length<=40),s=e.match(/[一-鿿]/g);if(s)for(const t of s)n.push(t);const o=e.match(/[一-鿿]{2,}/g);if(o)for(const t of o)for(let e=0;e<t.length-1;e++)n.push(t.slice(e,e+2));return n}function*i(n,o=0,r=14){let c;try{c=t.readdirSync(n,{withFileTypes:!0})}catch{return}for(const t of c){if(IGNORE.has(t.name)||t.name.startsWith("."))continue;const c=e.join(n,t.name);t.isDirectory()?o<r&&(yield*i(c,o+1,r)):t.isFile()&&s.has(e.extname(t.name).toLowerCase())&&(yield c)}}export function buildIndex(n){const s=[],o=new Map;let r=0;t:for(const c of i(n)){let i;try{if(t.statSync(c).size>524288)continue;const e=t.readFileSync(c);if(e.includes(0))continue;i=e.toString("utf8")}catch{continue}const l=e.relative(n,c).split(e.sep).join("/"),a=i.split("\n");for(let t=0;t<a.length;t+=40){const e=Math.min(a.length,t+50),n=a.slice(t,e),i=tokenize(n.join("\n"));if(!i.length)continue;const c=new Map;for(const t of i)c.set(t,(c.get(t)||0)+1);for(const t of c.keys())o.set(t,(o.get(t)||0)+1);if(s.push({file:l,start:t+1,end:e,tf:c,len:i.length,text:n.join("\n")}),r+=i.length,s.length>=6e3)break t;if(e>=a.length)break}}return{chunks:s,df:o,N:s.length,avgLen:s.length?r/s.length:1}}export function getIndex(t){const e=o.get(t);if(e&&Date.now()-e.builtAt<3e5)return e.index;const n=buildIndex(t);return o.set(t,{index:n,builtAt:Date.now()}),n}export function invalidate(t){o.delete(t)}export function search(t,e,n=8){const s=[...new Set(tokenize(e))];if(!s.length||!t.N)return[];const{chunks:o,df:i,N:r,avgLen:c}=t,l=[];for(const t of o){let e=0;for(const n of s){const s=t.tf.get(n);if(!s)continue;const o=i.get(n)||1;e+=Math.log(1+(r-o+.5)/(o+.5))*(2.5*s/(s+1.5*(.25+t.len/c*.75)))}e>0&&l.push({file:t.file,start:t.start,end:t.end,text:t.text,score:e})}return l.sort((t,e)=>e.score-t.score),l.slice(0,n)}export function cosine(t,e){if(!t||!e)return 0;let n=0,s=0,o=0;const i=Math.min(t.length,e.length);for(let r=0;r<i;r++)n+=t[r]*e[r],s+=t[r]*t[r],o+=e[r]*e[r];return s&&o?n/(Math.sqrt(s)*Math.sqrt(o)):0}export async function searchCode(t,e,s){const o=String(e?.query||"").trim();if(!o)return"缺少 query(要检索的概念/功能描述)";const i=Number.isFinite(e?.k)&&e.k>0?Math.min(e.k,20):8,r=getIndex(t);if(!r.N)return"(当前目录未索引到代码文件)";const c=!!(s&&s.embeddingModel&&s.baseUrl&&s.apiKey);let l=search(r,o,c?Math.min(30,Math.max(3*i,15)):i);if(!l.length)return`(未找到与「${o}」明显相关的代码;可换关键词,或用 grep 精确匹配)`;let a=!1;if(c&&l.length>1)try{const t=l.map(t=>t.text.slice(0,2e3)),e=await n(s.baseUrl,s.apiKey,s.embeddingModel,[o,...t]);if(e&&e.length===t.length+1&&e[0]){const t=e[0];l=l.map((n,s)=>({...n,score:cosine(t,e[s+1])})).sort((t,e)=>e.score-t.score),a=!0}}catch{}return(a?"(向量语义重排)\n":"")+l.slice(0,i).map(t=>{const e=t.text.length>1200?t.text.slice(0,1200)+"\n…(本块过长已截断)":t.text;return`${t.file}:${t.start}-${t.end}\n${e}`}).join("\n\n─────\n\n")}function r(t){const e=new Set,n=[/(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_]\w*)/g,/\bclass\s+([A-Za-z_]\w*)/g,/\bdef\s+([A-Za-z_]\w*)/g,/\b(?:export\s+)?const\s+([A-Za-z_]\w*)\s*=\s*(?:async\s*)?(?:function|\([^)]*\)\s*=>)/g,/@(?:Get|Post|Put|Delete|Request)Mapping\(\s*"([^"]+)"/g,/\bpublic\s+[\w<>[\]]+\s+([A-Za-z_]\w*)\s*\(/g];for(const s of n){let n;for(;(n=s.exec(t))&&e.size<12;)e.add(n[1])}return[...e]}export function buildRepoMap(n,{maxFiles:s=40,maxChars:o=2500}={}){const c=[];for(const s of i(n))try{const o=t.statSync(s);if(o.size>524288)continue;c.push({abs:s,size:o.size,rel:e.relative(n,s).split(e.sep).join("/")})}catch{}if(!c.length)return"";c.sort((t,e)=>e.size-t.size);const l=[];let a=0;for(const e of c.slice(0,s)){let n=[];try{n=r(t.readFileSync(e.abs,"utf8").slice(0,4e4))}catch{}const s=`- ${e.rel}${n.length?":"+n.slice(0,8).join(", "):""}`;if(a+s.length>o)break;l.push(s),a+=s.length+1}return l.join("\n")}
|
package/src/colors.js
CHANGED
|
@@ -1,47 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// 运行时可切换:setColorMode('mono') 强制无色(/theme);setAccent(name) 设强调色(/color)。
|
|
3
|
-
|
|
4
|
-
const baseEnabled = process.env.NO_COLOR == null && process.env.TERM !== 'dumb' && process.stdout.isTTY;
|
|
5
|
-
let mono = false; // /theme mono:强制关闭颜色
|
|
6
|
-
let accentName = 'cyan'; // /color:强调色(提示符/高亮)
|
|
7
|
-
|
|
8
|
-
const on = () => baseEnabled && !mono;
|
|
9
|
-
const wrap = (code) => (s) => (on() ? `\x1b[${code}m${s}\x1b[0m` : String(s));
|
|
10
|
-
|
|
11
|
-
export const ACCENTS = ['cyan', 'green', 'magenta', 'blue', 'yellow', 'red'];
|
|
12
|
-
|
|
13
|
-
/** 切换主题:'mono' 关闭颜色,其它值恢复(按 env/TTY 判定)。 */
|
|
14
|
-
export function setColorMode(mode) {
|
|
15
|
-
mono = mode === 'mono';
|
|
16
|
-
}
|
|
17
|
-
/** 设置强调色(cyan/green/magenta/blue/yellow/red);非法值忽略。返回是否生效。 */
|
|
18
|
-
export function setAccent(name) {
|
|
19
|
-
if (ACCENTS.includes(name)) {
|
|
20
|
-
accentName = name;
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
export function getAccent() {
|
|
26
|
-
return accentName;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const c = {
|
|
30
|
-
reset: '\x1b[0m',
|
|
31
|
-
bold: wrap('1'),
|
|
32
|
-
dim: wrap('2'),
|
|
33
|
-
italic: wrap('3'),
|
|
34
|
-
underline: wrap('4'),
|
|
35
|
-
red: wrap('31'),
|
|
36
|
-
green: wrap('32'),
|
|
37
|
-
yellow: wrap('33'),
|
|
38
|
-
blue: wrap('34'),
|
|
39
|
-
magenta: wrap('35'),
|
|
40
|
-
cyan: wrap('36'),
|
|
41
|
-
gray: wrap('90'),
|
|
42
|
-
/** 强调色(随 /color 变化)。 */
|
|
43
|
-
accent: (s) => (c[accentName] ? c[accentName](s) : String(s)),
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// 兼容旧引用:静态基线(不随运行时主题变化;如需动态请用 c.* / on())
|
|
47
|
-
export const colorEnabled = baseEnabled;
|
|
1
|
+
const e=null==process.env.NO_COLOR&&"dumb"!==process.env.TERM&&process.stdout.isTTY;let n=!1,t="cyan";const o=t=>o=>e&&!n?`[${t}m${o}[0m`:String(o);export const ACCENTS=["cyan","green","magenta","blue","yellow","red"];export function setColorMode(e){n="mono"===e}export function setAccent(e){return!!ACCENTS.includes(e)&&(t=e,!0)}export function getAccent(){return t}export const c={reset:"[0m",bold:o("1"),dim:o("2"),italic:o("3"),underline:o("4"),red:o("31"),green:o("32"),yellow:o("33"),blue:o("34"),magenta:o("35"),cyan:o("36"),gray:o("90"),accent:e=>c[t]?c[t](e):String(e)};export const colorEnabled=e;
|