ai-worktool 1.0.72 → 1.0.74

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/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- ## 1.0.72 (2025-08-20)
1
+ ## 1.0.74 (2025-08-20)
2
2
 
3
3
 
4
4
  ### Bug Fixes
@@ -117,6 +117,7 @@
117
117
  * **program:** 增加代码覆盖率报告展示功能 ([60da9b5](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/60da9b59e4b913695daa8697dd9aeec16cf78a5f))
118
118
  * **program:** 增加代码覆盖率报告展示功能 ([f693e27](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/f693e272ef0fe7e58083d2589339af17e45cff95))
119
119
  * **program:** 增加代码覆盖率的 web 报表展示功能 ([cccf58d](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/cccf58d2bfdcf2016fee011b1623821598d97e21))
120
+ * **program:** 增加日志记录功能并优化输出 ([1332b7f](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/1332b7fc2bb773e266e511bdcd2e6029eec09d45))
120
121
  * **program:** 添加测试配置文件选项并优化 Jest 命令生成 ([5f16c21](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/5f16c21da3a12e46a49611dbd5b2a6e3c7cdcf24))
121
122
  * **project:** 优化项目类型检测逻辑 ([9bc024d](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/9bc024deeed239c05c9cab810f9f12a6a3306aab))
122
123
  * **project:** 增加加载 aicoder 配置文件功能 ([b4dc70b](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/b4dc70b3afa88cf45135cf56e63cbb2ffa2fed82))
