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,108 @@
|
|
|
1
|
+
# 端到端测试:空目录中创建多文件项目
|
|
2
|
+
|
|
3
|
+
## 测试目的
|
|
4
|
+
验证在空目录中使用 Closer Code 时,AI 能够成功创建多文件项目,而不会因为目录不存在而失败。
|
|
5
|
+
|
|
6
|
+
## 测试场景
|
|
7
|
+
用户在一个全新的空目录中运行 Closer Code,要求创建一个包含多个文件和子目录的玄幻故事项目。
|
|
8
|
+
|
|
9
|
+
## 测试步骤
|
|
10
|
+
|
|
11
|
+
### 1. 准备测试环境
|
|
12
|
+
```bash
|
|
13
|
+
# 创建临时测试目录
|
|
14
|
+
mkdir /tmp/test-closer-empty
|
|
15
|
+
cd /tmp/test-closer-empty
|
|
16
|
+
|
|
17
|
+
# 确保目录为空
|
|
18
|
+
ls -la
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2. 启动 Closer Code
|
|
22
|
+
```bash
|
|
23
|
+
# 从项目根目录运行
|
|
24
|
+
cd /path/to/closer-code
|
|
25
|
+
node dist/index.js --dir /tmp/test-closer-empty --batch
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 3. 发送任务提示
|
|
29
|
+
```
|
|
30
|
+
写一个50000字的玄幻故事,要求:
|
|
31
|
+
1. 故事名称为《天道诀》
|
|
32
|
+
2. 分为10个章节,每章约5000字
|
|
33
|
+
3. 每个章节保存为独立的文件,格式为 chapters/chapter-01.txt 到 chapters/chapter-10.txt
|
|
34
|
+
4. 创建一个 README.md 文件,包含故事简介和章节列表
|
|
35
|
+
5. 创建一个 outline.md 文件,包含详细的故事大纲
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 4. 预期行为
|
|
39
|
+
AI 应该:
|
|
40
|
+
1. 立即开始使用 writeFile 工具创建文件
|
|
41
|
+
2. 自动创建 chapters/ 目录(无需手动 mkdir)
|
|
42
|
+
3. 创建所有章节文件
|
|
43
|
+
4. 创建 README.md 和 outline.md
|
|
44
|
+
5. 不会因为目录不存在而报错
|
|
45
|
+
|
|
46
|
+
### 5. 验证结果
|
|
47
|
+
```bash
|
|
48
|
+
# 检查目录结构
|
|
49
|
+
tree /tmp/test-closer-empty
|
|
50
|
+
# 或
|
|
51
|
+
find /tmp/test-closer-empty -type f
|
|
52
|
+
|
|
53
|
+
# 检查文件数量
|
|
54
|
+
find /tmp/test-closer-empty -name "*.txt" | wc -l
|
|
55
|
+
# 预期输出: 10
|
|
56
|
+
|
|
57
|
+
# 检查 README.md
|
|
58
|
+
cat /tmp/test-closer-empty/README.md
|
|
59
|
+
|
|
60
|
+
# 检查第一章
|
|
61
|
+
head -30 /tmp/test-closer-empty/chapters/chapter-01.txt
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 预期结果
|
|
65
|
+
- ✅ chapters/ 目录被自动创建
|
|
66
|
+
- ✅ 10个章节文件全部存在
|
|
67
|
+
- ✅ README.md 存在且包含故事简介
|
|
68
|
+
- ✅ outline.md 存在且包含大纲
|
|
69
|
+
- ✅ 所有文件内容完整,无错误
|
|
70
|
+
|
|
71
|
+
## 测试通过标准
|
|
72
|
+
1. 所有文件和目录都被正确创建
|
|
73
|
+
2. AI 没有报告任何目录相关错误
|
|
74
|
+
3. 文件内容符合要求
|
|
75
|
+
4. 整个流程顺利完成,没有中断
|
|
76
|
+
|
|
77
|
+
## 技术实现
|
|
78
|
+
writeFile 工具现在包含以下代码:
|
|
79
|
+
```javascript
|
|
80
|
+
// 自动创建父目录
|
|
81
|
+
const parentDir = path.dirname(fullPath);
|
|
82
|
+
try {
|
|
83
|
+
await fs.mkdir(parentDir, { recursive: true });
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error.code !== 'EEXIST') {
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 系统提示词改进
|
|
92
|
+
在系统提示词中添加了明确的说明:
|
|
93
|
+
```
|
|
94
|
+
## File Creation and Directory Handling (IMPORTANT)
|
|
95
|
+
**The writeFile tool automatically creates parent directories as needed.**
|
|
96
|
+
You do NOT need to create directories manually before writing files.
|
|
97
|
+
|
|
98
|
+
**When creating files:**
|
|
99
|
+
- Simply use writeFile with the full path
|
|
100
|
+
- The tool will automatically create parent directories
|
|
101
|
+
- This works for nested paths of any depth
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 相关文件
|
|
105
|
+
- `src/tools.js` - writeFile 工具实现
|
|
106
|
+
- `src/conversation.js` - 系统提示词
|
|
107
|
+
- `test/test-auto-mkdir.js` - 单元测试
|
|
108
|
+
- `test-manual-file-creation.js` - 手动测试脚本
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 测试导出功能的逻辑
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
async function testExportLogic() {
|
|
10
|
+
console.log('🧪 测试导出功能逻辑\n');
|
|
11
|
+
console.log('═'.repeat(60));
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
// 模拟对话数据
|
|
15
|
+
const mockConversation = {
|
|
16
|
+
messages: [
|
|
17
|
+
{ role: 'user', content: '你好' },
|
|
18
|
+
{ role: 'assistant', content: '你好!有什么可以帮助你的吗?' },
|
|
19
|
+
{ role: 'user', content: '请帮我写一个 Python 脚本' },
|
|
20
|
+
{ role: 'assistant', content: '好的,我来帮你写一个 Python 脚本。' }
|
|
21
|
+
],
|
|
22
|
+
export() {
|
|
23
|
+
return {
|
|
24
|
+
messages: this.messages
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
console.log('\n📝 测试消息:');
|
|
30
|
+
mockConversation.messages.forEach((msg, i) => {
|
|
31
|
+
console.log(` ${i + 1}. [${msg.role}] ${msg.content.substring(0, 30)}...`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 导出逻辑(复制自 closer-cli.jsx)
|
|
35
|
+
const exportData = mockConversation.export();
|
|
36
|
+
const messages = exportData.messages || [];
|
|
37
|
+
|
|
38
|
+
// 生成文本格式
|
|
39
|
+
let textContent = '';
|
|
40
|
+
textContent += '='.repeat(80) + '\n';
|
|
41
|
+
textContent += 'Closer Code - Conversation Export\n';
|
|
42
|
+
textContent += '='.repeat(80) + '\n';
|
|
43
|
+
textContent += `Export Date: ${new Date().toLocaleString('zh-CN')}\n`;
|
|
44
|
+
textContent += `Total Messages: ${messages.length}\n`;
|
|
45
|
+
textContent += '='.repeat(80) + '\n\n';
|
|
46
|
+
|
|
47
|
+
messages.forEach((msg, index) => {
|
|
48
|
+
const role = msg.role || 'unknown';
|
|
49
|
+
const roleLabel = {
|
|
50
|
+
'user': '👤 User',
|
|
51
|
+
'assistant': '🤖 Assistant',
|
|
52
|
+
'system': 'ℹ️ System',
|
|
53
|
+
'error': '❌ Error'
|
|
54
|
+
}[role] || role;
|
|
55
|
+
|
|
56
|
+
textContent += `[${index + 1}] ${roleLabel}\n`;
|
|
57
|
+
textContent += '-'.repeat(80) + '\n';
|
|
58
|
+
|
|
59
|
+
const content = msg.content;
|
|
60
|
+
if (typeof content === 'string') {
|
|
61
|
+
textContent += content + '\n';
|
|
62
|
+
} else if (Array.isArray(content)) {
|
|
63
|
+
// 处理工具调用等复杂内容
|
|
64
|
+
content.forEach(block => {
|
|
65
|
+
if (block.type === 'text') {
|
|
66
|
+
textContent += block.text + '\n';
|
|
67
|
+
} else if (block.type === 'tool_use') {
|
|
68
|
+
textContent += `[Tool: ${block.name}]\n`;
|
|
69
|
+
textContent += JSON.stringify(block.input, null, 2) + '\n';
|
|
70
|
+
} else if (block.type === 'tool_result') {
|
|
71
|
+
textContent += `[Tool Result]\n`;
|
|
72
|
+
textContent += block.content + '\n';
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
textContent += JSON.stringify(content, null, 2) + '\n';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
textContent += '\n';
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
textContent += '='.repeat(80) + '\n';
|
|
83
|
+
textContent += 'End of Export\n';
|
|
84
|
+
textContent += '='.repeat(80) + '\n';
|
|
85
|
+
|
|
86
|
+
// 写入文件
|
|
87
|
+
const filename = 'test-export.txt';
|
|
88
|
+
fs.writeFileSync(filename, textContent, 'utf-8');
|
|
89
|
+
|
|
90
|
+
console.log('\n✅ 导出成功!');
|
|
91
|
+
console.log(` 文件路径: ${filename}`);
|
|
92
|
+
|
|
93
|
+
// 读取并显示前几行
|
|
94
|
+
const content = fs.readFileSync(filename, 'utf-8');
|
|
95
|
+
const lines = content.split('\n');
|
|
96
|
+
|
|
97
|
+
console.log('\n📄 导出内容预览(前25行):\n');
|
|
98
|
+
console.log(lines.slice(0, 25).join('\n'));
|
|
99
|
+
console.log('\n... (内容已截断)\n');
|
|
100
|
+
|
|
101
|
+
// 清理测试文件
|
|
102
|
+
fs.unlinkSync(filename);
|
|
103
|
+
console.log('🧹 测试文件已清理\n');
|
|
104
|
+
|
|
105
|
+
console.log('═'.repeat(60));
|
|
106
|
+
console.log('\n✅ 所有测试通过!\n');
|
|
107
|
+
console.log('📊 测试总结:');
|
|
108
|
+
console.log(' ✅ 导出逻辑正确');
|
|
109
|
+
console.log(' ✅ 文本格式正确');
|
|
110
|
+
console.log(' ✅ 消息序列化正确');
|
|
111
|
+
console.log(' ✅ 文件读写正确\n');
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('\n❌ 测试失败:', error.message);
|
|
114
|
+
console.error(error.stack);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 运行测试
|
|
119
|
+
testExportLogic().catch(console.error);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 测试全局 cloco.md 配置功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
import { createConversation } from '../src/conversation.js';
|
|
10
|
+
import { loadConfig } from '../src/config.js';
|
|
11
|
+
|
|
12
|
+
async function testGlobalCloco() {
|
|
13
|
+
console.log('='.repeat(70));
|
|
14
|
+
console.log('测试全局 cloco.md 配置功能');
|
|
15
|
+
console.log('='.repeat(70));
|
|
16
|
+
|
|
17
|
+
// 1. 检查全局配置文件是否存在
|
|
18
|
+
const homeDir = os.homedir();
|
|
19
|
+
const globalClocoPath = path.join(homeDir, '.closer-code', 'cloco.md');
|
|
20
|
+
|
|
21
|
+
console.log('\n1️⃣ 检查全局配置文件');
|
|
22
|
+
console.log(` 路径: ${globalClocoPath}`);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const globalContent = await fs.readFile(globalClocoPath, 'utf-8');
|
|
26
|
+
console.log(` ✅ 全局配置文件存在 (${globalContent.length} 字符)`);
|
|
27
|
+
console.log(` 📄 内容预览:\n${globalContent.split('\n').slice(0, 5).join('\n')}...`);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error.code === 'ENOENT') {
|
|
30
|
+
console.log(' ⚠️ 全局配置文件不存在(这是正常的)');
|
|
31
|
+
} else {
|
|
32
|
+
console.log(` ❌ 读取失败: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. 检查项目配置文件
|
|
37
|
+
const projectClocoPath = path.join(process.cwd(), 'cloco.md');
|
|
38
|
+
|
|
39
|
+
console.log('\n2️⃣ 检查项目配置文件');
|
|
40
|
+
console.log(` 路径: ${projectClocoPath}`);
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const projectContent = await fs.readFile(projectClocoPath, 'utf-8');
|
|
44
|
+
console.log(` ✅ 项目配置文件存在 (${projectContent.length} 字符)`);
|
|
45
|
+
console.log(` 📄 内容预览:\n${projectContent.split('\n').slice(0, 5).join('\n')}...`);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error.code === 'ENOENT') {
|
|
48
|
+
console.log(' ⚠️ 项目配置文件不存在(这是正常的)');
|
|
49
|
+
} else {
|
|
50
|
+
console.log(` ❌ 读取失败: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 3. 测试配置加载
|
|
55
|
+
console.log('\n3️⃣ 测试配置加载');
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const config = await loadConfig();
|
|
59
|
+
console.log(' ✅ 配置加载成功');
|
|
60
|
+
console.log(` 📋 工作目录: ${config.behavior.workingDir}`);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.log(` ❌ 配置加载失败: ${error.message}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 4. 测试对话初始化
|
|
67
|
+
console.log('\n4️⃣ 测试对话初始化');
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const conversation = await createConversation(await loadConfig(), false);
|
|
71
|
+
console.log(' ✅ 对话初始化成功');
|
|
72
|
+
|
|
73
|
+
// 检查系统提示词
|
|
74
|
+
const systemPrompt = conversation.systemPrompt;
|
|
75
|
+
console.log(` 📝 系统提示词长度: ${systemPrompt.length} 字符`);
|
|
76
|
+
|
|
77
|
+
// 检查是否包含全局配置
|
|
78
|
+
if (systemPrompt.includes('Global Behavior Guidelines')) {
|
|
79
|
+
console.log(' ✅ 系统提示词包含全局行为规范');
|
|
80
|
+
} else {
|
|
81
|
+
console.log(' ℹ️ 系统提示词未包含全局行为规范(可能文件不存在)');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 检查是否包含项目配置
|
|
85
|
+
if (systemPrompt.includes('Project Behavior Guidelines')) {
|
|
86
|
+
console.log(' ✅ 系统提示词包含项目行为规范');
|
|
87
|
+
} else {
|
|
88
|
+
console.log(' ℹ️ 系统提示词未包含项目行为规范(可能文件不存在)');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 显示系统提示词的关键部分
|
|
92
|
+
console.log('\n5️⃣ 系统提示词关键部分:');
|
|
93
|
+
const lines = systemPrompt.split('\n');
|
|
94
|
+
let inGuidelines = false;
|
|
95
|
+
let guidelineCount = 0;
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < lines.length && guidelineCount < 10; i++) {
|
|
98
|
+
const line = lines[i];
|
|
99
|
+
if (line.includes('Behavior Guidelines')) {
|
|
100
|
+
inGuidelines = true;
|
|
101
|
+
}
|
|
102
|
+
if (inGuidelines) {
|
|
103
|
+
console.log(` ${line}`);
|
|
104
|
+
guidelineCount++;
|
|
105
|
+
if (line.trim() === '' && guidelineCount > 3) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.log(` ❌ 对话初始化失败: ${error.message}`);
|
|
113
|
+
console.log(` 📋 错误堆栈: ${error.stack}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log('\n' + '='.repeat(70));
|
|
118
|
+
console.log('✅ 测试完成');
|
|
119
|
+
console.log('='.repeat(70));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 运行测试
|
|
123
|
+
testGlobalCloco().catch(error => {
|
|
124
|
+
console.error('测试失败:', error);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
});
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 测试项目历史隔离功能
|
|
4
|
+
*
|
|
5
|
+
* 这个脚本验证:
|
|
6
|
+
* 1. 不同项目的历史是隔离的
|
|
7
|
+
* 2. 历史正确保存和加载
|
|
8
|
+
* 3. 元数据正确生成
|
|
9
|
+
* 4. 旧历史可以迁移
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { resolve, join } from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import {
|
|
15
|
+
loadHistory,
|
|
16
|
+
saveHistory,
|
|
17
|
+
clearHistory,
|
|
18
|
+
listHistory,
|
|
19
|
+
migrateHistory
|
|
20
|
+
} from '../src/config.js';
|
|
21
|
+
|
|
22
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
23
|
+
|
|
24
|
+
// 颜色输出
|
|
25
|
+
const colors = {
|
|
26
|
+
reset: '\x1b[0m',
|
|
27
|
+
green: '\x1b[32m',
|
|
28
|
+
red: '\x1b[31m',
|
|
29
|
+
blue: '\x1b[34m',
|
|
30
|
+
yellow: '\x1b[33m',
|
|
31
|
+
cyan: '\x1b[36m'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function log(message, color = 'reset') {
|
|
35
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function section(title) {
|
|
39
|
+
console.log('\n' + '='.repeat(60));
|
|
40
|
+
log(title, 'cyan');
|
|
41
|
+
console.log('='.repeat(60));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 测试数据
|
|
45
|
+
const project1Path = '/home/user/project-alpha';
|
|
46
|
+
const project2Path = '/home/user/project-beta';
|
|
47
|
+
const project3Path = '/home/user/project-gamma';
|
|
48
|
+
|
|
49
|
+
const history1 = [
|
|
50
|
+
{ role: 'user', content: 'Hello from Project Alpha!' },
|
|
51
|
+
{ role: 'assistant', content: 'Hi! I am helping with Project Alpha.' },
|
|
52
|
+
{ role: 'user', content: 'Create a component' },
|
|
53
|
+
{ role: 'assistant', content: 'Creating component for Alpha...' }
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const history2 = [
|
|
57
|
+
{ role: 'user', content: 'Hello from Project Beta!' },
|
|
58
|
+
{ role: 'assistant', content: 'Hi! I am helping with Project Beta.' },
|
|
59
|
+
{ role: 'user', content: 'Fix the bug' },
|
|
60
|
+
{ role: 'assistant', content: 'Fixing bug in Beta...' }
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const history3 = [
|
|
64
|
+
{ role: 'user', content: 'Hello from Project Gamma!' },
|
|
65
|
+
{ role: 'assistant', content: 'Hi! I am helping with Project Gamma.' }
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
async function test1_SaveAndLoad() {
|
|
69
|
+
section('测试 1: 保存和加载历史');
|
|
70
|
+
|
|
71
|
+
log('保存 Project Alpha 历史...', 'yellow');
|
|
72
|
+
saveHistory(history1, project1Path);
|
|
73
|
+
|
|
74
|
+
log('保存 Project Beta 历史...', 'yellow');
|
|
75
|
+
saveHistory(history2, project2Path);
|
|
76
|
+
|
|
77
|
+
log('加载 Project Alpha 历史...', 'yellow');
|
|
78
|
+
const loaded1 = loadHistory(project1Path);
|
|
79
|
+
|
|
80
|
+
log('加载 Project Beta 历史...', 'yellow');
|
|
81
|
+
const loaded2 = loadHistory(project2Path);
|
|
82
|
+
|
|
83
|
+
const success1 = JSON.stringify(loaded1) === JSON.stringify(history1);
|
|
84
|
+
const success2 = JSON.stringify(loaded2) === JSON.stringify(history2);
|
|
85
|
+
|
|
86
|
+
if (success1 && success2) {
|
|
87
|
+
log('✓ 历史保存和加载成功!', 'green');
|
|
88
|
+
return true;
|
|
89
|
+
} else {
|
|
90
|
+
log('✗ 历史保存或加载失败!', 'red');
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function test2_Isolation() {
|
|
96
|
+
section('测试 2: 项目隔离');
|
|
97
|
+
|
|
98
|
+
log('验证 Project Alpha 和 Project Beta 的历史是否隔离...', 'yellow');
|
|
99
|
+
|
|
100
|
+
const loaded1 = loadHistory(project1Path);
|
|
101
|
+
const loaded2 = loadHistory(project2Path);
|
|
102
|
+
|
|
103
|
+
const isIsolated = loaded1.some(msg =>
|
|
104
|
+
msg.content.includes('Alpha') && !msg.content.includes('Beta')
|
|
105
|
+
) && loaded2.some(msg =>
|
|
106
|
+
msg.content.includes('Beta') && !msg.content.includes('Alpha')
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (isIsolated) {
|
|
110
|
+
log('✓ 项目历史正确隔离!', 'green');
|
|
111
|
+
log(` Project Alpha 有 ${loaded1.length} 条消息`, 'blue');
|
|
112
|
+
log(` Project Beta 有 ${loaded2.length} 条消息`, 'blue');
|
|
113
|
+
return true;
|
|
114
|
+
} else {
|
|
115
|
+
log('✗ 项目历史未正确隔离!', 'red');
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function test3_IndependentUpdates() {
|
|
121
|
+
section('测试 3: 独立更新');
|
|
122
|
+
|
|
123
|
+
log('向 Project Alpha 添加新消息...', 'yellow');
|
|
124
|
+
const loaded1 = loadHistory(project1Path);
|
|
125
|
+
loaded1.push({ role: 'user', content: 'New message for Alpha' });
|
|
126
|
+
saveHistory(loaded1, project1Path);
|
|
127
|
+
|
|
128
|
+
log('向 Project Beta 添加新消息...', 'yellow');
|
|
129
|
+
const loaded2 = loadHistory(project2Path);
|
|
130
|
+
loaded2.push({ role: 'user', content: 'New message for Beta' });
|
|
131
|
+
saveHistory(loaded2, project2Path);
|
|
132
|
+
|
|
133
|
+
const final1 = loadHistory(project1Path);
|
|
134
|
+
const final2 = loadHistory(project2Path);
|
|
135
|
+
|
|
136
|
+
const success1 = final1.length === history1.length + 1;
|
|
137
|
+
const success2 = final2.length === history2.length + 1;
|
|
138
|
+
const noCross = !final1.some(msg => msg.content.includes('Beta')) &&
|
|
139
|
+
!final2.some(msg => msg.content.includes('Alpha'));
|
|
140
|
+
|
|
141
|
+
if (success1 && success2 && noCross) {
|
|
142
|
+
log('✓ 项目可以独立更新!', 'green');
|
|
143
|
+
log(` Project Alpha 现在有 ${final1.length} 条消息`, 'blue');
|
|
144
|
+
log(` Project Beta 现在有 ${final2.length} 条消息`, 'blue');
|
|
145
|
+
return true;
|
|
146
|
+
} else {
|
|
147
|
+
log('✗ 项目独立更新失败!', 'red');
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function test4_ClearHistory() {
|
|
153
|
+
section('测试 4: 清除历史');
|
|
154
|
+
|
|
155
|
+
log('清除 Project Alpha 历史...', 'yellow');
|
|
156
|
+
clearHistory(project1Path);
|
|
157
|
+
|
|
158
|
+
const loaded1 = loadHistory(project1Path);
|
|
159
|
+
const loaded2 = loadHistory(project2Path);
|
|
160
|
+
|
|
161
|
+
const success1 = loaded1.length === 0;
|
|
162
|
+
const success2 = loaded2.length > 0;
|
|
163
|
+
|
|
164
|
+
if (success1 && success2) {
|
|
165
|
+
log('✓ 历史清除成功,不影响其他项目!', 'green');
|
|
166
|
+
log(` Project Alpha 有 ${loaded1.length} 条消息(已清除)`, 'blue');
|
|
167
|
+
log(` Project Beta 有 ${loaded2.length} 条消息(未受影响)`, 'blue');
|
|
168
|
+
return true;
|
|
169
|
+
} else {
|
|
170
|
+
log('✗ 历史清除失败!', 'red');
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function test5_ListHistory() {
|
|
176
|
+
section('测试 5: 列出所有历史');
|
|
177
|
+
|
|
178
|
+
log('添加 Project Gamma...', 'yellow');
|
|
179
|
+
saveHistory(history3, project3Path);
|
|
180
|
+
|
|
181
|
+
log('列出所有项目历史...', 'yellow');
|
|
182
|
+
const projects = listHistory();
|
|
183
|
+
|
|
184
|
+
log(`找到 ${projects.length} 个项目:`, 'blue');
|
|
185
|
+
projects.forEach(p => {
|
|
186
|
+
log(` - ${p.projectPath}`, 'cyan');
|
|
187
|
+
log(` 消息数: ${p.messageCount}, 最后更新: ${p.lastUpdated}`, 'blue');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const hasBeta = projects.some(p => p.projectPath === project2Path);
|
|
191
|
+
const hasGamma = projects.some(p => p.projectPath === project3Path);
|
|
192
|
+
|
|
193
|
+
if (hasBeta && hasGamma) {
|
|
194
|
+
log('✓ 历史列表功能正常!', 'green');
|
|
195
|
+
return true;
|
|
196
|
+
} else {
|
|
197
|
+
log('✗ 历史列表功能异常!', 'red');
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function test6_MetaData() {
|
|
203
|
+
section('测试 6: 元数据验证');
|
|
204
|
+
|
|
205
|
+
log('检查元数据文件...', 'yellow');
|
|
206
|
+
|
|
207
|
+
const fs = await import('fs');
|
|
208
|
+
const path = await import('path');
|
|
209
|
+
const os = await import('os');
|
|
210
|
+
|
|
211
|
+
const HISTORY_DIR = path.join(os.homedir(), '.closer-code', 'history');
|
|
212
|
+
|
|
213
|
+
const metaFiles = fs.readdirSync(HISTORY_DIR)
|
|
214
|
+
.filter(file => file.endsWith('.meta.json'));
|
|
215
|
+
|
|
216
|
+
log(`找到 ${metaFiles.length} 个元数据文件`, 'blue');
|
|
217
|
+
|
|
218
|
+
let allValid = true;
|
|
219
|
+
for (const file of metaFiles) {
|
|
220
|
+
const metaPath = path.join(HISTORY_DIR, file);
|
|
221
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
222
|
+
|
|
223
|
+
const hasRequiredFields = meta.projectPath &&
|
|
224
|
+
meta.messageCount !== undefined &&
|
|
225
|
+
meta.lastUpdated;
|
|
226
|
+
|
|
227
|
+
if (hasRequiredFields) {
|
|
228
|
+
log(` ✓ ${file}`, 'green');
|
|
229
|
+
log(` 项目: ${meta.projectPath}`, 'blue');
|
|
230
|
+
log(` 消息: ${meta.messageCount}`, 'blue');
|
|
231
|
+
} else {
|
|
232
|
+
log(` ✗ ${file} 缺少必要字段`, 'red');
|
|
233
|
+
allValid = false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (allValid) {
|
|
238
|
+
log('✓ 元数据验证通过!', 'green');
|
|
239
|
+
return true;
|
|
240
|
+
} else {
|
|
241
|
+
log('✗ 元数据验证失败!', 'red');
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function runAllTests() {
|
|
247
|
+
console.log('\n' + '█'.repeat(60));
|
|
248
|
+
log(' 项目历史隔离功能测试套件', 'cyan');
|
|
249
|
+
console.log('█'.repeat(60));
|
|
250
|
+
|
|
251
|
+
const results = [];
|
|
252
|
+
|
|
253
|
+
results.push(await test1_SaveAndLoad());
|
|
254
|
+
results.push(await test2_Isolation());
|
|
255
|
+
results.push(await test3_IndependentUpdates());
|
|
256
|
+
results.push(await test4_ClearHistory());
|
|
257
|
+
results.push(await test5_ListHistory());
|
|
258
|
+
results.push(await test6_MetaData());
|
|
259
|
+
|
|
260
|
+
section('测试结果汇总');
|
|
261
|
+
|
|
262
|
+
const passed = results.filter(r => r).length;
|
|
263
|
+
const total = results.length;
|
|
264
|
+
|
|
265
|
+
results.forEach((result, i) => {
|
|
266
|
+
const status = result ? '✓ 通过' : '✗ 失败';
|
|
267
|
+
const color = result ? 'green' : 'red';
|
|
268
|
+
log(`测试 ${i + 1}: ${status}`, color);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
console.log('\n' + '='.repeat(60));
|
|
272
|
+
|
|
273
|
+
if (passed === total) {
|
|
274
|
+
log(`🎉 所有测试通过!(${passed}/${total})`, 'green');
|
|
275
|
+
console.log('='.repeat(60) + '\n');
|
|
276
|
+
return 0;
|
|
277
|
+
} else {
|
|
278
|
+
log(`⚠️ 部分测试失败 (${passed}/${total} 通过)`, 'yellow');
|
|
279
|
+
console.log('='.repeat(60) + '\n');
|
|
280
|
+
return 1;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 运行测试
|
|
285
|
+
runAllTests().then(exitCode => {
|
|
286
|
+
process.exit(exitCode);
|
|
287
|
+
}).catch(error => {
|
|
288
|
+
log(`测试运行出错: ${error.message}`, 'red');
|
|
289
|
+
console.error(error);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 测试改进后的 thinking 显示效果
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
|
|
8
|
+
console.log('='.repeat(70));
|
|
9
|
+
console.log('测试改进后的 Thinking 显示效果');
|
|
10
|
+
console.log('='.repeat(70));
|
|
11
|
+
console.log('\n改进内容:');
|
|
12
|
+
console.log('1. budget_tokens: 1600 → 20000 (增加12.5倍)');
|
|
13
|
+
console.log('2. UI显示数量: 10条 → 30条 (增加3倍)');
|
|
14
|
+
console.log('3. 添加thinking长度统计 (字符数和token数)');
|
|
15
|
+
console.log('\n' + '='.repeat(70));
|
|
16
|
+
console.log('\n建议测试问题:');
|
|
17
|
+
console.log('1. 简单问题: "2+2=?"');
|
|
18
|
+
console.log('2. 中等问题: "解释什么是递归"');
|
|
19
|
+
console.log('3. 复杂问题: "分析快速排序的时间复杂度"');
|
|
20
|
+
console.log('4. 超级复杂: "设计一个微服务架构的电商系统"');
|
|
21
|
+
console.log('\n' + '='.repeat(70));
|
|
22
|
+
console.log('\n按 Enter 键启动 Closer Code...');
|
|
23
|
+
|
|
24
|
+
await new Promise(resolve => {
|
|
25
|
+
process.stdin.once('data', resolve);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log('\n启动 Closer Code...\n');
|
|
29
|
+
|
|
30
|
+
// 启动 CLI
|
|
31
|
+
const cli = spawn('node', ['dist/closer-cli.js'], {
|
|
32
|
+
cwd: process.cwd(),
|
|
33
|
+
stdio: 'inherit',
|
|
34
|
+
env: {
|
|
35
|
+
...process.env,
|
|
36
|
+
CLOSER_AUTO_PLAN: 'false',
|
|
37
|
+
CLOSER_AUTO_EXECUTE: 'false'
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
cli.on('exit', (code) => {
|
|
42
|
+
console.log(`\nCloser Code 已退出 (code: ${code})`);
|
|
43
|
+
});
|