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,71 @@
1
+ #!/bin/bash
2
+ # 项目历史隔离功能验证脚本
3
+
4
+ echo "=========================================="
5
+ echo " 项目历史隔离功能验证"
6
+ echo "=========================================="
7
+ echo ""
8
+
9
+ # 颜色定义
10
+ GREEN='\033[0;32m'
11
+ RED='\033[0;31m'
12
+ YELLOW='\033[1;33m'
13
+ NC='\033[0m' # No Color
14
+
15
+ # 检查 Node.js
16
+ echo "1. 检查环境..."
17
+ if command -v node &> /dev/null; then
18
+ echo -e "${GREEN}✓${NC} Node.js 已安装: $(node --version)"
19
+ else
20
+ echo -e "${RED}✗${NC} Node.js 未安装"
21
+ exit 1
22
+ fi
23
+
24
+ # 运行单元测试
25
+ echo ""
26
+ echo "2. 运行单元测试..."
27
+ if node test/test-history-isolation.js; then
28
+ echo -e "${GREEN}✓${NC} 单元测试通过"
29
+ else
30
+ echo -e "${RED}✗${NC} 单元测试失败"
31
+ exit 1
32
+ fi
33
+
34
+ # 运行场景测试
35
+ echo ""
36
+ echo "3. 运行场景测试..."
37
+ if node test/test-real-scenario.js; then
38
+ echo -e "${GREEN}✓${NC} 场景测试通过"
39
+ else
40
+ echo -e "${RED}✗${NC} 场景测试失败"
41
+ exit 1
42
+ fi
43
+
44
+ # 测试命令行工具
45
+ echo ""
46
+ echo "4. 测试命令行工具..."
47
+ if node src/commands/history.js list > /dev/null 2>&1; then
48
+ echo -e "${GREEN}✓${NC} 命令行工具正常"
49
+ else
50
+ echo -e "${RED}✗${NC} 命令行工具异常"
51
+ exit 1
52
+ fi
53
+
54
+ # 显示当前项目历史
55
+ echo ""
56
+ echo "5. 显示当前项目历史..."
57
+ node src/commands/history.js show "$(pwd)"
58
+
59
+ # 总结
60
+ echo ""
61
+ echo "=========================================="
62
+ echo -e "${GREEN}✓ 所有验证通过!${NC}"
63
+ echo "=========================================="
64
+ echo ""
65
+ echo "功能已就绪,可以开始使用!"
66
+ echo ""
67
+ echo "常用命令:"
68
+ echo " - 查看所有项目: node src/commands/history.js list"
69
+ echo " - 查看当前项目: node src/commands/history.js show \$(pwd)"
70
+ echo " - 清除项目历史: node src/commands/history.js clear \$(pwd)"
71
+ echo ""
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Thinking 功能验证测试
4
+ *
5
+ * 验证 AI thinking 功能是否正确实现(符合 SDK Extended Thinking 规范)
6
+ */
7
+
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+
11
+ // 颜色输出
12
+ const colors = {
13
+ reset: '\x1b[0m',
14
+ green: '\x1b[32m',
15
+ red: '\x1b[31m',
16
+ cyan: '\x1b[36m',
17
+ yellow: '\x1b[33m'
18
+ };
19
+
20
+ function log(message, color = 'reset') {
21
+ const colorCode = colors[color] || colors.reset;
22
+ console.log(`${colorCode}${message}${colors.reset}`);
23
+ }
24
+
25
+ /**
26
+ * 验证源代码中的 thinking 实现
27
+ */
28
+ async function verifySourceCode() {
29
+ log('\n验证源代码实现...', 'cyan');
30
+
31
+ const checks = [];
32
+
33
+ // 1. 检查 ai-client.js
34
+ log('\n1. 检查 ai-client.js', 'yellow');
35
+ const aiClientPath = path.join(process.cwd(), 'src', 'ai-client.js');
36
+ const aiClientContent = await fs.readFile(aiClientPath, 'utf-8');
37
+
38
+ const hasThinkingInChat = /thinking:/.test(aiClientContent);
39
+ const hasStreamEventListener = /stream\.on\('thinking'/.test(aiClientContent);
40
+ const hasTextEventListener = /stream\.on\('text'/.test(aiClientContent);
41
+ const hasSignatureEventListener = /stream\.on\('signature'/.test(aiClientContent);
42
+ const hasFinalMessage = /finalMessage/.test(aiClientContent);
43
+
44
+ checks.push({
45
+ file: 'ai-client.js',
46
+ check: 'chat 方法中的 thinking 配置',
47
+ success: hasThinkingInChat
48
+ });
49
+ checks.push({
50
+ file: 'ai-client.js',
51
+ check: '使用 stream.on("thinking") 事件监听器',
52
+ success: hasStreamEventListener
53
+ });
54
+ checks.push({
55
+ file: 'ai-client.js',
56
+ check: '使用 stream.on("text") 事件监听器',
57
+ success: hasTextEventListener
58
+ });
59
+ checks.push({
60
+ file: 'ai-client.js',
61
+ check: '使用 stream.on("signature") 事件监听器',
62
+ success: hasSignatureEventListener
63
+ });
64
+ checks.push({
65
+ file: 'ai-client.js',
66
+ check: '使用 stream.finalMessage() 获取最终消息',
67
+ success: hasFinalMessage
68
+ });
69
+
70
+ log(` ${hasThinkingInChat ? '✓' : '✗'} chat 方法中的 thinking 配置`, hasThinkingInChat ? 'green' : 'red');
71
+ log(` ${hasStreamEventListener ? '✓' : '✗'} 使用 stream.on("thinking") 事件监听器`, hasStreamEventListener ? 'green' : 'red');
72
+ log(` ${hasTextEventListener ? '✓' : '✗'} 使用 stream.on("text") 事件监听器`, hasTextEventListener ? 'green' : 'red');
73
+ log(` ${hasSignatureEventListener ? '✓' : '✗'} 使用 stream.on("signature") 事件监听器`, hasSignatureEventListener ? 'green' : 'red');
74
+ log(` ${hasFinalMessage ? '✓' : '✗'} 使用 stream.finalMessage() 获取最终消息`, hasFinalMessage ? 'green' : 'red');
75
+
76
+ // 2. 检查 conversation.js
77
+ log('\n2. 检查 conversation.js', 'yellow');
78
+ const conversationPath = path.join(process.cwd(), 'src', 'conversation.js');
79
+ const conversationContent = await fs.readFile(conversationPath, 'utf-8');
80
+
81
+ const hasThinkingHandler = /type:\s*['"]thinking['"]/.test(conversationContent);
82
+ const hasSnapshotInStream = /chunk\.snapshot/.test(conversationContent);
83
+ const hasSignatureHandler = /thinking_signature/.test(conversationContent);
84
+ const hasRedactedHandler = /thinking_redacted/.test(conversationContent);
85
+ const hasRedactedThinkingBlock = /redacted_thinking/.test(conversationContent);
86
+
87
+ checks.push({
88
+ file: 'conversation.js',
89
+ check: 'thinking 事件处理',
90
+ success: hasThinkingHandler
91
+ });
92
+ checks.push({
93
+ file: 'conversation.js',
94
+ check: '流式模式中的 snapshot 支持',
95
+ success: hasSnapshotInStream
96
+ });
97
+ checks.push({
98
+ file: 'conversation.js',
99
+ check: 'thinking signature 处理',
100
+ success: hasSignatureHandler
101
+ });
102
+ checks.push({
103
+ file: 'conversation.js',
104
+ check: 'redacted thinking 处理',
105
+ success: hasRedactedHandler
106
+ });
107
+ checks.push({
108
+ file: 'conversation.js',
109
+ check: '提取 redacted_thinking 块',
110
+ success: hasRedactedThinkingBlock
111
+ });
112
+
113
+ log(` ${hasThinkingHandler ? '✓' : '✗'} thinking 事件处理`, hasThinkingHandler ? 'green' : 'red');
114
+ log(` ${hasSnapshotInStream ? '✓' : '✗'} 流式模式中的 snapshot 支持`, hasSnapshotInStream ? 'green' : 'red');
115
+ log(` ${hasSignatureHandler ? '✓' : '✗'} thinking signature 处理`, hasSignatureHandler ? 'green' : 'red');
116
+ log(` ${hasRedactedHandler ? '✓' : '✗'} redacted thinking 处理`, hasRedactedHandler ? 'green' : 'red');
117
+ log(` ${hasRedactedThinkingBlock ? '✓' : '✗'} 提取 redacted_thinking 块`, hasRedactedThinkingBlock ? 'green' : 'red');
118
+
119
+ // 3. 检查 closer-cli.jsx
120
+ log('\n3. 检查 closer-cli.jsx', 'yellow');
121
+ const cliPath = path.join(process.cwd(), 'src', 'closer-cli.jsx');
122
+ const cliContent = await fs.readFile(cliPath, 'utf-8');
123
+
124
+ const hasThinkingProgressHandler = /progress\.type\s*===?\s*['"]thinking['"]/.test(cliContent);
125
+ const hasThinkingState = /setThinking/.test(cliContent);
126
+ const hasThinkingUI = /AI Thinking Process/.test(cliContent);
127
+ const hasSnapshotUsage = /progress\.snapshot/.test(cliContent);
128
+ const hasSignatureUIHandler = /thinking_signature/.test(cliContent);
129
+ const hasRedactedUIHandler = /thinking_redacted/.test(cliContent);
130
+
131
+ checks.push({
132
+ file: 'closer-cli.jsx',
133
+ check: 'thinking 进度处理',
134
+ success: hasThinkingProgressHandler
135
+ });
136
+ checks.push({
137
+ file: 'closer-cli.jsx',
138
+ check: 'thinking 状态更新',
139
+ success: hasThinkingState
140
+ });
141
+ checks.push({
142
+ file: 'closer-cli.jsx',
143
+ check: 'thinking UI 区域',
144
+ success: hasThinkingUI
145
+ });
146
+ checks.push({
147
+ file: 'closer-cli.jsx',
148
+ check: '使用 snapshot 避免重复',
149
+ success: hasSnapshotUsage
150
+ });
151
+ checks.push({
152
+ file: 'closer-cli.jsx',
153
+ check: 'signature 事件处理',
154
+ success: hasSignatureUIHandler
155
+ });
156
+ checks.push({
157
+ file: 'closer-cli.jsx',
158
+ check: 'redacted thinking 处理',
159
+ success: hasRedactedUIHandler
160
+ });
161
+
162
+ log(` ${hasThinkingProgressHandler ? '✓' : '✗'} thinking 进度处理`, hasThinkingProgressHandler ? 'green' : 'red');
163
+ log(` ${hasThinkingState ? '✓' : '✗'} thinking 状态更新`, hasThinkingState ? 'green' : 'red');
164
+ log(` ${hasThinkingUI ? '✓' : '✗'} thinking UI 区域`, hasThinkingUI ? 'green' : 'red');
165
+ log(` ${hasSnapshotUsage ? '✓' : '✗'} 使用 snapshot 避免重复`, hasSnapshotUsage ? 'green' : 'red');
166
+ log(` ${hasSignatureUIHandler ? '✓' : '✗'} signature 事件处理`, hasSignatureUIHandler ? 'green' : 'red');
167
+ log(` ${hasRedactedUIHandler ? '✓' : '✗'} redacted thinking 处理`, hasRedactedUIHandler ? 'green' : 'red');
168
+
169
+ return checks;
170
+ }
171
+
172
+ /**
173
+ * 验证编译后的代码
174
+ */
175
+ async function verifyCompiledCode() {
176
+ log('\n验证编译后的代码...', 'cyan');
177
+
178
+ const compiledPath = path.join(process.cwd(), 'dist', 'closer-cli.js');
179
+ const compiledContent = await fs.readFile(compiledPath, 'utf-8');
180
+
181
+ const checks = [];
182
+
183
+ // 检查关键字符串是否存在
184
+ const keywords = [
185
+ 'thinking',
186
+ 'AI Thinking Process',
187
+ 'budget_tokens',
188
+ 'enabled',
189
+ 'snapshot',
190
+ 'signature'
191
+ ];
192
+
193
+ for (const keyword of keywords) {
194
+ const found = compiledContent.includes(keyword);
195
+ checks.push({
196
+ file: 'closer-cli.js (compiled)',
197
+ check: `包含关键字 "${keyword}"`,
198
+ success: found
199
+ });
200
+ log(` ${found ? '✓' : '✗'} 包含关键字 "${keyword}"`, found ? 'green' : 'red');
201
+ }
202
+
203
+ return checks;
204
+ }
205
+
206
+ /**
207
+ * 验证文档
208
+ */
209
+ async function verifyDocumentation() {
210
+ log('\n验证文档...', 'cyan');
211
+
212
+ const docsPath = path.join(process.cwd(), 'docs', 'THINKING_FEATURE.md');
213
+ const checks = [];
214
+
215
+ try {
216
+ const docsContent = await fs.readFile(docsPath, 'utf-8');
217
+
218
+ const hasOverview = /## 概述/.test(docsContent);
219
+ const hasUsage = /## 使用场景/.test(docsContent);
220
+ const hasTechnical = /## 技术细节/.test(docsContent);
221
+ const hasExamples = /## 示例/.test(docsContent);
222
+
223
+ checks.push({
224
+ file: 'THINKING_FEATURE.md',
225
+ check: '概述部分',
226
+ success: hasOverview
227
+ });
228
+ checks.push({
229
+ file: 'THINKING_FEATURE.md',
230
+ check: '使用场景',
231
+ success: hasUsage
232
+ });
233
+ checks.push({
234
+ file: 'THINKING_FEATURE.md',
235
+ check: '技术细节',
236
+ success: hasTechnical
237
+ });
238
+ checks.push({
239
+ file: 'THINKING_FEATURE.md',
240
+ check: '示例',
241
+ success: hasExamples
242
+ });
243
+
244
+ log(` ${hasOverview ? '✓' : '✗'} 概述部分`, hasOverview ? 'green' : 'red');
245
+ log(` ${hasUsage ? '✓' : '✗'} 使用场景`, hasUsage ? 'green' : 'red');
246
+ log(` ${hasTechnical ? '✓' : '✗'} 技术细节`, hasTechnical ? 'green' : 'red');
247
+ log(` ${hasExamples ? '✓' : '✗'} 示例`, hasExamples ? 'green' : 'red');
248
+ } catch (error) {
249
+ log(` ✗ 文档文件不存在`, 'red');
250
+ checks.push({
251
+ file: 'THINKING_FEATURE.md',
252
+ check: '文档存在',
253
+ success: false
254
+ });
255
+ }
256
+
257
+ return checks;
258
+ }
259
+
260
+ /**
261
+ * 生成测试报告
262
+ */
263
+ async function generateReport(allChecks) {
264
+ const report = {
265
+ timestamp: new Date().toISOString(),
266
+ summary: {
267
+ total: allChecks.length,
268
+ passed: 0,
269
+ failed: 0
270
+ },
271
+ checks: allChecks
272
+ };
273
+
274
+ for (const check of allChecks) {
275
+ if (check.success) {
276
+ report.summary.passed++;
277
+ } else {
278
+ report.summary.failed++;
279
+ }
280
+ }
281
+
282
+ // 保存报告
283
+ const reportPath = path.join(process.cwd(), 'test', 'thinking-verification-result.log');
284
+ await fs.writeFile(reportPath, JSON.stringify(report, null, 2));
285
+
286
+ return report;
287
+ }
288
+
289
+ /**
290
+ * 主测试函数
291
+ */
292
+ async function main() {
293
+ log('='.repeat(70), 'cyan');
294
+ log('AI Thinking 功能验证测试(符合 SDK Extended Thinking 规范)', 'cyan');
295
+ log('='.repeat(70), 'cyan');
296
+
297
+ try {
298
+ // 1. 验证源代码
299
+ const sourceCodeChecks = await verifySourceCode();
300
+
301
+ // 2. 验证编译后的代码
302
+ const compiledCodeChecks = await verifyCompiledCode();
303
+
304
+ // 3. 验证文档
305
+ const documentationChecks = await verifyDocumentation();
306
+
307
+ // 4. 生成报告
308
+ const allChecks = [...sourceCodeChecks, ...compiledCodeChecks, ...documentationChecks];
309
+ const report = await generateReport(allChecks);
310
+
311
+ // 5. 输出总结
312
+ log('\n' + '='.repeat(70), 'cyan');
313
+ log('测试总结', 'cyan');
314
+ log('='.repeat(70), 'cyan');
315
+ log(`总测试数: ${report.summary.total}`, 'cyan');
316
+ log(`通过: ${report.summary.passed}`, 'green');
317
+ log(`失败: ${report.summary.failed}`, report.summary.failed > 0 ? 'red' : 'green');
318
+ log(`成功率: ${((report.summary.passed / report.summary.total) * 100).toFixed(1)}%`, 'cyan');
319
+ log(`详细报告: test/thinking-verification-result.log`, 'cyan');
320
+
321
+ // 6. 判断是否成功
322
+ const successRate = report.summary.passed / report.summary.total;
323
+ if (successRate >= 0.9) {
324
+ log('\n✓ AI Thinking 功能验证通过!', 'green');
325
+ process.exit(0);
326
+ } else {
327
+ log('\n✗ AI Thinking 功能验证未通过,成功率低于 90%', 'red');
328
+ process.exit(1);
329
+ }
330
+
331
+ } catch (error) {
332
+ log(`\n测试过程中出错: ${error.message}`, 'red');
333
+ console.error(error);
334
+ process.exit(1);
335
+ }
336
+ }
337
+
338
+ // 运行测试
339
+ main();
@@ -0,0 +1,51 @@
1
+ # Workflow 测试:空目录中创建文件
2
+
3
+ ## 测试场景
4
+ 在一个空目录中运行 Closer Code,要求 AI 创建一个多文件的玄幻故事。
5
+
6
+ ## 第1轮:执行任务
7
+
8
+ ### 任务描述
9
+ ```
10
+ 写一个50000字的玄幻故事,要求:
11
+ 1. 故事名称为《天道诀》
12
+ 2. 分为10个章节,每章约5000字
13
+ 3. 每个章节保存为独立的文件,格式为 chapters/chapter-01.txt 到 chapters/chapter-10.txt
14
+ 4. 创建一个 README.md 文件,包含故事简介和章节列表
15
+ ```
16
+
17
+ ### 验收标准
18
+ 1. 目录 `chapters/` 被自动创建
19
+ 2. 文件 `chapters/chapter-01.txt` 到 `chapters/chapter-10.txt` 全部存在
20
+ 3. 文件 `README.md` 存在并包含故事简介
21
+ 4. 每个章节文件内容不为空,至少包含该章节的标题和内容
22
+ 5. 所有文件都可以正常读取
23
+
24
+ ## 第2轮:验证结果
25
+
26
+ ### 验证命令
27
+ ```bash
28
+ # 检查目录结构
29
+ ls -la
30
+
31
+ # 检查 chapters 目录
32
+ ls -la chapters/
33
+
34
+ # 检查 README.md
35
+ cat README.md
36
+
37
+ # 检查第一章内容
38
+ head -20 chapters/chapter-01.txt
39
+
40
+ # 统计文件数量
41
+ find chapters -name "*.txt" | wc -l
42
+ ```
43
+
44
+ ### 预期结果
45
+ - ✅ chapters 目录存在
46
+ - ✅ 10个章节文件全部存在
47
+ - ✅ README.md 存在且内容完整
48
+ - ✅ 章节文件包含实际故事内容
49
+
50
+ ## 测试目的
51
+ 验证 writeFile 工具能够自动创建父目录,无需手动使用 mkdir 命令。
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ASCII 茶杯绘制命令行程序
4
+ * 支持多种风格和交互式选择
5
+ */
6
+
7
+ const readline = require('readline');
8
+
9
+ // 茶杯样式库
10
+ const cupStyles = {
11
+ classic: {
12
+ name: '经典茶杯',
13
+ art: `
14
+ .::::::::::.
15
+ .:::::::::::::.
16
+ :::::::::::::::::
17
+ ::::::::::::::::::
18
+ ::::::::::::::::::
19
+ :::::::::::::::::
20
+ :::::::::::::::
21
+ ':::::::::'
22
+ ':'
23
+ `
24
+ },
25
+
26
+ elegant: {
27
+ name: '优雅茶杯',
28
+ art: `
29
+ _____
30
+ / \\
31
+ | |
32
+ | |
33
+ \\_____/
34
+ \\___/
35
+ | |
36
+ __|_|__
37
+ / \\
38
+ |_________|
39
+ `
40
+ },
41
+
42
+ steaming: {
43
+ name: '热气腾腾',
44
+ art: `
45
+ ) (
46
+ ( )
47
+ [_____]
48
+ \\___/
49
+ `
50
+ },
51
+
52
+ english: {
53
+ name: '英式下午茶',
54
+ art: `
55
+ )
56
+ ( )
57
+ [___]
58
+ \\_/
59
+ `
60
+ },
61
+
62
+ coffee: {
63
+ name: '咖啡杯',
64
+ art: `
65
+ ______
66
+ | |
67
+ | ~~ |
68
+ |______|
69
+ \\ /
70
+ \\ /
71
+ \\/
72
+ `
73
+ },
74
+
75
+ teapot: {
76
+ name: '茶壶',
77
+ art: `
78
+ ___.-----._
79
+ /.___.-----. \\
80
+ | _ _ |
81
+ | (o) (o) |
82
+ | < |
83
+ \\ __ /
84
+ '._( )_.'
85
+ '-'
86
+ `
87
+ },
88
+
89
+ heart: {
90
+ name: '爱心茶杯',
91
+ art: `
92
+ ______
93
+ | ~~ |
94
+ | <3 |
95
+ |______|
96
+ \\ /
97
+ \\ /
98
+ \\/
99
+ `
100
+ }
101
+ };
102
+
103
+ // 显示菜单
104
+ function showMenu() {
105
+ console.log('\n╔════════════════════════════════╗');
106
+ console.log('║ 🍵 ASCII 茶杯绘制程序 ║');
107
+ console.log('╚════════════════════════════════╝\n');
108
+
109
+ const styles = Object.entries(cupStyles);
110
+ styles.forEach(([key, value], index) => {
111
+ console.log(` ${index + 1}. ${value.name}`);
112
+ });
113
+
114
+ console.log(` ${styles.length + 1}. 显示所有样式`);
115
+ console.log(` 0. 退出\n`);
116
+ }
117
+
118
+ // 绘制选定的茶杯
119
+ function drawCup(styleKey) {
120
+ const cup = cupStyles[styleKey];
121
+ if (cup) {
122
+ console.log(`\n 【${cup.name}】\n`);
123
+ console.log(cup.art);
124
+ console.log('');
125
+ }
126
+ }
127
+
128
+ // 显示所有茶杯
129
+ function showAllCups() {
130
+ console.log('\n╔════════════════════════════════╗');
131
+ console.log('║ 🍵 茶杯样式展示 ║');
132
+ console.log('╚════════════════════════════════╝\n');
133
+
134
+ Object.entries(cupStyles).forEach(([key, cup]) => {
135
+ console.log(` ┌─ ${cup.name} ─────────────────┐`);
136
+ console.log(cup.art);
137
+ console.log(' └────────────────────────────┘\n');
138
+ });
139
+ }
140
+
141
+ // 主交互循环
142
+ async function main() {
143
+ const rl = readline.createInterface({
144
+ input: process.stdin,
145
+ output: process.stdout
146
+ });
147
+
148
+ const styles = Object.keys(cupStyles);
149
+
150
+ while (true) {
151
+ showMenu();
152
+
153
+ const choice = await new Promise(resolve => {
154
+ rl.question(' 请选择茶杯样式 (输入数字): ', resolve);
155
+ });
156
+
157
+ const num = parseInt(choice.trim());
158
+
159
+ if (num === 0) {
160
+ console.log('\n 👋 再见!感谢使用!\n');
161
+ rl.close();
162
+ break;
163
+ } else if (num === styles.length + 1) {
164
+ showAllCups();
165
+ } else if (num >= 1 && num <= styles.length) {
166
+ drawCup(styles[num - 1]);
167
+ } else {
168
+ console.log('\n ❌ 无效选择,请重新输入\n');
169
+ }
170
+
171
+ // 等待用户按回车继续
172
+ if (num !== 0) {
173
+ await new Promise(resolve => {
174
+ rl.question(' 按回车键继续...', resolve);
175
+ });
176
+ }
177
+ }
178
+ }
179
+
180
+ // 直接运行模式(命令行参数)
181
+ function directRun(style) {
182
+ if (style === 'all') {
183
+ showAllCups();
184
+ } else if (cupStyles[style]) {
185
+ drawCup(style);
186
+ } else {
187
+ console.log('\n ❌ 未知的茶杯样式\n');
188
+ console.log(' 可用样式:', Object.keys(cupStyles).join(', '));
189
+ console.log(' 使用 "node ascii-teacup.js all" 查看所有样式\n');
190
+ }
191
+ }
192
+
193
+ // 程序入口
194
+ if (process.argv.length > 2) {
195
+ const style = process.argv[2];
196
+ directRun(style);
197
+ } else {
198
+ main();
199
+ }