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,349 @@
1
+ /**
2
+ * Git 集成功能
3
+ */
4
+
5
+ import { spawn } from 'child_process';
6
+
7
+ /**
8
+ * 执行 Git 命令
9
+ */
10
+ async function gitExec(args, options = {}) {
11
+ return new Promise((resolve, reject) => {
12
+ const git = spawn('git', args, {
13
+ cwd: options.cwd || process.cwd(),
14
+ stdio: ['pipe', 'pipe', 'pipe']
15
+ });
16
+
17
+ let stdout = '';
18
+ let stderr = '';
19
+
20
+ git.stdout.on('data', (data) => {
21
+ stdout += data.toString();
22
+ });
23
+
24
+ git.stderr.on('data', (data) => {
25
+ stderr += data.toString();
26
+ });
27
+
28
+ git.on('close', (code) => {
29
+ if (code === 0) {
30
+ resolve({ stdout: stdout.trim(), stderr: stderr.trim(), exitCode: code });
31
+ } else {
32
+ reject({ stdout: stdout.trim(), stderr: stderr.trim(), exitCode: code });
33
+ }
34
+ });
35
+
36
+ git.on('error', (error) => {
37
+ reject({ error: error.message, exitCode: -1 });
38
+ });
39
+ });
40
+ }
41
+
42
+ /**
43
+ * Git 助手类
44
+ */
45
+ export class GitHelper {
46
+ constructor(cwd = process.cwd()) {
47
+ this.cwd = cwd;
48
+ }
49
+
50
+ /**
51
+ * 检查是否在 Git 仓库中
52
+ */
53
+ async isRepo() {
54
+ try {
55
+ await gitExec(['rev-parse', '--is-inside-work-tree'], { cwd: this.cwd });
56
+ return true;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * 获取当前分支
64
+ */
65
+ async getCurrentBranch() {
66
+ try {
67
+ const result = await gitExec(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: this.cwd });
68
+ return result.stdout;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * 获取状态
76
+ */
77
+ async getStatus() {
78
+ try {
79
+ const result = await gitExec(['status', '--porcelain'], { cwd: this.cwd });
80
+ const lines = result.stdout.split('\n').filter(Boolean);
81
+
82
+ return {
83
+ staged: lines.filter(l => l.startsWith('M ').length),
84
+ modified: lines.filter(l => l.startsWith(' M').length),
85
+ untracked: lines.filter(l => l.startsWith('??').length),
86
+ total: lines.length
87
+ };
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * 获取差异
95
+ */
96
+ async getDiff(file = null) {
97
+ const args = ['diff'];
98
+ if (file) args.push(file);
99
+
100
+ try {
101
+ const result = await gitExec(args, { cwd: this.cwd });
102
+ return result.stdout;
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * 获取提交历史
110
+ */
111
+ async getLog(limit = 10) {
112
+ try {
113
+ const result = await gitExec(
114
+ ['log', '--oneline', `-${limit}`, '--pretty=format:%H|%s|%an|%ar'],
115
+ { cwd: this.cwd }
116
+ );
117
+
118
+ return result.stdout.split('\n').filter(Boolean).map(line => {
119
+ const [hash, message, author, date] = line.split('|');
120
+ return { hash, message, author, date };
121
+ });
122
+ } catch {
123
+ return [];
124
+ }
125
+ }
126
+
127
+ /**
128
+ * 添加文件
129
+ */
130
+ async add(files) {
131
+ try {
132
+ const args = ['add', ...(Array.isArray(files) ? files : [files])];
133
+ const result = await gitExec(args, { cwd: this.cwd });
134
+ return { success: true, ...result };
135
+ } catch (error) {
136
+ return { success: false, error };
137
+ }
138
+ }
139
+
140
+ /**
141
+ * 提交
142
+ */
143
+ async commit(message, options = {}) {
144
+ try {
145
+ const args = ['commit', '-m', message];
146
+ if (options.amend) args.push('--amend');
147
+ if (options.noVerify) args.push('--no-verify');
148
+
149
+ const result = await gitExec(args, { cwd: this.cwd });
150
+ return { success: true, ...result };
151
+ } catch (error) {
152
+ return { success: false, error };
153
+ }
154
+ }
155
+
156
+ /**
157
+ * 推送
158
+ */
159
+ async push(remote = 'origin', branch = null) {
160
+ try {
161
+ const args = ['push', remote];
162
+ if (branch) args.push(branch);
163
+
164
+ const result = await gitExec(args, { cwd: this.cwd });
165
+ return { success: true, ...result };
166
+ } catch (error) {
167
+ return { success: false, error };
168
+ }
169
+ }
170
+
171
+ /**
172
+ * 拉取
173
+ */
174
+ async pull(remote = 'origin', branch = null) {
175
+ try {
176
+ const args = ['pull', remote];
177
+ if (branch) args.push(branch);
178
+
179
+ const result = await gitExec(args, { cwd: this.cwd });
180
+ return { success: true, ...result };
181
+ } catch (error) {
182
+ return { success: false, error };
183
+ }
184
+ }
185
+
186
+ /**
187
+ * 创建分支
188
+ */
189
+ async createBranch(name, base = null) {
190
+ try {
191
+ const args = ['checkout', '-b', name];
192
+ if (base) args.push(base);
193
+
194
+ const result = await gitExec(args, { cwd: this.cwd });
195
+ return { success: true, ...result };
196
+ } catch (error) {
197
+ return { success: false, error };
198
+ }
199
+ }
200
+
201
+ /**
202
+ * 切换分支
203
+ */
204
+ async checkoutBranch(name) {
205
+ try {
206
+ const result = await gitExec(['checkout', name], { cwd: this.cwd });
207
+ return { success: true, ...result };
208
+ } catch (error) {
209
+ return { success: false, error };
210
+ }
211
+ }
212
+
213
+ /**
214
+ * 删除分支
215
+ */
216
+ async deleteBranch(name, force = false) {
217
+ try {
218
+ const args = ['branch', force ? '-D' : '-d', name];
219
+ const result = await gitExec(args, { cwd: this.cwd });
220
+ return { success: true, ...result };
221
+ } catch (error) {
222
+ return { success: false, error };
223
+ }
224
+ }
225
+
226
+ /**
227
+ * 获取所有分支
228
+ */
229
+ async getBranches() {
230
+ try {
231
+ const result = await gitExec(['branch', '-a'], { cwd: this.cwd });
232
+ const branches = result.stdout.split('\n').filter(Boolean);
233
+
234
+ const current = await this.getCurrentBranch();
235
+
236
+ return {
237
+ current,
238
+ local: branches.filter(b => !b.includes('HEAD ->') && !b.includes('remotes')).map(b => b.trim().replace('* ', '')),
239
+ remote: branches.filter(b => b.includes('remotes')).map(b => b.trim())
240
+ };
241
+ } catch {
242
+ return { current: null, local: [], remote: [] };
243
+ }
244
+ }
245
+
246
+ /**
247
+ * 获取远程仓库
248
+ */
249
+ async getRemotes() {
250
+ try {
251
+ const result = await gitExec(['remote', '-v'], { cwd: this.cwd });
252
+ const lines = result.stdout.split('\n').filter(Boolean);
253
+
254
+ return lines.map(line => {
255
+ const [name, url] = line.split('\t');
256
+ return { name, url: url.replace(' (fetch)', '').replace(' (push)', '') };
257
+ });
258
+ } catch {
259
+ return [];
260
+ }
261
+ }
262
+
263
+ /**
264
+ * 克隆仓库
265
+ */
266
+ static async clone(url, directory, options = {}) {
267
+ return new Promise((resolve, reject) => {
268
+ const args = ['clone', url, directory];
269
+ if (options.branch) args.splice(1, 0, '-b', options.branch);
270
+ if (options.depth) args.splice(1, 0, `--depth=${options.depth}`);
271
+
272
+ const git = spawn('git', args, {
273
+ cwd: options.cwd || process.cwd(),
274
+ stdio: 'inherit'
275
+ });
276
+
277
+ git.on('close', (code) => {
278
+ if (code === 0) {
279
+ resolve({ success: true });
280
+ } else {
281
+ reject({ success: false, exitCode: code });
282
+ }
283
+ });
284
+
285
+ git.on('error', (error) => {
286
+ reject({ success: false, error: error.message });
287
+ });
288
+ });
289
+ }
290
+
291
+ /**
292
+ * 智能提交助手
293
+ */
294
+ async smartCommit(files, messageGenerator) {
295
+ // 1. 获取状态
296
+ const status = await this.getStatus();
297
+
298
+ // 2. 添加文件
299
+ await this.add(files || '.');
300
+
301
+ // 3. 生成或使用提交信息
302
+ let message = files?.message;
303
+
304
+ if (!message && messageGenerator) {
305
+ // 生成提交信息
306
+ const diff = await this.getDiff();
307
+ message = await messageGenerator(diff);
308
+ }
309
+
310
+ if (!message) {
311
+ return { success: false, error: 'No commit message provided' };
312
+ }
313
+
314
+ // 4. 提交
315
+ return await this.commit(message);
316
+ }
317
+
318
+ /**
319
+ * 获取仓库统计
320
+ */
321
+ async getStats() {
322
+ try {
323
+ const [currentBranch, status, log, branches, remotes] = await Promise.all([
324
+ this.getCurrentBranch(),
325
+ this.getStatus(),
326
+ this.getLog(5),
327
+ this.getBranches(),
328
+ this.getRemotes()
329
+ ]);
330
+
331
+ return {
332
+ branch: currentBranch,
333
+ status,
334
+ recentCommits: log,
335
+ branches,
336
+ remotes
337
+ };
338
+ } catch {
339
+ return null;
340
+ }
341
+ }
342
+ }
343
+
344
+ /**
345
+ * 创建 Git 助手
346
+ */
347
+ export function createGitHelper(cwd) {
348
+ return new GitHelper(cwd);
349
+ }
package/src/index.js ADDED
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Cloco - AI 编程助手统一CLI入口
4
+ *
5
+ * 使用方式:
6
+ * cloco [options] [prompt]
7
+ * cloco -b|--batch [options] <prompt>
8
+ * cloco config [subcommand] [args]
9
+ * cloco setup|upgrade|version|help
10
+ */
11
+
12
+ import { parseOptions } from './utils/cli.js';
13
+ import { showHelp } from './commands/help.js';
14
+ import { showVersion } from './utils/version.js';
15
+ import { hasConfig } from './config.js';
16
+
17
+ // 子命令映射
18
+ const commands = {
19
+ config: () => import('./commands/config.js'),
20
+ setup: () => import('./commands/setup.js'),
21
+ upgrade: () => import('./commands/upgrade.js'),
22
+ 'workflow-tests': () => import('./commands/workflow-tests.js'),
23
+ };
24
+
25
+ /**
26
+ * 错误处理
27
+ */
28
+ function handleError(error) {
29
+ console.error('❌ 发生错误:', error.message);
30
+
31
+ if (error.code === 'ENOENT') {
32
+ console.error('💡 文件或目录不存在');
33
+ } else if (error.code === 'EACCES') {
34
+ console.error('💡 权限不足,请检查文件权限');
35
+ } else if (process.env.CLOSER_DEBUG_LOG) {
36
+ console.error('调试信息:', error.stack);
37
+ }
38
+
39
+ process.exit(1);
40
+ }
41
+
42
+ /**
43
+ * 主函数
44
+ */
45
+ async function main() {
46
+ const { options, args, specialCommand } = parseOptions(process.argv.slice(2));
47
+
48
+ // 处理特殊命令
49
+ if (specialCommand === 'help' || options.help) {
50
+ await showHelp();
51
+ return;
52
+ }
53
+
54
+ if (specialCommand === 'version' || options.version) {
55
+ showVersion();
56
+ return;
57
+ }
58
+
59
+ // 处理子命令
60
+ if (specialCommand && commands[specialCommand]) {
61
+ const module = await commands[specialCommand]();
62
+ await module.default(args, options);
63
+ return;
64
+ }
65
+
66
+ // 批处理模式
67
+ if (options.batch || options.b) {
68
+ const { default: batchMode } = await import('./commands/batch.js');
69
+ await batchMode(args, options);
70
+ return;
71
+ }
72
+
73
+ // 交互模式(默认)
74
+ const { default: chatMode } = await import('./commands/chat.js');
75
+
76
+ // 检查配置,如无则自动运行setup
77
+ if (!await hasConfig()) {
78
+ console.log('⚙️ 首次使用,让我们完成配置...\n');
79
+ const { default: setup } = await import('./commands/setup.js');
80
+ await setup([], {});
81
+ console.log('\n✅ 配置完成!\n');
82
+ }
83
+
84
+ await chatMode(args, options);
85
+ }
86
+
87
+ // 运行主函数
88
+ main().catch(handleError);