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