6aspec 2.0.0-dev.10

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 (110) hide show
  1. package/.6aspec/rules/biz/api_rule.md +578 -0
  2. package/.6aspec/rules/biz/background_job_rule.md +719 -0
  3. package/.6aspec/rules/biz/c_user_system_rule.md +240 -0
  4. package/.6aspec/rules/biz/code.md +39 -0
  5. package/.6aspec/rules/biz/event_subscriber_rule.md +529 -0
  6. package/.6aspec/rules/biz/project-structure.md +90 -0
  7. package/.6aspec/rules/biz/scheduled_job_rule.md +850 -0
  8. package/.6aspec/rules/brown/brown_archive_sop.md +132 -0
  9. package/.6aspec/rules/brown/brown_constitution.md +20 -0
  10. package/.6aspec/rules/brown/brown_continue_sop.md +97 -0
  11. package/.6aspec/rules/brown/brown_design_sop.md +155 -0
  12. package/.6aspec/rules/brown/brown_ff_sop.md +194 -0
  13. package/.6aspec/rules/brown/brown_impact_sop.md +293 -0
  14. package/.6aspec/rules/brown/brown_implement_sop.md +133 -0
  15. package/.6aspec/rules/brown/brown_list_sop.md +69 -0
  16. package/.6aspec/rules/brown/brown_new_sop.md +257 -0
  17. package/.6aspec/rules/brown/brown_proposal_sop.md +160 -0
  18. package/.6aspec/rules/brown/brown_quick_sop.md +134 -0
  19. package/.6aspec/rules/brown/brown_review_sop.md +270 -0
  20. package/.6aspec/rules/brown/brown_rollback_sop.md +188 -0
  21. package/.6aspec/rules/brown/brown_specs_sop.md +228 -0
  22. package/.6aspec/rules/brown/brown_status_sop.md +135 -0
  23. package/.6aspec/rules/brown/brown_tasks_sop.md +202 -0
  24. package/.6aspec/rules/brown/brown_understand_sop.md +208 -0
  25. package/.6aspec/rules/brown/brown_verify_sop.md +360 -0
  26. package/.6aspec/rules/green/6A_archive_sop.md +301 -0
  27. package/.6aspec/rules/green/6A_clarify_sop.md +238 -0
  28. package/.6aspec/rules/green/6A_code_implementation_sop.md +110 -0
  29. package/.6aspec/rules/green/6A_constitution.md +52 -0
  30. package/.6aspec/rules/green/6A_continue_sop.md +186 -0
  31. package/.6aspec/rules/green/6A_design_sop.md +228 -0
  32. package/.6aspec/rules/green/6A_import_model_table_sop.md +120 -0
  33. package/.6aspec/rules/green/6A_init_event_list_sop.md +62 -0
  34. package/.6aspec/rules/green/6A_init_map_sop.md +79 -0
  35. package/.6aspec/rules/green/6A_model_sop.md +210 -0
  36. package/.6aspec/rules/green/6A_new_sop.md +319 -0
  37. package/.6aspec/rules/green/6A_rollback_sop.md +198 -0
  38. package/.6aspec/rules/green/6A_status_sop.md +275 -0
  39. package/.6aspec/rules/green/6A_tasks_sop.md +181 -0
  40. package/.6aspec/rules/green/6A_visual_logic_sop.md +121 -0
  41. package/.6aspec/rules/green/green_status_schema.md +293 -0
  42. package/.6aspec/script/create_entities_from_markdown.py +688 -0
  43. package/.claude/commands/6aspec/brown/archive.md +11 -0
  44. package/.claude/commands/6aspec/brown/continue.md +11 -0
  45. package/.claude/commands/6aspec/brown/design.md +11 -0
  46. package/.claude/commands/6aspec/brown/ff.md +11 -0
  47. package/.claude/commands/6aspec/brown/impact.md +11 -0
  48. package/.claude/commands/6aspec/brown/implement.md +11 -0
  49. package/.claude/commands/6aspec/brown/list.md +11 -0
  50. package/.claude/commands/6aspec/brown/new.md +11 -0
  51. package/.claude/commands/6aspec/brown/proposal.md +11 -0
  52. package/.claude/commands/6aspec/brown/quick.md +11 -0
  53. package/.claude/commands/6aspec/brown/review.md +11 -0
  54. package/.claude/commands/6aspec/brown/rollback.md +11 -0
  55. package/.claude/commands/6aspec/brown/specs.md +11 -0
  56. package/.claude/commands/6aspec/brown/status.md +11 -0
  57. package/.claude/commands/6aspec/brown/tasks.md +11 -0
  58. package/.claude/commands/6aspec/brown/understand.md +11 -0
  59. package/.claude/commands/6aspec/brown/verify.md +11 -0
  60. package/.claude/commands/6aspec/green/archive.md +8 -0
  61. package/.claude/commands/6aspec/green/clarify.md +13 -0
  62. package/.claude/commands/6aspec/green/continue.md +8 -0
  63. package/.claude/commands/6aspec/green/design.md +8 -0
  64. package/.claude/commands/6aspec/green/execute-task.md +20 -0
  65. package/.claude/commands/6aspec/green/import-model-table.md +8 -0
  66. package/.claude/commands/6aspec/green/init.md +14 -0
  67. package/.claude/commands/6aspec/green/model.md +12 -0
  68. package/.claude/commands/6aspec/green/new.md +13 -0
  69. package/.claude/commands/6aspec/green/rollback.md +8 -0
  70. package/.claude/commands/6aspec/green/status.md +8 -0
  71. package/.claude/commands/6aspec/green/tasks.md +8 -0
  72. package/.claude/commands/6aspec/green/visual-logic.md +9 -0
  73. package/.claude/settings.local.json +8 -0
  74. package/.cursor/commands/6aspec/brown/archive.md +9 -0
  75. package/.cursor/commands/6aspec/brown/continue.md +9 -0
  76. package/.cursor/commands/6aspec/brown/design.md +9 -0
  77. package/.cursor/commands/6aspec/brown/ff.md +9 -0
  78. package/.cursor/commands/6aspec/brown/impact.md +9 -0
  79. package/.cursor/commands/6aspec/brown/implement.md +9 -0
  80. package/.cursor/commands/6aspec/brown/list.md +9 -0
  81. package/.cursor/commands/6aspec/brown/new.md +9 -0
  82. package/.cursor/commands/6aspec/brown/proposal.md +9 -0
  83. package/.cursor/commands/6aspec/brown/quick.md +9 -0
  84. package/.cursor/commands/6aspec/brown/review.md +9 -0
  85. package/.cursor/commands/6aspec/brown/rollback.md +9 -0
  86. package/.cursor/commands/6aspec/brown/specs.md +9 -0
  87. package/.cursor/commands/6aspec/brown/status.md +9 -0
  88. package/.cursor/commands/6aspec/brown/tasks.md +9 -0
  89. package/.cursor/commands/6aspec/brown/understand.md +9 -0
  90. package/.cursor/commands/6aspec/brown/verify.md +9 -0
  91. package/.cursor/commands/6aspec/green/archive.md +9 -0
  92. package/.cursor/commands/6aspec/green/clarify.md +14 -0
  93. package/.cursor/commands/6aspec/green/continue.md +9 -0
  94. package/.cursor/commands/6aspec/green/design.md +9 -0
  95. package/.cursor/commands/6aspec/green/execute-task.md +21 -0
  96. package/.cursor/commands/6aspec/green/import-model-table.md +9 -0
  97. package/.cursor/commands/6aspec/green/init.md +15 -0
  98. package/.cursor/commands/6aspec/green/model.md +13 -0
  99. package/.cursor/commands/6aspec/green/new.md +14 -0
  100. package/.cursor/commands/6aspec/green/rollback.md +9 -0
  101. package/.cursor/commands/6aspec/green/status.md +9 -0
  102. package/.cursor/commands/6aspec/green/tasks.md +9 -0
  103. package/.cursor/commands/6aspec/green/visual-logic.md +10 -0
  104. package/README.en.md +36 -0
  105. package/README.md +146 -0
  106. package/bin/6a-spec-install +54 -0
  107. package/bin/6aspec +64 -0
  108. package/lib/cli.js +451 -0
  109. package/lib/installer.js +333 -0
  110. package/package.json +62 -0
