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,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 测试长消息显示
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { writeFile } from 'fs/promises';
|
|
7
|
+
|
|
8
|
+
async function testLongMessage() {
|
|
9
|
+
console.log('🧪 测试长消息显示\n');
|
|
10
|
+
console.log('═'.repeat(60));
|
|
11
|
+
|
|
12
|
+
// 创建一个超长的测试消息
|
|
13
|
+
const longContent = [];
|
|
14
|
+
|
|
15
|
+
// 1. 测试多行文本(100行)
|
|
16
|
+
longContent.push('## 测试1: 多行文本(100行)');
|
|
17
|
+
for (let i = 1; i <= 100; i++) {
|
|
18
|
+
longContent.push(`这是第 ${i} 行内容,用于测试长文本显示功能。`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 2. 测试长字符串(5000字符)
|
|
22
|
+
longContent.push('\n## 测试2: 长字符串(5000字符)');
|
|
23
|
+
const longString = 'A'.repeat(5000);
|
|
24
|
+
longContent.push(longString);
|
|
25
|
+
|
|
26
|
+
// 3. 测试代码块(50行代码)
|
|
27
|
+
longContent.push('\n## 测试3: 代码块(50行)');
|
|
28
|
+
longContent.push('```javascript');
|
|
29
|
+
for (let i = 1; i <= 50; i++) {
|
|
30
|
+
longContent.push(` const line${i} = "这是第 ${i} 行代码";`);
|
|
31
|
+
}
|
|
32
|
+
longContent.push('```');
|
|
33
|
+
|
|
34
|
+
// 4. 测试混合内容
|
|
35
|
+
longContent.push('\n## 测试4: 混合内容');
|
|
36
|
+
longContent.push('这是一些普通文本...');
|
|
37
|
+
longContent.push(''.padStart(200, '-')); // 200个横线
|
|
38
|
+
longContent.push('更多内容...');
|
|
39
|
+
|
|
40
|
+
const fullContent = longContent.join('\n');
|
|
41
|
+
|
|
42
|
+
console.log('\n📊 统计信息:');
|
|
43
|
+
console.log(` 总行数: ${fullContent.split('\n').length}`);
|
|
44
|
+
console.log(` 总字符数: ${fullContent.length}`);
|
|
45
|
+
console.log(` 总大小: ${(fullContent.length / 1024).toFixed(2)} KB`);
|
|
46
|
+
|
|
47
|
+
console.log('\n✅ 测试消息已生成!');
|
|
48
|
+
console.log('\n💡 使用方法:');
|
|
49
|
+
console.log(' 1. 启动 cloco');
|
|
50
|
+
console.log(' 2. 粘贴上面的测试内容');
|
|
51
|
+
console.log(' 3. 检查是否完整显示(不应有 "truncated" 提示)');
|
|
52
|
+
console.log(' 4. 使用上下箭头滚动查看');
|
|
53
|
+
console.log(' 5. 按 Enter 回到底部\n');
|
|
54
|
+
|
|
55
|
+
// 保存到文件供参考
|
|
56
|
+
await writeFile('test-long-message.txt', fullContent, 'utf-8');
|
|
57
|
+
console.log('✅ 测试内容已保存到: test-long-message.txt\n');
|
|
58
|
+
|
|
59
|
+
console.log('═'.repeat(60));
|
|
60
|
+
console.log('\n📋 测试内容预览(前20行):\n');
|
|
61
|
+
console.log(fullContent.split('\n').slice(0, 20).join('\n'));
|
|
62
|
+
console.log('\n... (还有更多内容)\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
testLongMessage().catch(console.error);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 测试 Plan 功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Plan, PlanType, PlanStatus, StepStatus } from '../src/plan.js';
|
|
7
|
+
|
|
8
|
+
async function testPlan() {
|
|
9
|
+
console.log('🧪 测试 Plan 功能\n');
|
|
10
|
+
console.log('═'.repeat(60));
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
// 测试1: 创建 Plan
|
|
14
|
+
console.log('\n测试1: 创建 Plan');
|
|
15
|
+
const plan = new Plan('创建一个 React 组件', PlanType.COMMAND);
|
|
16
|
+
console.log('✅ Plan 创建成功');
|
|
17
|
+
console.log(` ID: ${plan.id}`);
|
|
18
|
+
console.log(` 类型: ${plan.type}`);
|
|
19
|
+
console.log(` 描述: ${plan.description}`);
|
|
20
|
+
console.log(` 状态: ${plan.status}`);
|
|
21
|
+
|
|
22
|
+
// 测试2: 添加步骤
|
|
23
|
+
console.log('\n测试2: 添加步骤');
|
|
24
|
+
plan.addStep('创建组件文件');
|
|
25
|
+
plan.addStep('编写组件代码');
|
|
26
|
+
plan.addStep('添加样式');
|
|
27
|
+
console.log(`✅ 添加了 ${plan.steps.length} 个步骤`);
|
|
28
|
+
|
|
29
|
+
// 测试3: 开始执行
|
|
30
|
+
console.log('\n测试3: 开始执行');
|
|
31
|
+
plan.start();
|
|
32
|
+
console.log(`✅ Plan 状态: ${plan.status}`);
|
|
33
|
+
|
|
34
|
+
// 测试4: 更新步骤状态
|
|
35
|
+
console.log('\n测试4: 更新步骤状态');
|
|
36
|
+
const step1 = plan.steps[0];
|
|
37
|
+
plan.updateStep(step1.id, StepStatus.IN_PROGRESS);
|
|
38
|
+
console.log(`✅ 步骤1状态: ${step1.status}`);
|
|
39
|
+
|
|
40
|
+
plan.updateStep(step1.id, StepStatus.COMPLETED);
|
|
41
|
+
console.log(`✅ 步骤1已完成`);
|
|
42
|
+
|
|
43
|
+
const step2 = plan.steps[1];
|
|
44
|
+
plan.updateStep(step2.id, StepStatus.IN_PROGRESS);
|
|
45
|
+
console.log(`✅ 步骤2进行中`);
|
|
46
|
+
|
|
47
|
+
// 测试5: 获取进度
|
|
48
|
+
console.log('\n测试5: 获取进度');
|
|
49
|
+
const progress = plan.getProgress();
|
|
50
|
+
console.log(`✅ 总步骤: ${progress.total}`);
|
|
51
|
+
console.log(`✅ 已完成: ${progress.completed}`);
|
|
52
|
+
console.log(`✅ 进行中: ${progress.inProgress}`);
|
|
53
|
+
console.log(`✅ 进度: ${progress.percentage}%`);
|
|
54
|
+
|
|
55
|
+
// 测试6: 完成所有步骤
|
|
56
|
+
console.log('\n测试6: 完成所有步骤');
|
|
57
|
+
plan.updateStep(step2.id, StepStatus.COMPLETED);
|
|
58
|
+
const step3 = plan.steps[2];
|
|
59
|
+
plan.updateStep(step3.id, StepStatus.COMPLETED);
|
|
60
|
+
|
|
61
|
+
const finalProgress = plan.getProgress();
|
|
62
|
+
console.log(`✅ 最终进度: ${finalProgress.percentage}%`);
|
|
63
|
+
console.log(`✅ Plan 状态: ${plan.status}`);
|
|
64
|
+
|
|
65
|
+
// 测试7: JSON 序列化
|
|
66
|
+
console.log('\n测试7: JSON 序列化');
|
|
67
|
+
const json = plan.toJSON();
|
|
68
|
+
console.log(`✅ 序列化成功`);
|
|
69
|
+
console.log(` 包含 ${json.steps.length} 个步骤`);
|
|
70
|
+
|
|
71
|
+
// 测试8: 从 JSON 恢复
|
|
72
|
+
console.log('\n测试8: 从 JSON 恢复');
|
|
73
|
+
const restoredPlan = Plan.fromJSON(json);
|
|
74
|
+
console.log(`✅ 恢复成功`);
|
|
75
|
+
console.log(` ID: ${restoredPlan.id}`);
|
|
76
|
+
console.log(` 状态: ${restoredPlan.status}`);
|
|
77
|
+
|
|
78
|
+
console.log('\n' + '═'.repeat(60));
|
|
79
|
+
console.log('\n✅ 所有测试通过!\n');
|
|
80
|
+
console.log('📊 测试总结:');
|
|
81
|
+
console.log(' ✅ Plan 创建');
|
|
82
|
+
console.log(' ✅ 步骤管理');
|
|
83
|
+
console.log(' ✅ 状态更新');
|
|
84
|
+
console.log(' ✅ 进度计算');
|
|
85
|
+
console.log(' ✅ JSON 序列化');
|
|
86
|
+
console.log(' ✅ 从 JSON 恢复\n');
|
|
87
|
+
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('\n❌ 测试失败:', error.message);
|
|
90
|
+
console.error(error.stack);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 运行测试
|
|
95
|
+
testPlan().catch(console.error);
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 真实场景测试:模拟在不同项目中使用AI助手
|
|
4
|
+
*
|
|
5
|
+
* 这个测试模拟以下场景:
|
|
6
|
+
* 1. 在项目A中对话
|
|
7
|
+
* 2. 切换到项目B对话
|
|
8
|
+
* 3. 回到项目A,验证历史是否正确
|
|
9
|
+
* 4. 验证两个项目的上下文不会混淆
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
loadHistory,
|
|
14
|
+
saveHistory,
|
|
15
|
+
clearHistory
|
|
16
|
+
} from '../src/config.js';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
|
|
19
|
+
const colors = {
|
|
20
|
+
reset: '\x1b[0m',
|
|
21
|
+
green: '\x1b[32m',
|
|
22
|
+
red: '\x1b[31m',
|
|
23
|
+
blue: '\x1b[34m',
|
|
24
|
+
yellow: '\x1b[33m',
|
|
25
|
+
cyan: '\x1b[36m'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function log(message, color = 'reset') {
|
|
29
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function section(title) {
|
|
33
|
+
console.log('\n' + '='.repeat(70));
|
|
34
|
+
log(title, 'cyan');
|
|
35
|
+
console.log('='.repeat(70));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 模拟项目路径
|
|
39
|
+
const projectA = path.join(process.cwd(), 'test-projects', 'project-a');
|
|
40
|
+
const projectB = path.join(process.cwd(), 'test-projects', 'project-b');
|
|
41
|
+
|
|
42
|
+
async function simulateConversation(projectPath, projectName, conversations) {
|
|
43
|
+
log(`\n📍 在项目 ${projectName} 中开始对话...`, 'yellow');
|
|
44
|
+
log(` 路径: ${projectPath}`, 'blue');
|
|
45
|
+
|
|
46
|
+
// 加载现有历史
|
|
47
|
+
let history = loadHistory(projectPath);
|
|
48
|
+
log(` 加载了 ${history.length} 条历史消息`, 'blue');
|
|
49
|
+
|
|
50
|
+
// 模拟对话
|
|
51
|
+
for (const conv of conversations) {
|
|
52
|
+
log(`\n 用户: ${conv.user}`, 'cyan');
|
|
53
|
+
history.push({ role: 'user', content: conv.user });
|
|
54
|
+
|
|
55
|
+
log(` 助手: ${conv.assistant}`, 'green');
|
|
56
|
+
history.push({ role: 'assistant', content: conv.assistant });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 保存历史
|
|
60
|
+
saveHistory(history, projectPath);
|
|
61
|
+
log(`\n ✓ 保存了 ${history.length} 条消息到项目历史`, 'green');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function main() {
|
|
65
|
+
console.log('\n' + '█'.repeat(70));
|
|
66
|
+
log(' 真实场景测试:多项目上下文隔离', 'cyan');
|
|
67
|
+
console.log('█'.repeat(70));
|
|
68
|
+
|
|
69
|
+
section('场景 1: 在项目 A 中开发 React 组件');
|
|
70
|
+
|
|
71
|
+
await simulateConversation(projectA, 'A', [
|
|
72
|
+
{
|
|
73
|
+
user: '帮我创建一个 Button 组件',
|
|
74
|
+
assistant: '好的,我来创建一个 Button 组件。首先让我看看项目结构...'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
user: '添加 onClick 处理',
|
|
78
|
+
assistant: '我在组件中添加了 onClick 属性支持...'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
user: '现在添加样式',
|
|
82
|
+
assistant: '我为 Button 组件添加了 CSS 样式...'
|
|
83
|
+
}
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
section('场景 2: 切换到项目 B 开发 Node.js API');
|
|
87
|
+
|
|
88
|
+
await simulateConversation(projectB, 'B', [
|
|
89
|
+
{
|
|
90
|
+
user: '创建一个 Express 服务器',
|
|
91
|
+
assistant: '好的,我来创建 Express 服务器。首先初始化项目...'
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
user: '添加用户认证路由',
|
|
95
|
+
assistant: '我添加了 /api/auth 路由和 JWT 认证...'
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
user: '现在添加数据库连接',
|
|
99
|
+
assistant: '我配置了 MongoDB 连接和 Mongoose 模型...'
|
|
100
|
+
}
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
section('场景 3: 回到项目 A 继续开发');
|
|
104
|
+
|
|
105
|
+
const historyA = loadHistory(projectA);
|
|
106
|
+
log(`\n📍 回到项目 A`, 'yellow');
|
|
107
|
+
log(` 路径: ${projectA}`, 'blue');
|
|
108
|
+
log(` 找到 ${historyA.length} 条历史消息`, 'blue');
|
|
109
|
+
|
|
110
|
+
// 验证历史内容
|
|
111
|
+
const hasReactContext = historyA.some(msg =>
|
|
112
|
+
msg.content.includes('Button') ||
|
|
113
|
+
msg.content.includes('React') ||
|
|
114
|
+
msg.content.includes('组件')
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const hasNodeContext = historyA.some(msg =>
|
|
118
|
+
msg.content.includes('Express') ||
|
|
119
|
+
msg.content.includes('API') ||
|
|
120
|
+
msg.content.includes('MongoDB')
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
log('\n 验证上下文:', 'yellow');
|
|
124
|
+
log(` - 包含 React/组件相关内容: ${hasReactContext ? '✓' : '✗'}`, hasReactContext ? 'green' : 'red');
|
|
125
|
+
log(` - 包含 Node.js/API 相关内容: ${hasNodeContext ? '✗ (不应该有)' : '✓ (正确隔离)'}`, hasNodeContext ? 'red' : 'green');
|
|
126
|
+
|
|
127
|
+
await simulateConversation(projectA, 'A', [
|
|
128
|
+
{
|
|
129
|
+
user: '继续添加 Button 的 hover 效果',
|
|
130
|
+
assistant: '好的,我在 CSS 中添加了 :hover 伪类...'
|
|
131
|
+
}
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
section('场景 4: 再次切换到项目 B');
|
|
135
|
+
|
|
136
|
+
const historyB = loadHistory(projectB);
|
|
137
|
+
log(`\n📍 切换到项目 B`, 'yellow');
|
|
138
|
+
log(` 路径: ${projectB}`, 'blue');
|
|
139
|
+
log(` 找到 ${historyB.length} 条历史消息`, 'blue');
|
|
140
|
+
|
|
141
|
+
// 验证历史内容
|
|
142
|
+
const hasNodeContextB = historyB.some(msg =>
|
|
143
|
+
msg.content.includes('Express') ||
|
|
144
|
+
msg.content.includes('API') ||
|
|
145
|
+
msg.content.includes('MongoDB')
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const hasReactContextB = historyB.some(msg =>
|
|
149
|
+
msg.content.includes('Button') ||
|
|
150
|
+
msg.content.includes('React') ||
|
|
151
|
+
msg.content.includes('hover')
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
log('\n 验证上下文:', 'yellow');
|
|
155
|
+
log(` - 包含 Node.js/API 相关内容: ${hasNodeContextB ? '✓' : '✗'}`, hasNodeContextB ? 'green' : 'red');
|
|
156
|
+
log(` - 包含 React/Button 相关内容: ${hasReactContextB ? '✗ (不应该有)' : '✓ (正确隔离)'}`, hasReactContextB ? 'red' : 'green');
|
|
157
|
+
|
|
158
|
+
section('场景 5: 清除项目 A 的历史');
|
|
159
|
+
|
|
160
|
+
log(`\n🗑️ 清除项目 A 的历史...`, 'yellow');
|
|
161
|
+
clearHistory(projectA);
|
|
162
|
+
|
|
163
|
+
const historyAAfterClear = loadHistory(projectA);
|
|
164
|
+
const historyBAfterClear = loadHistory(projectB);
|
|
165
|
+
|
|
166
|
+
log(` 项目 A 历史消息数: ${historyAAfterClear.length}`, historyAAfterClear.length === 0 ? 'green' : 'red');
|
|
167
|
+
log(` 项目 B 历史消息数: ${historyBAfterClear.length} (应该保持不变)`, 'blue');
|
|
168
|
+
|
|
169
|
+
section('测试结果');
|
|
170
|
+
|
|
171
|
+
const tests = [
|
|
172
|
+
{ name: '项目 A 保存历史', pass: historyA.length > 0 },
|
|
173
|
+
{ name: '项目 B 保存历史', pass: historyB.length > 0 },
|
|
174
|
+
{ name: '项目 A 有 React 上下文', pass: hasReactContext },
|
|
175
|
+
{ name: '项目 A 没有 Node.js 上下文', pass: !hasNodeContext },
|
|
176
|
+
{ name: '项目 B 有 Node.js 上下文', pass: hasNodeContextB },
|
|
177
|
+
{ name: '项目 B 没有 React 上下文', pass: !hasReactContextB },
|
|
178
|
+
{ name: '清除项目 A 不影响项目 B', pass: historyAAfterClear.length === 0 && historyBAfterClear.length > 0 }
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
tests.forEach(test => {
|
|
182
|
+
const icon = test.pass ? '✓' : '✗';
|
|
183
|
+
const color = test.pass ? 'green' : 'red';
|
|
184
|
+
log(` ${icon} ${test.name}`, color);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const passed = tests.filter(t => t.pass).length;
|
|
188
|
+
const total = tests.length;
|
|
189
|
+
|
|
190
|
+
console.log('\n' + '='.repeat(70));
|
|
191
|
+
|
|
192
|
+
if (passed === total) {
|
|
193
|
+
log(`🎉 所有场景测试通过!(${passed}/${total})`, 'green');
|
|
194
|
+
log('\n✨ 项目历史隔离功能工作正常,不同项目的上下文完全独立!', 'green');
|
|
195
|
+
} else {
|
|
196
|
+
log(`⚠️ 部分场景测试失败 (${passed}/${total} 通过)`, 'yellow');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log('='.repeat(70) + '\n');
|
|
200
|
+
|
|
201
|
+
// 清理测试数据
|
|
202
|
+
log('🧹 清理测试数据...', 'yellow');
|
|
203
|
+
clearHistory(projectA);
|
|
204
|
+
clearHistory(projectB);
|
|
205
|
+
log('✓ 清理完成', 'green');
|
|
206
|
+
|
|
207
|
+
return passed === total ? 0 : 1;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
main().then(exitCode => {
|
|
211
|
+
process.exit(exitCode);
|
|
212
|
+
}).catch(error => {
|
|
213
|
+
log(`\n✗ 测试失败: ${error.message}`, 'red');
|
|
214
|
+
console.error(error);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Thinking 显示功能快速测试
|
|
4
|
+
*
|
|
5
|
+
* 测试 AI 的 thinking 内容是否能正确显示
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
|
|
10
|
+
console.log('========================================');
|
|
11
|
+
console.log('Thinking 显示功能测试');
|
|
12
|
+
console.log('========================================\n');
|
|
13
|
+
|
|
14
|
+
console.log('这个测试将启动 Closer Code 并发送一个简单的问题。');
|
|
15
|
+
console.log('请观察 "AI Thinking Process" 区域是否有内容显示。\n');
|
|
16
|
+
|
|
17
|
+
console.log('提示:');
|
|
18
|
+
console.log('- Thinking 内容会以 🤔 开头');
|
|
19
|
+
console.log('- 工具调用会以 ⚡ 开头');
|
|
20
|
+
console.log('- 工具结果会以 📊 开头');
|
|
21
|
+
console.log('- 响应生成会以 ✍️ 开头\n');
|
|
22
|
+
|
|
23
|
+
console.log('按 Enter 键开始测试...');
|
|
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: ['pipe', 'inherit', 'inherit'],
|
|
34
|
+
env: {
|
|
35
|
+
...process.env,
|
|
36
|
+
CLOSER_AUTO_PLAN: 'false',
|
|
37
|
+
CLOSER_AUTO_EXECUTE: 'false'
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// 等待 CLI 启动
|
|
42
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
43
|
+
|
|
44
|
+
console.log('\n发送测试问题: "列出当前目录的文件"\n');
|
|
45
|
+
|
|
46
|
+
cli.stdin.write('列出当前目录的文件\n');
|
|
47
|
+
|
|
48
|
+
// 等待响应
|
|
49
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
50
|
+
|
|
51
|
+
console.log('\n\n测试完成!');
|
|
52
|
+
console.log('\n如果你在 "AI Thinking Process" 区域看到了内容,说明 thinking 功能正常工作。');
|
|
53
|
+
console.log('如果没有看到内容,可能是因为:');
|
|
54
|
+
console.log('1. API 没有返回 thinking 内容(某些简单查询可能不会触发 thinking)');
|
|
55
|
+
console.log('2. 网络延迟导致 thinking 内容没有及时显示');
|
|
56
|
+
console.log('\n建议尝试更复杂的问题,例如:');
|
|
57
|
+
console.log('"请分析一下这个项目的架构"');
|
|
58
|
+
console.log('"帮我检查代码中的安全问题"');
|
|
59
|
+
|
|
60
|
+
// 终止进程
|
|
61
|
+
cli.kill('SIGTERM');
|
|
62
|
+
|
|
63
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
64
|
+
|
|
65
|
+
process.exit(0);
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* UI 验证测试脚本
|
|
4
|
+
*
|
|
5
|
+
* 用于无人值守验证 UI 优化是否合格
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
const TEST_TIMEOUT = 30000;
|
|
13
|
+
const LOG_FILE = path.join(process.cwd(), 'test', 'ui-verification-result.log');
|
|
14
|
+
|
|
15
|
+
// 测试场景
|
|
16
|
+
const testScenarios = [
|
|
17
|
+
{
|
|
18
|
+
name: '启动测试',
|
|
19
|
+
input: 'hello\n',
|
|
20
|
+
expectedOutputs: [
|
|
21
|
+
'Closer Code',
|
|
22
|
+
'Conversation',
|
|
23
|
+
'Task Progress',
|
|
24
|
+
'Tool Execution',
|
|
25
|
+
'Latest Logs',
|
|
26
|
+
'AI Thinking Process'
|
|
27
|
+
],
|
|
28
|
+
timeout: 5000
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: '长对话测试',
|
|
32
|
+
input: '请列出当前目录的文件\n',
|
|
33
|
+
expectedOutputs: [
|
|
34
|
+
'bash',
|
|
35
|
+
'Tool Execution'
|
|
36
|
+
],
|
|
37
|
+
timeout: 10000
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'Thinking 测试',
|
|
41
|
+
input: '请帮我分析一下这个项目的结构\n',
|
|
42
|
+
expectedOutputs: [
|
|
43
|
+
'Thinking',
|
|
44
|
+
'AI'
|
|
45
|
+
],
|
|
46
|
+
timeout: 15000
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// 颜色输出
|
|
51
|
+
const colors = {
|
|
52
|
+
reset: '\x1b[0m',
|
|
53
|
+
green: '\x1b[32m',
|
|
54
|
+
red: '\x1b[31m',
|
|
55
|
+
yellow: '\x1b[33m',
|
|
56
|
+
blue: '\x1b[34m',
|
|
57
|
+
cyan: '\x1b[36m'
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function log(message, color = 'reset') {
|
|
61
|
+
const timestamp = new Date().toISOString();
|
|
62
|
+
const colorCode = colors[color] || colors.reset;
|
|
63
|
+
console.log(`${colorCode}[${timestamp}] ${message}${colors.reset}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 验证 UI 元素是否存在
|
|
68
|
+
*/
|
|
69
|
+
async function verifyUIElements() {
|
|
70
|
+
log('\n验证 UI 元素...', 'cyan');
|
|
71
|
+
|
|
72
|
+
// 读取编译后的文件
|
|
73
|
+
const cliPath = path.join(process.cwd(), 'dist', 'closer-cli.js');
|
|
74
|
+
const content = await fs.readFile(cliPath, 'utf-8');
|
|
75
|
+
|
|
76
|
+
const checks = [
|
|
77
|
+
{
|
|
78
|
+
name: 'Conversation 滚动限制',
|
|
79
|
+
pattern: /maxVisibleMessages/,
|
|
80
|
+
expected: true,
|
|
81
|
+
description: '应该限制 Conversation 显示的消息数量'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'Tool Execution 数量限制',
|
|
85
|
+
pattern: /slice\(-\d+\)/,
|
|
86
|
+
expected: true,
|
|
87
|
+
description: '应该限制 Tool Execution 显示的数量'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'Thinking 区域',
|
|
91
|
+
pattern: /AI Thinking Process/,
|
|
92
|
+
expected: true,
|
|
93
|
+
description: '应该有 AI Thinking Process 区域'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'Thinking 状态处理',
|
|
97
|
+
pattern: /thinking/,
|
|
98
|
+
expected: true,
|
|
99
|
+
description: '应该处理 thinking 事件'
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'Thinking API 配置',
|
|
103
|
+
pattern: /type.*enabled/,
|
|
104
|
+
expected: true,
|
|
105
|
+
description: '应该启用 thinking API'
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const results = [];
|
|
110
|
+
for (const check of checks) {
|
|
111
|
+
const found = check.pattern.test(content);
|
|
112
|
+
const success = found === check.expected;
|
|
113
|
+
results.push({
|
|
114
|
+
name: check.name,
|
|
115
|
+
success,
|
|
116
|
+
description: check.description
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (success) {
|
|
120
|
+
log(` ✓ ${check.name}: ${check.description}`, 'green');
|
|
121
|
+
} else {
|
|
122
|
+
log(` ✗ ${check.name}: ${check.description}`, 'red');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return results;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 生成测试报告
|
|
131
|
+
*/
|
|
132
|
+
async function generateReport(elementResults) {
|
|
133
|
+
const report = {
|
|
134
|
+
timestamp: new Date().toISOString(),
|
|
135
|
+
summary: {
|
|
136
|
+
total: elementResults.length,
|
|
137
|
+
passed: 0,
|
|
138
|
+
failed: 0
|
|
139
|
+
},
|
|
140
|
+
elements: elementResults
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// 统计结果
|
|
144
|
+
for (const result of elementResults) {
|
|
145
|
+
if (result.success) {
|
|
146
|
+
report.summary.passed++;
|
|
147
|
+
} else {
|
|
148
|
+
report.summary.failed++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 保存报告
|
|
153
|
+
await fs.writeFile(LOG_FILE, JSON.stringify(report, null, 2));
|
|
154
|
+
|
|
155
|
+
return report;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 主测试函数
|
|
160
|
+
*/
|
|
161
|
+
async function main() {
|
|
162
|
+
log('='.repeat(60), 'blue');
|
|
163
|
+
log('UI 验证测试开始', 'blue');
|
|
164
|
+
log('='.repeat(60), 'blue');
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// 1. 验证 UI 元素
|
|
168
|
+
log('\n第 1 步: 验证 UI 元素', 'cyan');
|
|
169
|
+
const elementResults = await verifyUIElements();
|
|
170
|
+
|
|
171
|
+
// 2. 生成报告
|
|
172
|
+
log('\n第 2 步: 生成测试报告', 'cyan');
|
|
173
|
+
const report = await generateReport(elementResults);
|
|
174
|
+
|
|
175
|
+
// 3. 输出总结
|
|
176
|
+
log('\n' + '='.repeat(60), 'blue');
|
|
177
|
+
log('测试总结', 'blue');
|
|
178
|
+
log('='.repeat(60), 'blue');
|
|
179
|
+
log(`总测试数: ${report.summary.total}`, 'cyan');
|
|
180
|
+
log(`通过: ${report.summary.passed}`, 'green');
|
|
181
|
+
log(`失败: ${report.summary.failed}`, report.summary.failed > 0 ? 'red' : 'green');
|
|
182
|
+
log(`成功率: ${((report.summary.passed / report.summary.total) * 100).toFixed(1)}%`, 'cyan');
|
|
183
|
+
log(`详细报告: ${LOG_FILE}`, 'cyan');
|
|
184
|
+
|
|
185
|
+
// 4. 判断是否成功
|
|
186
|
+
const successRate = report.summary.passed / report.summary.total;
|
|
187
|
+
if (successRate >= 0.8) {
|
|
188
|
+
log('\n✓ UI 验证测试通过!', 'green');
|
|
189
|
+
process.exit(0);
|
|
190
|
+
} else {
|
|
191
|
+
log('\n✗ UI 验证测试未通过,成功率低于 80%', 'red');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
} catch (error) {
|
|
196
|
+
log(`\n测试过程中出错: ${error.message}`, 'red');
|
|
197
|
+
console.error(error);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 运行测试
|
|
203
|
+
main();
|