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.
Files changed (100) hide show
  1. package/.env.example +83 -0
  2. package/API_GUIDE.md +1411 -0
  3. package/AUTO_MKDIR_IMPROVEMENT.md +354 -0
  4. package/CLAUDE.md +55 -0
  5. package/CTRL_C_EXPERIMENT.md +90 -0
  6. package/PROJECT_CLEANUP_SUMMARY.md +121 -0
  7. package/README.md +686 -0
  8. package/cloco.md +51 -0
  9. package/config.example.json +116 -0
  10. package/dist/bash-runner.js +128 -0
  11. package/dist/batch-cli.js +20736 -0
  12. package/dist/closer-cli.js +21190 -0
  13. package/dist/index.js +31228 -0
  14. package/docs/EXPORT_COMMAND.md +152 -0
  15. package/docs/FILE_NAMING_IMPROVEMENT.md +168 -0
  16. package/docs/GLOBAL_CONFIG.md +128 -0
  17. package/docs/LONG_MESSAGE_DISPLAY_FIX.md +202 -0
  18. package/docs/PROJECT_HISTORY_ISOLATION.md +315 -0
  19. package/docs/QUICK_START_HISTORY.md +207 -0
  20. package/docs/TASK_PROGRESS_FEATURE.md +190 -0
  21. package/docs/THINKING_CONTENT_RESEARCH.md +267 -0
  22. package/docs/THINKING_FEATURE.md +187 -0
  23. package/docs/THINKING_IMPROVEMENT_COMPARISON.md +193 -0
  24. package/docs/THINKING_OPTIMIZATION_SUMMARY.md +242 -0
  25. package/docs/UI_IMPROVEMENTS_2025-01-18.md +256 -0
  26. package/docs/WHY_THINKING_SHORT.md +201 -0
  27. package/package.json +49 -0
  28. package/scenarios/README.md +234 -0
  29. package/scenarios/run-all-scenarios.js +342 -0
  30. package/scenarios/scenario1-batch-converter.js +247 -0
  31. package/scenarios/scenario2-code-analyzer.js +375 -0
  32. package/scenarios/scenario3-doc-generator.js +371 -0
  33. package/scenarios/scenario4-log-analyzer.js +496 -0
  34. package/scenarios/scenario5-tdd-helper.js +681 -0
  35. package/src/ai-client-legacy.js +171 -0
  36. package/src/ai-client.js +221 -0
  37. package/src/bash-runner.js +148 -0
  38. package/src/batch-cli.js +327 -0
  39. package/src/cli.jsx +166 -0
  40. package/src/closer-cli.jsx +1103 -0
  41. package/src/closer-cli.jsx.backup +948 -0
  42. package/src/commands/batch.js +62 -0
  43. package/src/commands/chat.js +10 -0
  44. package/src/commands/config.js +154 -0
  45. package/src/commands/help.js +76 -0
  46. package/src/commands/history.js +192 -0
  47. package/src/commands/setup.js +17 -0
  48. package/src/commands/upgrade.js +101 -0
  49. package/src/commands/workflow-tests.js +125 -0
  50. package/src/config.js +343 -0
  51. package/src/conversation.js +962 -0
  52. package/src/git-helper.js +349 -0
  53. package/src/index.js +88 -0
  54. package/src/logger.js +347 -0
  55. package/src/plan.js +193 -0
  56. package/src/planner.js +397 -0
  57. package/src/search.js +195 -0
  58. package/src/setup.js +147 -0
  59. package/src/shortcuts.js +269 -0
  60. package/src/snippets.js +430 -0
  61. package/src/test-modules.js +118 -0
  62. package/src/tools.js +398 -0
  63. package/src/utils/cli.js +124 -0
  64. package/src/utils/validator.js +184 -0
  65. package/src/utils/version.js +33 -0
  66. package/src/utils/workflow-test.js +271 -0
  67. package/src/utils/workflow.js +268 -0
  68. package/test/demo-file-naming.js +92 -0
  69. package/test/demo-thinking.js +124 -0
  70. package/test/final-verification-report.md +303 -0
  71. package/test/research-thinking.js +130 -0
  72. package/test/test-auto-mkdir.js +123 -0
  73. package/test/test-e2e-empty-dir.md +108 -0
  74. package/test/test-export-logic.js +119 -0
  75. package/test/test-global-cloco.js +126 -0
  76. package/test/test-history-isolation.js +291 -0
  77. package/test/test-improved-thinking.js +43 -0
  78. package/test/test-long-message.js +65 -0
  79. package/test/test-plan-functionality.js +95 -0
  80. package/test/test-real-scenario.js +216 -0
  81. package/test/test-thinking-display.js +65 -0
  82. package/test/ui-verification-test.js +203 -0
  83. package/test/verify-history-isolation.sh +71 -0
  84. package/test/verify-thinking.js +339 -0
  85. package/test/workflows/empty-dir-creation.md +51 -0
  86. package/test/workflows/inventor/ascii-teacup.js +199 -0
  87. package/test/workflows/inventor/ascii-teacup.mjs +199 -0
  88. package/test/workflows/inventor/ascii_apple.hs +84 -0
  89. package/test/workflows/inventor/ascii_apple.py +91 -0
  90. package/test/workflows/inventor/cloco.md +3 -0
  91. package/test/workflows/longtalk/cloco.md +19 -0
  92. package/test/workflows/longtalk/emoji_500.txt +63 -0
  93. package/test/workflows/longtalk/emoji_list.txt +20 -0
  94. package/test/workflows/programmer/adder.md +33 -0
  95. package/test/workflows/programmer/expect.md +2 -0
  96. package/test/workflows/programmer/prompt.md +3 -0
  97. package/test/workflows/test-empty-dir-creation.js +113 -0
  98. package/test-ctrl-c.jsx +126 -0
  99. package/test-manual-file-creation.js +151 -0
  100. package/winfix.md +3 -0