package/lib/cli.js ADDED
@@ -0,0 +1,451 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const readline = require('readline');
4
+ const installer = require('./installer');
5
+
6
+ const CYAN = '\x1b[36m';
7
+ const GREEN = '\x1b[32m';
8
+ const YELLOW = '\x1b[33m';
9
+ const DIM = '\x1b[2m';
10
+ const BOLD = '\x1b[1m';
11
+ const RESET = '\x1b[0m';
12
+ const WHITE = '\x1b[37m';
13
+ const MAGENTA = '\x1b[35m';
14
+
15
+ const CONFIG_FILE = '.6aspec/init.json';
16
+
17
+ const TOOLS = [
18
+ { id: 'cursor', name: 'Cursor', desc: 'Cursor IDE 配置' },
19
+ { id: 'claude', name: 'Claude Code', desc: 'Claude Code CLI 配置' },
20
+ ];
21
+
22
+ // Only the 6aspec-owned subdirectory is removed — user's own commands are untouched
23
+ const TOOL_DIRS = {
24
+ cursor: ['.cursor/commands/6aspec'],
25
+ claude: ['.claude/commands/6aspec'],
26
+ };
27
+
28
+ // ─── Welcome Screen with Animation ──────────────────────────────────
29
+
30
+ // Braille spinner frames
31
+ const SPINNER_CHARS = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
32
+
33
+ // 4-phase ornament pulse: dim → mid → bright → mid
34
+ const ORN_FRAMES = [
35
+ { ch: '░░', c: DIM + CYAN },
36
+ { ch: '▒▒', c: CYAN },
37
+ { ch: '██', c: BOLD + CYAN },
38
+ { ch: '▒▒', c: CYAN },
39
+ ];
40
+
41
+ // Logo: 18 lines tall to match right-side content height
42
+ // Visible width of each line = 20 chars (" ████ ")
43
+ function buildLogoLines(ornIdx, spinIdx) {
44
+ const { ch, c } = ORN_FRAMES[ornIdx % ORN_FRAMES.length];
45
+ const sp = SPINNER_CHARS[spinIdx % SPINNER_CHARS.length];
46
+ const B = BOLD + CYAN;
47
+ const R = RESET;
48
+ const _ = ' '; // 20-char blank line
49
+
50
+ return [
51
+ _,
52
+ ` ${B}████${R} `,
53
+ ` ${B}████${R} `,
54
+ ` ${c}${ch}${R} ${c}${ch}${R} `,
55
+ ` ${c}${ch}${R} ${c}${ch}${R} `,
56
+ ` ${B}████${R} `,
57
+ ` ${B}████${R} `,
58
+ ` ${B}████${R} `,
59
+ ` ${B}████${R} `,
60
+ ` ${c}${ch}${R} ${c}${ch}${R} `,
61
+ ` ${c}${ch}${R} ${c}${ch}${R} `,
62
+ ` ${B}████${R} `,
63
+ ` ${B}████${R} `,
64
+ _,
65
+ ` ${CYAN}${sp}${R} `,
66
+ _,
67
+ _,
68
+ _,
69
+ ];
70
+ }
71
+
72
+ function buildRightLines() {
73
+ return [
74
+ `${BOLD}${WHITE}Welcome to 6Aspec${RESET}`,
75
+ `${DIM}A lightweight spec-driven framework${RESET}`,
76
+ ``,
77
+ `${YELLOW}This setup will configure:${RESET}`,
78
+ ` ${GREEN}•${RESET} Agent Skills for AI tools`,
79
+ ` ${GREEN}•${RESET} /6aspec:* slash commands`,
80
+ ``,
81
+ `${YELLOW}Quick start after setup:${RESET}`,
82
+ ``,
83
+ ` ${MAGENTA}Brownfield (existing project):${RESET}`,
84
+ ` ${GREEN}/6aspec:brown:new${RESET} Create a change`,
85
+ ` ${GREEN}/6aspec:brown:continue${RESET} Next artifact`,
86
+ ` ${GREEN}/6aspec:brown:implement${RESET} Implement tasks`,
87
+ ``,
88
+ ` ${MAGENTA}Greenfield (new project):${RESET}`,
89
+ ` ${GREEN}/6aspec:green:new${RESET} Create a module`,
90
+ ` ${GREEN}/6aspec:green:continue${RESET} Next artifact`,
91
+ ` ${GREEN}/6aspec:green:execute-task${RESET} Execute tasks`,
92
+ ];
93
+ }
94
+
95
+ function buildScreenLines(logoLines, rightLines) {
96
+ const lines = [];
97
+ lines.push('');
98
+ const len = Math.max(logoLines.length, rightLines.length);
99
+ for (let i = 0; i < len; i++) {
100
+ const l = i < logoLines.length ? logoLines[i] : ' ';
101
+ const r = i < rightLines.length ? rightLines[i] : '';
102
+ lines.push(` ${l} ${r}`);
103
+ }
104
+ lines.push('');
105
+ lines.push(` ${' '} ${DIM}Press Enter to select tools...${RESET}`);
106
+ lines.push('');
107
+ return lines;
108
+ }
109
+
110
+ // ─── Animated welcome: renders in-place until Enter is pressed ───────
111
+
112
+ function waitForEnterAnimated() {
113
+ return new Promise((resolve) => {
114
+ const rightLines = buildRightLines();
115
+
116
+ let ornIdx = 0;
117
+ let spinIdx = 0;
118
+ let screenLineCount = 0;
119
+ let firstRender = true;
120
+
121
+ function render() {
122
+ const logoLines = buildLogoLines(ornIdx, spinIdx);
123
+ const screenLines = buildScreenLines(logoLines, rightLines);
124
+ const output = screenLines.join('\n') + '\n';
125
+
126
+ if (firstRender) {
127
+ process.stdout.write(output);
128
+ screenLineCount = screenLines.length;
129
+ firstRender = false;
130
+ } else {
131
+ // Move cursor up to start of screen, overwrite in-place
132
+ process.stdout.write(`\x1b[${screenLineCount}A${output}`);
133
+ }
134
+ }
135
+
136
+ render();
137
+
138
+ const interval = setInterval(() => {
139
+ ornIdx = (ornIdx + 1) % ORN_FRAMES.length;
140
+ spinIdx = (spinIdx + 1) % SPINNER_CHARS.length;
141
+ render();
142
+ }, 150);
143
+
144
+ const isRaw = process.stdin.isTTY;
145
+ if (isRaw) process.stdin.setRawMode(true);
146
+ process.stdin.resume();
147
+ process.stdin.setEncoding('utf8');
148
+
149
+ function onKey(key) {
150
+ if (key === '\r' || key === '\n' || key === ' ') {
151
+ clearInterval(interval);
152
+ process.stdin.removeListener('data', onKey);
153
+ if (isRaw) process.stdin.setRawMode(false);
154
+ process.stdin.pause();
155
+ // Clear welcome screen
156
+ process.stdout.write(`\x1b[${screenLineCount}A\x1b[J`);
157
+ resolve();
158
+ } else if (key === '\u0003') {
159
+ clearInterval(interval);
160
+ process.exit(0);
161
+ }
162
+ }
163
+
164
+ process.stdin.on('data', onKey);
165
+ });
166
+ }
167
+
168
+ function selectTools(preSelected) {
169
+ return new Promise((resolve) => {
170
+ const selected = TOOLS.map((t) => preSelected.includes(t.id));
171
+ let cursor = 0;
172
+
173
+ const raw = process.stdin.isTTY;
174
+ if (raw) {
175
+ process.stdin.setRawMode(true);
176
+ }
177
+ process.stdin.resume();
178
+ process.stdin.setEncoding('utf8');
179
+
180
+ function render() {
181
+ // Move cursor up to re-render (clear previous lines)
182
+ const totalLines = TOOLS.length + 4;
183
+ process.stdout.write(`\x1b[${totalLines}A\x1b[J`);
184
+ draw();
185
+ }
186
+
187
+ function draw() {
188
+ console.log('');
189
+ console.log(`${BOLD} Select tools to initialize:${RESET} ${DIM}(↑↓ move, Enter select, Tab confirm)${RESET}`);
190
+ console.log('');
191
+ TOOLS.forEach((tool, i) => {
192
+ const check = selected[i] ? `${GREEN}◉${RESET}` : `○`;
193
+ const pointer = i === cursor ? `${CYAN}❯${RESET}` : ' ';
194
+ let hint = '';
195
+ if (preSelected.includes(tool.id)) {
196
+ hint = selected[i]
197
+ ? ` ${DIM}(installed)${RESET}`
198
+ : ` ${YELLOW}(will remove)${RESET}`;
199
+ }
200
+ console.log(` ${pointer} ${check} ${tool.name} ${DIM}- ${tool.desc}${RESET}${hint}`);
201
+ });
202
+ console.log('');
203
+ }
204
+
205
+ draw();
206
+
207
+ function onKey(key) {
208
+ if (key === '\u001b[A') {
209
+ // Up
210
+ cursor = (cursor - 1 + TOOLS.length) % TOOLS.length;
211
+ render();
212
+ } else if (key === '\u001b[B') {
213
+ // Down
214
+ cursor = (cursor + 1) % TOOLS.length;
215
+ render();
216
+ } else if (key === '\r' || key === '\n') {
217
+ // Enter = toggle select
218
+ selected[cursor] = !selected[cursor];
219
+ render();
220
+ } else if (key === '\t') {
221
+ // Tab = confirm
222
+ cleanup();
223
+ const result = TOOLS.filter((_, i) => selected[i]).map((t) => t.id);
224
+ resolve(result);
225
+ } else if (key === '\u0003') {
226
+ // Ctrl+C
227
+ cleanup();
228
+ process.exit(0);
229
+ }
230
+ }
231
+
232
+ function cleanup() {
233
+ process.stdin.removeListener('data', onKey);
234
+ if (raw) {
235
+ process.stdin.setRawMode(false);
236
+ }
237
+ process.stdin.pause();
238
+ }
239
+
240
+ process.stdin.on('data', onKey);
241
+ });
242
+ }
243
+
244
+ // ─── Config Management ──────────────────────────────────────────────
245
+
246
+ function readConfig(targetDir) {
247
+ const configPath = path.join(targetDir, CONFIG_FILE);
248
+ if (fs.existsSync(configPath)) {
249
+ try {
250
+ return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
251
+ } catch {
252
+ return null;
253
+ }
254
+ }
255
+ return null;
256
+ }
257
+
258
+ function writeConfig(targetDir, tools) {
259
+ const configPath = path.join(targetDir, CONFIG_FILE);
260
+ const dir = path.dirname(configPath);
261
+ if (!fs.existsSync(dir)) {
262
+ fs.mkdirSync(dir, { recursive: true });
263
+ }
264
+ const config = {
265
+ version: require('../package.json').version,
266
+ tools,
267
+ updatedAt: new Date().toISOString(),
268
+ };
269
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
270
+ return config;
271
+ }
272
+
273
+ // ─── Tool removal ───────────────────────────────────────────────────
274
+
275
+ function removeDirRecursive(dirPath) {
276
+ if (!fs.existsSync(dirPath)) return;
277
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
278
+ for (const entry of entries) {
279
+ const full = path.join(dirPath, entry.name);
280
+ if (entry.isDirectory()) removeDirRecursive(full);
281
+ else fs.unlinkSync(full);
282
+ }
283
+ fs.rmdirSync(dirPath);
284
+ }
285
+
286
+ function removeToolFiles(targetDir, tools) {
287
+ for (const tool of tools) {
288
+ const dirs = TOOL_DIRS[tool] || [];
289
+ for (const rel of dirs) {
290
+ const full = path.join(targetDir, rel);
291
+ if (fs.existsSync(full)) {
292
+ removeDirRecursive(full);
293
+ console.log(`${GREEN}[INFO]${RESET} 已删除 ${rel}`);
294
+ }
295
+ }
296
+ }
297
+ }
298
+
299
+ // Simple y/N prompt (readline, no raw mode needed)
300
+ function promptConfirm(question) {
301
+ return new Promise((resolve) => {
302
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
303
+ rl.question(`${question} ${DIM}[y/N]${RESET} `, (answer) => {
304
+ rl.close();
305
+ resolve(answer.trim().toLowerCase() === 'y');
306
+ });
307
+ });
308
+ }
309
+
310
+ // ─── Parse non-interactive --tool flag ──────────────────────────────
311
+
312
+ function parseToolFlag(args) {
313
+ for (let i = 0; i < args.length; i++) {
314
+ if ((args[i] === '--tool' || args[i] === '-t') && args[i + 1]) {
315
+ return args[i + 1].split(',').map((s) => s.trim().toLowerCase());
316
+ }
317
+ }
318
+ return null;
319
+ }
320
+
321
+ // ─── Commands ───────────────────────────────────────────────────────
322
+
323
+ async function init(args) {
324
+ const targetDir = process.cwd();
325
+ const config = readConfig(targetDir);
326
+ const preSelected = config ? config.tools : [];
327
+
328
+ const toolFlag = parseToolFlag(args);
329
+
330
+ let selectedTools;
331
+
332
+ if (toolFlag) {
333
+ // Non-interactive mode
334
+ const valid = toolFlag.filter((t) => TOOLS.some((tt) => tt.id === t));
335
+ if (valid.length === 0) {
336
+ throw new Error(`无效的工具: ${toolFlag.join(', ')}。支持: ${TOOLS.map((t) => t.id).join(', ')}`);
337
+ }
338
+ selectedTools = [...new Set([...preSelected, ...valid])];
339
+ console.log(`\n${GREEN}[INFO]${RESET} 非交互模式:初始化 ${selectedTools.join(', ')}`);
340
+ } else {
341
+ // Interactive mode
342
+ await waitForEnterAnimated();
343
+ selectedTools = await selectTools(preSelected);
344
+
345
+ if (selectedTools.length === 0) {
346
+ console.log(`\n${YELLOW}[WARN]${RESET} 未选择任何工具,退出。`);
347
+ return;
348
+ }
349
+ }
350
+
351
+ console.log('');
352
+
353
+ // Determine what's new vs existing
354
+ const newTools = selectedTools.filter((t) => !preSelected.includes(t));
355
+ const existingTools = selectedTools.filter((t) => preSelected.includes(t));
356
+
357
+ if (existingTools.length > 0) {
358
+ console.log(`${DIM} Already installed: ${existingTools.join(', ')} (will merge update)${RESET}`);
359
+ }
360
+ if (newTools.length > 0) {
361
+ console.log(`${GREEN} New install: ${newTools.join(', ')}${RESET}`);
362
+ }
363
+ console.log('');
364
+
365
+ // Always install shared .6aspec config (merge for existing, copy for new)
366
+ const isFirstTime = !config;
367
+ installer.targetDir = targetDir;
368
+ installer.mergeMode = !isFirstTime;
369
+ installer.install6aspec();
370
+
371
+ // Install each selected tool
372
+ for (const tool of selectedTools) {
373
+ const isNew = newTools.includes(tool);
374
+ installer.mergeMode = !isNew;
375
+
376
+ switch (tool) {
377
+ case 'cursor':
378
+ installer.installCursor();
379
+ break;
380
+ case 'claude':
381
+ installer.installClaude();
382
+ break;
383
+ }
384
+ }
385
+
386
+ // Handle deselected tools (were installed, now unchecked)
387
+ const removedTools = preSelected.filter((t) => !selectedTools.includes(t));
388
+ if (removedTools.length > 0) {
389
+ console.log(`\n${YELLOW}[WARN]${RESET} 以下工具已取消选择: ${removedTools.join(', ')}`);
390
+ for (const tool of removedTools) {
391
+ const dirs = TOOL_DIRS[tool] || [];
392
+ dirs.forEach((d) => console.log(` ${DIM}${d}${RESET}`));
393
+ }
394
+ const confirm = await promptConfirm(' 是否同时删除这些文件?');
395
+ if (confirm) {
396
+ removeToolFiles(targetDir, removedTools);
397
+ } else {
398
+ console.log(`${DIM} 已跳过文件删除,文件保留在磁盘上。${RESET}`);
399
+ }
400
+ }
401
+
402
+ // Save config
403
+ const savedConfig = writeConfig(targetDir, selectedTools);
404
+ console.log(`\n${GREEN}[SUCCESS]${RESET} 初始化完成!配置已保存到 ${CONFIG_FILE}`);
405
+ console.log(`${DIM} 工具: ${selectedTools.length > 0 ? selectedTools.join(', ') : '(none)'}${RESET}`);
406
+ console.log(`${DIM} 版本: ${savedConfig.version}${RESET}\n`);
407
+ }
408
+
409
+ async function update(args) {
410
+ const targetDir = process.cwd();
411
+ const config = readConfig(targetDir);
412
+
413
+ if (!config || !config.tools || config.tools.length === 0) {
414
+ throw new Error(
415
+ '未找到初始化配置。请先运行 6aspec init 进行初始化。'
416
+ );
417
+ }
418
+
419
+ const tools = config.tools;
420
+ const pkgVersion = require('../package.json').version;
421
+
422
+ console.log(`\n${CYAN}${BOLD}6Aspec Update${RESET}`);
423
+ console.log(`${DIM} 当前配置版本: ${config.version || 'unknown'}${RESET}`);
424
+ console.log(`${DIM} 包版本: ${pkgVersion}${RESET}`);
425
+ console.log(`${DIM} 已安装工具: ${tools.join(', ')}${RESET}`);
426
+ console.log('');
427
+
428
+ // Always merge mode for update
429
+ installer.targetDir = targetDir;
430
+ installer.mergeMode = true;
431
+
432
+ installer.install6aspec();
433
+
434
+ for (const tool of tools) {
435
+ switch (tool) {
436
+ case 'cursor':
437
+ installer.installCursor();
438
+ break;
439
+ case 'claude':
440
+ installer.installClaude();
441
+ break;
442
+ }
443
+ }
444
+
445
+ // Update config version
446
+ const savedConfig = writeConfig(targetDir, tools);
447
+ console.log(`\n${GREEN}[SUCCESS]${RESET} 更新完成!`);
448
+ console.log(`${DIM} 版本: ${savedConfig.version}${RESET}\n`);
449
+ }
450
+
451
+ module.exports = { init, update };