@@ -59,7 +59,7 @@ async function handleToolCall(toolCall, cwd) {
59
59
  let success;
60
60
  try {
61
61
  data = await (0, toolCall_1.callWithObject)(toolCall.toolName, toolCall.params, cwd);
62
- console.log(data);
62
+ // console.log(data);
63
63
  success = true;
64
64
  }
65
65
  catch (err) {
@@ -161,7 +161,7 @@ async function startAgent(name, question, vars, withConversation, ctrl, mode, on
161
161
  lasttool = '';
162
162
  }
163
163
  if (ev?.event === 'DONE') {
164
- console.log(msg);
164
+ // console.log(msg);
165
165
  }
166
166
  if (ev?.event === 'ERROR') {
167
167
  err = new AgentException(data.error, data.name, data.code);
@@ -144,8 +144,7 @@ async function callWithObject(name, argsObj, projectDir) {
144
144
  function getParamNames(fn) {
145
145
  // 匹配函数参数列表的正则表达式
146
146
  const paramRegex = /\(([^)]*)\)/;
147
- // 匹配参数名的正则表达式(处理默认值等情况)
148
- const nameRegex = /([^\s,=]+)/g;
147
+ // 修正后的修正
149
148
  // 获取函数源码
150
149
  const fnStr = fn.toString();
151
150
  // 提取参数列表字符串
@@ -157,13 +156,16 @@ function getParamNames(fn) {
157
156
  if (!params) {
158
157
  return [];
159
158
  }
160
- // 提取参数名(处理有默认值的情况)
161
- const paramNames = [];
162
- let nameMatch;
163
- while ((nameMatch = nameRegex.exec(params)) !== null) {
164
- paramNames.push(nameMatch[1]);
165
- }
166
- return paramNames;
159
+ // 分割参数并提取参数名(处理有默认值的情况)
160
+ return params
161
+ .split(',')
162
+ .map((param) => {
163
+ // 移除可能的默认值部分
164
+ const paramName = param.split('=')[0].trim();
165
+ // 排除空字符串(处理可能的空参数情况)
166
+ return paramName || null;
167
+ })
168
+ .filter(Boolean);
167
169
  }
168
170
  function formatDisplayText(txt) {
169
171
  return txt.length > 50 ? txt.slice(0, 50) + '...' : txt;
@@ -174,7 +176,7 @@ toolCall, root, result) {
174
176
  root = root || '/';
175
177
  const params = toolCall.params || {};
176
178
  if (toolCall.function_name === 'readFile') {
177
- const outputResult = result ? (result?.success ? ` ✅` : ` ❌${result?.message}`) : '';
179
+ const outputResult = result ? (result?.success ? ` ✅` : ` ❌${result?.message}`) : '...';
178
180
  const filePath = params[0] || params.filePath;
179
181
  return `读取文件:${formatDisplayText(filePath ? (path_1.default.isAbsolute(filePath) ? path_1.default.relative(root, filePath) : filePath) : '')}${outputResult}`;
180
182
  }
@@ -183,9 +185,9 @@ toolCall, root, result) {
183
185
  toolCall.function_name === 'modifyLine' ||
184
186
  toolCall.function_name === 'deleteLine' ||
185
187
  toolCall.function_name === 'batchModifyLines') {
186
- const outputResult = result ? (result?.success ? ` ✅` : ` ❌${result?.message}`) : '';
188
+ const outputResult = result ? (result?.success ? ` ✅` : ` ❌${result?.message}`) : '...';
187
189
  const filePath = params[0] || params.filePath;
188
- return `修改文件:${formatDisplayText(filePath ? (path_1.default.isAbsolute(filePath) ? path_1.default.relative(root, filePath) : filePath) : '')}${outputResult}`;
190
+ return `修改文件${process.env.NODE_ENV === 'test' ? `(${toolCall.function_name})` : ''}: ${formatDisplayText(filePath ? (path_1.default.isAbsolute(filePath) ? path_1.default.relative(root, filePath) : filePath) : '')}${outputResult}`;
189
191
  }
190
192
  if (toolCall.function_name === 'searchFilesByExtension') {
191
193
  const outputResult = result
package/dist/logger.js ADDED
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LogLevel = void 0;
7
+ exports.debug = debug;
8
+ exports.info = info;
9
+ exports.warn = warn;
10
+ exports.error = error;
11
+ exports.createLogger = createLogger;
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const util_1 = require("util");
15
+ // 定义日志级别
16
+ var LogLevel;
17
+ (function (LogLevel) {
18
+ LogLevel["DEBUG"] = "DEBUG";
19
+ LogLevel["INFO"] = "INFO";
20
+ LogLevel["WARN"] = "WARN";
21
+ LogLevel["ERROR"] = "ERROR";
22
+ })(LogLevel || (exports.LogLevel = LogLevel = {}));
23
+ // 内部使用的异步工具函数
24
+ const appendFile = (0, util_1.promisify)(fs_1.default.appendFile);
25
+ const exists = (0, util_1.promisify)(fs_1.default.exists);
26
+ const mkdir = (0, util_1.promisify)(fs_1.default.mkdir);
27
+ const stat = (0, util_1.promisify)(fs_1.default.stat);
28
+ const rename = (0, util_1.promisify)(fs_1.default.rename);
29
+ // 日志任务队列和状态管理
30
+ const logQueue = [];
31
+ let isProcessing = false;
32
+ /**
33
+ * 处理日志队列中的任务
34
+ */
35
+ async function processLogQueue() {
36
+ // 如果正在处理或队列为空,则退出
37
+ if (isProcessing || logQueue.length === 0) {
38
+ return;
39
+ }
40
+ isProcessing = true;
41
+ try {
42
+ // 取出队列中的第一个任务并执行
43
+ const logTask = logQueue.shift();
44
+ if (logTask) {
45
+ await logTask();
46
+ }
47
+ }
48
+ catch (error) {
49
+ console.error('Error processing log queue:', error);
50
+ }
51
+ finally {
52
+ isProcessing = false;
53
+ // 递归处理下一个任务
54
+ processLogQueue();
55
+ }
56
+ }
57
+ /**
58
+ * 确保日志目录存在(内部异步)
59
+ */
60
+ async function ensureLogDirExists(logDir) {
61
+ try {
62
+ const dirExists = await exists(logDir);
63
+ if (!dirExists) {
64
+ await mkdir(logDir, { recursive: true });
65
+ }
66
+ }
67
+ catch (error) {
68
+ console.error('Failed to create log directory:', error);
69
+ }
70
+ }
71
+ /**
72
+ * 获取当前日志文件名(按日期)
73
+ */
74
+ function getCurrentLogFileName(prefix) {
75
+ const date = new Date();
76
+ const year = date.getFullYear();
77
+ const month = String(date.getMonth() + 1).padStart(2, '0');
78
+ const day = String(date.getDate()).padStart(2, '0');
79
+ return `${prefix}-${year}-${month}-${day}.log`;
80
+ }
81
+ /**
82
+ * 获取当前日志文件路径
83
+ */
84
+ function getCurrentLogFilePath(config) {
85
+ return path_1.default.join(config.logDir, getCurrentLogFileName(config.fileNamePrefix));
86
+ }
87
+ /**
88
+ * 检查文件是否需要轮转(内部异步)
89
+ */
90
+ async function shouldRotateLogFile(filePath, maxSize) {
91
+ try {
92
+ const stats = await stat(filePath);
93
+ return stats.size >= maxSize;
94
+ }
95
+ catch (error) {
96
+ return false; // 文件不存在时不需要轮转
97
+ }
98
+ }
99
+ /**
100
+ * 轮转日志文件(内部异步)
101
+ */
102
+ async function rotateLogFile(filePath) {
103
+ const timestamp = new Date().getTime();
104
+ const newPath = `${filePath}.${timestamp}`;
105
+ await rename(filePath, newPath);
106
+ }
107
+ /**
108
+ * 核心日志写入函数(加入队列确保顺序)
109
+ */
110
+ function writeLog(config, level, message, meta) {
111
+ // 确保配置完整
112
+ const fullConfig = {
113
+ maxFileSize: 1024 * 1024 * 5, // 默认5MB
114
+ ...config
115
+ };
116
+ // 创建日志任务并加入队列
117
+ const logTask = async () => {
118
+ try {
119
+ // 确保日志目录存在
120
+ await ensureLogDirExists(fullConfig.logDir);
121
+ // 构建日志消息
122
+ const now = new Date().toISOString();
123
+ let logMessage = `[${now}] [${level}] ${message}`;
124
+ // 添加元数据
125
+ if (meta) {
126
+ logMessage += ` | ${JSON.stringify(meta)}`;
127
+ }
128
+ logMessage += '\n';
129
+ // 获取日志文件路径
130
+ const logFilePath = getCurrentLogFilePath(fullConfig);
131
+ // 检查是否需要轮转
132
+ if (await shouldRotateLogFile(logFilePath, fullConfig.maxFileSize)) {
133
+ await rotateLogFile(logFilePath);
134
+ }
135
+ // 异步写入日志
136
+ await appendFile(logFilePath, logMessage, 'utf8');
137
+ }
138
+ catch (error) {
139
+ console.error('Failed to write log:', error, 'Log message:', message);
140
+ }
141
+ };
142
+ // 将任务加入队列
143
+ logQueue.push(logTask);
144
+ // 开始处理队列
145
+ processLogQueue();
146
+ }
147
+ /**
148
+ * 记录DEBUG级别日志
149
+ */
150
+ function debug(config, message, meta) {
151
+ writeLog(config, LogLevel.DEBUG, message, meta);
152
+ }
153
+ /**
154
+ * 记录INFO级别日志
155
+ */
156
+ function info(config, message, meta) {
157
+ writeLog(config, LogLevel.INFO, message, meta);
158
+ }
159
+ /**
160
+ * 记录WARN级别日志
161
+ */
162
+ function warn(config, message, meta) {
163
+ writeLog(config, LogLevel.WARN, message, meta);
164
+ }
165
+ /**
166
+ * 记录ERROR级别日志
167
+ */
168
+ function error(config, message, meta) {
169
+ writeLog(config, LogLevel.ERROR, message, meta);
170
+ }
171
+ /**
172
+ * 创建带预设配置的日志函数
173
+ */
174
+ function createLogger(config) {
175
+ return {
176
+ debug: (message, meta) => debug(config, message, meta),
177
+ info: (message, meta) => info(config, message, meta),
178
+ warn: (message, meta) => warn(config, message, meta),
179
+ error: (message, meta) => error(config, message, meta),
180
+ // 新增:等待所有日志写入完成
181
+ flush: async () => {
182
+ // 等待队列处理完成
183
+ while (isProcessing || logQueue.length > 0) {
184
+ await new Promise(resolve => setTimeout(resolve, 10));
185
+ }
186
+ }
187
+ };
188
+ }
189
+ //# sourceMappingURL=logger.js.map
package/dist/program.js CHANGED
@@ -13,25 +13,36 @@ const tools_1 = require("./tools");
13
13
  const view_1 = require("./view");
14
14
  const user_1 = require("./user");
15
15
  const report_1 = require("./report");
16
+ const logger_1 = require("./logger");
16
17
  const program = new commander_1.Command();
18
+ const logger = (0, logger_1.createLogger)({
19
+ fileNamePrefix: 'ai-worktools-log',
20
+ logDir: 'logs'
21
+ });
17
22
  program.name('testAgent').description('吃豆豆:单测智能体').version('1.0.1');
18
23
  // 屏蔽调试日志
19
24
  let lastLineNo;
20
- const outputLine = (message, id) => {
21
- if (id && lastLineNo && lastLineNo !== id) {
25
+ let lastMessage = '';
26
+ const outputLine = (message, id, level = 'info') => {
27
+ if (lastLineNo !== id && lastMessage) {
22
28
  process.stdout.write('\n');
29
+ logger[level](lastMessage);
30
+ lastMessage = '';
23
31
  }
24
32
  process.stdout.write(message);
33
+ lastMessage = message;
25
34
  if (!id) {
26
35
  process.stdout.write('\n');
36
+ logger[level](lastMessage);
37
+ lastMessage = '';
27
38
  }
28
39
  lastLineNo = id;
29
40
  };
30
41
  if (process.env.NODE_ENV !== 'test') {
31
- console.log = () => { };
42
+ console.log = console.error = console.warn = console.debug = console.info = () => { };
32
43
  }
33
44
  else {
34
- console.log = console.error = (...txt) => outputLine(txt
45
+ const injectLog = (level) => (...txt) => outputLine(txt
35
46
  .map((arg) => {
36
47
  // 处理 null 和 undefined
37
48
  if (arg === null) {
@@ -58,7 +69,11 @@ else {
58
69
  return String(arg);
59
70
  }
60
71
  })
61
- .join(''));
72
+ .join(' '), undefined, level);
73
+ console.log = injectLog('info');
74
+ console.error = injectLog('error');
75
+ console.warn = injectLog('warn');
76
+ console.debug = injectLog('debug');
62
77
  }
63
78
  const tokenFile = path_1.default.join(os_1.default.homedir(), '.aiworktools');
64
79
  // token存储在系统用户文件夹的.ai-worktools
@@ -91,6 +106,7 @@ program
91
106
  try {
92
107
  outputLine(`正在打开浏览器进行登录...`);
93
108
  await (0, user_1.login)('ai-worktools-cli');
109
+ await logger.flush();
94
110
  outputLine('登录成功!');
95
111
  }
96
112
  catch (err) {
@@ -103,6 +119,7 @@ program
103
119
  .action(async () => {
104
120
  await (0, user_1.logout)();
105
121
  outputLine('已成功退出');
122
+ await logger.flush();
106
123
  });
107
124
  program
108
125
  .command('start [projectRoot]')
@@ -119,14 +136,13 @@ program
119
136
  .option('-a, --autoCommit <boolean>', '覆盖率报告文件夹', (txt) => txt === 'true', true)
120
137
  .option('--fullCoverageEnd <boolean>', '是否在达到100%覆盖率后结束', (txt) => txt === 'true', true)
121
138
  .action(async (projectRoot, options) => {
122
- const tasks = [];
123
139
  const result = await (0, testAgent_1.startTestAgent)({
124
140
  ...options,
125
141
  projectRoot: projectRoot || options.projectRoot,
126
142
  mode: process.env.TEXT_MODE || 'increment',
127
143
  onMessage: outputLine
128
144
  });
129
- await Promise.all(tasks);
145
+ await logger.flush();
130
146
  if (result?.name === '') {
131
147
  if (result?.name === 'ExpiredException') {
132
148
  outputLine('您的Tokens已用完,请充值。执行下面命令打开使用面板:');
@@ -140,7 +156,7 @@ program
140
156
  .option('--projectRoot <directory>', '源代码文件夹', (txt) => path_1.default.resolve(txt), process.cwd())
141
157
  .option('--coverageDir <string>', '覆盖率报告文件夹', 'coverage')
142
158
  .option('--web <boolean>', '是否打开web报表页面', (txt) => txt === 'false', false)
143
- .option('--retest <boolean>', '是否重新生成测试报表', (txt) => txt !== 'false', true)
159
+ .option('--retest <boolean>', '是否重新生成测试报表', (txt) => txt === 'true', false)
144
160
  .option('-w, --watch <boolean>', '是否监控代码覆盖率报告变化', (txt) => txt !== 'false', false)
145
161
  .action(async (projectRoot, options) => {
146
162
  if (options.retest) {
@@ -166,6 +182,7 @@ program
166
182
  await (0, view_1.loadAndDisplayCoverage)(path_1.default.join(projectRoot || options.projectRoot, options.coverageDir, view_1.COVERAGE_FILE_PATH));
167
183
  }
168
184
  }
185
+ await logger.flush();
169
186
  });
170
187
  program
171
188
  .command('usage')
package/dist/report.js CHANGED
@@ -142,7 +142,7 @@ function setupProjectWatcher(projectName) {
142
142
  if (eventType === 'change' || eventType === 'rename') {
143
143
  const time = Date.now();
144
144
  lastChangeTime.set(projectName, time);
145
- console.log(`项目 ${projectName} 的文件变化 detected at ${new Date(time).toLocaleTimeString()}`);
145
+ // console.log(`项目 ${projectName} 的文件变化 detected at ${new Date(time).toLocaleTimeString()}`);
146
146
  // 通知该项目的所有客户端
147
147
  const projectClients = clients.filter((c) => c.project === projectName);
148
148
  projectClients.forEach(({ res }) => {
@@ -228,7 +228,7 @@ function injectSilentRefreshScript(htmlContent, projectName) {
228
228
  document.body.innerHTML = newDocument.body.innerHTML;
229
229
 
230
230
  // 重新加载样式表
231
- reloadStylesheets();
231
+ // reloadStylesheets();
232
232
 
233
233
  // 恢复滚动位置
234
234
  restoreScrollPosition(scrollPos);
@@ -240,10 +240,12 @@ function injectSilentRefreshScript(htmlContent, projectName) {
240
240
  window.location.reload();
241
241
  }
242
242
  }
243
+
244
+ var lastChange = 0;
243
245
 
244
246
  // 长轮询检查变化
245
247
  function checkForChanges() {
246
- fetch(\`/\${currentProject}/__watch\`)
248
+ fetch(\`/\${currentProject}/__watch?lastChange=\${lastChange}\`)
247
249
  .then(response => {
248
250
  if (response.ok) {
249
251
  return response.text();
@@ -257,6 +259,7 @@ function injectSilentRefreshScript(htmlContent, projectName) {
257
259
 
258
260
  // 项目页面的内容变化
259
261
  if (jsonData.changed && jsonData.project === currentProject) {
262
+ lastChange = jsonData.time;
260
263
  console.log(\`检测到项目 \${currentProject} 的文件变化,正在更新页面...\`);
261
264
  updatePageContent();
262
265
  }
@@ -294,9 +294,7 @@ async function writeFile(filePath, content, encoding = 'utf8', overwrite = true)
294
294
  const data = content.replace(/\r\n/g, '\n');
295
295
  const old = !!onChanged && exists ? await fs.readFile(filePath, encoding) : '';
296
296
  await fs.writeFile(filePath, data, encoding);
297
- if (!!onChanged) {
298
- await onChanged(filePath, old, data);
299
- }
297
+ await onChanged?.(filePath, old, data);
300
298
  }
301
299
  catch (error) {
302
300
  throw new Error(`写入文件失败: ${error.message}`);
@@ -356,7 +354,7 @@ async function renameFile(oldPath, newPath, overwrite = false) {
356
354
  * @param filePath 文件路径,如 'src/main.js'
357
355
  * @param startLine 起始行号(从1开始)
358
356
  * @param endLine 结束行号(可选,默认为startLine)
359
- * @param newText 新的文本内容(字符串或字符串数组)
357
+ * @param newText 新的文本内容(多行用\n分割)
360
358
  * @param encoding 文件编码,默认为 'utf8'
361
359
  * @example
362
360
  * await modifyLine('file.txt', 5, 5, 'New content for line 5');
@@ -368,15 +366,19 @@ async function modifyLine(filePath, startLine, endLine = startLine, newText, enc
368
366
  throw new Error(`行号参数错误: startLine=${startLine}, endLine=${endLine}`);
369
367
  }
370
368
  encoding = encoding || 'utf8';
371
- const lines = (await readFile(filePath, encoding, false)).split('\n');
369
+ const old = await readFile(filePath, encoding);
370
+ const lines = old.split('\n');
372
371
  const lineCount = lines.length;
373
372
  if (endLine > lineCount) {
374
373
  throw new Error(`行号超出范围: ${endLine} (总行数: ${lineCount})`);
375
374
  }
376
- const replacementLines = Array.isArray(newText) ? newText : [newText];
375
+ console.debug(filePath, startLine, endLine, newText);
376
+ const replacementLines = newText.split('\n');
377
377
  const replaceCount = endLine - startLine + 1;
378
378
  lines.splice(startLine - 1, replaceCount, ...replacementLines);
379
- await writeFile(filePath, lines.join('\n'), encoding, true);
379
+ const data = lines.join('\n');
380
+ await writeFile(filePath, data, encoding, true);
381
+ await onChanged?.(filePath, old, data);
380
382
  }
381
383
  /**
382
384
  * 在指定位置插入一行文本
@@ -390,9 +392,12 @@ async function modifyLine(filePath, startLine, endLine = startLine, newText, enc
390
392
  */
391
393
  async function insertLine(filePath, lineNumber, text, encoding = 'utf8') {
392
394
  encoding = encoding || 'utf8';
393
- const lines = (await readFile(filePath, encoding, false)).split('\n');
395
+ const old = await readFile(filePath, encoding, false);
396
+ const lines = old.split('\n');
394
397
  lines.splice(lineNumber, 0, text);
395
- await writeFile(filePath, lines.join('\n'), encoding, true);
398
+ const data = lines.join('\n');
399
+ await writeFile(filePath, data, encoding, true);
400
+ await onChanged?.(filePath, old, data);
396
401
  }
397
402
  /**
398
403
  * 查找指定行的文本内容
@@ -426,14 +431,17 @@ async function deleteLine(filePath, startLine, endLine = startLine, encoding = '
426
431
  throw new Error(`行号参数错误: startLine=${startLine}, endLine=${endLine}`);
427
432
  }
428
433
  encoding = encoding || 'utf8';
429
- const lines = (await readFile(filePath, encoding, false)).split('\n');
434
+ const old = await readFile(filePath, encoding, false);
435
+ const lines = old.split('\n');
430
436
  const lineCount = lines.length;
431
437
  if (endLine > lineCount) {
432
438
  throw new Error(`行号超出范围: ${endLine} (总行数: ${lineCount})`);
433
439
  }
434
440
  const deleteCount = endLine - startLine + 1;
435
441
  lines.splice(startLine - 1, deleteCount);
436
- await writeFile(filePath, lines.join('\n'), encoding, true);
442
+ const data = lines.join('\n');
443
+ await writeFile(filePath, data, encoding, true);
444
+ await onChanged?.(filePath, old, data);
437
445
  }
438
446
  /**
439
447
  * 多行批量操作,支持同时插入、更新和删除操作非连续行。
@@ -450,6 +458,7 @@ async function batchModifyLines(filePath, operations, encoding = 'utf8') {
450
458
  }
451
459
  // 解析操作字符串
452
460
  const ops = parseOperations(operations);
461
+ console.debug(ops);
453
462
  encoding = encoding || 'utf8';
454
463
  const originalContent = await readFile(filePath, encoding, false);
455
464
  let lines = originalContent.split('\n');
@@ -504,7 +513,9 @@ async function batchModifyLines(filePath, operations, encoding = 'utf8') {
504
513
  lines.splice(actualStartLine, 0, ...insertLines);
505
514
  });
506
515
  // 写回文件
507
- await writeFile(filePath, lines.join('\n'), encoding, true);
516
+ const data = lines.join('\n');
517
+ await writeFile(filePath, data, encoding, true);
518
+ await onChanged?.(filePath, originalContent, data);
508
519
  }
509
520
  /**
510
521
  * 安全递归删除文件夹及其内容
package/dist/view.js CHANGED
@@ -97,12 +97,50 @@ async function readCoverageFile(coveragePath) {
97
97
  });
98
98
  });
99
99
  }
100
+ /**
101
+ * 计算多个文件路径的共同前缀,并返回每个路径相对于该前缀的相对路径
102
+ * @param filePaths 要处理的文件路径数组
103
+ * @returns 包含共同前缀和相对路径数组的对象
104
+ */
105
+ function getRelativePathsFromCommonPrefix(filePaths) {
106
+ // 处理空数组情况
107
+ if (filePaths.length === 0) {
108
+ return { commonPrefix: '', relativePaths: [] };
109
+ }
110
+ // 标准化所有路径,统一路径格式和分隔符
111
+ const normalizedPaths = filePaths.map((fp) => path_1.default.normalize(fp));
112
+ // 将每个路径分割为组件数组
113
+ const pathComponents = normalizedPaths.map((fp) => fp.split(path_1.default.sep));
114
+ // 找到所有路径组件数组的共同前缀
115
+ const minLength = Math.min(...pathComponents.map((components) => components.length));
116
+ const commonComponents = [];
117
+ for (let i = 0; i < minLength; i++) {
118
+ // 获取当前索引位置的所有组件
119
+ const currentComponents = pathComponents.map((components) => components[i]);
120
+ // 检查所有组件是否相同
121
+ const allSame = currentComponents.every((comp) => comp === currentComponents[0]);
122
+ if (allSame) {
123
+ commonComponents.push(currentComponents[0]);
124
+ }
125
+ else {
126
+ break;
127
+ }
128
+ }
129
+ // 构建共同前缀路径
130
+ const commonPrefix = commonComponents.join(path_1.default.sep);
131
+ // 计算每个路径相对于共同前缀的相对路径
132
+ const relativePaths = normalizedPaths.map((fp) => {
133
+ return path_1.default.relative(commonPrefix, fp);
134
+ });
135
+ return { commonPrefix, relativePaths };
136
+ }
100
137
  // 生成表格数据
101
138
  function generateTableData(coverage) {
102
139
  const tableData = [['文件路径', '行覆盖率(%)', '语句覆盖率(%)', '函数覆盖率(%)', '分支覆盖率(%)']];
103
140
  // 提取并排序文件路径
104
141
  const filePaths = Object.keys(coverage).filter((key) => key !== 'total');
105
142
  filePaths.sort();
143
+ const { commonPrefix } = getRelativePathsFromCommonPrefix(filePaths);
106
144
  // 添加总计行
107
145
  tableData.push([
108
146
  cli_color_1.default.bold('总计'),
@@ -117,7 +155,7 @@ function generateTableData(coverage) {
117
155
  filePaths.forEach((filePath) => {
118
156
  const fileCoverage = coverage[filePath];
119
157
  tableData.push([
120
- path_1.default.basename(filePath),
158
+ path_1.default.relative(commonPrefix, filePath),
121
159
  formatPercentage(fileCoverage.lines.pct),
122
160
  formatPercentage(fileCoverage.statements.pct),
123
161
  formatPercentage(fileCoverage.functions.pct),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ai-worktool",
3
3
  "displayName": "吃豆豆:单测智能体",
4
- "version": "1.0.72",
4
+ "version": "1.0.74",
5
5
  "description": "单元测试智能体,帮你开发单元测试用例代码直到100%单测覆盖通过。",
6
6
  "author": "jianguoke.cn",
7
7
  "license": "MIT",
@@ -28,11 +28,11 @@
28
28
  "createup": "node ./uposs.js create-local-folder",
29
29
  "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
30
30
  "puball": "yarn ver && yarn exepack && yarn vspack && yarn pubvs && yarn pubovsx && yarn puboss && yarn pubnpm",
31
- "exepack": "yarn createup && cross-env PKG_CACHE_PATH=./binaries pkg -o packages/aicoder-1.0.72 . && yarn upicon",
31
+ "exepack": "yarn createup && cross-env PKG_CACHE_PATH=./binaries pkg -o packages/aicoder-1.0.74 . && yarn upicon",
32
32
  "pubvs": "yarn remove-deps && yarn changelog && vsce publish --yarn --baseContentUrl https://aicoder.jianguoke.cn/assets && yarn restore-deps",
33
33
  "pubovsx": "yarn remove-deps && ovsx publish -p 47621ff6-be56-4814-865e-d2a8e8a76f86 --yarn --baseContentUrl https://aicoder.jianguoke.cn/assets && yarn restore-deps",
34
34
  "patch": "yarn remove-deps && vsce publish patch --yarn && yarn restore-deps",
35
- "vspack": "yarn createup && yarn remove-deps && vsce package -o packages/aicoder-1.0.72.vsix --yarn --baseContentUrl https://aicoder.jianguoke.cn/assets && yarn restore-deps",
35
+ "vspack": "yarn createup && yarn remove-deps && vsce package -o packages/aicoder-1.0.74.vsix --yarn --baseContentUrl https://aicoder.jianguoke.cn/assets && yarn restore-deps",
36
36
  "puboss": "node ./uposs.js upload",
37
37
  "pubnpm": "npm publish --registry=https://registry.npmjs.org",
38
38
  "prepare": "husky"