yuangs 5.52.0 → 5.54.0

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.
@@ -24,54 +24,121 @@ const CONTEXT_MAX_TOKENS = 100000;
24
24
  async function handleSpecialSyntax(input, stdinData) {
25
25
  const trimmed = input.trim();
26
26
  // 处理 @ 文件引用语法
27
- if (trimmed.startsWith('@')) {
27
+ if (trimmed.startsWith("@")) {
28
28
  // 如果是 @ 开头的语法,跳转到独立的处理器
29
29
  return await handleAtSyntax(trimmed, stdinData);
30
30
  }
31
31
  // 处理 # 目录引用语法
32
- if (trimmed.startsWith('#')) {
33
- const dirMatch = trimmed.match(/^#\s*(.+?)\s*(?:\n(.*))?$/s);
34
- if (dirMatch) {
35
- const dirPath = dirMatch[1].trim();
36
- const hasQuestion = !!dirMatch[2] || !!stdinData;
37
- const question = dirMatch[2] || (stdinData ? `分析以下目录内容:\n\n${stdinData}` : '请分析这个目录');
32
+ // 支持两种格式:
33
+ // 1. `#folder\n问题` (换行分隔,旧格式)
34
+ // 2. `#folder 问题` (空格分隔,自然输入格式)
35
+ if (trimmed.startsWith("#")) {
36
+ // 先尝试换行分隔格式(优先级更高,避免目录名含空格时误判)
37
+ const newlineDirMatch = trimmed.match(/^#\s*(.+?)\s*\n(.*)$/s);
38
+ if (newlineDirMatch) {
39
+ const dirPath = newlineDirMatch[1].trim();
40
+ const question = newlineDirMatch[2].trim() ||
41
+ (stdinData ? `分析以下目录内容:\n\n${stdinData}` : undefined);
42
+ const hasQuestion = !!question || !!stdinData;
38
43
  const res = await handleDirectoryReference(dirPath, question);
39
44
  return {
40
45
  ...res,
41
46
  isPureReference: !hasQuestion,
42
- type: 'directory'
47
+ type: "directory",
43
48
  };
44
49
  }
50
+ // 再尝试空格分隔格式:#dirPath 问题
51
+ // 策略:第一个 token(空格前)视为目录路径,其余视为问题
52
+ // 如果目录路径在磁盘上存在,则认为识别正确
53
+ const spaceMatch = trimmed.match(/^#\s*(\S+)(?:\s+(.+))?$/s);
54
+ if (spaceMatch) {
55
+ const candidatePath = spaceMatch[1].trim();
56
+ const inlineQuestion = spaceMatch[2]?.trim();
57
+ // 检查目录路径是否真实存在
58
+ const candidateFullPath = path_1.default.resolve(candidatePath);
59
+ const diskExists = (() => {
60
+ try {
61
+ return fs_1.default.statSync(candidateFullPath).isDirectory();
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ })();
67
+ if (diskExists) {
68
+ // 明确是合法目录路径
69
+ const question = inlineQuestion ||
70
+ (stdinData ? `分析以下目录内容:\n\n${stdinData}` : undefined);
71
+ const hasQuestion = !!question || !!stdinData;
72
+ const res = await handleDirectoryReference(candidatePath, question);
73
+ return {
74
+ ...res,
75
+ isPureReference: !hasQuestion,
76
+ type: "directory",
77
+ };
78
+ }
79
+ else if (!inlineQuestion) {
80
+ // 没有内联问题:整体视为目录路径(可能路径带空格,走旧逻辑)
81
+ const dirMatch = trimmed.match(/^#\s*(.+?)\s*$/s);
82
+ if (dirMatch) {
83
+ const dirPath = dirMatch[1].trim();
84
+ const question = stdinData
85
+ ? `分析以下目录内容:\n\n${stdinData}`
86
+ : undefined;
87
+ const hasQuestion = !!stdinData;
88
+ const res = await handleDirectoryReference(dirPath, question);
89
+ return {
90
+ ...res,
91
+ isPureReference: !hasQuestion,
92
+ type: "directory",
93
+ };
94
+ }
95
+ }
96
+ else {
97
+ // 目录不存在,给出明确错误提示
98
+ return {
99
+ processed: true,
100
+ result: `错误: 目录 "${candidatePath}" 不存在或不是一个目录\n💡 提示: 使用格式 #目录路径 问题,例如: #src 解释这个文件夹的作用`,
101
+ error: true,
102
+ isPureReference: false,
103
+ type: "directory",
104
+ };
105
+ }
106
+ }
45
107
  }
46
108
  // 处理 :ls 命令
47
- if (trimmed === ':ls') {
109
+ if (trimmed === ":ls") {
48
110
  const res = await handleListContext();
49
- return { ...res, type: 'management' };
111
+ return { ...res, type: "management" };
50
112
  }
51
113
  // 场景 5.1: :exec 原子执行
52
- if (trimmed.startsWith(':exec ')) {
114
+ if (trimmed.startsWith(":exec ")) {
53
115
  const command = trimmed.slice(6).trim();
54
116
  const res = await handleAtomicExec(command);
55
- return { ...res, type: 'command' };
117
+ return { ...res, type: "command" };
56
118
  }
57
119
  // 处理 :cat [index] 命令 (支持 :cat index:start-end)
58
- if (trimmed === ':cat' || trimmed.startsWith(':cat ')) {
120
+ if (trimmed === ":cat" || trimmed.startsWith(":cat ")) {
59
121
  const spec = trimmed.slice(4).trim();
60
122
  if (!spec) {
61
123
  const res = await handleCatContext(null);
62
- return { ...res, type: 'management' };
124
+ return { ...res, type: "management" };
63
125
  }
64
126
  const parsed = parseCatSpec(spec);
65
127
  if (parsed.error) {
66
- return { processed: true, result: parsed.error, error: true, type: 'management' };
128
+ return {
129
+ processed: true,
130
+ result: parsed.error,
131
+ error: true,
132
+ type: "management",
133
+ };
67
134
  }
68
135
  const res = await handleCatContext(parsed.index, parsed.startLine, parsed.endLine);
69
- return { ...res, type: 'management' };
136
+ return { ...res, type: "management" };
70
137
  }
71
138
  // 处理 :clear 命令
72
- if (trimmed === ':clear') {
139
+ if (trimmed === ":clear") {
73
140
  const res = await handleClearContext();
74
- return { ...res, type: 'management' };
141
+ return { ...res, type: "management" };
75
142
  }
76
143
  // 如果不是特殊语法,返回未处理
77
144
  return { processed: false };
@@ -86,13 +153,23 @@ function parseCatSpec(spec) {
86
153
  }
87
154
  const match = spec.match(/^(\d+)(?::(\d+)(?:-(\d+))?)?$/);
88
155
  if (!match) {
89
- return { index: null, startLine: null, endLine: null, error: `错误: 无效的索引格式 "${spec}"。请使用 :cat index 或 :cat index:start-end (例如 :cat 1:10-20)` };
156
+ return {
157
+ index: null,
158
+ startLine: null,
159
+ endLine: null,
160
+ error: `错误: 无效的索引格式 "${spec}"。请使用 :cat index 或 :cat index:start-end (例如 :cat 1:10-20)`,
161
+ };
90
162
  }
91
163
  const index = parseInt(match[1]);
92
164
  const startLine = match[2] ? parseInt(match[2]) : null;
93
165
  const endLine = match[3] ? parseInt(match[3]) : null;
94
166
  if (isNaN(index)) {
95
- return { index: null, startLine: null, endLine: null, error: `错误: 索引 "${match[1]}" 不是有效的数字` };
167
+ return {
168
+ index: null,
169
+ startLine: null,
170
+ endLine: null,
171
+ error: `错误: 索引 "${match[1]}" 不是有效的数字`,
172
+ };
96
173
  }
97
174
  return { index, startLine, endLine };
98
175
  }
@@ -108,9 +185,9 @@ function parseCatSpec(spec) {
108
185
  function tokenizeWithQuotes(input) {
109
186
  const tokens = [];
110
187
  const isQuoted = [];
111
- let current = '';
188
+ let current = "";
112
189
  let inQuotes = false;
113
- let quoteChar = '';
190
+ let quoteChar = "";
114
191
  let escaped = false;
115
192
  for (let i = 0; i < input.length; i++) {
116
193
  const char = input[i];
@@ -119,7 +196,7 @@ function tokenizeWithQuotes(input) {
119
196
  escaped = false;
120
197
  continue;
121
198
  }
122
- if (char === '\\') {
199
+ if (char === "\\") {
123
200
  escaped = true;
124
201
  continue;
125
202
  }
@@ -131,13 +208,13 @@ function tokenizeWithQuotes(input) {
131
208
  inQuotes = false;
132
209
  tokens.push(current);
133
210
  isQuoted.push(true);
134
- current = '';
211
+ current = "";
135
212
  }
136
- else if (!inQuotes && (char === ',' || char === '' || char === ' ')) {
213
+ else if (!inQuotes && (char === "," || char === "" || char === " ")) {
137
214
  if (current) {
138
215
  tokens.push(current.trim());
139
216
  isQuoted.push(false);
140
- current = '';
217
+ current = "";
141
218
  }
142
219
  }
143
220
  else {
@@ -175,26 +252,27 @@ async function handleAtSyntax(trimmed, stdinData) {
175
252
  const startLine = lineRangeMatch[2] ? parseInt(lineRangeMatch[2]) : null;
176
253
  const endLine = lineRangeMatch[3] ? parseInt(lineRangeMatch[3]) : null;
177
254
  const alias = lineRangeMatch[4];
178
- let question = lineRangeMatch[5]?.trim() || (stdinData ? `分析以下内容:\n\n${stdinData}` : undefined);
255
+ let question = lineRangeMatch[5]?.trim() ||
256
+ (stdinData ? `分析以下内容:\n\n${stdinData}` : undefined);
179
257
  const { filePaths, extraQuestion } = await resolveFilePathsAndQuestion(rawPart);
180
258
  if (extraQuestion) {
181
259
  question = question ? `${extraQuestion}\n\n${question}` : extraQuestion;
182
260
  }
183
261
  const hasQuestion = !!question || !!stdinData;
184
262
  if (filePaths.length > 1) {
185
- let warningPrefix = '';
263
+ let warningPrefix = "";
186
264
  if (alias) {
187
- warningPrefix += chalk_1.default.yellow('⚠️ 警告: 别名 (alias) 仅支持单个文件引用,当前多个文件引用将忽略别名。\n');
265
+ warningPrefix += chalk_1.default.yellow("⚠️ 警告: 别名 (alias) 仅支持单个文件引用,当前多个文件引用将忽略别名。\n");
188
266
  }
189
267
  if (startLine !== null) {
190
- warningPrefix += chalk_1.default.yellow('⚠️ 警告: 行号范围仅支持单个文件引用,当前多个文件引用将忽略行号范围。\n');
268
+ warningPrefix += chalk_1.default.yellow("⚠️ 警告: 行号范围仅支持单个文件引用,当前多个文件引用将忽略行号范围。\n");
191
269
  }
192
270
  const res = await handleMultipleFileReferences(filePaths, question, !hasQuestion);
193
271
  return {
194
272
  ...res,
195
273
  result: warningPrefix + res.result,
196
274
  isPureReference: !hasQuestion,
197
- type: 'file'
275
+ type: "file",
198
276
  };
199
277
  }
200
278
  else if (filePaths.length === 1) {
@@ -202,14 +280,14 @@ async function handleAtSyntax(trimmed, stdinData) {
202
280
  return {
203
281
  ...res,
204
282
  isPureReference: !hasQuestion,
205
- type: 'file'
283
+ type: "file",
206
284
  };
207
285
  }
208
286
  else {
209
287
  return {
210
288
  processed: true,
211
289
  result: `错误: 未找到有效的文件或序号引用 "${rawPart}"`,
212
- error: true
290
+ error: true,
213
291
  };
214
292
  }
215
293
  }
@@ -253,7 +331,9 @@ async function resolveFilePathsAndQuestion(input) {
253
331
  if (quoted)
254
332
  continue;
255
333
  const isRange = /^\d+-\d+$/.test(token);
256
- const isIndex = !isNaN(parseInt(token)) && parseInt(token) > 0 && parseInt(token) <= persisted.length;
334
+ const isIndex = !isNaN(parseInt(token)) &&
335
+ parseInt(token) > 0 &&
336
+ parseInt(token) <= persisted.length;
257
337
  // 【智能边界识别】即便没有空格,如果 token 开头是序号但后面跟着非数字(如 @1分析),也要切分
258
338
  // 或者当前的 token 本身就不可识别为路径/索引
259
339
  if (!existsOnDisk) {
@@ -278,7 +358,7 @@ async function resolveFilePathsAndQuestion(input) {
278
358
  if (questionStartIndex !== -1) {
279
359
  pathTokens = tokens.slice(0, questionStartIndex);
280
360
  pathStats = stats.slice(0, questionStartIndex);
281
- extraQuestion = tokens.slice(questionStartIndex).join(' ');
361
+ extraQuestion = tokens.slice(questionStartIndex).join(" ");
282
362
  }
283
363
  // 4. 解析确定的路径部分
284
364
  for (let i = 0; i < pathTokens.length; i++) {
@@ -311,7 +391,7 @@ async function resolveFilePathsAndQuestion(input) {
311
391
  }
312
392
  return {
313
393
  filePaths: [...new Set(filePaths)],
314
- extraQuestion
394
+ extraQuestion,
315
395
  };
316
396
  }
317
397
  /**
@@ -329,7 +409,7 @@ async function handleMultipleFileReferences(filePaths, question, isPureReference
329
409
  const fullPath = path_1.default.resolve(filePath);
330
410
  try {
331
411
  await fs_1.default.promises.access(fullPath, fs_1.default.constants.F_OK);
332
- const content = await fs_1.default.promises.readFile(fullPath, 'utf-8');
412
+ const content = await fs_1.default.promises.readFile(fullPath, "utf-8");
333
413
  return { filePath, content, success: true };
334
414
  }
335
415
  catch (e) {
@@ -341,9 +421,9 @@ async function handleMultipleFileReferences(filePaths, question, isPureReference
341
421
  if (res.success && res.content !== undefined) {
342
422
  contentMap.set(res.filePath, res.content);
343
423
  contextBuffer.add({
344
- type: 'file',
424
+ type: "file",
345
425
  path: res.filePath,
346
- content: res.content
426
+ content: res.content,
347
427
  });
348
428
  addedFiles.push(res.filePath);
349
429
  }
@@ -351,22 +431,22 @@ async function handleMultipleFileReferences(filePaths, question, isPureReference
351
431
  warningList.push(`警告: 跳过 "${res.filePath}": ${res.error}`);
352
432
  }
353
433
  }
354
- const warnings = warningList.length > 0 ? warningList.join('\n') + '\n' : '';
434
+ const warnings = warningList.length > 0 ? warningList.join("\n") + "\n" : "";
355
435
  if (addedFiles.length === 0) {
356
436
  return {
357
437
  processed: true,
358
- result: warnings || '❌ 未找到任何有效的文件引用',
359
- error: true
438
+ result: warnings || "❌ 未找到任何有效的文件引用",
439
+ error: true,
360
440
  };
361
441
  }
362
442
  await (0, contextStorage_1.saveContext)(contextBuffer.export());
363
443
  if (isPureReference) {
364
444
  return {
365
445
  processed: true,
366
- result: `${warnings}✅ 已将 ${addedFiles.length} 个文件加入上下文:\n${addedFiles.map(f => ` • ${f}`).join('\n')}`
446
+ result: `${warnings}✅ 已将 ${addedFiles.length} 个文件加入上下文:\n${addedFiles.map((f) => ` • ${f}`).join("\n")}`,
367
447
  };
368
448
  }
369
- const prompt = (0, fileReader_1.buildPromptWithFileContent)(`引用了 ${addedFiles.length} 个文件`, addedFiles, contentMap, question || '请分析以上文件');
449
+ const prompt = (0, fileReader_1.buildPromptWithFileContent)(`引用了 ${addedFiles.length} 个文件`, addedFiles, contentMap, question || "请分析以上文件");
370
450
  return { processed: true, result: warnings + prompt };
371
451
  }
372
452
  async function handleFileReference(filePath, startLine = null, endLine = null, question, alias, isPureReference = false) {
@@ -375,15 +455,15 @@ async function handleFileReference(filePath, startLine = null, endLine = null, q
375
455
  await fs_1.default.promises.access(fullPath, fs_1.default.constants.F_OK);
376
456
  const stats = await fs_1.default.promises.stat(fullPath);
377
457
  if (!stats.isFile())
378
- throw new Error('不是一个文件');
379
- let content = await fs_1.default.promises.readFile(fullPath, 'utf-8');
458
+ throw new Error("不是一个文件");
459
+ let content = await fs_1.default.promises.readFile(fullPath, "utf-8");
380
460
  // 如果指定了行号范围,则提取相应行
381
461
  if (startLine !== null) {
382
- const lines = content.split('\n');
462
+ const lines = content.split("\n");
383
463
  if (startLine < 1 || startLine > lines.length) {
384
464
  return {
385
465
  processed: true,
386
- result: `错误: 起始行号 ${startLine} 超出文件范围 (文件共有 ${lines.length} 行)`
466
+ result: `错误: 起始行号 ${startLine} 超出文件范围 (文件共有 ${lines.length} 行)`,
387
467
  };
388
468
  }
389
469
  const startIdx = startLine - 1;
@@ -391,10 +471,10 @@ async function handleFileReference(filePath, startLine = null, endLine = null, q
391
471
  if (endLine && (endLine < startLine || endLine > lines.length)) {
392
472
  return {
393
473
  processed: true,
394
- result: `错误: 结束行号 ${endLine} 超出有效范围 (应在 ${startLine}-${lines.length} 之间)`
474
+ result: `错误: 结束行号 ${endLine} 超出有效范围 (应在 ${startLine}-${lines.length} 之间)`,
395
475
  };
396
476
  }
397
- content = lines.slice(startIdx, endIdx).join('\n');
477
+ content = lines.slice(startIdx, endIdx).join("\n");
398
478
  }
399
479
  const contentMap = new Map();
400
480
  contentMap.set(filePath, content);
@@ -403,23 +483,26 @@ async function handleFileReference(filePath, startLine = null, endLine = null, q
403
483
  const persisted = await (0, contextStorage_1.loadContext)();
404
484
  contextBuffer.import(persisted);
405
485
  contextBuffer.add({
406
- type: 'file',
407
- path: filePath + (startLine !== null ? `:${startLine}${endLine ? `-${endLine}` : ''}` : ''),
486
+ type: "file",
487
+ path: filePath +
488
+ (startLine !== null
489
+ ? `:${startLine}${endLine ? `-${endLine}` : ""}`
490
+ : ""),
408
491
  content: content,
409
- alias: alias
492
+ alias: alias,
410
493
  });
411
494
  await (0, contextStorage_1.saveContext)(contextBuffer.export());
412
495
  if (isPureReference) {
413
496
  return { processed: true, result: `✅ 已将文件 ${filePath} 加入上下文` };
414
497
  }
415
- const prompt = (0, fileReader_1.buildPromptWithFileContent)(`文件: ${filePath}${startLine !== null ? `:${startLine}${endLine ? `-${endLine}` : ''}` : ''}`, [filePath], contentMap, question || `请分析文件: ${filePath}`);
498
+ const prompt = (0, fileReader_1.buildPromptWithFileContent)(`文件: ${filePath}${startLine !== null ? `:${startLine}${endLine ? `-${endLine}` : ""}` : ""}`, [filePath], contentMap, question || `请分析文件: ${filePath}`);
416
499
  return { processed: true, result: prompt };
417
500
  }
418
501
  catch (error) {
419
502
  return {
420
503
  processed: true,
421
504
  result: `错误: 无法处理文件 "${filePath}": ${error.message}`,
422
- error: true
505
+ error: true,
423
506
  };
424
507
  }
425
508
  }
@@ -428,22 +511,28 @@ async function handleDirectoryReference(dirPath, question) {
428
511
  if (!fs_1.default.existsSync(fullPath) || !fs_1.default.statSync(fullPath).isDirectory()) {
429
512
  return {
430
513
  processed: true,
431
- result: `错误: 目录 "${dirPath}" 不存在或不是一个目录`
514
+ result: `错误: 目录 "${dirPath}" 不存在或不是一个目录`,
432
515
  };
433
516
  }
434
517
  try {
435
- const findCommand = process.platform === 'darwin' || process.platform === 'linux'
518
+ const findCommand = process.platform === "darwin" || process.platform === "linux"
436
519
  ? `find "${fullPath}" -type f`
437
520
  : `dir /s /b "${fullPath}"`;
438
521
  const { stdout } = await execAsync(findCommand);
439
- const filePaths = stdout.trim().split('\n').filter(f => f);
522
+ const filePaths = stdout
523
+ .trim()
524
+ .split("\n")
525
+ .filter((f) => f);
440
526
  if (filePaths.length === 0) {
441
527
  return {
442
528
  processed: true,
443
- result: `目录 "${dirPath}" 下没有文件`
529
+ result: `目录 "${dirPath}" 下没有文件`,
444
530
  };
445
531
  }
446
- const contentMap = await (0, fileReader_1.readFilesContent)(filePaths, { showProgress: true, concurrency: 5 });
532
+ const contentMap = await (0, fileReader_1.readFilesContent)(filePaths, {
533
+ showProgress: true,
534
+ concurrency: 5,
535
+ });
447
536
  // 持久化到上下文
448
537
  const contextBuffer = new contextBuffer_1.ContextBuffer();
449
538
  const persisted = await (0, contextStorage_1.loadContext)();
@@ -458,9 +547,9 @@ async function handleDirectoryReference(dirPath, question) {
458
547
  continue;
459
548
  }
460
549
  contextBuffer.add({
461
- type: 'file',
550
+ type: "file",
462
551
  path: filePath,
463
- content: content
552
+ content: content,
464
553
  });
465
554
  successfullyAddedCount++;
466
555
  }
@@ -468,21 +557,21 @@ async function handleDirectoryReference(dirPath, question) {
468
557
  return {
469
558
  processed: true,
470
559
  result: `错误: 目录 "${dirPath}" 中的文件都太大,无法加入上下文`,
471
- error: true
560
+ error: true,
472
561
  };
473
562
  }
474
563
  await (0, contextStorage_1.saveContext)(contextBuffer.export());
475
564
  return {
476
565
  processed: true,
477
566
  result: `已成功加入 ${successfullyAddedCount} 个文件到上下文 (共找到 ${filePaths.length} 个文件)`,
478
- itemCount: successfullyAddedCount
567
+ itemCount: successfullyAddedCount,
479
568
  };
480
569
  }
481
570
  catch (error) {
482
571
  return {
483
572
  processed: true,
484
573
  result: `错误: 读取目录失败: ${error}`,
485
- error: true
574
+ error: true,
486
575
  };
487
576
  }
488
577
  }
@@ -491,12 +580,12 @@ async function handleImmediateExec(filePath) {
491
580
  if (!fs_1.default.existsSync(fullPath)) {
492
581
  return {
493
582
  processed: true,
494
- result: `错误: 文件 "${filePath}" 不存在`
583
+ result: `错误: 文件 "${filePath}" 不存在`,
495
584
  };
496
585
  }
497
586
  try {
498
587
  // 1. 读取脚本内容
499
- const content = fs_1.default.readFileSync(fullPath, 'utf-8');
588
+ const content = fs_1.default.readFileSync(fullPath, "utf-8");
500
589
  console.log(chalk_1.default.gray(`正在执行 ${filePath} 并捕获输出...`));
501
590
  // 2. 执行脚本
502
591
  // 注意:这里使用 execAsync 捕获输出
@@ -523,10 +612,10 @@ ${stderr}
523
612
  const persisted = await (0, contextStorage_1.loadContext)();
524
613
  contextBuffer.import(persisted);
525
614
  contextBuffer.add({
526
- type: 'file',
615
+ type: "file",
527
616
  path: `${filePath} (Runtime Log)`,
528
617
  content: combinedContext,
529
- summary: '包含脚本源码和执行后的输出日志'
618
+ summary: "包含脚本源码和执行后的输出日志",
530
619
  });
531
620
  await (0, contextStorage_1.saveContext)(contextBuffer.export());
532
621
  // 返回给 AI 的 Prompt
@@ -543,26 +632,26 @@ async function handleAtomicExec(command) {
543
632
  console.log(chalk_1.default.cyan(`\n⚡️ [Atomic Exec] 执行命令: ${command}\n`));
544
633
  try {
545
634
  // 对于原子执行,我们希望用户能实时看到输出,所以用 inherit
546
- const { spawn } = require('child_process');
635
+ const { spawn } = require("child_process");
547
636
  const child = spawn(command, {
548
637
  shell: true,
549
- stdio: 'inherit'
638
+ stdio: "inherit",
550
639
  });
551
640
  await new Promise((resolve, reject) => {
552
- child.on('close', (code) => {
641
+ child.on("close", (code) => {
553
642
  if (code === 0)
554
643
  resolve();
555
644
  else
556
645
  reject(new Error(`Exit code ${code}`));
557
646
  });
558
- child.on('error', reject);
647
+ child.on("error", reject);
559
648
  });
560
649
  // 原子执行不将结果传给 AI,直接返回空结果表示处理完成
561
- return { processed: true, result: '' };
650
+ return { processed: true, result: "" };
562
651
  }
563
652
  catch (error) {
564
653
  console.error(chalk_1.default.red(`执行失败: ${error}`));
565
- return { processed: true, result: '' };
654
+ return { processed: true, result: "" };
566
655
  }
567
656
  }
568
657
  async function handleListContext() {
@@ -571,13 +660,13 @@ async function handleListContext() {
571
660
  const contextBuffer = new contextBuffer_1.ContextBuffer();
572
661
  contextBuffer.import(persisted);
573
662
  if (contextBuffer.isEmpty()) {
574
- return { processed: true, result: '当前没有上下文' };
663
+ return { processed: true, result: "当前没有上下文" };
575
664
  }
576
665
  const list = contextBuffer.list();
577
666
  // 格式化时间显示
578
667
  const formatAge = (ageMin) => {
579
668
  if (ageMin < 1)
580
- return '刚刚';
669
+ return "刚刚";
581
670
  if (ageMin < 60)
582
671
  return `${ageMin}分钟前`;
583
672
  const hours = Math.floor(ageMin / 60);
@@ -590,12 +679,12 @@ async function handleListContext() {
590
679
  const formatImportance = (importance) => {
591
680
  const value = parseFloat(importance);
592
681
  if (value >= 0.8)
593
- return chalk_1.default.red('★★★');
682
+ return chalk_1.default.red("★★★");
594
683
  if (value >= 0.6)
595
- return chalk_1.default.yellow('★★☆');
684
+ return chalk_1.default.yellow("★★☆");
596
685
  if (value >= 0.4)
597
- return chalk_1.default.green('★☆☆');
598
- return chalk_1.default.gray('☆☆☆');
686
+ return chalk_1.default.green("★☆☆");
687
+ return chalk_1.default.gray("☆☆☆");
599
688
  };
600
689
  // 列宽常量定义
601
690
  const IMPORTANCE_WIDTH = 6; // "重要度"文本宽度
@@ -605,29 +694,29 @@ async function handleListContext() {
605
694
  const MAX_PATH_DISPLAY_WIDTH = 40;
606
695
  // 计算动态列宽
607
696
  const maxIndexWidth = Math.max(String(list.length).length, 1);
608
- const maxTypeWidth = Math.max(...list.map(item => item.type.length), 4);
609
- const pathColWidth = Math.min(Math.max(...list.map(item => item.path.length), 4), MAX_PATH_DISPLAY_WIDTH);
697
+ const maxTypeWidth = Math.max(...list.map((item) => item.type.length), 4);
698
+ const pathColWidth = Math.min(Math.max(...list.map((item) => item.path.length), 4), MAX_PATH_DISPLAY_WIDTH);
610
699
  // 构建表格边框
611
- const header = `┌${''.repeat(maxIndexWidth + 2)}┬${''.repeat(PINNED_WIDTH + 2)}┬${''.repeat(maxTypeWidth + 2)}┬${''.repeat(pathColWidth + 2)}┬${''.repeat(IMPORTANCE_WIDTH + 2)}┬${''.repeat(AGE_WIDTH + 2)}┬${''.repeat(TOKENS_WIDTH + 2)}┐`;
612
- const separator = `├${''.repeat(maxIndexWidth + 2)}┼${''.repeat(PINNED_WIDTH + 2)}┼${''.repeat(maxTypeWidth + 2)}┼${''.repeat(pathColWidth + 2)}┼${''.repeat(IMPORTANCE_WIDTH + 2)}┼${''.repeat(AGE_WIDTH + 2)}┼${''.repeat(TOKENS_WIDTH + 2)}┤`;
613
- const footer = `└${''.repeat(maxIndexWidth + 2)}┴${''.repeat(PINNED_WIDTH + 2)}┴${''.repeat(maxTypeWidth + 2)}┴${''.repeat(pathColWidth + 2)}┴${''.repeat(IMPORTANCE_WIDTH + 2)}┴${''.repeat(AGE_WIDTH + 2)}┴${''.repeat(TOKENS_WIDTH + 2)}┘`;
700
+ const header = `┌${"".repeat(maxIndexWidth + 2)}┬${"".repeat(PINNED_WIDTH + 2)}┬${"".repeat(maxTypeWidth + 2)}┬${"".repeat(pathColWidth + 2)}┬${"".repeat(IMPORTANCE_WIDTH + 2)}┬${"".repeat(AGE_WIDTH + 2)}┬${"".repeat(TOKENS_WIDTH + 2)}┐`;
701
+ const separator = `├${"".repeat(maxIndexWidth + 2)}┼${"".repeat(PINNED_WIDTH + 2)}┼${"".repeat(maxTypeWidth + 2)}┼${"".repeat(pathColWidth + 2)}┼${"".repeat(IMPORTANCE_WIDTH + 2)}┼${"".repeat(AGE_WIDTH + 2)}┼${"".repeat(TOKENS_WIDTH + 2)}┤`;
702
+ const footer = `└${"".repeat(maxIndexWidth + 2)}┴${"".repeat(PINNED_WIDTH + 2)}┴${"".repeat(maxTypeWidth + 2)}┴${"".repeat(pathColWidth + 2)}┴${"".repeat(IMPORTANCE_WIDTH + 2)}┴${"".repeat(AGE_WIDTH + 2)}┴${"".repeat(TOKENS_WIDTH + 2)}┘`;
614
703
  // 表头
615
- const headerRow = `│ ${chalk_1.default.bold('#'.padEnd(maxIndexWidth))} │ ${chalk_1.default.bold('📌'.padEnd(PINNED_WIDTH))} │ ${chalk_1.default.bold('Type'.padEnd(maxTypeWidth))} │ ${chalk_1.default.bold('Path'.padEnd(pathColWidth))} │ ${chalk_1.default.bold('重要度')} │ ${chalk_1.default.bold('添加时间'.padEnd(AGE_WIDTH))} │ ${chalk_1.default.bold('Tokens'.padEnd(TOKENS_WIDTH))} │`;
616
- let result = chalk_1.default.cyan.bold('📋 当前上下文列表\n\n');
617
- result += chalk_1.default.blue.dim(header) + '\n';
618
- result += headerRow + '\n';
619
- result += chalk_1.default.blue.dim(separator) + '\n';
704
+ const headerRow = `│ ${chalk_1.default.bold("#".padEnd(maxIndexWidth))} │ ${chalk_1.default.bold("📌".padEnd(PINNED_WIDTH))} │ ${chalk_1.default.bold("Type".padEnd(maxTypeWidth))} │ ${chalk_1.default.bold("Path".padEnd(pathColWidth))} │ ${chalk_1.default.bold("重要度")} │ ${chalk_1.default.bold("添加时间".padEnd(AGE_WIDTH))} │ ${chalk_1.default.bold("Tokens".padEnd(TOKENS_WIDTH))} │`;
705
+ let result = chalk_1.default.cyan.bold("📋 当前上下文列表\n\n");
706
+ result += chalk_1.default.blue.dim(header) + "\n";
707
+ result += headerRow + "\n";
708
+ result += chalk_1.default.blue.dim(separator) + "\n";
620
709
  // 行内虚线分隔符 (使用更清晰的蓝色和更饱满的字符)
621
- const rowSeparator = `├${''.repeat(maxIndexWidth + 2)}┼${''.repeat(PINNED_WIDTH + 2)}┼${''.repeat(maxTypeWidth + 2)}┼${''.repeat(pathColWidth + 2)}┼${''.repeat(IMPORTANCE_WIDTH + 2)}┼${''.repeat(AGE_WIDTH + 2)}┼${''.repeat(TOKENS_WIDTH + 2)}┤`;
710
+ const rowSeparator = `├${"".repeat(maxIndexWidth + 2)}┼${"".repeat(PINNED_WIDTH + 2)}┼${"".repeat(maxTypeWidth + 2)}┼${"".repeat(pathColWidth + 2)}┼${"".repeat(IMPORTANCE_WIDTH + 2)}┼${"".repeat(AGE_WIDTH + 2)}┼${"".repeat(TOKENS_WIDTH + 2)}┤`;
622
711
  // 数据行
623
712
  list.forEach((item, index) => {
624
713
  const indexStr = String(index + 1).padEnd(maxIndexWidth);
625
- const pinnedStr = (item.pinned ? '📌' : ' ').padEnd(PINNED_WIDTH);
714
+ const pinnedStr = (item.pinned ? "📌" : " ").padEnd(PINNED_WIDTH);
626
715
  const typeStr = item.type.padEnd(maxTypeWidth);
627
716
  // 路径截断处理
628
717
  let pathStr = item.path;
629
718
  if (pathStr.length > MAX_PATH_DISPLAY_WIDTH) {
630
- pathStr = '...' + pathStr.slice(-(MAX_PATH_DISPLAY_WIDTH - 3));
719
+ pathStr = "..." + pathStr.slice(-(MAX_PATH_DISPLAY_WIDTH - 3));
631
720
  }
632
721
  pathStr = pathStr.padEnd(pathColWidth);
633
722
  const importanceStr = formatImportance(item.importance);
@@ -635,28 +724,28 @@ async function handleListContext() {
635
724
  const tokensStr = String(item.tokens).padStart(TOKENS_WIDTH);
636
725
  // 根据类型着色
637
726
  let typeColor = chalk_1.default.cyan;
638
- if (item.type === 'memory')
727
+ if (item.type === "memory")
639
728
  typeColor = chalk_1.default.magenta;
640
- if (item.type === 'antipattern')
729
+ if (item.type === "antipattern")
641
730
  typeColor = chalk_1.default.red;
642
731
  result += `│ ${chalk_1.default.yellow(indexStr)} │ ${pinnedStr} │ ${typeColor(typeStr)} │ ${chalk_1.default.white(pathStr)} │ ${importanceStr} │ ${chalk_1.default.gray(ageStr)} │ ${chalk_1.default.green(tokensStr)} │\n`;
643
732
  // 如果不是最后一行,添加虚线分隔符
644
733
  if (index < list.length - 1) {
645
- result += chalk_1.default.blue.dim(rowSeparator) + '\n';
734
+ result += chalk_1.default.blue.dim(rowSeparator) + "\n";
646
735
  }
647
736
  });
648
737
  result += chalk_1.default.blue.dim(footer);
649
738
  // 统计信息(单行)
650
739
  const totalTokens = list.reduce((sum, item) => sum + item.tokens, 0);
651
- const pinnedCount = list.filter(item => item.pinned).length;
652
- const memoryCount = list.filter(item => item.type === 'memory').length;
653
- result += `\n\n${chalk_1.default.cyan('📊')} ${chalk_1.default.gray('总计:')} ${chalk_1.default.yellow(list.length)} ${chalk_1.default.gray('|')} ${chalk_1.default.gray('固定:')} ${chalk_1.default.yellow(pinnedCount)} ${chalk_1.default.gray('|')} ${chalk_1.default.gray('记忆:')} ${chalk_1.default.magenta(memoryCount)} ${chalk_1.default.gray('|')} ${chalk_1.default.gray('Token:')} ${chalk_1.default.green(totalTokens.toLocaleString())}`;
740
+ const pinnedCount = list.filter((item) => item.pinned).length;
741
+ const memoryCount = list.filter((item) => item.type === "memory").length;
742
+ result += `\n\n${chalk_1.default.cyan("📊")} ${chalk_1.default.gray("总计:")} ${chalk_1.default.yellow(list.length)} ${chalk_1.default.gray("|")} ${chalk_1.default.gray("固定:")} ${chalk_1.default.yellow(pinnedCount)} ${chalk_1.default.gray("|")} ${chalk_1.default.gray("记忆:")} ${chalk_1.default.magenta(memoryCount)} ${chalk_1.default.gray("|")} ${chalk_1.default.gray("Token:")} ${chalk_1.default.green(totalTokens.toLocaleString())}`;
654
743
  return { processed: true, result };
655
744
  }
656
745
  catch (error) {
657
746
  return {
658
747
  processed: true,
659
- result: `读取上下文失败: ${error}`
748
+ result: `读取上下文失败: ${error}`,
660
749
  };
661
750
  }
662
751
  }
@@ -666,21 +755,24 @@ async function handleCatContext(index, startLine = null, endLine = null) {
666
755
  const contextBuffer = new contextBuffer_1.ContextBuffer();
667
756
  contextBuffer.import(persisted);
668
757
  if (contextBuffer.isEmpty()) {
669
- return { processed: true, result: '当前没有上下文' };
758
+ return { processed: true, result: "当前没有上下文" };
670
759
  }
671
760
  const items = contextBuffer.export();
672
761
  if (index !== null) {
673
762
  // 查看指定索引
674
763
  if (index < 1 || index > items.length) {
675
- return { processed: true, result: `错误: 索引 ${index} 超出范围 (共有 ${items.length} 个项目)` };
764
+ return {
765
+ processed: true,
766
+ result: `错误: 索引 ${index} 超出范围 (共有 ${items.length} 个项目)`,
767
+ };
676
768
  }
677
769
  const item = items[index - 1];
678
- let content = item.content || '(无内容)';
770
+ let content = item.content || "(无内容)";
679
771
  // 获取语言提示 (使用增强的识别逻辑)
680
772
  const lang = getLanguageByPath(item.path);
681
773
  // 行号切片
682
774
  if (startLine !== null) {
683
- const lines = content.split('\n');
775
+ const lines = content.split("\n");
684
776
  // 边界校验:起始行号归一化 (不允许小于 1)
685
777
  const clampedStart = Math.max(1, startLine);
686
778
  const startIdx = clampedStart - 1;
@@ -688,45 +780,53 @@ async function handleCatContext(index, startLine = null, endLine = null) {
688
780
  let endIdx = lines.length;
689
781
  if (endLine !== null) {
690
782
  if (endLine < clampedStart) {
691
- return { processed: true, result: `错误: 结束行号 ${endLine} 不能小于起始行号 ${clampedStart}` };
783
+ return {
784
+ processed: true,
785
+ result: `错误: 结束行号 ${endLine} 不能小于起始行号 ${clampedStart}`,
786
+ };
692
787
  }
693
788
  endIdx = Math.min(endLine, lines.length);
694
789
  }
695
790
  if (startIdx >= lines.length) {
696
- return { processed: true, result: `错误: 起始行号 ${startLine} 超出范围 (该文件共有 ${lines.length} 行)` };
791
+ return {
792
+ processed: true,
793
+ result: `错误: 起始行号 ${startLine} 超出范围 (该文件共有 ${lines.length} 行)`,
794
+ };
697
795
  }
698
- content = lines.slice(startIdx, endIdx).join('\n');
699
- const rangeLabel = endLine ? `${clampedStart}-${endIdx}` : `${clampedStart}-末尾`;
796
+ content = lines.slice(startIdx, endIdx).join("\n");
797
+ const rangeLabel = endLine
798
+ ? `${clampedStart}-${endIdx}`
799
+ : `${clampedStart}-末尾`;
700
800
  // 渲染高亮内容
701
801
  const highlighted = (0, renderer_1.renderMarkdown)(`\`\`\`${lang}\n${content}\n\`\`\``);
702
802
  return {
703
803
  processed: true,
704
- result: `${chalk_1.default.blue.bold(`--- [${index}] ${item.type}: ${item.path} (第 ${rangeLabel} 行) ---`)}\n${highlighted}\n${chalk_1.default.blue.bold('--- End ---')}`
804
+ result: `${chalk_1.default.blue.bold(`--- [${index}] ${item.type}: ${item.path} (第 ${rangeLabel} 行) ---`)}\n${highlighted}\n${chalk_1.default.blue.bold("--- End ---")}`,
705
805
  };
706
806
  }
707
807
  // 渲染完整内容的高亮
708
808
  const highlighted = (0, renderer_1.renderMarkdown)(`\`\`\`${lang}\n${content}\n\`\`\``);
709
809
  return {
710
810
  processed: true,
711
- result: `${chalk_1.default.blue.bold(`--- [${index}] ${item.type}: ${item.path} ---`)}\n${highlighted}\n${chalk_1.default.blue.bold('--- End ---')}`
811
+ result: `${chalk_1.default.blue.bold(`--- [${index}] ${item.type}: ${item.path} ---`)}\n${highlighted}\n${chalk_1.default.blue.bold("--- End ---")}`,
712
812
  };
713
813
  }
714
814
  else {
715
815
  // 查看全部 (也要高亮每一个)
716
- let result = chalk_1.default.cyan.bold('=== 当前完整上下文内容 ===\n\n');
816
+ let result = chalk_1.default.cyan.bold("=== 当前完整上下文内容 ===\n\n");
717
817
  items.forEach((item, i) => {
718
818
  const lang = getLanguageByPath(item.path);
719
- const highlighted = (0, renderer_1.renderMarkdown)(`\`\`\`${lang}\n${item.content || '(空)'}\n\`\`\``);
819
+ const highlighted = (0, renderer_1.renderMarkdown)(`\`\`\`${lang}\n${item.content || "(空)"}\n\`\`\``);
720
820
  result += `${chalk_1.default.blue.bold(`--- [${i + 1}] ${item.type}: ${item.path} ---`)}\n${highlighted}\n\n`;
721
821
  });
722
- result += chalk_1.default.cyan.bold('==========================');
822
+ result += chalk_1.default.cyan.bold("==========================");
723
823
  return { processed: true, result };
724
824
  }
725
825
  }
726
826
  catch (error) {
727
827
  return {
728
828
  processed: true,
729
- result: `读取上下文失败: ${error}`
829
+ result: `读取上下文失败: ${error}`,
730
830
  };
731
831
  }
732
832
  }
@@ -734,12 +834,12 @@ async function handleClearContext() {
734
834
  try {
735
835
  // 清除持久化存储
736
836
  await (0, contextStorage_1.saveContext)([]);
737
- return { processed: true, result: '上下文已清空(含持久化)' };
837
+ return { processed: true, result: "上下文已清空(含持久化)" };
738
838
  }
739
839
  catch (error) {
740
840
  return {
741
841
  processed: true,
742
- result: `清除上下文失败: ${error}`
842
+ result: `清除上下文失败: ${error}`,
743
843
  };
744
844
  }
745
845
  }
@@ -747,21 +847,28 @@ async function handleFileAndCommand(filePath, command) {
747
847
  try {
748
848
  const fullPath = path_1.default.resolve(filePath);
749
849
  if (!fs_1.default.existsSync(fullPath)) {
750
- return { processed: true, result: `错误: 文件 "${filePath}" 不存在`, isPureReference: true, type: 'file' };
850
+ return {
851
+ processed: true,
852
+ result: `错误: 文件 "${filePath}" 不存在`,
853
+ isPureReference: true,
854
+ type: "file",
855
+ };
751
856
  }
752
- const content = await fs_1.default.promises.readFile(fullPath, 'utf-8');
857
+ const content = await fs_1.default.promises.readFile(fullPath, "utf-8");
753
858
  const contextBuffer = new contextBuffer_1.ContextBuffer();
754
859
  const persisted = await (0, contextStorage_1.loadContext)();
755
860
  contextBuffer.import(persisted);
756
861
  contextBuffer.add({
757
- type: 'file',
862
+ type: "file",
758
863
  path: filePath,
759
- content: content
864
+ content: content,
760
865
  });
761
866
  await (0, contextStorage_1.saveContext)(contextBuffer.export());
762
867
  console.log(chalk_1.default.green(`✓ 已将文件 "${filePath}" 加入上下文`));
763
868
  console.log(chalk_1.default.cyan(`⚡️ 正在执行: ${command}\n`));
764
- const { stdout, stderr } = await execAsync(command, { cwd: path_1.default.dirname(fullPath) });
869
+ const { stdout, stderr } = await execAsync(command, {
870
+ cwd: path_1.default.dirname(fullPath),
871
+ });
765
872
  if (stdout)
766
873
  console.log(stdout);
767
874
  if (stderr)
@@ -770,7 +877,7 @@ async function handleFileAndCommand(filePath, command) {
770
877
  processed: true,
771
878
  result: `命令执行完成`,
772
879
  isPureReference: true,
773
- type: 'command'
880
+ type: "command",
774
881
  };
775
882
  }
776
883
  catch (error) {
@@ -778,7 +885,7 @@ async function handleFileAndCommand(filePath, command) {
778
885
  processed: true,
779
886
  result: `错误: 执行失败: ${error}`,
780
887
  isPureReference: true,
781
- type: 'command'
888
+ type: "command",
782
889
  };
783
890
  }
784
891
  }
@@ -788,34 +895,34 @@ async function handleFileAndCommand(filePath, command) {
788
895
  function getLanguageByPath(filePath) {
789
896
  const ext = path_1.default.extname(filePath).toLowerCase().slice(1);
790
897
  if (!ext)
791
- return 'text';
898
+ return "text";
792
899
  const langMap = {
793
- 'ts': 'typescript',
794
- 'js': 'javascript',
795
- 'tsx': 'typescript',
796
- 'jsx': 'javascript',
797
- 'py': 'python',
798
- 'rb': 'ruby',
799
- 'sh': 'bash',
800
- 'zsh': 'bash',
801
- 'yml': 'yaml',
802
- 'yaml': 'yaml',
803
- 'md': 'markdown',
804
- 'json': 'json',
805
- 'rs': 'rust',
806
- 'go': 'go',
807
- 'c': 'c',
808
- 'cpp': 'cpp',
809
- 'h': 'cpp',
810
- 'java': 'java',
811
- 'kt': 'kotlin',
812
- 'css': 'css',
813
- 'scss': 'scss',
814
- 'html': 'html',
815
- 'sql': 'sql',
816
- 'vue': 'html',
817
- 'makefile': 'makefile',
818
- 'dockerfile': 'dockerfile'
900
+ ts: "typescript",
901
+ js: "javascript",
902
+ tsx: "typescript",
903
+ jsx: "javascript",
904
+ py: "python",
905
+ rb: "ruby",
906
+ sh: "bash",
907
+ zsh: "bash",
908
+ yml: "yaml",
909
+ yaml: "yaml",
910
+ md: "markdown",
911
+ json: "json",
912
+ rs: "rust",
913
+ go: "go",
914
+ c: "c",
915
+ cpp: "cpp",
916
+ h: "cpp",
917
+ java: "java",
918
+ kt: "kotlin",
919
+ css: "css",
920
+ scss: "scss",
921
+ html: "html",
922
+ sql: "sql",
923
+ vue: "html",
924
+ makefile: "makefile",
925
+ dockerfile: "dockerfile",
819
926
  };
820
927
  return langMap[ext] || ext;
821
928
  }