@@ -0,0 +1,430 @@
1
+ /**
2
+ * 代码片段管理
3
+ */
4
+
5
+ import fs from 'fs/promises';
6
+ import path from 'path';
7
+ import { homedir } from 'os';
8
+
9
+ const SNIPPETS_DIR = path.join(homedir(), '.closer-code/snippets');
10
+
11
+ /**
12
+ * 代码片段
13
+ */
14
+ export class CodeSnippet {
15
+ constructor(name, content, language = 'text', tags = []) {
16
+ this.name = name;
17
+ this.content = content;
18
+ this.language = language;
19
+ this.tags = tags;
20
+ this.createdAt = new Date().toISOString();
21
+ this.usedCount = 0;
22
+ }
23
+
24
+ incrementUsage() {
25
+ this.usedCount++;
26
+ this.lastUsed = new Date().toISOString();
27
+ }
28
+ }
29
+
30
+ /**
31
+ * 代码片段管理器
32
+ */
33
+ export class SnippetManager {
34
+ constructor() {
35
+ this.snippets = new Map();
36
+ }
37
+
38
+ /**
39
+ * 初始化
40
+ */
41
+ async initialize() {
42
+ await this.loadSnippets();
43
+ }
44
+
45
+ /**
46
+ * 加载所有代码片段
47
+ */
48
+ async loadSnippets() {
49
+ try {
50
+ await fs.mkdir(SNIPPETS_DIR, { recursive: true });
51
+
52
+ const files = await fs.readdir(SNIPPETS_DIR);
53
+
54
+ for (const file of files) {
55
+ if (file.endsWith('.json')) {
56
+ try {
57
+ const filePath = path.join(SNIPPETS_DIR, file);
58
+ const content = await fs.readFile(filePath, 'utf-8');
59
+ const data = JSON.parse(content);
60
+
61
+ const snippet = Object.assign(new CodeSnippet(), data);
62
+ this.snippets.set(data.name, snippet);
63
+ } catch (error) {
64
+ console.warn(`Failed to load snippet ${file}:`, error.message);
65
+ }
66
+ }
67
+ }
68
+ } catch (error) {
69
+ console.warn('Failed to load snippets:', error.message);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 保存代码片段
75
+ */
76
+ async saveSnippet(snippet) {
77
+ const filePath = path.join(SNIPPETS_DIR, `${snippet.name}.json`);
78
+
79
+ await fs.mkdir(SNIPPETS_DIR, { recursive: true });
80
+ await fs.writeFile(filePath, JSON.stringify(snippet, null, 2));
81
+
82
+ this.snippets.set(snippet.name, snippet);
83
+ }
84
+
85
+ /**
86
+ * 创建新代码片段
87
+ */
88
+ async create(name, content, language = 'text', tags = []) {
89
+ if (this.snippets.has(name)) {
90
+ throw new Error(`Snippet "${name}" already exists`);
91
+ }
92
+
93
+ const snippet = new CodeSnippet(name, content, language, tags);
94
+ await this.saveSnippet(snippet);
95
+
96
+ return snippet;
97
+ }
98
+
99
+ /**
100
+ * 获取代码片段
101
+ */
102
+ get(name) {
103
+ const snippet = this.snippets.get(name);
104
+
105
+ if (snippet) {
106
+ snippet.incrementUsage();
107
+ this.saveSnippet(snippet); // 更新使用统计
108
+ }
109
+
110
+ return snippet;
111
+ }
112
+
113
+ /**
114
+ * 搜索代码片段
115
+ */
116
+ search(query) {
117
+ const results = [];
118
+ const lowerQuery = query.toLowerCase();
119
+
120
+ for (const [name, snippet] of this.snippets) {
121
+ // 搜索名称
122
+ if (name.toLowerCase().includes(lowerQuery)) {
123
+ results.push(snippet);
124
+ continue;
125
+ }
126
+
127
+ // 搜索标签
128
+ if (snippet.tags.some(tag => tag.toLowerCase().includes(lowerQuery))) {
129
+ results.push(snippet);
130
+ continue;
131
+ }
132
+
133
+ // 搜索内容
134
+ if (snippet.content.toLowerCase().includes(lowerQuery)) {
135
+ results.push(snippet);
136
+ }
137
+ }
138
+
139
+ return results;
140
+ }
141
+
142
+ /**
143
+ * 按标签搜索
144
+ */
145
+ getByTag(tag) {
146
+ const results = [];
147
+
148
+ for (const snippet of this.snippets.values()) {
149
+ if (snippet.tags.includes(tag)) {
150
+ results.push(snippet);
151
+ }
152
+ }
153
+
154
+ return results;
155
+ }
156
+
157
+ /**
158
+ * 按语言搜索
159
+ */
160
+ getByLanguage(language) {
161
+ const results = [];
162
+
163
+ for (const snippet of this.snippets.values()) {
164
+ if (snippet.language === language) {
165
+ results.push(snippet);
166
+ }
167
+ }
168
+
169
+ return results;
170
+ }
171
+
172
+ /**
173
+ * 列出所有代码片段
174
+ */
175
+ list() {
176
+ return Array.from(this.snippets.values());
177
+ }
178
+
179
+ /**
180
+ * 删除代码片段
181
+ */
182
+ async delete(name) {
183
+ if (!this.snippets.has(name)) {
184
+ return false;
185
+ }
186
+
187
+ const filePath = path.join(SNIPPETS_DIR, `${name}.json`);
188
+ await fs.unlink(filePath);
189
+
190
+ this.snippets.delete(name);
191
+ return true;
192
+ }
193
+
194
+ /**
195
+ * 更新代码片段
196
+ */
197
+ async update(name, updates) {
198
+ const snippet = this.snippets.get(name);
199
+
200
+ if (!snippet) {
201
+ throw new Error(`Snippet "${name}" not found`);
202
+ }
203
+
204
+ Object.assign(snippet, updates);
205
+ await this.saveSnippet(snippet);
206
+
207
+ return snippet;
208
+ }
209
+
210
+ /**
211
+ * 获取统计信息
212
+ */
213
+ getStats() {
214
+ const stats = {
215
+ total: this.snippets.size,
216
+ byLanguage: {},
217
+ mostUsed: [],
218
+ recentlyUsed: []
219
+ };
220
+
221
+ // 按语言统计
222
+ for (const snippet of this.snippets.values()) {
223
+ stats.byLanguage[snippet.language] = (stats.byLanguage[snippet.language] || 0) + 1;
224
+ }
225
+
226
+ // 最常用
227
+ stats.mostUsed = Array.from(this.snippets.values())
228
+ .sort((a, b) => b.usedCount - a.usedCount)
229
+ .slice(0, 10);
230
+
231
+ // 最近使用
232
+ stats.recentlyUsed = Array.from(this.snippets.values())
233
+ .filter(s => s.lastUsed)
234
+ .sort((a, b) => new Date(b.lastUsed) - new Date(a.lastUsed))
235
+ .slice(0, 10);
236
+
237
+ return stats;
238
+ }
239
+
240
+ /**
241
+ * 导出代码片段
242
+ */
243
+ async export(name) {
244
+ const snippet = this.snippets.get(name);
245
+
246
+ if (!snippet) {
247
+ throw new Error(`Snippet "${name}" not found`);
248
+ }
249
+
250
+ return {
251
+ name: snippet.name,
252
+ content: snippet.content,
253
+ language: snippet.language,
254
+ tags: snippet.tags
255
+ };
256
+ }
257
+
258
+ /**
259
+ * 导入代码片段
260
+ */
261
+ async import(data) {
262
+ const { name, content, language = 'text', tags = [] } = data;
263
+
264
+ if (this.snippets.has(name)) {
265
+ throw new Error(`Snippet "${name}" already exists`);
266
+ }
267
+
268
+ const snippet = new CodeSnippet(name, content, language, tags);
269
+ await this.saveSnippet(snippet);
270
+
271
+ return snippet;
272
+ }
273
+ }
274
+
275
+ /**
276
+ * 创建代码片段管理器
277
+ */
278
+ export async function createSnippetManager() {
279
+ const manager = new SnippetManager();
280
+ await manager.initialize();
281
+ return manager;
282
+ }
283
+
284
+ /**
285
+ * 预定义代码片段模板
286
+ */
287
+ export const SNIPPET_TEMPLATES = {
288
+ // React 组件
289
+ 'react-component': {
290
+ content: `import React from 'react';
291
+
292
+ export function {{name}}({{props}}) {
293
+ return (
294
+ <div>
295
+ {/* Component content */}
296
+ </div>
297
+ );
298
+ }
299
+
300
+ export default {{name}};`,
301
+ language: 'jsx',
302
+ tags: ['react', 'component']
303
+ },
304
+
305
+ // React Hook
306
+ 'react-hook': {
307
+ content: `import { useState, useEffect } from 'react';
308
+
309
+ export function use{{name}}() {
310
+ const [state, setState] = useState(null);
311
+
312
+ useEffect(() => {
313
+ // Effect logic
314
+ }, []);
315
+
316
+ return state;
317
+ }`,
318
+ language: 'jsx',
319
+ tags: ['react', 'hook']
320
+ },
321
+
322
+ // Express 路由
323
+ 'express-route': {
324
+ content: `import express from 'express';
325
+
326
+ const router = express.Router();
327
+
328
+ router.get('/', async (req, res) => {
329
+ try {
330
+ // Handle GET request
331
+ res.json({ success: true });
332
+ } catch (error) {
333
+ res.status(500).json({ error: error.message });
334
+ }
335
+ });
336
+
337
+ router.post('/', async (req, res) => {
338
+ try {
339
+ // Handle POST request
340
+ res.json({ success: true });
341
+ } catch (error) {
342
+ res.status(500).json({ error: error.message });
343
+ }
344
+ });
345
+
346
+ export default router;`,
347
+ language: 'javascript',
348
+ tags: ['express', 'route', 'api']
349
+ },
350
+
351
+ // Node.js 模块
352
+ 'node-module': {
353
+ content: `/**
354
+ * {{description}}
355
+ */
356
+
357
+ export function {{functionName}}({{params}}) {
358
+ // Implementation
359
+ return {{returnValue}};
360
+ }
361
+
362
+ export default {{functionName}};`,
363
+ language: 'javascript',
364
+ tags: ['node', 'module']
365
+ },
366
+
367
+ // Python 函数
368
+ 'python-function': {
369
+ content: `def {{function_name}}({{params}}):
370
+ """
371
+ {{description}}
372
+ """
373
+ # Implementation
374
+ return {{return_value}}`,
375
+ language: 'python',
376
+ tags: ['python', 'function']
377
+ },
378
+
379
+ // Git Commit
380
+ 'git-commit': {
381
+ content: `git add .
382
+ git commit -m "{{message}}"
383
+ git push`,
384
+ language: 'bash',
385
+ tags: ['git', 'commit']
386
+ },
387
+
388
+ // NPM Package
389
+ 'npm-package': {
390
+ content: `{
391
+ "name": "{{package-name}}",
392
+ "version": "1.0.0",
393
+ "description": "{{description}}",
394
+ "main": "index.js",
395
+ "type": "module",
396
+ "scripts": {
397
+ "start": "node index.js",
398
+ "dev": "node --watch index.js",
399
+ "test": "node --test"
400
+ },
401
+ "keywords": [],
402
+ "author": "",
403
+ "license": "MIT"
404
+ }`,
405
+ language: 'json',
406
+ tags: ['npm', 'package']
407
+ }
408
+ };
409
+
410
+ /**
411
+ * 从模板创建代码片段
412
+ */
413
+ export async function createFromTemplate(templateName, name, variables = {}) {
414
+ const template = SNIPPET_TEMPLATES[templateName];
415
+
416
+ if (!template) {
417
+ throw new Error(`Template "${templateName}" not found`);
418
+ }
419
+
420
+ let content = template.content;
421
+
422
+ // 替换变量
423
+ for (const [key, value] of Object.entries(variables)) {
424
+ const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
425
+ content = content.replace(regex, value);
426
+ }
427
+
428
+ const manager = await createSnippetManager();
429
+ return await manager.create(name, content, template.language, template.tags);
430
+ }
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 简单的模块验证测试
4
+ */
5
+
6
+ import { getConfig, saveConfig } from './config.js';
7
+ import { createAIClient, checkConfig } from './ai-client.js';
8
+ import { getToolDefinitions, setToolExecutorContext } from './tools.js';
9
+
10
+ async function testConfig() {
11
+ console.log('🔧 测试配置模块...');
12
+ try {
13
+ const config = getConfig();
14
+ console.log('✅ 配置加载成功');
15
+ console.log(` - 提供商: ${config.ai.provider}`);
16
+ console.log(` - 工作目录: ${config.behavior.workingDir}`);
17
+ console.log(` - 启用工具: ${config.tools.enabled.length}个`);
18
+ return true;
19
+ } catch (error) {
20
+ console.log('❌ 配置测试失败:', error.message);
21
+ return false;
22
+ }
23
+ }
24
+
25
+ async function testAIClient() {
26
+ console.log('\n🤖 测试 AI 客户端...');
27
+ try {
28
+ const config = getConfig();
29
+ checkConfig(config);
30
+
31
+ const client = createAIClient(config);
32
+ console.log('✅ AI 客户端创建成功');
33
+ console.log(` - 类型: ${config.ai.provider}`);
34
+ return true;
35
+ } catch (error) {
36
+ console.log('⚠️ AI 客户端测试:', error.message);
37
+ console.log(' 提示: 请运行 npm run setup 配置 API 密钥');
38
+ return false;
39
+ }
40
+ }
41
+
42
+ async function testTools() {
43
+ console.log('\n🔨 测试工具模块...');
44
+ try {
45
+ const config = getConfig();
46
+
47
+ // 设置工具执行上下文
48
+ setToolExecutorContext(config);
49
+
50
+ // 获取工具定义
51
+ const tools = getToolDefinitions(config.tools.enabled);
52
+
53
+ console.log('✅ 工具模块正常');
54
+ console.log(` - 可用工具: ${tools.length}个`);
55
+ tools.forEach(tool => {
56
+ console.log(` - ${tool.name}`);
57
+ });
58
+
59
+ return true;
60
+ } catch (error) {
61
+ console.log('❌ 工具模块测试失败:', error.message);
62
+ return false;
63
+ }
64
+ }
65
+
66
+ async function testBashRunner() {
67
+ console.log('\n💻 测试 Bash 执行器...');
68
+ try {
69
+ const { executeBashCommand } = await import('./bash-runner.js');
70
+ const result = await executeBashCommand('echo "Hello from Closer Code!"');
71
+
72
+ if (result.success) {
73
+ console.log('✅ Bash 执行器正常');
74
+ console.log(` - 输出: ${result.stdout.trim()}`);
75
+ return true;
76
+ } else {
77
+ console.log('❌ Bash 执行失败');
78
+ console.log(` - stderr: ${result.stderr}`);
79
+ return false;
80
+ }
81
+ } catch (error) {
82
+ console.log('❌ Bash 执行器测试失败:', error.message);
83
+ return false;
84
+ }
85
+ }
86
+
87
+ async function runTests() {
88
+ console.log('🧪 Closer Code 模块测试\n');
89
+ console.log('═'.repeat(50));
90
+
91
+ const results = {
92
+ config: await testConfig(),
93
+ aiClient: await testAIClient(),
94
+ tools: await testTools(),
95
+ bashRunner: await testBashRunner()
96
+ };
97
+
98
+ console.log('\n' + '═'.repeat(50));
99
+ console.log('\n📊 测试结果汇总:');
100
+
101
+ const passed = Object.values(results).filter(r => r).length;
102
+ const total = Object.keys(results).length;
103
+
104
+ for (const [name, result] of Object.entries(results)) {
105
+ const status = result ? '✅ 通过' : '❌ 失败';
106
+ console.log(` ${status} - ${name}`);
107
+ }
108
+
109
+ console.log(`\n总计: ${passed}/${total} 通过`);
110
+
111
+ if (passed === total) {
112
+ console.log('\n🎉 所有测试通过!你可以运行 npm start 启动 Closer Code');
113
+ } else {
114
+ console.log('\n⚠️ 部分测试失败,请检查配置');
115
+ }
116
+ }
117
+
118
+ runTests().catch(console.error);