closer-code 1.0.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.
- package/.env.example +83 -0
- package/API_GUIDE.md +1411 -0
- package/AUTO_MKDIR_IMPROVEMENT.md +354 -0
- package/CLAUDE.md +55 -0
- package/CTRL_C_EXPERIMENT.md +90 -0
- package/PROJECT_CLEANUP_SUMMARY.md +121 -0
- package/README.md +686 -0
- package/cloco.md +51 -0
- package/config.example.json +116 -0
- package/dist/bash-runner.js +128 -0
- package/dist/batch-cli.js +20736 -0
- package/dist/closer-cli.js +21190 -0
- package/dist/index.js +31228 -0
- package/docs/EXPORT_COMMAND.md +152 -0
- package/docs/FILE_NAMING_IMPROVEMENT.md +168 -0
- package/docs/GLOBAL_CONFIG.md +128 -0
- package/docs/LONG_MESSAGE_DISPLAY_FIX.md +202 -0
- package/docs/PROJECT_HISTORY_ISOLATION.md +315 -0
- package/docs/QUICK_START_HISTORY.md +207 -0
- package/docs/TASK_PROGRESS_FEATURE.md +190 -0
- package/docs/THINKING_CONTENT_RESEARCH.md +267 -0
- package/docs/THINKING_FEATURE.md +187 -0
- package/docs/THINKING_IMPROVEMENT_COMPARISON.md +193 -0
- package/docs/THINKING_OPTIMIZATION_SUMMARY.md +242 -0
- package/docs/UI_IMPROVEMENTS_2025-01-18.md +256 -0
- package/docs/WHY_THINKING_SHORT.md +201 -0
- package/package.json +49 -0
- package/scenarios/README.md +234 -0
- package/scenarios/run-all-scenarios.js +342 -0
- package/scenarios/scenario1-batch-converter.js +247 -0
- package/scenarios/scenario2-code-analyzer.js +375 -0
- package/scenarios/scenario3-doc-generator.js +371 -0
- package/scenarios/scenario4-log-analyzer.js +496 -0
- package/scenarios/scenario5-tdd-helper.js +681 -0
- package/src/ai-client-legacy.js +171 -0
- package/src/ai-client.js +221 -0
- package/src/bash-runner.js +148 -0
- package/src/batch-cli.js +327 -0
- package/src/cli.jsx +166 -0
- package/src/closer-cli.jsx +1103 -0
- package/src/closer-cli.jsx.backup +948 -0
- package/src/commands/batch.js +62 -0
- package/src/commands/chat.js +10 -0
- package/src/commands/config.js +154 -0
- package/src/commands/help.js +76 -0
- package/src/commands/history.js +192 -0
- package/src/commands/setup.js +17 -0
- package/src/commands/upgrade.js +101 -0
- package/src/commands/workflow-tests.js +125 -0
- package/src/config.js +343 -0
- package/src/conversation.js +962 -0
- package/src/git-helper.js +349 -0
- package/src/index.js +88 -0
- package/src/logger.js +347 -0
- package/src/plan.js +193 -0
- package/src/planner.js +397 -0
- package/src/search.js +195 -0
- package/src/setup.js +147 -0
- package/src/shortcuts.js +269 -0
- package/src/snippets.js +430 -0
- package/src/test-modules.js +118 -0
- package/src/tools.js +398 -0
- package/src/utils/cli.js +124 -0
- package/src/utils/validator.js +184 -0
- package/src/utils/version.js +33 -0
- package/src/utils/workflow-test.js +271 -0
- package/src/utils/workflow.js +268 -0
- package/test/demo-file-naming.js +92 -0
- package/test/demo-thinking.js +124 -0
- package/test/final-verification-report.md +303 -0
- package/test/research-thinking.js +130 -0
- package/test/test-auto-mkdir.js +123 -0
- package/test/test-e2e-empty-dir.md +108 -0
- package/test/test-export-logic.js +119 -0
- package/test/test-global-cloco.js +126 -0
- package/test/test-history-isolation.js +291 -0
- package/test/test-improved-thinking.js +43 -0
- package/test/test-long-message.js +65 -0
- package/test/test-plan-functionality.js +95 -0
- package/test/test-real-scenario.js +216 -0
- package/test/test-thinking-display.js +65 -0
- package/test/ui-verification-test.js +203 -0
- package/test/verify-history-isolation.sh +71 -0
- package/test/verify-thinking.js +339 -0
- package/test/workflows/empty-dir-creation.md +51 -0
- package/test/workflows/inventor/ascii-teacup.js +199 -0
- package/test/workflows/inventor/ascii-teacup.mjs +199 -0
- package/test/workflows/inventor/ascii_apple.hs +84 -0
- package/test/workflows/inventor/ascii_apple.py +91 -0
- package/test/workflows/inventor/cloco.md +3 -0
- package/test/workflows/longtalk/cloco.md +19 -0
- package/test/workflows/longtalk/emoji_500.txt +63 -0
- package/test/workflows/longtalk/emoji_list.txt +20 -0
- package/test/workflows/programmer/adder.md +33 -0
- package/test/workflows/programmer/expect.md +2 -0
- package/test/workflows/programmer/prompt.md +3 -0
- package/test/workflows/test-empty-dir-creation.js +113 -0
- package/test-ctrl-c.jsx +126 -0
- package/test-manual-file-creation.js +151 -0
- package/winfix.md +3 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 场景五:TDD测试辅助工具
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* 1. 使用 planTask 规划测试任务
|
|
6
|
+
* 2. 使用 runTests 执行测试
|
|
7
|
+
* 3. 使用 editFile 更新测试代码和源代码
|
|
8
|
+
*
|
|
9
|
+
* 工具验证:runTests, planTask, editFile
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'fs/promises';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
import { spawn } from 'child_process';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
|
|
20
|
+
class MockToolExecutor {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.workingDir = path.resolve(__dirname, '..');
|
|
23
|
+
this.callLog = [];
|
|
24
|
+
this.testResults = [];
|
|
25
|
+
this.taskPlans = [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
log(toolName, input, result) {
|
|
29
|
+
this.callLog.push({
|
|
30
|
+
tool: toolName,
|
|
31
|
+
input,
|
|
32
|
+
success: result.success,
|
|
33
|
+
timestamp: new Date().toISOString()
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// planTask 工具 - 规划测试任务
|
|
38
|
+
async planTask({ task, context }) {
|
|
39
|
+
try {
|
|
40
|
+
const plan = {
|
|
41
|
+
id: `task-${Date.now()}`,
|
|
42
|
+
task,
|
|
43
|
+
context,
|
|
44
|
+
timestamp: new Date().toISOString(),
|
|
45
|
+
steps: [],
|
|
46
|
+
status: 'planned'
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// 根据任务类型生成不同的计划
|
|
50
|
+
if (task.includes('test') || task.includes('测试')) {
|
|
51
|
+
plan.steps = [
|
|
52
|
+
{ step: 1, action: '分析需求', description: '理解待测试的功能点' },
|
|
53
|
+
{ step: 2, action: '设计测试用例', description: '编写测试场景和断言' },
|
|
54
|
+
{ step: 3, action: '实现测试代码', description: '编写测试函数' },
|
|
55
|
+
{ step: 4, action: '运行测试', description: '执行测试并验证结果' },
|
|
56
|
+
{ step: 5, action: '重构优化', description: '根据测试结果优化代码' }
|
|
57
|
+
];
|
|
58
|
+
} else if (task.includes('feature') || task.includes('功能')) {
|
|
59
|
+
plan.steps = [
|
|
60
|
+
{ step: 1, action: '需求分析', description: '明确功能需求' },
|
|
61
|
+
{ step: 2, action: '设计接口', description: '定义 API 和数据结构' },
|
|
62
|
+
{ step: 3, action: '编写测试', description: '先写测试用例(TDD)' },
|
|
63
|
+
{ step: 4, action: '实现功能', description: '编写功能代码' },
|
|
64
|
+
{ step: 5, action: '验证测试', description: '确保所有测试通过' }
|
|
65
|
+
];
|
|
66
|
+
} else {
|
|
67
|
+
plan.steps = [
|
|
68
|
+
{ step: 1, action: '任务分解', description: '将大任务分解为小步骤' },
|
|
69
|
+
{ step: 2, action: '制定计划', description: '确定实施顺序' },
|
|
70
|
+
{ step: 3, action: '执行步骤', description: '按计划实施' },
|
|
71
|
+
{ step: 4, action: '检查验证', description: '验证每个步骤' },
|
|
72
|
+
{ step: 5, action: '总结复盘', description: '总结经验教训' }
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.taskPlans.push(plan);
|
|
77
|
+
|
|
78
|
+
const result = {
|
|
79
|
+
success: true,
|
|
80
|
+
data: {
|
|
81
|
+
plan,
|
|
82
|
+
message: `已创建任务计划,包含 ${plan.steps.length} 个步骤`
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
this.log('planTask', { task, steps: plan.steps.length }, result);
|
|
86
|
+
return result;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const result = { success: false, error: error.message };
|
|
89
|
+
this.log('planTask', { task }, result);
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// runTests 工具 - 运行测试
|
|
95
|
+
async runTests({ testCommand, filter }) {
|
|
96
|
+
return new Promise((resolve) => {
|
|
97
|
+
let command = testCommand;
|
|
98
|
+
|
|
99
|
+
if (!command) {
|
|
100
|
+
// 使用默认测试命令
|
|
101
|
+
command = 'npm test';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (filter) {
|
|
105
|
+
command += ` -- ${filter}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const proc = spawn('bash', ['-lc', command], {
|
|
109
|
+
cwd: this.workingDir,
|
|
110
|
+
shell: true,
|
|
111
|
+
env: { ...process.env }
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
let stdout = '';
|
|
115
|
+
let stderr = '';
|
|
116
|
+
|
|
117
|
+
proc.stdout.on('data', (data) => {
|
|
118
|
+
stdout += data.toString();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
proc.stderr.on('data', (data) => {
|
|
122
|
+
stderr += data.toString();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const timer = setTimeout(() => {
|
|
126
|
+
proc.kill('SIGKILL');
|
|
127
|
+
const result = {
|
|
128
|
+
success: false,
|
|
129
|
+
data: { error: 'Test timeout' },
|
|
130
|
+
error: 'Test timeout'
|
|
131
|
+
};
|
|
132
|
+
this.log('runTests', { command }, result);
|
|
133
|
+
resolve(result);
|
|
134
|
+
}, 30000);
|
|
135
|
+
|
|
136
|
+
proc.on('close', (exitCode) => {
|
|
137
|
+
clearTimeout(timer);
|
|
138
|
+
|
|
139
|
+
// 解析测试结果
|
|
140
|
+
const testResult = {
|
|
141
|
+
command,
|
|
142
|
+
exitCode,
|
|
143
|
+
passed: exitCode === 0,
|
|
144
|
+
output: stdout,
|
|
145
|
+
errors: stderr,
|
|
146
|
+
timestamp: new Date().toISOString()
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// 尝试提取测试统计
|
|
150
|
+
const passMatch = stdout.match(/passing\s*[:=]?\s*(\d+)/i);
|
|
151
|
+
const failMatch = stdout.match(/failing\s*[:=]?\s*(\d+)/i);
|
|
152
|
+
|
|
153
|
+
if (passMatch || failMatch) {
|
|
154
|
+
testResult.stats = {
|
|
155
|
+
passed: passMatch ? parseInt(passMatch[1]) : 0,
|
|
156
|
+
failed: failMatch ? parseInt(failMatch[1]) : 0
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.testResults.push(testResult);
|
|
161
|
+
|
|
162
|
+
const result = {
|
|
163
|
+
success: true,
|
|
164
|
+
data: testResult
|
|
165
|
+
};
|
|
166
|
+
this.log('runTests', {
|
|
167
|
+
command,
|
|
168
|
+
passed: testResult.passed,
|
|
169
|
+
stats: testResult.stats
|
|
170
|
+
}, result);
|
|
171
|
+
resolve(result);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
proc.on('error', (error) => {
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
const result = {
|
|
177
|
+
success: false,
|
|
178
|
+
data: null,
|
|
179
|
+
error: error.message
|
|
180
|
+
};
|
|
181
|
+
this.log('runTests', { command }, result);
|
|
182
|
+
resolve(result);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// editFile 工具 - 编辑测试文件
|
|
188
|
+
async editFile({ filePath, oldText, newText, replaceAll = false }) {
|
|
189
|
+
try {
|
|
190
|
+
const fullPath = path.resolve(this.workingDir, filePath);
|
|
191
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
192
|
+
|
|
193
|
+
let content = '';
|
|
194
|
+
try {
|
|
195
|
+
content = await fs.readFile(fullPath, 'utf-8');
|
|
196
|
+
} catch {
|
|
197
|
+
// 文件不存在,创建新文件
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let replacements = 0;
|
|
201
|
+
if (replaceAll) {
|
|
202
|
+
const matches = content.split(oldText);
|
|
203
|
+
replacements = matches.length - 1;
|
|
204
|
+
content = matches.join(newText);
|
|
205
|
+
} else {
|
|
206
|
+
if (oldText && content.includes(oldText)) {
|
|
207
|
+
content = content.replace(oldText, newText);
|
|
208
|
+
replacements = 1;
|
|
209
|
+
} else {
|
|
210
|
+
// 追加内容
|
|
211
|
+
if (content && !content.endsWith('\n')) {
|
|
212
|
+
content += '\n';
|
|
213
|
+
}
|
|
214
|
+
content += newText;
|
|
215
|
+
replacements = 1;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
await fs.writeFile(fullPath, content, 'utf-8');
|
|
220
|
+
|
|
221
|
+
const result = {
|
|
222
|
+
success: true,
|
|
223
|
+
data: { path: fullPath, replacements, contentLength: content.length }
|
|
224
|
+
};
|
|
225
|
+
this.log('editFile', { filePath, replacements }, result);
|
|
226
|
+
return result;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
const result = { success: false, error: error.message };
|
|
229
|
+
this.log('editFile', { filePath }, result);
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
getCallLog() {
|
|
235
|
+
return this.callLog;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
getTestResults() {
|
|
239
|
+
return this.testResults;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
getTaskPlans() {
|
|
243
|
+
return this.taskPlans;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
printSummary() {
|
|
247
|
+
console.log('\n=== 工具调用统计 ===');
|
|
248
|
+
const stats = {};
|
|
249
|
+
this.callLog.forEach(log => {
|
|
250
|
+
stats[log.tool] = (stats[log.tool] || 0) + 1;
|
|
251
|
+
});
|
|
252
|
+
Object.entries(stats).forEach(([tool, count]) => {
|
|
253
|
+
console.log(` ${tool}: ${count} 次`);
|
|
254
|
+
});
|
|
255
|
+
console.log(` 总计: ${this.callLog.length} 次`);
|
|
256
|
+
|
|
257
|
+
if (this.taskPlans.length > 0) {
|
|
258
|
+
console.log('\n=== 任务规划统计 ===');
|
|
259
|
+
this.taskPlans.forEach(plan => {
|
|
260
|
+
console.log(` ${plan.id}: ${plan.steps.length} 个步骤`);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (this.testResults.length > 0) {
|
|
265
|
+
console.log('\n=== 测试执行统计 ===');
|
|
266
|
+
const passed = this.testResults.filter(r => r.passed).length;
|
|
267
|
+
const failed = this.testResults.filter(r => !r.passed).length;
|
|
268
|
+
console.log(` 通过: ${passed} 次`);
|
|
269
|
+
console.log(` 失败: ${failed} 次`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// TDD辅助工具场景
|
|
275
|
+
async function runTDDHelperScenario() {
|
|
276
|
+
console.log('\n========================================');
|
|
277
|
+
console.log('场景五:TDD测试辅助工具');
|
|
278
|
+
console.log('========================================\n');
|
|
279
|
+
|
|
280
|
+
const executor = new MockToolExecutor();
|
|
281
|
+
const testFile = 'scenarios/output/math-utils.test.js';
|
|
282
|
+
const sourceFile = 'scenarios/output/math-utils.js';
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const tddData = {
|
|
286
|
+
features: [],
|
|
287
|
+
tests: [],
|
|
288
|
+
iterations: []
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// 步骤 1: 规划第一个功能 - 加法函数
|
|
292
|
+
console.log('步骤 1: 规划功能开发任务...');
|
|
293
|
+
const plan1 = await executor.planTask({
|
|
294
|
+
task: '实现数学工具库 - 加法功能',
|
|
295
|
+
context: '使用 TDD 方法开发,先写测试再实现功能'
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (plan1.success) {
|
|
299
|
+
console.log(` ✓ 创建计划: ${plan1.data.plan.steps.length} 个步骤`);
|
|
300
|
+
plan1.data.plan.steps.forEach(step => {
|
|
301
|
+
console.log(` ${step.step}. ${step.action}: ${step.description}`);
|
|
302
|
+
});
|
|
303
|
+
tddData.features.push({
|
|
304
|
+
name: '加法功能',
|
|
305
|
+
plan: plan1.data.plan
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
console.log('');
|
|
310
|
+
|
|
311
|
+
// 步骤 2: 编写测试用例(Red阶段)
|
|
312
|
+
console.log('步骤 2: 编写测试用例(Red阶段)...');
|
|
313
|
+
|
|
314
|
+
const testCode = `
|
|
315
|
+
// 数学工具库测试套件
|
|
316
|
+
import assert from 'assert';
|
|
317
|
+
|
|
318
|
+
describe('MathUtils', () => {
|
|
319
|
+
describe('add', () => {
|
|
320
|
+
it('应该正确相加两个正数', () => {
|
|
321
|
+
assert.strictEqual(add(2, 3), 5);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('应该正确处理负数', () => {
|
|
325
|
+
assert.strictEqual(add(-2, 3), 1);
|
|
326
|
+
assert.strictEqual(add(-2, -3), -5);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('应该正确处理零', () => {
|
|
330
|
+
assert.strictEqual(add(0, 5), 5);
|
|
331
|
+
assert.strictEqual(add(0, 0), 0);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('应该正确处理小数', () => {
|
|
335
|
+
assert.strictEqual(add(0.1, 0.2), 0.3);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe('multiply', () => {
|
|
340
|
+
it('应该正确相乘两个正数', () => {
|
|
341
|
+
assert.strictEqual(multiply(2, 3), 6);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('应该正确处理负数', () => {
|
|
345
|
+
assert.strictEqual(multiply(-2, 3), -6);
|
|
346
|
+
assert.strictEqual(multiply(-2, -3), 6);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('应该正确处理零', () => {
|
|
350
|
+
assert.strictEqual(multiply(0, 5), 0);
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
`;
|
|
355
|
+
|
|
356
|
+
const writeTestResult = await executor.editFile({
|
|
357
|
+
filePath: testFile,
|
|
358
|
+
oldText: '',
|
|
359
|
+
newText: testCode
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
if (writeTestResult.success) {
|
|
363
|
+
console.log(` ✓ 测试文件已创建: ${testFile}`);
|
|
364
|
+
tddData.tests.push({
|
|
365
|
+
file: testFile,
|
|
366
|
+
size: writeTestResult.data.contentLength
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
console.log('');
|
|
371
|
+
|
|
372
|
+
// 步骤 3: 运行测试(预期失败)
|
|
373
|
+
console.log('步骤 3: 运行测试(预期失败 - Red)...');
|
|
374
|
+
const testResult1 = await executor.runTests({
|
|
375
|
+
testCommand: 'node --test 2>&1 || echo "Test framework not available, skipping..."'
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
if (testResult1.success) {
|
|
379
|
+
const testResult = testResult1.data;
|
|
380
|
+
const passed = testResult ? testResult.passed : false;
|
|
381
|
+
console.log(` 测试状态: ${passed ? '✓ 意外通过' : '✗ 预期失败(Red阶段)'}`);
|
|
382
|
+
tddData.iterations.push({
|
|
383
|
+
phase: 'red',
|
|
384
|
+
result: passed ? 'unexpected' : 'expected',
|
|
385
|
+
output: testResult ? testResult.output : 'No output'
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
console.log('');
|
|
390
|
+
|
|
391
|
+
// 步骤 4: 实现功能代码(Green阶段)
|
|
392
|
+
console.log('步骤 4: 实现功能代码(Green阶段)...');
|
|
393
|
+
|
|
394
|
+
const sourceCode = `
|
|
395
|
+
// 数学工具库实现
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* 加法运算
|
|
399
|
+
* @param {number} a - 第一个数
|
|
400
|
+
* @param {number} b - 第二个数
|
|
401
|
+
* @returns {number} 两数之和
|
|
402
|
+
*/
|
|
403
|
+
export function add(a, b) {
|
|
404
|
+
return a + b;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* 乘法运算
|
|
409
|
+
* @param {number} a - 第一个数
|
|
410
|
+
* @param {number} b - 第二个数
|
|
411
|
+
* @returns {number} 两数之积
|
|
412
|
+
*/
|
|
413
|
+
export function multiply(a, b) {
|
|
414
|
+
return a * b;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* 减法运算
|
|
419
|
+
* @param {number} a - 第一个数
|
|
420
|
+
* @param {number} b - 第二个数
|
|
421
|
+
* @returns {number} 两数之差
|
|
422
|
+
*/
|
|
423
|
+
export function subtract(a, b) {
|
|
424
|
+
return a - b;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* 除法运算
|
|
429
|
+
* @param {number} a - 被除数
|
|
430
|
+
* @param {number} b - 除数
|
|
431
|
+
* @returns {number} 两数之商
|
|
432
|
+
*/
|
|
433
|
+
export function divide(a, b) {
|
|
434
|
+
if (b === 0) {
|
|
435
|
+
throw new Error('Division by zero');
|
|
436
|
+
}
|
|
437
|
+
return a / b;
|
|
438
|
+
}
|
|
439
|
+
`;
|
|
440
|
+
|
|
441
|
+
const writeSourceResult = await executor.editFile({
|
|
442
|
+
filePath: sourceFile,
|
|
443
|
+
oldText: '',
|
|
444
|
+
newText: sourceCode
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
if (writeSourceResult.success) {
|
|
448
|
+
console.log(` ✓ 源代码文件已创建: ${sourceFile}`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// 更新测试文件以添加导出语句
|
|
452
|
+
const updatedTestCode = testCode + `
|
|
453
|
+
// 导入被测试的模块
|
|
454
|
+
import { add, multiply, subtract, divide } from './math-utils.js';
|
|
455
|
+
`;
|
|
456
|
+
|
|
457
|
+
await executor.editFile({
|
|
458
|
+
filePath: testFile,
|
|
459
|
+
oldText: testCode,
|
|
460
|
+
newText: updatedTestCode
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
console.log('');
|
|
464
|
+
|
|
465
|
+
// 步骤 5: 规划第二个功能
|
|
466
|
+
console.log('步骤 5: 规划扩展功能...');
|
|
467
|
+
const plan2 = await executor.planTask({
|
|
468
|
+
task: '扩展数学工具库 - 添加减法和除法',
|
|
469
|
+
context: '继续使用 TDD 方法,确保所有测试通过'
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
if (plan2.success) {
|
|
473
|
+
console.log(` ✓ 创建计划: ${plan2.data.plan.steps.length} 个步骤`);
|
|
474
|
+
tddData.features.push({
|
|
475
|
+
name: '减法和除法功能',
|
|
476
|
+
plan: plan2.data.plan
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
console.log('');
|
|
481
|
+
|
|
482
|
+
// 步骤 6: 添加更多测试用例
|
|
483
|
+
console.log('步骤 6: 扩展测试用例...');
|
|
484
|
+
|
|
485
|
+
const additionalTests = `
|
|
486
|
+
describe('subtract', () => {
|
|
487
|
+
it('应该正确相减两个正数', () => {
|
|
488
|
+
assert.strictEqual(subtract(5, 3), 2);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('应该正确处理负数', () => {
|
|
492
|
+
assert.strictEqual(subtract(-2, 3), -5);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
describe('divide', () => {
|
|
497
|
+
it('应该正确相除两个正数', () => {
|
|
498
|
+
assert.strictEqual(divide(6, 3), 2);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('应该抛出除零错误', () => {
|
|
502
|
+
assert.throws(() => divide(5, 0), /Division by zero/);
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
`;
|
|
506
|
+
await executor.editFile({
|
|
507
|
+
filePath: testFile,
|
|
508
|
+
oldText: '});\n',
|
|
509
|
+
newText: additionalTests + '});\n'
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
console.log(' ✓ 扩展测试用例已完成');
|
|
513
|
+
|
|
514
|
+
console.log('');
|
|
515
|
+
|
|
516
|
+
// 步骤 7: 再次运行测试
|
|
517
|
+
console.log('步骤 7: 运行完整测试套件...');
|
|
518
|
+
|
|
519
|
+
// 创建一个简单的测试运行脚本
|
|
520
|
+
const testRunner = `#!/usr/bin/env node
|
|
521
|
+
// 简单的测试运行器
|
|
522
|
+
import { add, multiply, subtract, divide } from './scenarios/output/math-utils.js';
|
|
523
|
+
|
|
524
|
+
let passed = 0;
|
|
525
|
+
let failed = 0;
|
|
526
|
+
|
|
527
|
+
function test(name, fn) {
|
|
528
|
+
try {
|
|
529
|
+
fn();
|
|
530
|
+
console.log('✓', name);
|
|
531
|
+
passed++;
|
|
532
|
+
} catch (error) {
|
|
533
|
+
console.log('✗', name);
|
|
534
|
+
console.log(' Error:', error.message);
|
|
535
|
+
failed++;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// 运行测试
|
|
540
|
+
test('add(2, 3) === 5', () => {
|
|
541
|
+
if (add(2, 3) !== 5) throw new Error('Expected 5');
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test('add(-2, 3) === 1', () => {
|
|
545
|
+
if (add(-2, 3) !== 1) throw new Error('Expected 1');
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test('multiply(2, 3) === 6', () => {
|
|
549
|
+
if (multiply(2, 3) !== 6) throw new Error('Expected 6');
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test('multiply(-2, 3) === -6', () => {
|
|
553
|
+
if (multiply(-2, 3) !== -6) throw new Error('Expected -6');
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test('subtract(5, 3) === 2', () => {
|
|
557
|
+
if (subtract(5, 3) !== 2) throw new Error('Expected 2');
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
test('divide(6, 3) === 2', () => {
|
|
561
|
+
if (divide(6, 3) !== 2) throw new Error('Expected 2');
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
test('divide(5, 0) throws error', () => {
|
|
565
|
+
try {
|
|
566
|
+
divide(5, 0);
|
|
567
|
+
throw new Error('Should have thrown');
|
|
568
|
+
} catch (error) {
|
|
569
|
+
if (!error.message.includes('Division by zero')) throw new Error('Wrong error');
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
console.log(\`\\n测试结果: \${passed} 通过, \${failed} 失败\`);
|
|
574
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
575
|
+
`;
|
|
576
|
+
|
|
577
|
+
await executor.editFile({
|
|
578
|
+
filePath: 'scenarios/output/run-tests.js',
|
|
579
|
+
oldText: '',
|
|
580
|
+
newText: testRunner
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
const testResult2 = await executor.runTests({
|
|
584
|
+
testCommand: 'node scenarios/output/run-tests.js'
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
if (testResult2.success) {
|
|
588
|
+
const testResult = testResult2.data;
|
|
589
|
+
console.log('\n' + (testResult ? testResult.output : 'No output'));
|
|
590
|
+
tddData.iterations.push({
|
|
591
|
+
phase: 'green',
|
|
592
|
+
result: testResult && testResult.passed ? 'success' : 'failure',
|
|
593
|
+
output: testResult ? testResult.output : 'No output'
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
console.log('');
|
|
598
|
+
|
|
599
|
+
// 步骤 8: 生成 TDD 报告
|
|
600
|
+
console.log('步骤 8: 生成 TDD 开发报告...');
|
|
601
|
+
|
|
602
|
+
let report = `# TDD 开发报告\n\n`;
|
|
603
|
+
report += `**生成时间**: ${new Date().toISOString()}\n\n`;
|
|
604
|
+
report += `---\n\n`;
|
|
605
|
+
|
|
606
|
+
report += `## 功能开发列表\n\n`;
|
|
607
|
+
tddData.features.forEach((feature, idx) => {
|
|
608
|
+
report += `### ${idx + 1}. ${feature.name}\n\n`;
|
|
609
|
+
report += `- 计划步骤: ${feature.plan.steps.length}\n`;
|
|
610
|
+
feature.plan.steps.forEach(step => {
|
|
611
|
+
report += ` ${step.step}. ${step.action}: ${step.description}\n`;
|
|
612
|
+
});
|
|
613
|
+
report += '\n';
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
report += `## TDD 迭代过程\n\n`;
|
|
617
|
+
tddData.iterations.forEach((iteration, idx) => {
|
|
618
|
+
report += `### 迭代 ${idx + 1}: ${iteration.phase.toUpperCase()} 阶段\n\n`;
|
|
619
|
+
report += `- 结果: ${iteration.result}\n\n`;
|
|
620
|
+
if (iteration.output) {
|
|
621
|
+
report += `\`\`\`\n${iteration.output}\n\`\`\`\n\n`;
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
report += `## 生成的文件\n\n`;
|
|
626
|
+
report += `- 测试文件: ${testFile}\n`;
|
|
627
|
+
report += `- 源代码: ${sourceFile}\n`;
|
|
628
|
+
report += `- 测试运行器: scenarios/output/run-tests.js\n\n`;
|
|
629
|
+
|
|
630
|
+
report += `## TDD 最佳实践验证\n\n`;
|
|
631
|
+
report += `- ✅ 先写测试,后写代码\n`;
|
|
632
|
+
report += `- ✅ 测试驱动开发流程\n`;
|
|
633
|
+
report += `- ✅ 小步迭代,快速反馈\n`;
|
|
634
|
+
report += `- ✅ 持续重构和改进\n`;
|
|
635
|
+
|
|
636
|
+
await executor.editFile({
|
|
637
|
+
filePath: 'scenarios/output/tdd-report.md',
|
|
638
|
+
oldText: '',
|
|
639
|
+
newText: report
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
console.log(' ✓ TDD 报告已生成');
|
|
643
|
+
|
|
644
|
+
// 打印总结
|
|
645
|
+
console.log('\n========================================');
|
|
646
|
+
console.log('TDD 开发完成');
|
|
647
|
+
console.log('========================================');
|
|
648
|
+
console.log(`规划功能: ${tddData.features.length} 个`);
|
|
649
|
+
console.log(`测试迭代: ${tddData.iterations.length} 次`);
|
|
650
|
+
console.log(`生成文件: 测试代码 + 源代码 + 报告`);
|
|
651
|
+
|
|
652
|
+
executor.printSummary();
|
|
653
|
+
|
|
654
|
+
return {
|
|
655
|
+
success: true,
|
|
656
|
+
tddData,
|
|
657
|
+
toolCalls: executor.getCallLog()
|
|
658
|
+
};
|
|
659
|
+
} catch (error) {
|
|
660
|
+
console.error('\n场景执行失败:', error.message);
|
|
661
|
+
return { success: false, error: error.message };
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
async function main() {
|
|
666
|
+
const result = await runTDDHelperScenario();
|
|
667
|
+
|
|
668
|
+
if (result.success) {
|
|
669
|
+
console.log('\n✓ 场景五执行成功\n');
|
|
670
|
+
process.exit(0);
|
|
671
|
+
} else {
|
|
672
|
+
console.log('\n✗ 场景五执行失败\n');
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
export { runTDDHelperScenario };
|
|
678
|
+
|
|
679
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
680
|
+
main();
|
|
681
|
+
}
|