yg-team-cli 2.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/dist/index.js ADDED
@@ -0,0 +1,4329 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command as Command13 } from "commander";
5
+ import chalk2 from "chalk";
6
+
7
+ // src/lib/logger.ts
8
+ import chalk from "chalk";
9
+ import ora from "ora";
10
+ var Logger = class {
11
+ spinner = null;
12
+ /**
13
+ * 打印标题(加粗)
14
+ */
15
+ header(text) {
16
+ console.log(chalk.bold(text));
17
+ }
18
+ /**
19
+ * 打印成功信息(绿色)
20
+ */
21
+ success(text) {
22
+ console.log(chalk.green("\u2713"), text);
23
+ }
24
+ /**
25
+ * 打印错误信息(红色)
26
+ */
27
+ error(text) {
28
+ console.error(chalk.red("\u2717"), text);
29
+ }
30
+ /**
31
+ * 打印警告信息(黄色)
32
+ */
33
+ warn(text) {
34
+ console.warn(chalk.yellow("\u26A0"), text);
35
+ }
36
+ /**
37
+ * 打印信息(蓝色)
38
+ */
39
+ info(text) {
40
+ console.info(chalk.blue("\u2139"), text);
41
+ }
42
+ /**
43
+ * 打印步骤信息
44
+ */
45
+ step(text) {
46
+ console.log(chalk.cyan("\u2192"), text);
47
+ }
48
+ /**
49
+ * 打印空行
50
+ */
51
+ newLine() {
52
+ console.log("");
53
+ }
54
+ /**
55
+ * 打印分隔线
56
+ */
57
+ separator(char = "\u2500", length = 50) {
58
+ console.log(chalk.gray(char.repeat(length)));
59
+ }
60
+ /**
61
+ * 开始加载动画
62
+ */
63
+ startLoading(text) {
64
+ this.spinner = ora({
65
+ text,
66
+ color: "cyan"
67
+ }).start();
68
+ return this.spinner;
69
+ }
70
+ /**
71
+ * 更新加载文本
72
+ */
73
+ updateLoading(text) {
74
+ if (this.spinner) {
75
+ this.spinner.text = text;
76
+ }
77
+ }
78
+ /**
79
+ * 停止加载并显示成功
80
+ */
81
+ succeedLoading(text) {
82
+ if (this.spinner) {
83
+ this.spinner.succeed(text);
84
+ this.spinner = null;
85
+ }
86
+ }
87
+ /**
88
+ * 停止加载并显示失败
89
+ */
90
+ failLoading(text) {
91
+ if (this.spinner) {
92
+ this.spinner.fail(text);
93
+ this.spinner = null;
94
+ }
95
+ }
96
+ /**
97
+ * 停止加载并显示警告
98
+ */
99
+ warnLoading(text) {
100
+ if (this.spinner) {
101
+ this.spinner.warn(text);
102
+ this.spinner = null;
103
+ }
104
+ }
105
+ /**
106
+ * 停止加载并显示信息
107
+ */
108
+ infoLoading(text) {
109
+ if (this.spinner) {
110
+ this.spinner.info(text);
111
+ this.spinner = null;
112
+ }
113
+ }
114
+ /**
115
+ * 打印表格
116
+ */
117
+ table(headers, rows) {
118
+ const columnWidths = headers.map((h, i) => {
119
+ const maxWidth = Math.max(
120
+ h.length,
121
+ ...rows.map((row) => (row[i] || "").length)
122
+ );
123
+ return maxWidth + 2;
124
+ });
125
+ const headerRow = headers.map((h, i) => chalk.bold(h.padEnd(columnWidths[i]))).join("");
126
+ console.log(headerRow);
127
+ const separator = columnWidths.map((w) => "\u2500".repeat(w)).join("\u253C");
128
+ console.log(chalk.gray(separator));
129
+ rows.forEach((row) => {
130
+ const dataRow = row.map((cell, i) => (cell || "").padEnd(columnWidths[i])).join("");
131
+ console.log(dataRow);
132
+ });
133
+ }
134
+ /**
135
+ * 打印代码块
136
+ */
137
+ code(code, language = "") {
138
+ if (language) {
139
+ console.log(chalk.gray(`\`\`\`${language}`));
140
+ }
141
+ console.log(chalk.gray(code));
142
+ if (language) {
143
+ console.log(chalk.gray("```"));
144
+ }
145
+ }
146
+ /**
147
+ * 打印列表
148
+ */
149
+ list(items, indent = 2) {
150
+ items.forEach((item) => {
151
+ console.log(" ".repeat(indent) + chalk.gray("\u2022") + " " + item);
152
+ });
153
+ }
154
+ /**
155
+ * 打印带标签的文本
156
+ */
157
+ labeled(label, text, labelColor = "blue") {
158
+ const coloredLabel = chalk[labelColor](`[${label}]`);
159
+ console.log(`${coloredLabel} ${text}`);
160
+ }
161
+ };
162
+ var logger = new Logger();
163
+
164
+ // src/commands/init.ts
165
+ import { Command } from "commander";
166
+ import inquirer from "inquirer";
167
+ import path2 from "path";
168
+ import fs2 from "fs-extra";
169
+
170
+ // src/lib/claude.ts
171
+ import { execa } from "execa";
172
+ var ClaudeAI = class {
173
+ verbose;
174
+ constructor(verbose = false) {
175
+ this.verbose = verbose;
176
+ }
177
+ /**
178
+ * 检查 Claude CLI 是否已安装
179
+ */
180
+ async checkInstalled() {
181
+ try {
182
+ await execa("claude", ["--version"], { stdio: "pipe" });
183
+ return true;
184
+ } catch {
185
+ return false;
186
+ }
187
+ }
188
+ /**
189
+ * 获取 Claude 版本
190
+ */
191
+ async getVersion() {
192
+ try {
193
+ const { stdout } = await execa("claude", ["--version"], { stdio: "pipe" });
194
+ return stdout.trim();
195
+ } catch {
196
+ throw new Error("\u65E0\u6CD5\u83B7\u53D6 Claude \u7248\u672C");
197
+ }
198
+ }
199
+ /**
200
+ * 发送 prompt 到 Claude
201
+ */
202
+ async prompt(promptText, options) {
203
+ const spinner = logger.startLoading("\u6B63\u5728\u8C03\u7528 Claude...");
204
+ try {
205
+ const args = ["--no-confirm", "-p", promptText];
206
+ if (options?.contextFiles && options.contextFiles.length > 0) {
207
+ for (const file of options.contextFiles) {
208
+ args.unshift("--context", file);
209
+ }
210
+ }
211
+ const { stdout } = await execa("claude", args, {
212
+ stdio: this.verbose ? "inherit" : "pipe",
213
+ timeout: options?.timeout || 3e5
214
+ // 默认 5 分钟
215
+ });
216
+ spinner.succeed("Claude \u54CD\u5E94\u5B8C\u6210");
217
+ return stdout;
218
+ } catch (error) {
219
+ spinner.fail("Claude \u8C03\u7528\u5931\u8D25");
220
+ if (error.killed && error.signal === "SIGTERM") {
221
+ throw new Error("Claude \u6267\u884C\u8D85\u65F6");
222
+ }
223
+ throw new Error(`Claude \u8C03\u7528\u5931\u8D25: ${error.message}`);
224
+ }
225
+ }
226
+ /**
227
+ * 使用 prompt 模板和参数发送请求
228
+ */
229
+ async promptWithTemplate(template, data, options) {
230
+ let promptText = template;
231
+ for (const [key, value] of Object.entries(data)) {
232
+ const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
233
+ promptText = promptText.replace(regex, String(value));
234
+ }
235
+ return await this.prompt(promptText, options);
236
+ }
237
+ /**
238
+ * 发送对话(支持上下文)
239
+ */
240
+ async chat(messages, options) {
241
+ const spinner = logger.startLoading("\u6B63\u5728\u8C03\u7528 Claude...");
242
+ try {
243
+ const fullPrompt = messages.map((msg) => {
244
+ const role = msg.role === "system" ? "\u7CFB\u7EDF\u6307\u4EE4" : `${msg.role === "user" ? "\u7528\u6237" : "\u52A9\u624B"}`;
245
+ return `[${role}]: ${msg.content}`;
246
+ }).join("\n\n");
247
+ const { stdout } = await execa("claude", ["--no-confirm", "-p", fullPrompt], {
248
+ stdio: this.verbose ? "inherit" : "pipe",
249
+ timeout: options?.timeout || 3e5
250
+ });
251
+ spinner.succeed("Claude \u54CD\u5E94\u5B8C\u6210");
252
+ return stdout;
253
+ } catch (error) {
254
+ spinner.fail("Claude \u8C03\u7528\u5931\u8D25");
255
+ throw error;
256
+ }
257
+ }
258
+ /**
259
+ * 分析代码
260
+ */
261
+ async analyzeCode(code, language, question) {
262
+ const prompt = `\u8BF7\u5206\u6790\u4EE5\u4E0B ${language} \u4EE3\u7801\uFF1A${question ? `\u5E76\u56DE\u7B54\uFF1A${question}` : ""}
263
+
264
+ \`\`\`${language}
265
+ ${code}
266
+ \`\`\`
267
+
268
+ \u8BF7\u63D0\u4F9B\uFF1A
269
+ 1. \u4EE3\u7801\u529F\u80FD\u6982\u8FF0
270
+ 2. \u6F5C\u5728\u95EE\u9898
271
+ 3. \u6539\u8FDB\u5EFA\u8BAE
272
+ `;
273
+ return await this.prompt(prompt);
274
+ }
275
+ /**
276
+ * 生成代码
277
+ */
278
+ async generateCode(description, language, context) {
279
+ let prompt = `\u8BF7\u7528 ${language} \u7F16\u5199\u4EE3\u7801\u5B9E\u73B0\u4EE5\u4E0B\u529F\u80FD\uFF1A
280
+
281
+ ${description}
282
+
283
+ `;
284
+ if (context) {
285
+ prompt += `\u53C2\u8003\u4E0A\u4E0B\u6587\uFF1A
286
+
287
+ ${context}
288
+
289
+ `;
290
+ }
291
+ prompt += `\u8981\u6C42\uFF1A
292
+ 1. \u4EE3\u7801\u7B80\u6D01\u6613\u8BFB
293
+ 2. \u5305\u542B\u5FC5\u8981\u7684\u6CE8\u91CA
294
+ 3. \u9075\u5FAA\u6700\u4F73\u5B9E\u8DF5
295
+ 4. \u53EA\u8FD4\u56DE\u4EE3\u7801\uFF0C\u4E0D\u8981\u989D\u5916\u89E3\u91CA
296
+ `;
297
+ return await this.prompt(prompt);
298
+ }
299
+ /**
300
+ * 审查代码
301
+ */
302
+ async reviewCode(code, language) {
303
+ const prompt = `\u8BF7\u5BA1\u67E5\u4EE5\u4E0B ${language} \u4EE3\u7801\uFF0C\u63D0\u4F9B\u8BE6\u7EC6\u53CD\u9988\uFF1A
304
+
305
+ \`\`\`${language}
306
+ ${code}
307
+ \`\`\`
308
+
309
+ \u8BF7\u6309\u4EE5\u4E0B\u683C\u5F0F\u56DE\u590D\uFF1A
310
+ ## \u53D1\u73B0\u7684\u95EE\u9898
311
+ - [\u4E25\u91CD\u7A0B\u5EA6] \u95EE\u9898\u63CF\u8FF0
312
+
313
+ ## \u6539\u8FDB\u5EFA\u8BAE
314
+ - \u5EFA\u8BAE\u5185\u5BB9
315
+
316
+ ## \u4EAE\u70B9
317
+ - \u505A\u5F97\u597D\u7684\u5730\u65B9
318
+
319
+ \u8BF7\u7528 JSON \u683C\u5F0F\u56DE\u590D\uFF1A
320
+ {
321
+ "issues": [{"severity": "high|medium|low", "message": "\u95EE\u9898\u63CF\u8FF0"}],
322
+ "suggestions": ["\u5EFA\u8BAE1", "\u5EFA\u8BAE2"],
323
+ "highlights": ["\u4EAE\u70B91", "\u4EAE\u70B92"]
324
+ }
325
+ `;
326
+ const response = await this.prompt(prompt);
327
+ try {
328
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
329
+ if (jsonMatch) {
330
+ return JSON.parse(jsonMatch[0]);
331
+ }
332
+ } catch {
333
+ }
334
+ return {
335
+ issues: [],
336
+ suggestions: [response],
337
+ highlights: []
338
+ };
339
+ }
340
+ /**
341
+ * 生成 Spec 文档
342
+ */
343
+ async generateSpec(featureName, description, projectContext, dependencies) {
344
+ let prompt = `Role: Senior Fullstack Developer
345
+
346
+ \u4F60\u662F\u4E00\u4E2A\u8D44\u6DF1\u7684\u5168\u6808\u5DE5\u7A0B\u5E08\uFF0C\u73B0\u5728\u9700\u8981\u4E3A\u4E00\u4E2A\u65B0\u529F\u80FD\u751F\u6210\u89C4\u683C\u6587\u6863\uFF08Spec\uFF09\u3002
347
+
348
+ ## \u529F\u80FD\u540D\u79F0
349
+ ${featureName}
350
+
351
+ ## \u529F\u80FD\u63CF\u8FF0
352
+ ${description}
353
+ `;
354
+ if (dependencies && dependencies.length > 0) {
355
+ prompt += `
356
+ ## \u4F9D\u8D56\u529F\u80FD
357
+ \u6B64\u529F\u80FD\u4F9D\u8D56\u4EE5\u4E0B\u5DF2\u5B8C\u6210\u7684\u529F\u80FD\uFF1A${dependencies.join("\u3001")}
358
+ `;
359
+ }
360
+ prompt += `
361
+ ## \u9879\u76EE\u6280\u672F\u6808
362
+ \u540E\u7AEF: Java 17 + Spring Boot 3 + MyBatis Plus + MySQL 8.0
363
+ \u524D\u7AEF: Next.js 14 (App Router) + TypeScript + Tailwind CSS
364
+
365
+ ## \u9879\u76EE\u5F53\u524D\u72B6\u6001
366
+ ${projectContext}
367
+
368
+ ## \u4EFB\u52A1
369
+ \u8BF7\u6839\u636E\u529F\u80FD\u63CF\u8FF0\uFF0C\u751F\u6210\u4E00\u4E2A\u5B8C\u6574\u7684\u529F\u80FD\u89C4\u683C\u6587\u6863 Spec\u3002
370
+
371
+ ## Spec \u6587\u6863\u683C\u5F0F\u8981\u6C42
372
+ \`\`\`markdown
373
+ # [\u529F\u80FD\u6807\u9898]
374
+
375
+ ## \u529F\u80FD\u6982\u8FF0
376
+ **\u529F\u80FD\u540D\u79F0**: [\u529F\u80FD\u4E2D\u6587\u540D]
377
+ **\u4F18\u5148\u7EA7**: P0/P1/P2 (\u6839\u636E\u529F\u80FD\u91CD\u8981\u6027\u5224\u65AD)
378
+ **\u9884\u4F30\u5DE5\u65F6**: X \u5929 (\u6839\u636E\u590D\u6742\u5EA6\u8BC4\u4F30\uFF1A\u7B80\u53551-2\u5929\uFF0C\u4E2D\u7B493-5\u5929\uFF0C\u590D\u67425-10\u5929)
379
+ **\u72B6\u6001**: \u5F85\u62C6\u5206
380
+ **\u521B\u5EFA\u65E5\u671F**: {{DATE}}
381
+
382
+ ## \u4F9D\u8D56\u5173\u7CFB
383
+ **\u524D\u7F6E\u4F9D\u8D56**:
384
+ ${dependencies ? dependencies.map((d) => `- [x] ${d}`).join("\n") : "- (\u65E0)"}
385
+
386
+ **\u88AB\u4F9D\u8D56\u4E8E**:
387
+ - (\u81EA\u52A8\u751F\u6210\uFF0C\u8868\u793A\u54EA\u4E9B spec \u4F9D\u8D56\u672C\u529F\u80FD)
388
+
389
+ ## \u80CC\u666F\u4E0E\u76EE\u6807
390
+ [\u6839\u636E\u529F\u80FD\u63CF\u8FF0\uFF0C\u7B80\u660E\u627C\u8981\u5730\u8BF4\u660E\u529F\u80FD\u7684\u80CC\u666F\u548C\u8981\u89E3\u51B3\u7684\u95EE\u9898]
391
+
392
+ ## \u529F\u80FD\u9700\u6C42
393
+ ### \u7528\u6237\u6545\u4E8B
394
+ \`\`\`
395
+ \u4F5C\u4E3A [\u5177\u4F53\u89D2\u8272]
396
+ \u6211\u5E0C\u671B [\u5177\u4F53\u529F\u80FD]
397
+ \u4EE5\u4FBF [\u5B9E\u73B0\u7684\u4EF7\u503C]
398
+ \`\`\`
399
+
400
+ ### \u529F\u80FD\u70B9
401
+ \u5217\u51FA 3-8 \u4E2A\u4E3B\u8981\u529F\u80FD\u70B9\uFF0C\u6BCF\u4E2A\u529F\u80FD\u70B9\u7528\u4E00\u53E5\u8BDD\u63CF\u8FF0\u3002
402
+
403
+ ## \u6280\u672F\u8BBE\u8BA1
404
+ ### API \u8BBE\u8BA1
405
+ \u5217\u51FA\u4E3B\u8981\u7684 API \u7AEF\u70B9\uFF0C\u683C\u5F0F\uFF1A
406
+ - \`METHOD /api/path\` - \u7B80\u77ED\u8BF4\u660E
407
+
408
+ ### \u6570\u636E\u6A21\u578B
409
+ \u5217\u51FA\u9700\u8981\u7684\u6570\u636E\u8868\uFF0C\u683C\u5F0F\uFF1A
410
+ - \`table_name\` (\u8868\u8BF4\u660E) - \u5B57\u6BB5\u8BF4\u660E
411
+
412
+ ## \u91CC\u7A0B\u7891 (Milestones)
413
+ > \u6CE8: \u4F7F\u7528 \`team-cli breakdown ${StringUtils.toKebabCase(featureName)}.md\` \u62C6\u5206\u6B64 spec \u4E3A milestones \u548C todos
414
+
415
+ ----
416
+ *\u751F\u6210\u4E8E: {{TIMESTAMP}} by Claude*
417
+ \`\`\`
418
+
419
+ ## \u6CE8\u610F\u4E8B\u9879
420
+ 1. \u53EA\u751F\u6210 Spec \u6587\u6863\uFF0C\u4E0D\u8981\u5B9E\u73B0\u4EFB\u4F55\u4EE3\u7801
421
+ 2. \u4F18\u5148\u7EA7\u5224\u65AD\u6807\u51C6\uFF1A
422
+ - P0: \u6838\u5FC3\u529F\u80FD\uFF0C\u963B\u585E\u5176\u4ED6\u529F\u80FD
423
+ - P1: \u91CD\u8981\u529F\u80FD\uFF0C\u5F71\u54CD\u7528\u6237\u4F53\u9A8C
424
+ - P2: \u589E\u5F3A\u529F\u80FD\uFF0C\u9526\u4E0A\u6DFB\u82B1
425
+ 3. \u5DE5\u65F6\u8BC4\u4F30\u6807\u51C6\uFF1A
426
+ - \u7B80\u5355\u529F\u80FD: 1-2 \u5929
427
+ - \u4E2D\u7B49\u529F\u80FD: 3-5 \u5929
428
+ - \u590D\u6742\u529F\u80FD: 5-10 \u5929
429
+ 4. \u529F\u80FD\u70B9\u8981\u5177\u4F53\u53EF\u6267\u884C\uFF0C\u907F\u514D\u8FC7\u4E8E\u62BD\u8C61
430
+ 5. API \u548C\u6570\u636E\u6A21\u578B\u8981\u7B26\u5408\u5B9E\u9645\u6280\u672F\u6808
431
+ `;
432
+ return await this.prompt(prompt, { timeout: 18e4 });
433
+ }
434
+ /**
435
+ * 生成 Bugfix 报告
436
+ */
437
+ async generateBugfixReport(bugDescription, reproductionSteps, projectContext) {
438
+ const prompt = `Role: Senior Fullstack Developer
439
+
440
+ \u7528\u6237\u62A5\u544A\u4E86\u4E00\u4E2A Bug\uFF0C\u9700\u8981\u4F60\u751F\u6210 Bugfix \u89C4\u683C\u6587\u6863\u3002
441
+
442
+ ## Bug \u63CF\u8FF0
443
+ ${bugDescription}
444
+
445
+ ## \u590D\u73B0\u6B65\u9AA4
446
+ ${reproductionSteps.map((step, i) => `${i + 1}. ${step}`).join("\n")}
447
+
448
+ ## \u9879\u76EE\u5F53\u524D\u72B6\u6001
449
+ ${projectContext}
450
+
451
+ ## \u4EFB\u52A1
452
+ \u8BF7\u751F\u6210\u4E00\u4E2A\u5B8C\u6574\u7684 Bugfix \u62A5\u544A\uFF0C\u5305\u62EC\uFF1A
453
+ 1. \u95EE\u9898\u5206\u6790
454
+ 2. \u6839\u672C\u539F\u56E0\u63A8\u6D4B
455
+ 3. \u4FEE\u590D\u5EFA\u8BAE
456
+ 4. \u6D4B\u8BD5\u8BA1\u5212
457
+ `;
458
+ return await this.prompt(prompt, { timeout: 12e4 });
459
+ }
460
+ };
461
+ var claudeAI = new ClaudeAI();
462
+
463
+ // src/lib/utils.ts
464
+ import fs from "fs-extra";
465
+ import path from "path";
466
+ import { glob } from "glob";
467
+ var FileUtils = class {
468
+ /**
469
+ * 确保目录存在
470
+ */
471
+ static async ensureDir(dir) {
472
+ await fs.ensureDir(dir);
473
+ }
474
+ /**
475
+ * 读取文件内容
476
+ */
477
+ static async read(file) {
478
+ return await fs.readFile(file, "utf-8");
479
+ }
480
+ /**
481
+ * 写入文件内容
482
+ */
483
+ static async write(file, content) {
484
+ await fs.writeFile(file, content, "utf-8");
485
+ }
486
+ /**
487
+ * 检查文件是否存在
488
+ */
489
+ static async exists(file) {
490
+ return await fs.pathExists(file);
491
+ }
492
+ /**
493
+ * 复制文件
494
+ */
495
+ static async copy(src, dest) {
496
+ await fs.copy(src, dest);
497
+ }
498
+ /**
499
+ * 删除文件或目录
500
+ */
501
+ static async remove(file) {
502
+ await fs.remove(file);
503
+ }
504
+ /**
505
+ * 移动文件
506
+ */
507
+ static async move(src, dest) {
508
+ await fs.move(src, dest);
509
+ }
510
+ /**
511
+ * 使用 glob 查找文件
512
+ */
513
+ static async findFiles(pattern, cwd) {
514
+ return await glob(pattern, {
515
+ cwd,
516
+ absolute: false,
517
+ nodir: true
518
+ });
519
+ }
520
+ /**
521
+ * 读取 JSON 文件
522
+ */
523
+ static async readJson(file) {
524
+ return await fs.readJson(file);
525
+ }
526
+ /**
527
+ * 写入 JSON 文件
528
+ */
529
+ static async writeJson(file, data) {
530
+ await fs.writeJson(file, data, { spaces: 2 });
531
+ }
532
+ };
533
+ var StringUtils2 = class {
534
+ /**
535
+ * 转换为 kebab-case
536
+ */
537
+ static toKebabCase(str) {
538
+ return str.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^\w\-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
539
+ }
540
+ /**
541
+ * 转换为 PascalCase
542
+ */
543
+ static toPascalCase(str) {
544
+ return str.replace(/[-_\s](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
545
+ }
546
+ /**
547
+ * 转换为 camelCase
548
+ */
549
+ static toCamelCase(str) {
550
+ const pascal = this.toPascalCase(str);
551
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
552
+ }
553
+ /**
554
+ * 转换为 snake_case
555
+ */
556
+ static toSnakeCase(str) {
557
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).replace(/^_/, "").replace(/-+/g, "_").replace(/[\s]+/g, "_");
558
+ }
559
+ /**
560
+ * 首字母大写
561
+ */
562
+ static capitalize(str) {
563
+ return str.charAt(0).toUpperCase() + str.slice(1);
564
+ }
565
+ /**
566
+ * 截断字符串
567
+ */
568
+ static truncate(str, length, suffix = "...") {
569
+ if (str.length <= length) return str;
570
+ return str.slice(0, length - suffix.length) + suffix;
571
+ }
572
+ /**
573
+ * 生成 slug
574
+ */
575
+ static slugify(str) {
576
+ return str.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
577
+ }
578
+ };
579
+ var DateUtils = class _DateUtils {
580
+ /**
581
+ * 格式化日期
582
+ */
583
+ static format(date = /* @__PURE__ */ new Date(), format = "YYYY-MM-DD HH:mm:ss") {
584
+ const year = date.getFullYear();
585
+ const month = String(date.getMonth() + 1).padStart(2, "0");
586
+ const day = String(date.getDate()).padStart(2, "0");
587
+ const hours = String(date.getHours()).padStart(2, "0");
588
+ const minutes = String(date.getMinutes()).padStart(2, "0");
589
+ const seconds = String(date.getSeconds()).padStart(2, "0");
590
+ return format.replace("YYYY", String(year)).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes).replace("ss", seconds);
591
+ }
592
+ /**
593
+ * 获取相对时间
594
+ */
595
+ static relative(date) {
596
+ const now = /* @__PURE__ */ new Date();
597
+ const diff = now.getTime() - date.getTime();
598
+ const seconds = Math.floor(diff / 1e3);
599
+ const minutes = Math.floor(seconds / 60);
600
+ const hours = Math.floor(minutes / 60);
601
+ const days = Math.floor(hours / 24);
602
+ if (days > 7) {
603
+ return _DateUtils.format(date, "YYYY-MM-DD");
604
+ } else if (days > 0) {
605
+ return `${days} \u5929\u524D`;
606
+ } else if (hours > 0) {
607
+ return `${hours} \u5C0F\u65F6\u524D`;
608
+ } else if (minutes > 0) {
609
+ return `${minutes} \u5206\u949F\u524D`;
610
+ } else {
611
+ return "\u521A\u521A";
612
+ }
613
+ }
614
+ };
615
+ var GitUtils = class {
616
+ /**
617
+ * 检查是否在 Git 仓库中
618
+ */
619
+ static async isGitRepo(cwd = process.cwd()) {
620
+ const gitDir = path.join(cwd, ".git");
621
+ return await FileUtils.exists(gitDir);
622
+ }
623
+ /**
624
+ * 获取当前分支名
625
+ */
626
+ static async getCurrentBranch(cwd = process.cwd()) {
627
+ const { execa: execa3 } = await import("execa");
628
+ const { stdout } = await execa3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
629
+ cwd
630
+ });
631
+ return stdout.trim();
632
+ }
633
+ /**
634
+ * 获取当前 commit hash
635
+ */
636
+ static async getCurrentCommit(cwd = process.cwd()) {
637
+ const { execa: execa3 } = await import("execa");
638
+ const { stdout } = await execa3("git", ["rev-parse", "HEAD"], { cwd });
639
+ return stdout.trim().slice(0, 7);
640
+ }
641
+ };
642
+ var SpecUtils = class {
643
+ /**
644
+ * 解析 spec 文件
645
+ */
646
+ static async parseSpec(file) {
647
+ const content = await FileUtils.read(file);
648
+ const spec = {};
649
+ const titleMatch = content.match(/^#\s+(.+)$/m);
650
+ if (titleMatch) {
651
+ spec.title = titleMatch[1];
652
+ }
653
+ const nameMatch = content.match(/\*\*功能名称\*\*:\s*(.+)$/m);
654
+ if (nameMatch) {
655
+ spec.name = nameMatch[1];
656
+ }
657
+ const priorityMatch = content.match(/\*\*优先级\*\*:\s*(P[0-2])/);
658
+ if (priorityMatch) {
659
+ spec.priority = priorityMatch[1];
660
+ }
661
+ const statusMatch = content.match(/\*\*状态\*\*:\s*(.+)$/m);
662
+ if (statusMatch) {
663
+ spec.status = statusMatch[1];
664
+ }
665
+ const depSection = content.match(/## 依赖关系\s+([\s\S]+?)##/m);
666
+ if (depSection) {
667
+ const depMatches = depSection[1].matchAll(/- \[([ x])\]\s*(.+)$/gm);
668
+ spec.dependencies = Array.from(depMatches).map((m) => ({
669
+ completed: m[1] === "x",
670
+ name: m[2]
671
+ }));
672
+ }
673
+ return spec;
674
+ }
675
+ /**
676
+ * 获取 spec 状态
677
+ */
678
+ static async getSpecStatus(file) {
679
+ try {
680
+ const spec = await this.parseSpec(file);
681
+ return spec.status || "\u672A\u77E5";
682
+ } catch {
683
+ return "\u9519\u8BEF";
684
+ }
685
+ }
686
+ };
687
+
688
+ // src/commands/init.ts
689
+ import { Listr } from "listr2";
690
+ var initCommand = new Command("init").argument("[project-name]", "\u9879\u76EE\u540D\u79F0").description("\u521D\u59CB\u5316\u65B0\u9879\u76EE").option("-d, --dir <directory>", "\u9879\u76EE\u76EE\u5F55", ".").option("--no-docker", "\u4E0D\u751F\u6210 Docker \u914D\u7F6E").option("--no-git", "\u4E0D\u521D\u59CB\u5316 Git").action(async (projectName, options) => {
691
+ try {
692
+ if (!projectName) {
693
+ const answers = await inquirer.prompt([
694
+ {
695
+ type: "input",
696
+ name: "projectName",
697
+ message: "\u8BF7\u8F93\u5165\u9879\u76EE\u540D\u79F0:",
698
+ default: "my-project",
699
+ validate: (input) => {
700
+ if (!/^[a-z0-9-]+$/.test(input)) {
701
+ return "\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26";
702
+ }
703
+ return true;
704
+ }
705
+ }
706
+ ]);
707
+ projectName = answers.projectName;
708
+ }
709
+ if (!StringUtils2.validateProjectName(projectName)) {
710
+ logger.error("\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26");
711
+ process.exit(1);
712
+ }
713
+ logger.header("\u521D\u59CB\u5316\u9879\u76EE");
714
+ logger.newLine();
715
+ const hasClaude = await claudeAI.checkInstalled();
716
+ if (!hasClaude) {
717
+ logger.error("\u672A\u68C0\u6D4B\u5230 Claude CLI");
718
+ logger.info("\u8BF7\u5B89\u88C5 Claude CLI: npm install -g @anthropic-ai/claude-code");
719
+ process.exit(1);
720
+ }
721
+ const projectPath = path2.resolve(options.dir, projectName);
722
+ if (await FileUtils.exists(projectPath)) {
723
+ logger.error(`\u76EE\u5F55\u5DF2\u5B58\u5728: ${projectPath}`);
724
+ logger.info("\u8BF7\u9009\u62E9\u5176\u4ED6\u9879\u76EE\u540D\u79F0\u6216\u5220\u9664\u73B0\u6709\u76EE\u5F55");
725
+ process.exit(1);
726
+ }
727
+ const tasks = new Listr(
728
+ [
729
+ {
730
+ title: "\u68C0\u67E5\u73AF\u5883",
731
+ task: async () => {
732
+ const version = await claudeAI.getVersion();
733
+ logger.info(`Claude \u7248\u672C: ${version}`);
734
+ }
735
+ },
736
+ {
737
+ title: "\u521B\u5EFA\u9879\u76EE\u76EE\u5F55",
738
+ task: async () => {
739
+ await FileUtils.ensureDir(projectPath);
740
+ await FileUtils.ensureDir(path2.join(projectPath, "frontend"));
741
+ await FileUtils.ensureDir(path2.join(projectPath, "backend"));
742
+ await FileUtils.ensureDir(path2.join(projectPath, "docs/specs"));
743
+ await FileUtils.ensureDir(path2.join(projectPath, "docs/api"));
744
+ await FileUtils.ensureDir(path2.join(projectPath, "docs/sessions"));
745
+ }
746
+ },
747
+ {
748
+ title: "\u751F\u6210\u6280\u672F\u6808\u6587\u6863",
749
+ task: async () => {
750
+ await generateTechStack(projectPath);
751
+ }
752
+ },
753
+ {
754
+ title: "\u751F\u6210\u5F00\u53D1\u89C4\u8303\u6587\u6863",
755
+ task: async () => {
756
+ await generateConventions(projectPath);
757
+ }
758
+ },
759
+ {
760
+ title: "\u751F\u6210 AI Memory",
761
+ task: async () => {
762
+ await generateAIMemory(projectPath, projectName);
763
+ }
764
+ },
765
+ {
766
+ title: "\u751F\u6210 Spec \u6A21\u677F",
767
+ task: async () => {
768
+ await generateSpecTemplate(projectPath);
769
+ }
770
+ },
771
+ {
772
+ title: "\u514B\u9686\u540E\u7AEF\u6A21\u677F",
773
+ task: async () => {
774
+ await cloneBackendTemplate(projectPath);
775
+ }
776
+ },
777
+ {
778
+ title: "\u751F\u6210\u524D\u7AEF\u811A\u624B\u67B6",
779
+ task: async () => {
780
+ await generateFrontendScaffold(projectPath);
781
+ }
782
+ },
783
+ ...options.docker ? [
784
+ {
785
+ title: "\u751F\u6210 Docker \u914D\u7F6E",
786
+ task: async () => {
787
+ await generateDockerFiles(projectPath);
788
+ }
789
+ }
790
+ ] : [],
791
+ ...options.git ? [
792
+ {
793
+ title: "\u521D\u59CB\u5316 Git",
794
+ task: async () => {
795
+ await initGit(projectPath, projectName);
796
+ }
797
+ }
798
+ ] : []
799
+ ],
800
+ {
801
+ concurrent: false,
802
+ exitOnError: true
803
+ }
804
+ );
805
+ await tasks.run();
806
+ logger.newLine();
807
+ logger.success(`\u9879\u76EE ${projectName} \u521D\u59CB\u5316\u5B8C\u6210\uFF01`);
808
+ logger.newLine();
809
+ logger.info("\u4E0B\u4E00\u6B65:");
810
+ logger.step(`cd ${projectName}`);
811
+ logger.step("team-cli add-feature <feature-name>");
812
+ logger.step("team-cli breakdown docs/specs/xxx.md");
813
+ logger.step("team-cli dev");
814
+ logger.newLine();
815
+ } catch (error) {
816
+ logger.error(`\u521D\u59CB\u5316\u5931\u8D25: ${error.message}`);
817
+ if (process.env.DEBUG) {
818
+ console.error(error);
819
+ }
820
+ process.exit(1);
821
+ }
822
+ });
823
+ async function generateTechStack(projectPath) {
824
+ const content = `# \u6280\u672F\u6808
825
+
826
+ ## \u540E\u7AEF\u6280\u672F\u6808
827
+
828
+ | \u7EC4\u4EF6 | \u6280\u672F\u9009\u578B | \u7248\u672C | \u8BF4\u660E |
829
+ |------|---------|------|------|
830
+ | \u8BED\u8A00 | Java | 17 | LTS \u7248\u672C |
831
+ | \u6846\u67B6 | Spring Boot | 3.2 | \u73B0\u4EE3\u5316 Java \u6846\u67B6 |
832
+ | \u6784\u5EFA\u5DE5\u5177 | Gradle | 8.x | \u5FEB\u901F\u3001\u7075\u6D3B\u7684\u6784\u5EFA\u5DE5\u5177 |
833
+ | ORM | MyBatis Plus | 3.5 | \u589E\u5F3A MyBatis\uFF0C\u7B80\u5316 CRUD |
834
+ | \u6570\u636E\u5E93 | MySQL | 8.0 | \u5173\u7CFB\u578B\u6570\u636E\u5E93 |
835
+ | \u7F13\u5B58 | Redis | 7.x | \u7F13\u5B58\u548C\u4F1A\u8BDD\u5B58\u50A8 |
836
+ | \u6587\u6863 | SpringDoc OpenAPI | 2.3 | API \u6587\u6863\u81EA\u52A8\u751F\u6210 |
837
+
838
+ ## \u524D\u7AEF\u6280\u672F\u6808
839
+
840
+ | \u7EC4\u4EF6 | \u6280\u672F\u9009\u578B | \u7248\u672C | \u8BF4\u660E |
841
+ |------|---------|------|------|
842
+ | \u6846\u67B6 | Next.js | 14 | React \u5168\u6808\u6846\u67B6 |
843
+ | \u8BED\u8A00 | TypeScript | 5.x | \u7C7B\u578B\u5B89\u5168 |
844
+ | \u6837\u5F0F | Tailwind CSS | 3.x | \u539F\u5B50\u5316 CSS |
845
+ | UI \u5E93 | Shadcn/UI | latest | \u57FA\u4E8E Radix UI \u7684\u7EC4\u4EF6\u5E93 |
846
+ | \u56FE\u6807 | Lucide React | latest | \u4E00\u81F4\u6027\u56FE\u6807\u5E93 |
847
+ | \u72B6\u6001\u7BA1\u7406 | React Context + Hooks | - | \u8F7B\u91CF\u7EA7\u72B6\u6001\u7BA1\u7406 |
848
+ | \u8868\u5355 | React Hook Form | 7.x | \u9AD8\u6027\u80FD\u8868\u5355 |
849
+ | \u6570\u636E\u9A8C\u8BC1 | Zod | 3.x | TypeScript \u4F18\u5148\u7684\u9A8C\u8BC1\u5E93 |
850
+
851
+ ## \u5F00\u53D1\u5DE5\u5177
852
+
853
+ | \u5DE5\u5177 | \u7528\u9014 |
854
+ |------|------|
855
+ | ESLint | \u4EE3\u7801\u68C0\u67E5 |
856
+ | Prettier | \u4EE3\u7801\u683C\u5F0F\u5316 |
857
+ | Husky | Git Hooks |
858
+ | Commitlint | \u63D0\u4EA4\u4FE1\u606F\u89C4\u8303 |
859
+ | Docker | \u5BB9\u5668\u5316\u90E8\u7F72 |
860
+
861
+ ## \u4EE3\u7801\u89C4\u8303
862
+
863
+ ### \u540E\u7AEF\u89C4\u8303
864
+
865
+ - \u4F7F\u7528 DTO \u6A21\u5F0F\u8FDB\u884C\u6570\u636E\u4F20\u8F93
866
+ - Controller \u8D1F\u8D23\u63A5\u6536\u8BF7\u6C42\uFF0CService \u5904\u7406\u4E1A\u52A1\u903B\u8F91
867
+ - \u4F7F\u7528 @Validated \u8FDB\u884C\u53C2\u6570\u6821\u9A8C
868
+ - \u7EDF\u4E00\u5F02\u5E38\u5904\u7406\u548C\u54CD\u5E94\u683C\u5F0F
869
+ - API \u8DEF\u5F84\u4F7F\u7528 kebab-case
870
+ - \u7C7B\u540D\u4F7F\u7528 PascalCase
871
+ - \u65B9\u6CD5\u540D\u4F7F\u7528 camelCase
872
+
873
+ ### \u524D\u7AEF\u89C4\u8303
874
+
875
+ - \u7EC4\u4EF6\u4F7F\u7528 PascalCase
876
+ - \u6587\u4EF6\u540D\u4F7F\u7528 kebab-case
877
+ - \u4F7F\u7528 TypeScript \u7C7B\u578B\u5B9A\u4E49
878
+ - Props \u63A5\u53E3\u5B9A\u4E49\u5728\u7EC4\u4EF6\u5185\u90E8
879
+ - \u4F7F\u7528 Semantic HTML
880
+ - \u54CD\u5E94\u5F0F\u8BBE\u8BA1\u4F18\u5148
881
+
882
+ ### Git \u89C4\u8303
883
+
884
+ - \u5206\u652F\u547D\u540D: \`feature/xxx\`, \`bugfix/xxx\`, \`hotfix/xxx\`
885
+ - \u63D0\u4EA4\u4FE1\u606F: \`feat: xxx\`, \`fix: xxx\`, \`docs: xxx\`
886
+ - \u63D0\u4EA4\u524D\u5FC5\u987B\u901A\u8FC7 lint \u68C0\u67E5
887
+
888
+ ## \u76EE\u5F55\u7ED3\u6784
889
+
890
+ \`\`\`
891
+ backend/
892
+ \u251C\u2500\u2500 src/main/java/com/example/demo/
893
+ \u2502 \u251C\u2500\u2500 controller/ # API \u63A7\u5236\u5668
894
+ \u2502 \u251C\u2500\u2500 service/ # \u4E1A\u52A1\u903B\u8F91
895
+ \u2502 \u251C\u2500\u2500 mapper/ # \u6570\u636E\u8BBF\u95EE
896
+ \u2502 \u251C\u2500\u2500 entity/ # \u6570\u636E\u6A21\u578B
897
+ \u2502 \u251C\u2500\u2500 dto/ # \u6570\u636E\u4F20\u8F93\u5BF9\u8C61
898
+ \u2502 \u251C\u2500\u2500 config/ # \u914D\u7F6E\u7C7B
899
+ \u2502 \u2514\u2500\u2500 util/ # \u5DE5\u5177\u7C7B
900
+ \u251C\u2500\u2500 src/main/resources/
901
+ \u2502 \u251C\u2500\u2500 mapper/ # MyBatis XML
902
+ \u2502 \u2514\u2500\u2500 application.yml # \u914D\u7F6E\u6587\u4EF6
903
+ \u2514\u2500\u2500 src/test/ # \u6D4B\u8BD5\u4EE3\u7801
904
+
905
+ frontend/
906
+ \u251C\u2500\u2500 src/
907
+ \u2502 \u251C\u2500\u2500 app/ # Next.js App Router
908
+ \u2502 \u251C\u2500\u2500 components/ # React \u7EC4\u4EF6
909
+ \u2502 \u251C\u2500\u2500 lib/ # \u5DE5\u5177\u5E93
910
+ \u2502 \u2514\u2500\u2500 types/ # TypeScript \u7C7B\u578B
911
+ \u2514\u2500\u2500 public/ # \u9759\u6001\u8D44\u6E90
912
+
913
+ docs/
914
+ \u251C\u2500\u2500 specs/ # \u529F\u80FD\u89C4\u683C\u6587\u6863
915
+ \u251C\u2500\u2500 api/ # API \u6587\u6863
916
+ \u2514\u2500\u2500 sessions/ # \u5F00\u53D1\u4F1A\u8BDD\u8BB0\u5F55
917
+ \`\`\`
918
+ `;
919
+ await FileUtils.write(path2.join(projectPath, "TECH_STACK.md"), content);
920
+ }
921
+ async function generateConventions(projectPath) {
922
+ const content = `# \u5F00\u53D1\u89C4\u8303
923
+
924
+ ## \u540E\u7AEF\u5F00\u53D1\u89C4\u8303
925
+
926
+ ### 1. \u5206\u5C42\u67B6\u6784
927
+
928
+ \`\`\`
929
+ Controller \u2192 Service \u2192 Mapper \u2192 Database
930
+ \u2193 \u2193
931
+ DTO Entity
932
+ \`\`\`
933
+
934
+ **\u804C\u8D23\u5212\u5206:**
935
+ - **Controller**: \u63A5\u6536 HTTP \u8BF7\u6C42\uFF0C\u53C2\u6570\u6821\u9A8C\uFF0C\u8C03\u7528 Service
936
+ - **Service**: \u4E1A\u52A1\u903B\u8F91\u5904\u7406\uFF0C\u4E8B\u52A1\u7BA1\u7406
937
+ - **Mapper**: \u6570\u636E\u5E93\u64CD\u4F5C\uFF0CSQL \u6267\u884C
938
+ - **DTO**: \u6570\u636E\u4F20\u8F93\u5BF9\u8C61\uFF0C\u7528\u4E8E API \u63A5\u53E3
939
+ - **Entity**: \u6570\u636E\u5E93\u5B9E\u4F53\u6620\u5C04
940
+
941
+ ### 2. \u547D\u540D\u89C4\u8303
942
+
943
+ | \u7C7B\u578B | \u89C4\u8303 | \u793A\u4F8B |
944
+ |------|------|------|
945
+ | \u7C7B\u540D | PascalCase | \`UserController\` |
946
+ | \u65B9\u6CD5\u540D | camelCase | \`getUserById\` |
947
+ | \u5E38\u91CF | UPPER_SNAKE_CASE | \`MAX_RETRY_COUNT\` |
948
+ | \u5305\u540D | \u5C0F\u5199\u70B9\u5206\u9694 | \`com.example.demo.controller\` |
949
+ | API \u8DEF\u5F84 | kebab-case | \`/api/users\` |
950
+
951
+ ### 3. API \u8BBE\u8BA1
952
+
953
+ **RESTful \u98CE\u683C:**
954
+
955
+ \`\`\`
956
+ GET /api/users # \u5217\u8868
957
+ GET /api/users/{id} # \u8BE6\u60C5
958
+ POST /api/users # \u521B\u5EFA
959
+ PUT /api/users/{id} # \u66F4\u65B0
960
+ DELETE /api/users/{id} # \u5220\u9664
961
+ \`\`\`
962
+
963
+ **\u7EDF\u4E00\u54CD\u5E94\u683C\u5F0F:**
964
+
965
+ \`\`\`json
966
+ {
967
+ "code": 200,
968
+ "message": "success",
969
+ "data": {...}
970
+ }
971
+ \`\`\`
972
+
973
+ ### 4. \u5F02\u5E38\u5904\u7406
974
+
975
+ \u4F7F\u7528 \`@ControllerAdvice\` \u7EDF\u4E00\u5904\u7406\u5F02\u5E38\uFF1A
976
+
977
+ \`\`\`java
978
+ @RestControllerAdvice
979
+ public class GlobalExceptionHandler {
980
+
981
+ @ExceptionHandler(BusinessException.class)
982
+ public Result<Void> handleBusinessException(BusinessException e) {
983
+ return Result.error(e.getMessage());
984
+ }
985
+ }
986
+ \`\`\`
987
+
988
+ ### 5. \u53C2\u6570\u6821\u9A8C
989
+
990
+ \u4F7F\u7528 \`@Validated\` \u548C JSR-303 \u6CE8\u89E3\uFF1A
991
+
992
+ \`\`\`java
993
+ @PostMapping("/users")
994
+ public Result<User> createUser(@Validated @RequestBody UserDTO dto) {
995
+ // ...
996
+ }
997
+ \`\`\`
998
+
999
+ ### 6. \u4E8B\u52A1\u7BA1\u7406
1000
+
1001
+ \u5728 Service \u5C42\u4F7F\u7528 \`@Transactional\`\uFF1A
1002
+
1003
+ \`\`\`java
1004
+ @Service
1005
+ public class UserService {
1006
+
1007
+ @Transactional(rollbackFor = Exception.class)
1008
+ public void createUserWithRole(User user, Role role) {
1009
+ // ...
1010
+ }
1011
+ }
1012
+ \`\`\`
1013
+
1014
+ ## \u524D\u7AEF\u5F00\u53D1\u89C4\u8303
1015
+
1016
+ ### 1. \u7EC4\u4EF6\u8BBE\u8BA1
1017
+
1018
+ **\u7EC4\u4EF6\u7ED3\u6784:**
1019
+
1020
+ \`\`\`typescript
1021
+ // \u7EC4\u4EF6\u5B9A\u4E49
1022
+ interface Props {
1023
+ // props \u7C7B\u578B\u5B9A\u4E49
1024
+ }
1025
+
1026
+ export function ComponentName({ prop1, prop2 }: Props) {
1027
+ // \u7EC4\u4EF6\u903B\u8F91
1028
+
1029
+ return (
1030
+ // JSX
1031
+ );
1032
+ }
1033
+ \`\`\`
1034
+
1035
+ **\u547D\u540D\u89C4\u8303:**
1036
+ - \u7EC4\u4EF6\u6587\u4EF6: kebab-case (\`user-card.tsx\`)
1037
+ - \u7EC4\u4EF6\u540D: PascalCase (\`UserCard\`)
1038
+ - Hook \u6587\u4EF6: kebab-case, \u4EE5 \`use-\` \u5F00\u5934 (\`use-user-data.ts\`)
1039
+
1040
+ ### 2. TypeScript \u4F7F\u7528
1041
+
1042
+ **\u5B9A\u4E49\u63A5\u53E3:**
1043
+
1044
+ \`\`\`typescript
1045
+ interface User {
1046
+ id: string;
1047
+ name: string;
1048
+ email: string;
1049
+ }
1050
+
1051
+ interface CreateUserDto {
1052
+ name: string;
1053
+ email: string;
1054
+ }
1055
+ \`\`\`
1056
+
1057
+ **\u907F\u514D\u4F7F\u7528 \`any\`:**
1058
+
1059
+ \`\`\`typescript
1060
+ // \u274C \u4E0D\u597D
1061
+ const data: any = await fetchData();
1062
+
1063
+ // \u2705 \u597D
1064
+ const data: User = await fetchData();
1065
+ \`\`\`
1066
+
1067
+ ### 3. \u72B6\u6001\u7BA1\u7406
1068
+
1069
+ **\u4F7F\u7528 React Context:**
1070
+
1071
+ \`\`\`typescript
1072
+ const UserContext = createContext<UserContextType>({
1073
+ user: null,
1074
+ login: async () => {},
1075
+ logout: () => {},
1076
+ });
1077
+ \`\`\`
1078
+
1079
+ **\u4F18\u5148\u4F7F\u7528\u672C\u5730\u72B6\u6001:**
1080
+
1081
+ \`\`\`typescript
1082
+ // \u7B80\u5355\u72B6\u6001\u4F7F\u7528 useState
1083
+ const [count, setCount] = useState(0);
1084
+
1085
+ // \u590D\u6742\u903B\u8F91\u4F7F\u7528 useReducer
1086
+ const [state, dispatch] = useReducer(reducer, initialState);
1087
+ \`\`\`
1088
+
1089
+ ### 4. \u6837\u5F0F\u89C4\u8303
1090
+
1091
+ **\u4F7F\u7528 Tailwind CSS:**
1092
+
1093
+ \`\`\`tsx
1094
+ <div className="flex items-center gap-4 p-4 bg-white rounded-lg">
1095
+ {/* ... */}
1096
+ </div>
1097
+ \`\`\`
1098
+
1099
+ **\u54CD\u5E94\u5F0F\u8BBE\u8BA1:**
1100
+
1101
+ \`\`\`tsx
1102
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
1103
+ {/* ... */}
1104
+ </div>
1105
+ \`\`\`
1106
+
1107
+ ### 5. \u8868\u5355\u5904\u7406
1108
+
1109
+ **\u4F7F\u7528 React Hook Form:**
1110
+
1111
+ \`\`\`typescript
1112
+ const { register, handleSubmit, formState: { errors } } = useForm<FormData>();
1113
+
1114
+ const onSubmit = (data: FormData) => {
1115
+ console.log(data);
1116
+ };
1117
+
1118
+ return (
1119
+ <form onSubmit={handleSubmit(onSubmit)}>
1120
+ <input {...register('name', { required: true })} />
1121
+ {errors.name && <span>\u5FC5\u586B</span>}
1122
+ </form>
1123
+ );
1124
+ \`\`\`
1125
+
1126
+ ### 6. \u6570\u636E\u83B7\u53D6
1127
+
1128
+ **\u4F7F\u7528 Server Components (Next.js 14):**
1129
+
1130
+ \`\`\`typescript
1131
+ async function getUser(id: string) {
1132
+ const res = await fetch(\`/api/users/\${id}\`);
1133
+ if (!res.ok) throw new Error('Failed to fetch');
1134
+ return res.json();
1135
+ }
1136
+
1137
+ export default async function UserPage({ params }) {
1138
+ const user = await getUser(params.id);
1139
+ return <div>{user.name}</div>;
1140
+ }
1141
+ \`\`\`
1142
+
1143
+ ## Git \u5DE5\u4F5C\u6D41
1144
+
1145
+ ### \u5206\u652F\u7B56\u7565
1146
+
1147
+ \`\`\`
1148
+ main # \u4E3B\u5206\u652F\uFF0C\u59CB\u7EC8\u4FDD\u6301\u7A33\u5B9A
1149
+ \u251C\u2500\u2500 feature/xxx # \u529F\u80FD\u5206\u652F
1150
+ \u251C\u2500\u2500 bugfix/xxx # Bug \u4FEE\u590D\u5206\u652F
1151
+ \u2514\u2500\u2500 hotfix/xxx # \u7D27\u6025\u4FEE\u590D\u5206\u652F
1152
+ \`\`\`
1153
+
1154
+ ### \u63D0\u4EA4\u4FE1\u606F\u89C4\u8303
1155
+
1156
+ \`\`\`
1157
+ <type>(<scope>): <subject>
1158
+
1159
+ <body>
1160
+
1161
+ <footer>
1162
+ \`\`\`
1163
+
1164
+ **\u7C7B\u578B (type):**
1165
+ - \`feat\`: \u65B0\u529F\u80FD
1166
+ - \`fix\`: Bug \u4FEE\u590D
1167
+ - \`docs\`: \u6587\u6863\u66F4\u65B0
1168
+ - \`style\`: \u4EE3\u7801\u683C\u5F0F\u8C03\u6574
1169
+ - \`refactor\`: \u4EE3\u7801\u91CD\u6784
1170
+ - \`test\`: \u6D4B\u8BD5\u76F8\u5173
1171
+ - \`chore\`: \u6784\u5EFA/\u5DE5\u5177\u76F8\u5173
1172
+
1173
+ **\u793A\u4F8B:**
1174
+
1175
+ \`\`\`
1176
+ feat(auth): add user login feature
1177
+
1178
+ implement JWT based authentication with:
1179
+ - login endpoint
1180
+ - token refresh
1181
+ - logout handling
1182
+
1183
+ Closes #123
1184
+ \`\`\`
1185
+
1186
+ ## \u6D4B\u8BD5\u89C4\u8303
1187
+
1188
+ ### \u540E\u7AEF\u6D4B\u8BD5
1189
+
1190
+ \`\`\`java
1191
+ @SpringBootTest
1192
+ class UserServiceTest {
1193
+
1194
+ @Autowired
1195
+ private UserService userService;
1196
+
1197
+ @Test
1198
+ void shouldCreateUser() {
1199
+ // given
1200
+ User user = new User("John");
1201
+
1202
+ // when
1203
+ User saved = userService.save(user);
1204
+
1205
+ // then
1206
+ assertNotNull(saved.getId());
1207
+ }
1208
+ }
1209
+ \`\`\`
1210
+
1211
+ ### \u524D\u7AEF\u6D4B\u8BD5
1212
+
1213
+ \`\`\`typescript
1214
+ import { render, screen } from '@testing-library/react';
1215
+
1216
+ test('renders user name', () => {
1217
+ render(<UserProfile name="John" />);
1218
+ expect(screen.getByText('John')).toBeInTheDocument();
1219
+ });
1220
+ \`\`\`
1221
+ `;
1222
+ await FileUtils.write(path2.join(projectPath, "CONVENTIONS.md"), content);
1223
+ }
1224
+ async function generateAIMemory(projectPath, projectName) {
1225
+ const content = `# AI Memory - \u9879\u76EE\u72B6\u6001\u8BB0\u5F55
1226
+
1227
+ ## \u9879\u76EE\u4FE1\u606F
1228
+ - **\u9879\u76EE\u540D\u79F0**: ${projectName}
1229
+ - **\u521B\u5EFA\u65F6\u95F4**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
1230
+ - **\u5F53\u524D\u9636\u6BB5**: \u521D\u59CB\u5316
1231
+ - **\u6700\u540E\u66F4\u65B0**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
1232
+
1233
+ ## \u529F\u80FD\u6E05\u5355 (Feature Inventory)
1234
+
1235
+ | \u529F\u80FD | Spec \u6587\u4EF6 | \u72B6\u6001 | \u8FDB\u5EA6 | \u5B8C\u6210\u65E5\u671F | \u5907\u6CE8 |
1236
+ |------|----------|------|------|---------|------|
1237
+
1238
+ ## API \u5217\u8868 (API Inventory)
1239
+
1240
+ > \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u626B\u63CF\u540E\u7AEF Controller \u751F\u6210
1241
+
1242
+ ## \u6570\u636E\u6A21\u578B\u6982\u89C8
1243
+
1244
+ | \u8868\u540D | \u8BF4\u660E | \u5B57\u6BB5\u6570 | \u5173\u8054\u8868 | \u72B6\u6001 |
1245
+ |------|------|--------|--------|------|
1246
+ | user | \u7528\u6237\u8868 | - | - | - |
1247
+
1248
+ ## \u5DF2\u5B8C\u6210\u7684 Milestones
1249
+
1250
+ ## \u5F53\u524D\u4EFB\u52A1
1251
+
1252
+ ## \u5F85\u5904\u7406\u4EFB\u52A1
1253
+
1254
+ ## \u6280\u672F\u503A\u52A1
1255
+
1256
+ | \u65E5\u671F | \u95EE\u9898 | \u8BA1\u5212\u4FEE\u590D |
1257
+ |------|------|---------|
1258
+
1259
+ ## \u5173\u952E\u51B3\u7B56\u8BB0\u5F55
1260
+
1261
+ | \u65E5\u671F | \u51B3\u7B56 | \u539F\u56E0 |
1262
+ |------|------|------|
1263
+ | ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} | \u521D\u59CB\u5316\u9879\u76EE | \u4F7F\u7528 Spring Boot 3 + Next.js 14 \u6280\u672F\u6808 |
1264
+
1265
+ ## \u6CE8\u610F\u4E8B\u9879
1266
+
1267
+ ## Bugfix \u8BB0\u5F55
1268
+
1269
+ | Bug ID | \u65E5\u671F | \u95EE\u9898\u63CF\u8FF0 | \u72B6\u6001 |
1270
+ |--------|------|---------|------|
1271
+ `;
1272
+ await FileUtils.write(path2.join(projectPath, "AI_MEMORY.md"), content);
1273
+ }
1274
+ async function generateSpecTemplate(projectPath) {
1275
+ const content = `# [\u529F\u80FD\u6807\u9898]
1276
+
1277
+ ## \u529F\u80FD\u6982\u8FF0
1278
+ **\u529F\u80FD\u540D\u79F0**: [\u529F\u80FD\u4E2D\u6587\u540D]
1279
+ **\u4F18\u5148\u7EA7**: P0/P1/P2
1280
+ **\u9884\u4F30\u5DE5\u65F6**: X \u5929
1281
+ **\u72B6\u6001**: \u5F85\u62C6\u5206
1282
+ **\u521B\u5EFA\u65E5\u671F**: {{DATE}}
1283
+
1284
+ ## \u4F9D\u8D56\u5173\u7CFB
1285
+ **\u524D\u7F6E\u4F9D\u8D56**:
1286
+ - [ ] dependency-1.md
1287
+ - [ ] dependency-2.md
1288
+
1289
+ **\u88AB\u4F9D\u8D56\u4E8E**:
1290
+ - (\u81EA\u52A8\u751F\u6210)
1291
+
1292
+ ## \u80CC\u666F\u4E0E\u76EE\u6807
1293
+ [\u7B80\u660E\u627C\u8981\u5730\u8BF4\u660E\u529F\u80FD\u7684\u80CC\u666F\u548C\u8981\u89E3\u51B3\u7684\u95EE\u9898]
1294
+
1295
+ ## \u529F\u80FD\u9700\u6C42
1296
+
1297
+ ### \u7528\u6237\u6545\u4E8B
1298
+ \`\`\`
1299
+ \u4F5C\u4E3A [\u5177\u4F53\u89D2\u8272]
1300
+ \u6211\u5E0C\u671B [\u5177\u4F53\u529F\u80FD]
1301
+ \u4EE5\u4FBF [\u5B9E\u73B0\u7684\u4EF7\u503C]
1302
+ \`\`\`
1303
+
1304
+ ### \u529F\u80FD\u70B9
1305
+ 1. \u529F\u80FD\u70B9\u4E00
1306
+ 2. \u529F\u80FD\u70B9\u4E8C
1307
+ 3. \u529F\u80FD\u70B9\u4E09
1308
+
1309
+ ## \u6280\u672F\u8BBE\u8BA1
1310
+
1311
+ ### API \u8BBE\u8BA1
1312
+ - \`POST /api/xxx\` - \u521B\u5EFA XXX
1313
+ - \`GET /api/xxx/{id}\` - \u83B7\u53D6 XXX \u8BE6\u60C5
1314
+ - \`PUT /api/xxx/{id}\` - \u66F4\u65B0 XXX
1315
+ - \`DELETE /api/xxx/{id}\` - \u5220\u9664 XXX
1316
+
1317
+ ### \u6570\u636E\u6A21\u578B
1318
+ - \`table_name\` (\u8868\u8BF4\u660E) - \u5B57\u6BB5\u8BF4\u660E
1319
+
1320
+ ## \u91CC\u7A0B\u7891 (Milestones)
1321
+
1322
+ > \u6CE8: \u4F7F\u7528 \`team-cli breakdown\` \u62C6\u5206\u6B64 spec \u4E3A milestones \u548C todos
1323
+
1324
+ ### Milestone 1: [\u91CC\u7A0B\u7891\u540D\u79F0]
1325
+ - [ ] Todo 1
1326
+ - [ ] Todo 2
1327
+ - [ ] Todo 3
1328
+
1329
+ ### Milestone 2: [\u91CC\u7A0B\u7891\u540D\u79F0]
1330
+ - [ ] Todo 1
1331
+ - [ ] Todo 2
1332
+
1333
+ ----
1334
+ *\u751F\u6210\u4E8E: {{TIMESTAMP}} by team-cli*
1335
+ `;
1336
+ await FileUtils.write(path2.join(projectPath, "docs/specs/template.md"), content);
1337
+ }
1338
+ async function cloneBackendTemplate(projectPath) {
1339
+ const templateRepo = process.env.TEMPLATE_REPO || "git@gitlab.yungu-inc.org:yungu-app/java-scaffold-template.git";
1340
+ const backendPath = path2.join(projectPath, "backend");
1341
+ try {
1342
+ const { execaCommand } = await import("execa");
1343
+ const tempDir = path2.join(projectPath, ".template-temp");
1344
+ await execaCommand(`git clone --depth=1 ${templateRepo} ${tempDir}`, {
1345
+ stdio: "inherit",
1346
+ timeout: 6e4
1347
+ });
1348
+ await fs2.copy(tempDir, backendPath, {
1349
+ filter: (src) => !src.includes(".git")
1350
+ });
1351
+ await fs2.remove(tempDir);
1352
+ const gitDir = path2.join(backendPath, ".git");
1353
+ if (await FileUtils.exists(gitDir)) {
1354
+ await FileUtils.remove(gitDir);
1355
+ }
1356
+ } catch (error) {
1357
+ logger.warn("\u514B\u9686\u540E\u7AEF\u6A21\u677F\u5931\u8D25\uFF0C\u521B\u5EFA\u57FA\u7840\u7ED3\u6784");
1358
+ await FileUtils.ensureDir(path2.join(backendPath, "src/main/java/com/example"));
1359
+ await FileUtils.ensureDir(path2.join(backendPath, "src/main/resources"));
1360
+ await FileUtils.ensureDir(path2.join(backendPath, "src/test/java"));
1361
+ }
1362
+ }
1363
+ async function generateFrontendScaffold(projectPath) {
1364
+ const frontendPath = path2.join(projectPath, "frontend");
1365
+ try {
1366
+ const prompt = `Read TECH_STACK.md and CONVENTIONS.md.
1367
+ Initialize a Next.js 14 frontend in ./frontend with:
1368
+ - TypeScript
1369
+ - Tailwind CSS
1370
+ - App Router structure
1371
+ - ESLint and Prettier configured
1372
+ - Basic layout and page components
1373
+
1374
+ Do not run any servers, just generate the folder structure and configuration files.`;
1375
+ await claudeAI.prompt(prompt, {
1376
+ contextFiles: [
1377
+ path2.join(projectPath, "TECH_STACK.md"),
1378
+ path2.join(projectPath, "CONVENTIONS.md")
1379
+ ]
1380
+ });
1381
+ } catch (error) {
1382
+ logger.warn("Claude \u751F\u6210\u524D\u7AEF\u5931\u8D25\uFF0C\u5C06\u521B\u5EFA\u57FA\u7840\u7ED3\u6784");
1383
+ await FileUtils.ensureDir(path2.join(frontendPath, "src/app"));
1384
+ await FileUtils.ensureDir(path2.join(frontendPath, "src/components"));
1385
+ await FileUtils.ensureDir(path2.join(frontendPath, "src/lib"));
1386
+ await FileUtils.ensureDir(path2.join(frontendPath, "public"));
1387
+ }
1388
+ }
1389
+ async function generateDockerFiles(projectPath) {
1390
+ logger.info("Docker \u914D\u7F6E\u751F\u6210\u5F85\u5B9E\u73B0");
1391
+ }
1392
+ async function initGit(projectPath, projectName) {
1393
+ try {
1394
+ const { execaCommand } = await import("execa");
1395
+ await execaCommand("git init", { cwd: projectPath, stdio: "pipe" });
1396
+ await execaCommand("git add .", { cwd: projectPath, stdio: "pipe" });
1397
+ await execaCommand(
1398
+ `git commit -m "feat: initialize ${projectName} project with team-cli"`,
1399
+ { cwd: projectPath, stdio: "pipe" }
1400
+ );
1401
+ logger.success("Git \u4ED3\u5E93\u521D\u59CB\u5316\u5B8C\u6210");
1402
+ } catch (error) {
1403
+ logger.warn(`Git \u521D\u59CB\u5316\u5931\u8D25: ${error.message}`);
1404
+ }
1405
+ }
1406
+
1407
+ // src/commands/breakdown.ts
1408
+ import { Command as Command2 } from "commander";
1409
+ import inquirer2 from "inquirer";
1410
+ import path3 from "path";
1411
+ import { Listr as Listr2 } from "listr2";
1412
+ var breakdownCommand = new Command2("breakdown").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u5C06 spec \u62C6\u5206\u4E3A milestones \u548C todos").action(async (specFile) => {
1413
+ try {
1414
+ logger.header("Spec \u62C6\u5206");
1415
+ logger.newLine();
1416
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
1417
+ if (!hasTechStack) {
1418
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
1419
+ logger.info("\u8BF7\u5148\u8FD0\u884C team-cli init \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
1420
+ process.exit(1);
1421
+ }
1422
+ const hasClaude = await claudeAI.checkInstalled();
1423
+ if (!hasClaude) {
1424
+ logger.error("\u672A\u68C0\u6D4B\u5230 Claude CLI");
1425
+ logger.info("\u8BF7\u5B89\u88C5 Claude CLI: npm install -g @anthropic-ai/claude-code");
1426
+ process.exit(1);
1427
+ }
1428
+ const tasks = new Listr2([
1429
+ {
1430
+ title: "\u626B\u63CF spec \u6587\u4EF6",
1431
+ task: async () => {
1432
+ const specDir = "docs/specs";
1433
+ const exists = await FileUtils.exists(specDir);
1434
+ if (!exists) {
1435
+ throw new Error(`specs \u76EE\u5F55\u4E0D\u5B58\u5728: ${specDir}`);
1436
+ }
1437
+ const files = await FileUtils.findFiles("*.md", specDir);
1438
+ return files.filter((f) => !f.includes("template"));
1439
+ }
1440
+ },
1441
+ {
1442
+ title: "\u9009\u62E9 spec \u6587\u4EF6",
1443
+ task: async (ctx) => {
1444
+ if (ctx.specs.length === 0) {
1445
+ throw new Error("\u672A\u627E\u5230 spec \u6587\u4EF6");
1446
+ }
1447
+ if (!specFile) {
1448
+ const { selectedFile } = await inquirer2.prompt([
1449
+ {
1450
+ type: "list",
1451
+ name: "selectedFile",
1452
+ message: "\u8BF7\u9009\u62E9\u8981\u62C6\u5206\u7684 spec \u6587\u4EF6:",
1453
+ choices: ctx.specs
1454
+ }
1455
+ ]);
1456
+ return path3.join("docs/specs", selectedFile);
1457
+ }
1458
+ const fullPath = specFile.startsWith("docs/specs/") ? specFile : path3.join("docs/specs", specFile);
1459
+ const exists = await FileUtils.exists(fullPath);
1460
+ if (!exists) {
1461
+ throw new Error(`Spec \u6587\u4EF6\u4E0D\u5B58\u5728: ${specFile}`);
1462
+ }
1463
+ return fullPath;
1464
+ }
1465
+ },
1466
+ {
1467
+ title: "\u8BFB\u53D6 spec \u5185\u5BB9",
1468
+ task: async (ctx) => {
1469
+ ctx.specContent = await FileUtils.read(ctx.selectedFile);
1470
+ logger.success(`\u5DF2\u9009\u62E9: ${ctx.selectedFile}`);
1471
+ }
1472
+ },
1473
+ {
1474
+ title: "\u8C03\u7528 Claude \u62C6\u5206 spec",
1475
+ task: async (ctx) => {
1476
+ const prompt = buildBreakdownPrompt(ctx.specContent);
1477
+ logger.newLine();
1478
+ logger.separator("\u2500", 60);
1479
+ logger.info("Claude \u6267\u884C\u4E2D...");
1480
+ logger.separator("\u2500", 60);
1481
+ logger.newLine();
1482
+ const result = await claudeAI.prompt(prompt, {
1483
+ contextFiles: ["TECH_STACK.md", "CONVENTIONS.md"]
1484
+ });
1485
+ logger.newLine();
1486
+ logger.separator("\u2500", 60);
1487
+ logger.newLine();
1488
+ return result;
1489
+ }
1490
+ },
1491
+ {
1492
+ title: "\u66F4\u65B0 spec \u6587\u4EF6",
1493
+ task: async (ctx) => {
1494
+ await FileUtils.write(ctx.selectedFile, ctx.breakdownResult);
1495
+ }
1496
+ }
1497
+ ]);
1498
+ try {
1499
+ const ctx = await tasks.run();
1500
+ logger.newLine();
1501
+ logger.header("Spec \u62C6\u5206\u5B8C\u6210!");
1502
+ logger.success(`Milestones \u5DF2\u6DFB\u52A0\u5230: ctx.selectedFile}`);
1503
+ logger.newLine();
1504
+ logger.info("\u4E0B\u4E00\u6B65:");
1505
+ logger.step("1. \u68C0\u67E5\u62C6\u5206\u7ED3\u679C\uFF0C\u6839\u636E\u9700\u8981\u8C03\u6574");
1506
+ logger.step("2. \u8FD0\u884C 'team-cli dev' \u9009\u62E9 milestone \u8FDB\u884C\u5F00\u53D1");
1507
+ logger.newLine();
1508
+ } catch (error) {
1509
+ if (error.message) {
1510
+ logger.error(error.message);
1511
+ }
1512
+ throw error;
1513
+ }
1514
+ } catch (error) {
1515
+ logger.error(`\u62C6\u5206\u5931\u8D25: ${error.message}`);
1516
+ if (process.env.DEBUG) {
1517
+ console.error(error);
1518
+ }
1519
+ process.exit(1);
1520
+ }
1521
+ });
1522
+ function buildBreakdownPrompt(specContent) {
1523
+ return `Role: Senior Technical Lead and Agile Coach
1524
+
1525
+ Task: Break down the following feature spec into milestones and todo lists.
1526
+
1527
+ Context:
1528
+ - Read TECH_STACK.md for technology constraints
1529
+ - Read CONVENTIONS.md for coding standards
1530
+ - Each milestone should be completable in 1-3 days
1531
+ - Each todo should be a concrete, actionable task
1532
+
1533
+ Spec Content:
1534
+ \`\`\`
1535
+ ${specContent}
1536
+ \`\`\`
1537
+
1538
+ Output Requirements:
1539
+ 1. Parse the existing spec content
1540
+ 2. Break it down into 2-5 milestones
1541
+ 3. Each milestone should have:
1542
+ - Clear name and objective
1543
+ - Estimated days (1-3 days per milestone)
1544
+ - Todo list with 3-8 actionable items
1545
+ 4. Todo items should be:
1546
+ - Concrete and specific
1547
+ - Testable
1548
+ - Independent as much as possible
1549
+
1550
+ Format the milestones section as:
1551
+
1552
+ \`\`\`markdown
1553
+ ## \u91CC\u7A0B\u7891 (Milestones)
1554
+
1555
+ ### Milestone 1: [\u91CC\u7A0B\u7891\u540D\u79F0]
1556
+ **\u76EE\u6807**: [\u7B80\u77ED\u63CF\u8FF0\u8FD9\u4E2A\u91CC\u7A0B\u7891\u7684\u76EE\u6807]
1557
+ **\u9884\u4F30**: 2 \u5929
1558
+
1559
+ - [ ] Todo 1 - \u5177\u4F53\u53EF\u6267\u884C\u7684\u4EFB\u52A1
1560
+ - [ ] Todo 2 - \u5177\u4F53\u53EF\u6267\u884C\u7684\u4EFB\u52A1
1561
+ - [ ] Todo 3 - \u5177\u4F53\u53EF\u6267\u884C\u7684\u4EFB\u52A1
1562
+
1563
+ ### Milestone 2: [\u91CC\u7A0B\u7891\u540D\u79F0]
1564
+ **\u76EE\u6807**: [\u7B80\u77ED\u63CF\u8FF0\u8FD9\u4E2A\u91CC\u7A0B\u7891\u7684\u76EE\u6807]
1565
+ **\u9884\u4F30**: 3 \u5929
1566
+
1567
+ - [ ] Todo 1
1568
+ - [ ] Todo 2
1569
+ - [ ] Todo 3
1570
+ - [ ] Todo 4
1571
+ \`\`\`
1572
+
1573
+ Important Instructions:
1574
+ 1. Update the spec file directly with the milestone breakdown
1575
+ 2. Keep all existing content, just add/update the milestones section
1576
+ 3. If milestones section exists, replace it with new breakdown
1577
+ 4. If milestones section doesn't exist, add it after "\u6280\u672F\u8BBE\u8BA1" section
1578
+ 5. After updating the file, exit immediately
1579
+ 6. Do not ask any questions
1580
+ 7. Make sure todos are actionable and can be completed independently
1581
+ 8. Consider dependencies when ordering todos within a milestone`;
1582
+ }
1583
+
1584
+ // src/commands/dev.ts
1585
+ import { Command as Command3 } from "commander";
1586
+ import inquirer3 from "inquirer";
1587
+ import path4 from "path";
1588
+ var devCommand = new Command3("dev").description("\u5F00\u53D1\u6A21\u5F0F\uFF0C\u6267\u884C\u5177\u4F53\u4EFB\u52A1").action(async () => {
1589
+ try {
1590
+ logger.header("\u5F00\u53D1\u6A21\u5F0F");
1591
+ logger.newLine();
1592
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
1593
+ if (!hasTechStack) {
1594
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
1595
+ logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
1596
+ process.exit(1);
1597
+ }
1598
+ logger.success("\u68C0\u6D4B\u5230\u9879\u76EE\u4E0A\u4E0B\u6587");
1599
+ const hasClaude = await claudeAI.checkInstalled();
1600
+ if (!hasClaude) {
1601
+ logger.error("\u672A\u68C0\u6D4B\u5230 Claude CLI");
1602
+ logger.info("\u8BF7\u5B89\u88C5 Claude CLI: npm install -g @anthropic-ai/claude-code");
1603
+ process.exit(1);
1604
+ }
1605
+ const selectedSpec = await selectSpec();
1606
+ const selectedMilestone = await selectMilestone(selectedSpec);
1607
+ const selectedTodo = await selectTodo(selectedSpec, selectedMilestone);
1608
+ await executeDevelopment(selectedSpec, selectedMilestone, selectedTodo);
1609
+ } catch (error) {
1610
+ logger.error(`\u5F00\u53D1\u6A21\u5F0F\u6267\u884C\u5931\u8D25: ${error.message}`);
1611
+ if (process.env.DEBUG) {
1612
+ console.error(error);
1613
+ }
1614
+ process.exit(1);
1615
+ }
1616
+ });
1617
+ async function selectSpec() {
1618
+ logger.step("\u6B65\u9AA4 1/3: \u9009\u62E9 spec \u6587\u4EF6...");
1619
+ logger.newLine();
1620
+ const specDir = "docs/specs";
1621
+ const exists = await FileUtils.exists(specDir);
1622
+ if (!exists) {
1623
+ throw new Error("docs/specs \u76EE\u5F55\u4E0D\u5B58\u5728");
1624
+ }
1625
+ const files = await FileUtils.findFiles("*.md", specDir);
1626
+ const specFiles = files.filter((f) => !f.includes("template"));
1627
+ if (specFiles.length === 0) {
1628
+ throw new Error("\u672A\u627E\u5230 spec \u6587\u4EF6");
1629
+ }
1630
+ const specs = [];
1631
+ for (let i = 0; i < specFiles.length; i++) {
1632
+ const file = path4.join(specDir, specFiles[i]);
1633
+ const spec = await FileUtils.read(file);
1634
+ const status = parseSpecStatus(spec);
1635
+ const dependencies = parseDependencies(spec);
1636
+ specs.push({
1637
+ file,
1638
+ name: specFiles[i],
1639
+ status,
1640
+ dependencies,
1641
+ index: i
1642
+ });
1643
+ }
1644
+ const sortedSpecs = topologicalSort(specs);
1645
+ logger.info("\u53EF\u7528\u7684 spec \u6587\u4EF6:");
1646
+ logger.newLine();
1647
+ const choices = sortedSpecs.map((spec, idx) => {
1648
+ const statusIcon = getStatusIcon(spec.status);
1649
+ const statusColor = getStatusColor(spec.status);
1650
+ const recommendInfo = idx === 0 ? "[\u63A8\u8350\u4ECE\u8FD9\u5F00\u59CB] " : "";
1651
+ const depInfo = spec.dependencies.length > 0 ? `[\u4F9D\u8D56: ${spec.dependencies.join(", ")}] ` : "";
1652
+ return {
1653
+ name: `${statusIcon} [${spec.status}] ${recommendInfo}${depInfo}${spec.name}`,
1654
+ value: spec.file,
1655
+ short: spec.name
1656
+ };
1657
+ });
1658
+ logger.info(" \u2713 = \u5DF2\u5B8C\u6210 \u27F3 = \u8FDB\u884C\u4E2D \u25C9 = \u5DF2\u62C6\u5206 \u25CB = \u672A\u5F00\u59CB");
1659
+ logger.info(" \u6839\u636E\u4F9D\u8D56\u5173\u7CFB\u63A8\u8350\u7684\u5F00\u53D1\u987A\u5E8F\u5DF2\u6807\u6CE8");
1660
+ logger.newLine();
1661
+ const { selectedFile } = await inquirer3.prompt([
1662
+ {
1663
+ type: "list",
1664
+ name: "selectedFile",
1665
+ message: "\u9009\u62E9\u8981\u5F00\u53D1\u7684 spec:",
1666
+ choices
1667
+ }
1668
+ ]);
1669
+ logger.success(`\u5DF2\u9009\u62E9: ${selectedFile}`);
1670
+ return selectedFile;
1671
+ }
1672
+ async function selectMilestone(specFile) {
1673
+ logger.newLine();
1674
+ logger.step("\u6B65\u9AA4 2/3: \u89E3\u6790 milestones...");
1675
+ logger.newLine();
1676
+ const specContent = await FileUtils.read(specFile);
1677
+ const milestones = parseMilestones(specContent);
1678
+ if (milestones.length === 0) {
1679
+ logger.info("\u8BE5 spec \u5C1A\u672A\u62C6\u5206 milestones");
1680
+ const { breakdownNow } = await inquirer3.prompt([
1681
+ {
1682
+ type: "confirm",
1683
+ name: "breakdownNow",
1684
+ message: "\u662F\u5426\u73B0\u5728\u62C6\u5206?",
1685
+ default: true
1686
+ }
1687
+ ]);
1688
+ if (breakdownNow) {
1689
+ logger.info("\u6B63\u5728\u8C03\u7528 breakdown...");
1690
+ throw new Error("breakdown \u547D\u4EE4\u9700\u8981\u8FDB\u4E00\u6B65\u5B9E\u73B0");
1691
+ } else {
1692
+ logger.info("\u5C06\u76F4\u63A5\u5B9E\u73B0\u6574\u4E2A spec");
1693
+ return "\u6574\u4E2A spec";
1694
+ }
1695
+ }
1696
+ const choices = milestones.map((m, idx) => ({
1697
+ name: `${idx + 1}. ${m.title} (${m.todos.length} \u4E2A\u4EFB\u52A1)`,
1698
+ value: m.title,
1699
+ short: m.title
1700
+ }));
1701
+ choices.push({
1702
+ name: `${milestones.length + 1}. \u6574\u4E2A spec (\u5168\u90E8 milestones)`,
1703
+ value: "\u6574\u4E2A spec",
1704
+ short: "\u6574\u4E2A spec"
1705
+ });
1706
+ const { milestone } = await inquirer3.prompt([
1707
+ {
1708
+ type: "list",
1709
+ name: "milestone",
1710
+ message: "\u9009\u62E9 milestone:",
1711
+ choices
1712
+ }
1713
+ ]);
1714
+ return milestone;
1715
+ }
1716
+ async function selectTodo(specFile, milestone) {
1717
+ if (milestone === "\u6574\u4E2A spec") {
1718
+ return "\u5168\u90E8\u529F\u80FD";
1719
+ }
1720
+ logger.newLine();
1721
+ logger.step("\u6B65\u9AA4 3/3: \u9009\u62E9 todo \u4EFB\u52A1...");
1722
+ logger.newLine();
1723
+ const specContent = await FileUtils.read(specFile);
1724
+ const todos = parseTodos(specContent, milestone);
1725
+ if (todos.length === 0) {
1726
+ logger.warn("\u8BE5 milestone \u6CA1\u6709 todo \u4EFB\u52A1");
1727
+ const { implementAll } = await inquirer3.prompt([
1728
+ {
1729
+ type: "confirm",
1730
+ name: "implementAll",
1731
+ message: "\u5B9E\u73B0\u6574\u4E2A milestone?",
1732
+ default: true
1733
+ }
1734
+ ]);
1735
+ return implementAll ? "\u5168\u90E8\u529F\u80FD" : milestone;
1736
+ }
1737
+ const choices = todos.map((todo2, idx) => ({
1738
+ name: `${idx + 1}. ${todo2}`,
1739
+ value: todo2,
1740
+ short: todo2
1741
+ }));
1742
+ choices.push({
1743
+ name: `${todos.length + 1}. \u5168\u90E8\u4EFB\u52A1 (\u6574\u4E2A milestone)`,
1744
+ value: "\u5168\u90E8\u4EFB\u52A1",
1745
+ short: "\u5168\u90E8\u4EFB\u52A1"
1746
+ });
1747
+ const { todo } = await inquirer3.prompt([
1748
+ {
1749
+ type: "list",
1750
+ name: "todo",
1751
+ message: "\u9009\u62E9 todo \u4EFB\u52A1:",
1752
+ choices
1753
+ }
1754
+ ]);
1755
+ return todo;
1756
+ }
1757
+ async function executeDevelopment(specFile, milestone, todo) {
1758
+ logger.newLine();
1759
+ logger.step("\u6784\u5EFA Prompt \u5E76\u8C03\u7528 Claude...");
1760
+ let taskDescription;
1761
+ if (milestone === "\u6574\u4E2A spec") {
1762
+ taskDescription = "\u5B9E\u73B0\u6574\u4E2A spec \u7684\u6240\u6709\u529F\u80FD";
1763
+ } else if (todo === "\u5168\u90E8\u529F\u80FD" || todo === "\u5168\u90E8\u4EFB\u52A1") {
1764
+ taskDescription = `\u5B9E\u73B0 milestone '${milestone}' \u7684\u6240\u6709\u4EFB\u52A1`;
1765
+ } else {
1766
+ taskDescription = `\u5B9E\u73B0 milestone '${milestone}' \u7684\u4EFB\u52A1: ${todo}`;
1767
+ }
1768
+ const prompt = buildDevPrompt(specFile, milestone, todo, taskDescription);
1769
+ logger.newLine();
1770
+ logger.separator("\u2500", 60);
1771
+ logger.info("Claude \u6267\u884C\u4E2D...");
1772
+ logger.separator("\u2500", 60);
1773
+ logger.newLine();
1774
+ logger.info(` \u4EFB\u52A1\u63CF\u8FF0: ${taskDescription}`);
1775
+ logger.info(` Spec \u6587\u4EF6: ${specFile}`);
1776
+ logger.newLine();
1777
+ const result = await claudeAI.prompt(prompt, {
1778
+ contextFiles: ["TECH_STACK.md", "CONVENTIONS.md", "AI_MEMORY.md", specFile]
1779
+ });
1780
+ logger.newLine();
1781
+ logger.separator("\u2500", 60);
1782
+ logger.newLine();
1783
+ await generateSessionLog(specFile, milestone, todo, taskDescription, result);
1784
+ logger.header("\u5F00\u53D1\u4EFB\u52A1\u5B8C\u6210!");
1785
+ logger.success("\u4F1A\u8BDD\u65E5\u5FD7\u5DF2\u4FDD\u5B58");
1786
+ logger.newLine();
1787
+ logger.info("\u4E0B\u4E00\u6B65:");
1788
+ logger.step("1. \u68C0\u67E5\u751F\u6210\u7684\u4EE3\u7801");
1789
+ logger.step("2. \u8FD0\u884C 'team-cli dev' \u7EE7\u7EED\u4E0B\u4E00\u4E2A\u4EFB\u52A1");
1790
+ logger.newLine();
1791
+ }
1792
+ function parseSpecStatus(spec) {
1793
+ const statusMatch = spec.match(/状态.*[::]\s*(.+)/);
1794
+ if (statusMatch) {
1795
+ const status = statusMatch[1].replace(/\*\*/g, "").trim();
1796
+ if (status.includes("\u5DF2\u5B8C\u6210")) return "\u5DF2\u5B8C\u6210";
1797
+ if (status.includes("\u8FDB\u884C\u4E2D")) return "\u8FDB\u884C\u4E2D";
1798
+ if (status.includes("\u5DF2\u62C6\u5206")) return "\u5DF2\u62C6\u5206";
1799
+ }
1800
+ return "\u672A\u5F00\u59CB";
1801
+ }
1802
+ function parseDependencies(spec) {
1803
+ const deps = [];
1804
+ let inDepsSection = false;
1805
+ const lines = spec.split("\n");
1806
+ for (const line of lines) {
1807
+ if (line.includes("## \u4F9D\u8D56\u5173\u7CFB") || line.includes("## \u4F9D\u8D56")) {
1808
+ inDepsSection = true;
1809
+ continue;
1810
+ }
1811
+ if (inDepsSection && line.startsWith("## ")) {
1812
+ break;
1813
+ }
1814
+ const match = line.match(/^-\s+\[[ x ]\]\s*(.+\.md)/);
1815
+ if (inDepsSection && match) {
1816
+ const dep = match[1];
1817
+ if (dep !== "\u65E0" && dep) {
1818
+ deps.push(dep);
1819
+ }
1820
+ }
1821
+ }
1822
+ return deps;
1823
+ }
1824
+ function parseMilestones(spec) {
1825
+ const milestones = [];
1826
+ const lines = spec.split("\n");
1827
+ let currentMilestone = null;
1828
+ let inMilestone = false;
1829
+ for (const line of lines) {
1830
+ if (line.match(/^###\s+Milestone\s+\d+:/)) {
1831
+ if (currentMilestone) {
1832
+ milestones.push(currentMilestone);
1833
+ }
1834
+ const title = line.replace(/^###\s+/, "").trim();
1835
+ currentMilestone = { title, todos: [] };
1836
+ inMilestone = true;
1837
+ continue;
1838
+ }
1839
+ if (inMilestone && currentMilestone) {
1840
+ if (line.match(/^###\s+Milestone/)) {
1841
+ milestones.push(currentMilestone);
1842
+ currentMilestone = null;
1843
+ continue;
1844
+ }
1845
+ const todoMatch = line.match(/^-\s+\[[ x ]\]\s*(.+)/);
1846
+ if (todoMatch) {
1847
+ currentMilestone.todos.push(todoMatch[1].trim());
1848
+ }
1849
+ }
1850
+ }
1851
+ if (currentMilestone) {
1852
+ milestones.push(currentMilestone);
1853
+ }
1854
+ return milestones;
1855
+ }
1856
+ function parseTodos(spec, milestoneTitle) {
1857
+ const lines = spec.split("\n");
1858
+ const todos = [];
1859
+ let inTargetMilestone = false;
1860
+ for (const line of lines) {
1861
+ if (line.includes(milestoneTitle)) {
1862
+ inTargetMilestone = true;
1863
+ continue;
1864
+ }
1865
+ if (inTargetMilestone) {
1866
+ if (line.match(/^###\s+Milestone/)) {
1867
+ break;
1868
+ }
1869
+ const todoMatch = line.match(/^-\s+\[[ x ]\]\s*(.+)/);
1870
+ if (todoMatch) {
1871
+ todos.push(todoMatch[1].trim());
1872
+ }
1873
+ }
1874
+ }
1875
+ return todos;
1876
+ }
1877
+ function topologicalSort(specs) {
1878
+ const sorted = [];
1879
+ const visited = /* @__PURE__ */ new Set();
1880
+ function visit(spec) {
1881
+ if (visited.has(spec.index)) return;
1882
+ visited.add(spec.index);
1883
+ for (const depName of spec.dependencies) {
1884
+ const dep = specs.find((s) => s.name === depName);
1885
+ if (dep) {
1886
+ visit(dep);
1887
+ }
1888
+ }
1889
+ sorted.push(spec);
1890
+ }
1891
+ for (const spec of specs) {
1892
+ visit(spec);
1893
+ }
1894
+ return sorted;
1895
+ }
1896
+ function getStatusIcon(status) {
1897
+ switch (status) {
1898
+ case "\u5DF2\u5B8C\u6210":
1899
+ return "\u2713";
1900
+ case "\u8FDB\u884C\u4E2D":
1901
+ return "\u27F3";
1902
+ case "\u5DF2\u62C6\u5206":
1903
+ return "\u25C9";
1904
+ default:
1905
+ return "\u25CB";
1906
+ }
1907
+ }
1908
+ function getStatusColor(status) {
1909
+ return status;
1910
+ }
1911
+ function buildDevPrompt(specFile, milestone, todo, taskDescription) {
1912
+ return `Role: Senior Fullstack Developer
1913
+
1914
+ Context:
1915
+ - Read and follow TECH_STACK.md for technology choices
1916
+ - Read and follow CONVENTIONS.md for coding standards
1917
+ - Check AI_MEMORY.md for project context and history
1918
+
1919
+ Task: ${taskDescription}
1920
+
1921
+ Spec File: ${specFile}
1922
+ Milestone: ${milestone}
1923
+ Todo: ${todo}
1924
+
1925
+ Process:
1926
+ 1. First, read and analyze the full spec file
1927
+ 2. Focus on the selected milestone/todo
1928
+ 3. Define API endpoints and data models if needed
1929
+ 4. Write tests first (TDD approach)
1930
+ 5. Implement the backend code in ./backend
1931
+ 6. Implement the frontend integration in ./frontend
1932
+ 7. Update AI_MEMORY.md with completed tasks
1933
+ 8. Update the spec file to mark completed todos (if applicable)
1934
+
1935
+ Important:
1936
+ - Follow the tech stack exactly (Java 17, Spring Boot 3, MyBatis Plus, Next.js 14, TypeScript)
1937
+ - Use DTO pattern for all API requests/responses
1938
+ - Write clean, well-documented code
1939
+ - After completing the task, exit immediately without waiting for further input
1940
+ `;
1941
+ }
1942
+ async function generateSessionLog(specFile, milestone, todo, taskDescription, result) {
1943
+ const sessionDir = "docs/sessions";
1944
+ await FileUtils.ensureDir(sessionDir);
1945
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1946
+ const specName = path4.basename(specFile, ".md");
1947
+ const logFile = path4.join(sessionDir, `${timestamp}_${specName}.md`);
1948
+ const content = `# \u5F00\u53D1\u4F1A\u8BDD\u8BB0\u5F55
1949
+
1950
+ **\u65F6\u95F4**: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}
1951
+ **Spec**: ${specFile}
1952
+ **Milestone**: ${milestone}
1953
+ **Todo**: ${todo}
1954
+
1955
+ ## \u4EFB\u52A1\u63CF\u8FF0
1956
+
1957
+ ${taskDescription}
1958
+
1959
+ ## \u6267\u884C\u7ED3\u679C
1960
+
1961
+ \`\`\`
1962
+ ${result}
1963
+ \`\`\`
1964
+
1965
+ ## \u751F\u6210\u7684\u6587\u4EF6
1966
+
1967
+ <!-- TODO: \u5217\u51FA\u751F\u6210\u7684\u6587\u4EF6 -->
1968
+
1969
+ ## \u4E0B\u4E00\u6B65
1970
+
1971
+ - [ ] \u6D4B\u8BD5\u751F\u6210\u7684\u4EE3\u7801
1972
+ - [ ] \u66F4\u65B0 spec \u6587\u4EF6\u4E2D\u7684 todo \u72B6\u6001
1973
+ - [ ] \u7EE7\u7EED\u4E0B\u4E00\u4E2A\u4EFB\u52A1
1974
+ `;
1975
+ await FileUtils.write(logFile, content);
1976
+ }
1977
+
1978
+ // src/commands/add-feature.ts
1979
+ import { Command as Command4 } from "commander";
1980
+ import inquirer4 from "inquirer";
1981
+ import path5 from "path";
1982
+ import { Listr as Listr3 } from "listr2";
1983
+ var addFeatureCommand = new Command4("add-feature").argument("<feature-name>", "\u529F\u80FD\u540D\u79F0").description("\u6DFB\u52A0\u65B0\u529F\u80FD\uFF08\u652F\u6301 PRD \u6216\u7B80\u5355\u63CF\u8FF0\u6A21\u5F0F\uFF09").action(async (featureName) => {
1984
+ try {
1985
+ logger.header("\u6DFB\u52A0\u65B0\u529F\u80FD");
1986
+ logger.newLine();
1987
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
1988
+ if (!hasTechStack) {
1989
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
1990
+ logger.info("\u8BF7\u5148\u8FD0\u884C team-cli init \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
1991
+ process.exit(1);
1992
+ }
1993
+ const hasClaude = await claudeAI.checkInstalled();
1994
+ if (!hasClaude) {
1995
+ logger.error("\u672A\u68C0\u6D4B\u5230 Claude CLI");
1996
+ logger.info("\u8BF7\u5B89\u88C5 Claude CLI: npm install -g @anthropic-ai/claude-code");
1997
+ process.exit(1);
1998
+ }
1999
+ const featureSlug = StringUtils2.toKebabCase(featureName);
2000
+ const specFile = path5.join("docs/specs", `${featureSlug}.md`);
2001
+ const specExists = await FileUtils.exists(specFile);
2002
+ if (specExists) {
2003
+ logger.error(`Spec \u6587\u4EF6\u5DF2\u5B58\u5728: ${specFile}`);
2004
+ logger.info("\u5982\u9700\u91CD\u65B0\u751F\u6210\uFF0C\u8BF7\u5148\u5220\u9664\uFF1A");
2005
+ logger.info(` rm ${specFile}`);
2006
+ process.exit(1);
2007
+ }
2008
+ const { mode } = await inquirer4.prompt([
2009
+ {
2010
+ type: "list",
2011
+ name: "mode",
2012
+ message: "\u9009\u62E9\u9700\u6C42\u8F93\u5165\u6A21\u5F0F:",
2013
+ choices: [
2014
+ { name: "PRD \u6587\u6863\u6A21\u5F0F (\u5DF2\u6709\u8BE6\u7EC6 PRD \u6587\u6863)", value: "prd" },
2015
+ { name: "\u7B80\u5355\u63CF\u8FF0\u6A21\u5F0F (\u4E00\u53E5\u8BDD\u9700\u6C42 + \u6A21\u5757\u4F9D\u8D56)", value: "simple" }
2016
+ ]
2017
+ }
2018
+ ]);
2019
+ if (mode === "prd") {
2020
+ await addFeatureFromPrd(featureName, featureSlug, specFile);
2021
+ } else {
2022
+ await addFeatureSimple(featureName, featureSlug, specFile);
2023
+ }
2024
+ } catch (error) {
2025
+ logger.error(`\u6DFB\u52A0\u529F\u80FD\u5931\u8D25: ${error.message}`);
2026
+ if (process.env.DEBUG) {
2027
+ console.error(error);
2028
+ }
2029
+ process.exit(1);
2030
+ }
2031
+ });
2032
+ async function addFeatureFromPrd(featureName, featureSlug, specFile) {
2033
+ const { prdPath } = await inquirer4.prompt([
2034
+ {
2035
+ type: "input",
2036
+ name: "prdPath",
2037
+ message: "\u8BF7\u8F93\u5165 PRD \u6587\u6863\u8DEF\u5F84:",
2038
+ validate: async (input) => {
2039
+ const exists = await FileUtils.exists(input);
2040
+ return exists || "PRD \u6587\u6863\u4E0D\u5B58\u5728";
2041
+ }
2042
+ }
2043
+ ]);
2044
+ const tasks = new Listr3([
2045
+ {
2046
+ title: "\u8BFB\u53D6 PRD \u6587\u6863",
2047
+ task: async (ctx2) => {
2048
+ ctx2.prdContent = await FileUtils.read(prdPath);
2049
+ }
2050
+ },
2051
+ {
2052
+ title: "\u626B\u63CF\u5DF2\u5B8C\u6210\u529F\u80FD",
2053
+ task: async (ctx2) => {
2054
+ const specDir = "docs/specs";
2055
+ const files = await FileUtils.findFiles("*.md", specDir);
2056
+ const specs = files.filter((f) => !f.includes("template"));
2057
+ ctx2.completedSpecs = [];
2058
+ for (const file of specs) {
2059
+ const status = await SpecUtils.getSpecStatus(path5.join(specDir, file));
2060
+ if (status === "\u5DF2\u5B8C\u6210") {
2061
+ ctx2.completedSpecs.push(file.replace(".md", ""));
2062
+ }
2063
+ }
2064
+ }
2065
+ },
2066
+ {
2067
+ title: "\u6784\u5EFA\u9879\u76EE\u4E0A\u4E0B\u6587",
2068
+ task: async (ctx2) => {
2069
+ ctx2.projectContext = await buildProjectContext();
2070
+ }
2071
+ },
2072
+ {
2073
+ title: "\u8C03\u7528 Claude \u751F\u6210 spec",
2074
+ task: async (ctx2) => {
2075
+ const prompt = buildPrdPrompt(
2076
+ featureName,
2077
+ ctx2.prdContent,
2078
+ ctx2.projectContext,
2079
+ ctx2.completedSpecs
2080
+ );
2081
+ logger.newLine();
2082
+ logger.separator("\u2500", 60);
2083
+ logger.info("Claude \u751F\u6210 spec \u4E2D...");
2084
+ logger.separator("\u2500", 60);
2085
+ logger.newLine();
2086
+ const result = await claudeAI.prompt(prompt, {
2087
+ contextFiles: ["TECH_STACK.md", "CONVENTIONS.md", "AI_MEMORY.md"]
2088
+ });
2089
+ logger.newLine();
2090
+ logger.separator("\u2500", 60);
2091
+ logger.newLine();
2092
+ return result;
2093
+ }
2094
+ },
2095
+ {
2096
+ title: "\u4FDD\u5B58 spec \u6587\u4EF6",
2097
+ task: async (ctx2) => {
2098
+ await FileUtils.write(specFile, ctx2.generatedSpec);
2099
+ }
2100
+ },
2101
+ {
2102
+ title: "\u66F4\u65B0 AI_MEMORY",
2103
+ task: async () => {
2104
+ await updateAiMemory(featureName, featureSlug);
2105
+ }
2106
+ }
2107
+ ]);
2108
+ const ctx = await tasks.run();
2109
+ logger.success(`Spec \u6587\u4EF6\u5DF2\u751F\u6210: ${specFile}`);
2110
+ await showSpecPreview(specFile);
2111
+ await askToAdjust(specFile);
2112
+ }
2113
+ async function addFeatureSimple(featureName, featureSlug, specFile) {
2114
+ const { description } = await inquirer4.prompt([
2115
+ {
2116
+ type: "input",
2117
+ name: "description",
2118
+ message: "\u529F\u80FD\u63CF\u8FF0 (\u4E00\u53E5\u8BDD\u8BF4\u660E):",
2119
+ validate: (input) => input.trim().length > 0 || "\u63CF\u8FF0\u4E0D\u80FD\u4E3A\u7A7A"
2120
+ }
2121
+ ]);
2122
+ const tasks = new Listr3([
2123
+ {
2124
+ title: "\u626B\u63CF\u5DF2\u5B8C\u6210\u529F\u80FD",
2125
+ task: async (ctx2) => {
2126
+ const specDir = "docs/specs";
2127
+ const files = await FileUtils.findFiles("*.md", specDir);
2128
+ const specs = files.filter((f) => !f.includes("template"));
2129
+ ctx2.completedSpecs = [];
2130
+ for (const file of specs) {
2131
+ const status = await SpecUtils.getSpecStatus(path5.join(specDir, file));
2132
+ if (status === "\u5DF2\u5B8C\u6210") {
2133
+ ctx2.completedSpecs.push(file.replace(".md", ""));
2134
+ }
2135
+ }
2136
+ }
2137
+ },
2138
+ {
2139
+ title: "\u9009\u62E9\u4F9D\u8D56\u529F\u80FD",
2140
+ task: async (ctx2) => {
2141
+ if (ctx2.completedSpecs.length === 0) {
2142
+ ctx2.selectedDeps = [];
2143
+ return;
2144
+ }
2145
+ const { dependencies } = await inquirer4.prompt([
2146
+ {
2147
+ type: "checkbox",
2148
+ name: "dependencies",
2149
+ message: "\u9009\u62E9\u6B64\u529F\u80FD\u4F9D\u8D56\u7684\u5DF2\u6709\u529F\u80FD (\u53EF\u591A\u9009\uFF0C\u76F4\u63A5\u56DE\u8F66\u8DF3\u8FC7):",
2150
+ choices: ctx2.completedSpecs
2151
+ }
2152
+ ]);
2153
+ ctx2.selectedDeps = dependencies;
2154
+ }
2155
+ },
2156
+ {
2157
+ title: "\u6784\u5EFA\u9879\u76EE\u4E0A\u4E0B\u6587",
2158
+ task: async (ctx2) => {
2159
+ ctx2.projectContext = await buildProjectContext();
2160
+ }
2161
+ },
2162
+ {
2163
+ title: "\u8C03\u7528 Claude \u751F\u6210 spec",
2164
+ task: async (ctx2) => {
2165
+ const prompt = buildSimplePrompt(
2166
+ featureName,
2167
+ description,
2168
+ ctx2.projectContext,
2169
+ ctx2.selectedDeps
2170
+ );
2171
+ logger.newLine();
2172
+ logger.separator("\u2500", 60);
2173
+ logger.info("Claude \u751F\u6210 spec \u4E2D...");
2174
+ logger.separator("\u2500", 60);
2175
+ logger.newLine();
2176
+ const result = await claudeAI.prompt(prompt, {
2177
+ contextFiles: ["TECH_STACK.md", "CONVENTIONS.md", "AI_MEMORY.md"]
2178
+ });
2179
+ logger.newLine();
2180
+ logger.separator("\u2500", 60);
2181
+ logger.newLine();
2182
+ return result;
2183
+ }
2184
+ },
2185
+ {
2186
+ title: "\u4FDD\u5B58 spec \u6587\u4EF6",
2187
+ task: async (ctx2) => {
2188
+ await FileUtils.write(specFile, ctx2.generatedSpec);
2189
+ }
2190
+ },
2191
+ {
2192
+ title: "\u66F4\u65B0 AI_MEMORY",
2193
+ task: async () => {
2194
+ await updateAiMemory(featureName, featureSlug);
2195
+ }
2196
+ }
2197
+ ]);
2198
+ const ctx = await tasks.run();
2199
+ logger.success(`Spec \u6587\u4EF6\u5DF2\u751F\u6210: ${specFile}`);
2200
+ await showSpecPreview(specFile);
2201
+ await askToAdjust(specFile);
2202
+ }
2203
+ async function buildProjectContext() {
2204
+ const context = [];
2205
+ if (await FileUtils.exists("backend/src/main/java")) {
2206
+ context.push("### \u540E\u7AEF\u7ED3\u6784");
2207
+ const backendDirs = await FileUtils.findFiles("*/", "backend/src/main/java");
2208
+ const limited = backendDirs.slice(0, 10);
2209
+ limited.forEach((dir) => {
2210
+ context.push(` - backend/src/main/java/${dir}`);
2211
+ });
2212
+ }
2213
+ if (await FileUtils.exists("frontend/src")) {
2214
+ context.push("");
2215
+ context.push("### \u524D\u7AEF\u7ED3\u6784");
2216
+ const frontendDirs = await FileUtils.findFiles("*/", "frontend/src");
2217
+ const limited = frontendDirs.slice(0, 10);
2218
+ limited.forEach((dir) => {
2219
+ context.push(` - frontend/src/${dir}`);
2220
+ });
2221
+ }
2222
+ if (await FileUtils.exists("docs/specs")) {
2223
+ context.push("");
2224
+ context.push("### \u5DF2\u6709\u529F\u80FD");
2225
+ const files = await FileUtils.findFiles("*.md", "docs/specs");
2226
+ const specs = files.filter((f) => !f.includes("template"));
2227
+ for (const file of specs) {
2228
+ const status = await SpecUtils.getSpecStatus(path5.join("docs/specs", file));
2229
+ context.push(` - ${file.replace(".md", "")} [${status}]`);
2230
+ }
2231
+ }
2232
+ return context.join("\n");
2233
+ }
2234
+ function buildPrdPrompt(featureName, prdContent, projectContext, completedSpecs) {
2235
+ const deps = completedSpecs.length > 0 ? completedSpecs.join("\u3001") : "(\u65E0)";
2236
+ return `Role: Senior Fullstack Developer
2237
+
2238
+ \u4F60\u73B0\u5728\u9700\u8981\u6839\u636E PRD \u6587\u6863\u751F\u6210\u4E00\u4E2A\u529F\u80FD\u89C4\u683C\uFF08Spec\uFF09\u6587\u6863\u3002
2239
+
2240
+ ## \u9879\u76EE\u6280\u672F\u6808
2241
+ \u540E\u7AEF: Java 17 + Spring Boot 3 + MyBatis Plus + MySQL 8.0
2242
+ \u524D\u7AEF: Next.js 14 (App Router) + TypeScript + Tailwind CSS
2243
+
2244
+ ## \u9879\u76EE\u5F53\u524D\u72B6\u6001
2245
+ ${projectContext}
2246
+
2247
+ ## \u529F\u80FD\u540D\u79F0
2248
+ ${featureName}
2249
+
2250
+ ## PRD \u6587\u6863\u5185\u5BB9
2251
+ ${prdContent}
2252
+
2253
+ ## \u4F9D\u8D56\u529F\u80FD
2254
+ \u6B64\u529F\u80FD\u53EF\u80FD\u4F9D\u8D56\u4EE5\u4E0B\u5DF2\u5B8C\u6210\u7684\u529F\u80FD\uFF1A${deps}
2255
+
2256
+ ## \u4EFB\u52A1
2257
+ \u8BF7\u6839\u636E PRD \u6587\u6863\uFF0C\u751F\u6210\u4E00\u4E2A\u5B8C\u6574\u7684\u529F\u80FD\u89C4\u683C\u6587\u6863 Spec\u3002
2258
+
2259
+ ## Spec \u6587\u6863\u683C\u5F0F\u8981\u6C42
2260
+ \`\`\`markdown
2261
+ # [\u529F\u80FD\u6807\u9898]
2262
+
2263
+ ## \u529F\u80FD\u6982\u8FF0
2264
+ **\u529F\u80FD\u540D\u79F0**: ${featureName}
2265
+ **\u4F18\u5148\u7EA7**: P0/P1/P2 (\u6839\u636E\u529F\u80FD\u91CD\u8981\u6027\u5224\u65AD)
2266
+ **\u9884\u4F30\u5DE5\u65F6**: X \u5929 (\u6839\u636E\u590D\u6742\u5EA6\u8BC4\u4F30\uFF1A\u7B80\u53551-2\u5929\uFF0C\u4E2D\u7B493-5\u5929\uFF0C\u590D\u67425-10\u5929)
2267
+ **\u72B6\u6001**: \u5F85\u62C6\u5206
2268
+ **\u521B\u5EFA\u65E5\u671F**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
2269
+
2270
+ ## \u4F9D\u8D56\u5173\u7CFB
2271
+ **\u524D\u7F6E\u4F9D\u8D56**:
2272
+ ${completedSpecs.map((s) => `- [x] ${s}`).join("\n") || "- (\u65E0)"}
2273
+
2274
+ **\u88AB\u4F9D\u8D56\u4E8E**:
2275
+ - (\u81EA\u52A8\u751F\u6210\uFF0C\u8868\u793A\u54EA\u4E9B spec \u4F9D\u8D56\u672C\u529F\u80FD)
2276
+
2277
+ ## \u80CC\u666F\u4E0E\u76EE\u6807
2278
+ [\u6839\u636E PRD \u63D0\u53D6\u80CC\u666F\u548C\u76EE\u6807]
2279
+
2280
+ ## \u529F\u80FD\u9700\u6C42
2281
+ ### \u7528\u6237\u6545\u4E8B
2282
+ \`\`\`
2283
+ \u4F5C\u4E3A [\u5177\u4F53\u89D2\u8272]
2284
+ \u6211\u5E0C\u671B [\u5177\u4F53\u529F\u80FD]
2285
+ \u4EE5\u4FBF [\u5B9E\u73B0\u7684\u4EF7\u503C]
2286
+ \`\`\`
2287
+
2288
+ ### \u529F\u80FD\u70B9
2289
+ [\u4ECE PRD \u63D0\u53D6 3-8 \u4E2A\u4E3B\u8981\u529F\u80FD\u70B9]
2290
+
2291
+ ## \u6280\u672F\u8BBE\u8BA1
2292
+ ### API \u8BBE\u8BA1
2293
+ \u5217\u51FA\u4E3B\u8981\u7684 API \u7AEF\u70B9\uFF0C\u683C\u5F0F\uFF1A
2294
+ - \`METHOD /api/path\` - \u7B80\u77ED\u8BF4\u660E
2295
+
2296
+ ### \u6570\u636E\u6A21\u578B
2297
+ \u5217\u51FA\u9700\u8981\u7684\u6570\u636E\u8868\uFF0C\u683C\u5F0F\uFF1A
2298
+ - \`table_name\` (\u8868\u8BF4\u660E) - \u5B57\u6BB5\u8BF4\u660E
2299
+
2300
+ ## \u91CC\u7A0B\u7891 (Milestones)
2301
+ > \u6CE8: \u4F7F\u7528 \`team-cli breakdown ${featureName}.md\` \u62C6\u5206\u6B64 spec \u4E3A milestones \u548C todos
2302
+
2303
+ ----
2304
+ *\u751F\u6210\u4E8E: ${(/* @__PURE__ */ new Date()).toISOString()} by Claude*
2305
+ \`\`\`
2306
+
2307
+ ## \u6CE8\u610F\u4E8B\u9879
2308
+ 1. \u53EA\u751F\u6210 Spec \u6587\u6863\uFF0C\u4E0D\u8981\u5B9E\u73B0\u4EFB\u4F55\u4EE3\u7801
2309
+ 2. \u4F18\u5148\u7EA7\u5224\u65AD\u6807\u51C6\uFF1A
2310
+ - P0: \u6838\u5FC3\u529F\u80FD\uFF0C\u963B\u585E\u5176\u4ED6\u529F\u80FD
2311
+ - P1: \u91CD\u8981\u529F\u80FD\uFF0C\u5F71\u54CD\u7528\u6237\u4F53\u9A8C
2312
+ - P2: \u589E\u5F3A\u529F\u80FD\uFF0C\u9526\u4E0A\u6DFB\u82B1
2313
+ 3. \u5DE5\u65F6\u8BC4\u4F30\u6807\u51C6\uFF1A
2314
+ - \u7B80\u5355\u529F\u80FD: 1-2 \u5929
2315
+ - \u4E2D\u7B49\u529F\u80FD: 3-5 \u5929
2316
+ - \u590D\u6742\u529F\u80FD: 5-10 \u5929
2317
+ 4. \u529F\u80FD\u70B9\u8981\u5177\u4F53\u53EF\u6267\u884C\uFF0C\u907F\u514D\u8FC7\u4E8E\u62BD\u8C61
2318
+ 5. API \u548C\u6570\u636E\u6A21\u578B\u8981\u7B26\u5408\u5B9E\u9645\u6280\u672F\u6808
2319
+ 6. \u4ECE PRD \u4E2D\u63D0\u53D6\u5173\u952E\u4FE1\u606F\uFF0C\u4FDD\u6301\u7B80\u6D01`;
2320
+ }
2321
+ function buildSimplePrompt(featureName, description, projectContext, dependencies) {
2322
+ const deps = dependencies.length > 0 ? dependencies.join("\u3001") : "(\u65E0)";
2323
+ return `Role: Senior Fullstack Developer
2324
+
2325
+ \u7528\u6237\u60F3\u8981\u6DFB\u52A0\u4E00\u4E2A\u65B0\u529F\u80FD\uFF0C\u9700\u8981\u4F60\u751F\u6210\u529F\u80FD\u89C4\u683C\u6587\u6863\uFF08Spec\uFF09\u3002
2326
+
2327
+ ## \u529F\u80FD\u540D\u79F0
2328
+ ${featureName}
2329
+
2330
+ ## \u529F\u80FD\u63CF\u8FF0
2331
+ ${description}
2332
+
2333
+ ## \u4F9D\u8D56\u529F\u80FD
2334
+ \u6B64\u529F\u80FD\u4F9D\u8D56\u4EE5\u4E0B\u5DF2\u5B8C\u6210\u7684\u529F\u80FD\uFF1A${deps}
2335
+
2336
+ ## \u9879\u76EE\u6280\u672F\u6808
2337
+ \u540E\u7AEF: Java 17 + Spring Boot 3 + MyBatis Plus + MySQL 8.0
2338
+ \u524D\u7AEF: Next.js 14 (App Router) + TypeScript + Tailwind CSS
2339
+
2340
+ ## \u9879\u76EE\u5F53\u524D\u72B6\u6001
2341
+ ${projectContext}
2342
+
2343
+ ## \u4EFB\u52A1
2344
+ \u8BF7\u6839\u636E\u529F\u80FD\u63CF\u8FF0\uFF0C\u751F\u6210\u4E00\u4E2A\u5B8C\u6574\u7684\u529F\u80FD\u89C4\u683C\u6587\u6863 Spec\u3002
2345
+
2346
+ ## Spec \u6587\u6863\u683C\u5F0F\u8981\u6C42
2347
+ \`\`\`markdown
2348
+ # [\u529F\u80FD\u6807\u9898]
2349
+
2350
+ ## \u529F\u80FD\u6982\u8FF0
2351
+ **\u529F\u80FD\u540D\u79F0**: ${featureName}
2352
+ **\u4F18\u5148\u7EA7**: P0/P1/P2 (\u6839\u636E\u529F\u80FD\u91CD\u8981\u6027\u5224\u65AD)
2353
+ **\u9884\u4F30\u5DE5\u65F6**: X \u5929 (\u6839\u636E\u590D\u6742\u5EA6\u8BC4\u4F30\uFF1A\u7B80\u53551-2\u5929\uFF0C\u4E2D\u7B493-5\u5929\uFF0C\u590D\u67425-10\u5929)
2354
+ **\u72B6\u6001**: \u5F85\u62C6\u5206
2355
+ **\u521B\u5EFA\u65E5\u671F**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
2356
+
2357
+ ## \u4F9D\u8D56\u5173\u7CFB
2358
+ **\u524D\u7F6E\u4F9D\u8D56**:
2359
+ ${dependencies.map((d) => `- [x] ${d}`).join("\n") || "- (\u65E0)"}
2360
+
2361
+ **\u88AB\u4F9D\u8D56\u4E8E**:
2362
+ - (\u81EA\u52A8\u751F\u6210\uFF0C\u8868\u793A\u54EA\u4E9B spec \u4F9D\u8D56\u672C\u529F\u80FD)
2363
+
2364
+ ## \u80CC\u666F\u4E0E\u76EE\u6807
2365
+ [\u6839\u636E\u529F\u80FD\u63CF\u8FF0\uFF0C\u7B80\u660E\u627C\u8981\u5730\u8BF4\u660E\u529F\u80FD\u7684\u80CC\u666F\u548C\u8981\u89E3\u51B3\u7684\u95EE\u9898]
2366
+
2367
+ ## \u529F\u80FD\u9700\u6C42
2368
+ ### \u7528\u6237\u6545\u4E8B
2369
+ \`\`\`
2370
+ \u4F5C\u4E3A [\u5177\u4F53\u89D2\u8272]
2371
+ \u6211\u5E0C\u671B [\u5177\u4F53\u529F\u80FD]
2372
+ \u4EE5\u4FBF [\u5B9E\u73B0\u7684\u4EF7\u503C]
2373
+ \`\`\`
2374
+
2375
+ ### \u529F\u80FD\u70B9
2376
+ \u5217\u51FA 3-8 \u4E2A\u4E3B\u8981\u529F\u80FD\u70B9\uFF0C\u6BCF\u4E2A\u529F\u80FD\u70B9\u7528\u4E00\u53E5\u8BDD\u63CF\u8FF0\u3002
2377
+
2378
+ ## \u6280\u672F\u8BBE\u8BA1
2379
+ ### API \u8BBE\u8BA1
2380
+ \u5217\u51FA\u4E3B\u8981\u7684 API \u7AEF\u70B9\uFF0C\u683C\u5F0F\uFF1A
2381
+ - \`METHOD /api/path\` - \u7B80\u77ED\u8BF4\u660E
2382
+
2383
+ ### \u6570\u636E\u6A21\u578B
2384
+ \u5217\u51FA\u9700\u8981\u7684\u6570\u636E\u8868\uFF0C\u683C\u5F0F\uFF1A
2385
+ - \`table_name\` (\u8868\u8BF4\u660E) - \u5B57\u6BB5\u8BF4\u660E
2386
+
2387
+ ## \u91CC\u7A0B\u7891 (Milestones)
2388
+ > \u6CE8: \u4F7F\u7528 \`team-cli breakdown ${featureName}.md\` \u62C6\u5206\u6B64 spec \u4E3A milestones \u548C todos
2389
+
2390
+ ----
2391
+ *\u751F\u6210\u4E8E: ${(/* @__PURE__ */ new Date()).toISOString()} by Claude*
2392
+ \`\`\`
2393
+
2394
+ ## \u6CE8\u610F\u4E8B\u9879
2395
+ 1. \u53EA\u751F\u6210 Spec \u6587\u6863\uFF0C\u4E0D\u8981\u5B9E\u73B0\u4EFB\u4F55\u4EE3\u7801
2396
+ 2. \u4F18\u5148\u7EA7\u5224\u65AD\u8981\u5408\u7406\uFF0C\u907F\u514D\u5168\u90E8\u662F P0
2397
+ 3. \u5DE5\u65F6\u8BC4\u4F30\u8981\u5B9E\u9645\uFF0C\u4E0D\u8981\u8FC7\u4E8E\u4E50\u89C2
2398
+ 4. \u529F\u80FD\u70B9\u8981\u5177\u4F53\u53EF\u6267\u884C
2399
+ 5. \u6839\u636E\u529F\u80FD\u63CF\u8FF0\u63A8\u65AD\u5408\u7406\u7684 API \u548C\u6570\u636E\u6A21\u578B
2400
+ 6. \u53C2\u8003\u5DF2\u6709\u529F\u80FD\u7684\u6280\u672F\u98CE\u683C\u4FDD\u6301\u4E00\u81F4\u6027`;
2401
+ }
2402
+ async function updateAiMemory(featureName, featureSlug) {
2403
+ const aiMemoryFile = "AI_MEMORY.md";
2404
+ const exists = await FileUtils.exists(aiMemoryFile);
2405
+ if (!exists) {
2406
+ logger.warn("AI_MEMORY.md \u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u66F4\u65B0");
2407
+ return;
2408
+ }
2409
+ let content = await FileUtils.read(aiMemoryFile);
2410
+ if (!content.includes("## \u529F\u80FD\u6E05\u5355")) {
2411
+ const featureList = `
2412
+ ## \u529F\u80FD\u6E05\u5355 (Feature Inventory)
2413
+
2414
+ | \u529F\u80FD | Spec \u6587\u4EF6 | \u72B6\u6001 | \u8FDB\u5EA6 | \u5B8C\u6210\u65E5\u671F | \u5907\u6CE8 |
2415
+ |------|----------|------|------|---------|------|
2416
+ | ${featureName} | ${featureSlug}.md | \u25CB \u672A\u5F00\u59CB | 0/0 | - | |
2417
+ `;
2418
+ content = content.replace("(/\u9879\u76EE\u4FE1\u606F/[^#]*)", `$1
2419
+ ${featureList}`);
2420
+ } else {
2421
+ const newRow = `| ${featureName} | ${featureSlug}.md | \u25CB \u672A\u5F00\u59CB | 0/0 | - | |`;
2422
+ content = content.replace(
2423
+ /(\| 功能 \| Spec 文件 \| 状态 \| 进度 \| 完成日期 \| 备注 \|)/,
2424
+ `$1
2425
+ ${newRow}`
2426
+ );
2427
+ }
2428
+ await FileUtils.write(aiMemoryFile, content);
2429
+ logger.success("AI_MEMORY.md \u5DF2\u66F4\u65B0");
2430
+ }
2431
+ async function showSpecPreview(specFile) {
2432
+ logger.newLine();
2433
+ logger.header("\u751F\u6210\u7684 Spec \u9884\u89C8:");
2434
+ logger.newLine();
2435
+ const content = await FileUtils.read(specFile);
2436
+ const lines = content.split("\n");
2437
+ const preview = lines.slice(0, 40).join("\n");
2438
+ console.log(preview);
2439
+ if (lines.length > 40) {
2440
+ console.log("...");
2441
+ console.log(`(\u5171 ${lines.length} \u884C)`);
2442
+ }
2443
+ console.log("");
2444
+ }
2445
+ async function askToAdjust(specFile) {
2446
+ const { needAdjust } = await inquirer4.prompt([
2447
+ {
2448
+ type: "confirm",
2449
+ name: "needAdjust",
2450
+ message: "\u662F\u5426\u9700\u8981\u8C03\u6574 Spec \u5185\u5BB9?",
2451
+ default: false
2452
+ }
2453
+ ]);
2454
+ if (needAdjust) {
2455
+ logger.info("\u6B63\u5728\u6253\u5F00\u7F16\u8F91\u5668...");
2456
+ const editor = process.env.EDITOR || "vim";
2457
+ const { execaCommand } = await import("execa");
2458
+ await execaCommand(`${editor} ${specFile}`, { stdio: "inherit" });
2459
+ }
2460
+ logger.newLine();
2461
+ logger.info("\u4E0B\u4E00\u6B65:");
2462
+ logger.step(`1. \u8FD0\u884C 'team-cli breakdown ${specFile}' \u62C6\u5206\u4E3A milestones`);
2463
+ logger.step("2. \u8FD0\u884C 'team-cli dev' \u9009\u62E9 milestone \u8FDB\u884C\u5F00\u53D1");
2464
+ logger.newLine();
2465
+ }
2466
+
2467
+ // src/commands/split-prd.ts
2468
+ import { Command as Command5 } from "commander";
2469
+ import path6 from "path";
2470
+ import { Listr as Listr4 } from "listr2";
2471
+ var splitPrdCommand = new Command5("split-prd").argument("<prd-folder>", "PRD \u6587\u6863\u76EE\u5F55").description("\u5C06 PRD \u62C6\u5206\u6210\u591A\u4E2A specs").action(async (prdFolder) => {
2472
+ try {
2473
+ logger.header("PRD \u62C6\u5206");
2474
+ logger.newLine();
2475
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
2476
+ if (!hasTechStack) {
2477
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
2478
+ logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
2479
+ process.exit(1);
2480
+ }
2481
+ const folderExists = await FileUtils.exists(prdFolder);
2482
+ if (!folderExists) {
2483
+ throw new Error(`PRD \u6587\u4EF6\u5939\u4E0D\u5B58\u5728: ${prdFolder}`);
2484
+ }
2485
+ const hasClaude = await claudeAI.checkInstalled();
2486
+ if (!hasClaude) {
2487
+ logger.error("\u672A\u68C0\u6D4B\u5230 Claude CLI");
2488
+ logger.info("\u8BF7\u5B89\u88C5 Claude CLI: npm install -g @anthropic-ai/claude-code");
2489
+ process.exit(1);
2490
+ }
2491
+ const tasks = new Listr4([
2492
+ {
2493
+ title: "\u626B\u63CF PRD \u6587\u4EF6",
2494
+ task: async (ctx2) => {
2495
+ const supportedExtensions = ["md", "txt", "markdown"];
2496
+ ctx2.prdFiles = [];
2497
+ for (const ext of supportedExtensions) {
2498
+ const files = await FileUtils.findFiles(`*.${ext}`, prdFolder);
2499
+ ctx2.prdFiles.push(...files.map((f) => path6.join(prdFolder, f)));
2500
+ }
2501
+ if (ctx2.prdFiles.length === 0) {
2502
+ throw new Error(
2503
+ "\u672A\u627E\u5230 PRD \u6587\u6863 (\u652F\u6301 .md, .txt, .markdown)"
2504
+ );
2505
+ }
2506
+ ctx2.prdFile = ctx2.prdFiles[0];
2507
+ if (ctx2.prdFiles.length > 1) {
2508
+ logger.info(`\u627E\u5230\u591A\u4E2A PRD \u6587\u6863\uFF0C\u4F7F\u7528: ${ctx2.prdFile}`);
2509
+ } else {
2510
+ logger.success(`\u627E\u5230 PRD \u6587\u6863: ${ctx2.prdFile}`);
2511
+ }
2512
+ }
2513
+ },
2514
+ {
2515
+ title: "\u626B\u63CF\u622A\u56FE\u6587\u4EF6",
2516
+ task: async (ctx2) => {
2517
+ const screenshotDir = path6.join(prdFolder, "screenshots");
2518
+ const dirExists = await FileUtils.exists(screenshotDir);
2519
+ if (!dirExists) {
2520
+ logger.info("\u672A\u627E\u5230 screenshots \u76EE\u5F55\uFF0C\u8DF3\u8FC7\u622A\u56FE");
2521
+ ctx2.screenshots = [];
2522
+ return;
2523
+ }
2524
+ const imageExtensions = ["png", "jpg", "jpeg", "gif", "webp"];
2525
+ ctx2.screenshots = [];
2526
+ for (const ext of imageExtensions) {
2527
+ const files = await FileUtils.findFiles(`*.${ext}`, screenshotDir);
2528
+ ctx2.screenshots.push(...files.map((f) => path6.join(screenshotDir, f)));
2529
+ }
2530
+ logger.success(`\u627E\u5230 ${ctx2.screenshots.length} \u4E2A\u622A\u56FE\u6587\u4EF6`);
2531
+ }
2532
+ },
2533
+ {
2534
+ title: "\u626B\u63CF demo \u4EE3\u7801\u4ED3\u5E93",
2535
+ task: async (ctx2) => {
2536
+ const entries = await FileUtils.findFiles("*/", prdFolder);
2537
+ ctx2.demoRepos = [];
2538
+ for (const entry of entries) {
2539
+ const dirPath = path6.join(prdFolder, entry);
2540
+ const gitDir = path6.join(dirPath, ".git");
2541
+ const hasGit = await FileUtils.exists(gitDir);
2542
+ if (hasGit) {
2543
+ ctx2.demoRepos.push(dirPath);
2544
+ }
2545
+ }
2546
+ if (ctx2.demoRepos.length > 0) {
2547
+ logger.success(`\u627E\u5230 demo \u4ED3\u5E93: ${ctx2.demoRepos.join(", ")}`);
2548
+ } else {
2549
+ logger.info("\u672A\u627E\u5230 demo \u4EE3\u7801\u4ED3\u5E93\uFF0C\u8DF3\u8FC7");
2550
+ }
2551
+ }
2552
+ },
2553
+ {
2554
+ title: "\u8BFB\u53D6 PRD \u5185\u5BB9",
2555
+ task: async (ctx2) => {
2556
+ ctx2.prdContent = await FileUtils.read(ctx2.prdFile);
2557
+ }
2558
+ },
2559
+ {
2560
+ title: "\u8C03\u7528 Claude \u62C6\u5206 PRD",
2561
+ task: async (ctx2) => {
2562
+ const prompt = buildSplitPrdPrompt(
2563
+ ctx2.prdContent,
2564
+ ctx2.screenshots,
2565
+ ctx2.demoRepos
2566
+ );
2567
+ logger.newLine();
2568
+ logger.separator("\u2500", 60);
2569
+ logger.info("Claude \u6267\u884C\u4E2D...");
2570
+ logger.separator("\u2500", 60);
2571
+ logger.newLine();
2572
+ return await claudeAI.prompt(prompt, {
2573
+ contextFiles: ["TECH_STACK.md", "CONVENTIONS.md"]
2574
+ });
2575
+ }
2576
+ }
2577
+ ]);
2578
+ const ctx = await tasks.run();
2579
+ logger.newLine();
2580
+ logger.separator("\u2500", 60);
2581
+ logger.newLine();
2582
+ logger.header("PRD \u62C6\u5206\u5B8C\u6210!");
2583
+ logger.success("Spec \u6587\u4EF6\u5DF2\u751F\u6210\u5230 docs/specs/ \u76EE\u5F55");
2584
+ logger.newLine();
2585
+ logger.info("\u4E0B\u4E00\u6B65:");
2586
+ logger.step("1. \u68C0\u67E5\u751F\u6210\u7684 spec \u6587\u4EF6");
2587
+ logger.step("2. \u8FD0\u884C 'team-cli breakdown <spec-file>' \u62C6\u5206 milestones");
2588
+ logger.step("3. \u8FD0\u884C 'team-cli dev' \u5F00\u59CB\u5F00\u53D1");
2589
+ logger.newLine();
2590
+ } catch (error) {
2591
+ logger.error(`PRD \u62C6\u5206\u5931\u8D25: ${error.message}`);
2592
+ if (process.env.DEBUG) {
2593
+ console.error(error);
2594
+ }
2595
+ process.exit(1);
2596
+ }
2597
+ });
2598
+ function buildSplitPrdPrompt(prdContent, screenshots, demoRepos) {
2599
+ let prompt = `Role: Senior Product Manager and Technical Architect
2600
+
2601
+ Task: Analyze the following PRD and split it into multiple independent feature specifications (specs).
2602
+
2603
+ Context:
2604
+ - Read TECH_STACK.md for technology constraints
2605
+ - Read CONVENTIONS.md for coding standards
2606
+ - Each spec should be a standalone feature that can be developed independently
2607
+
2608
+ PRD Content:
2609
+ \`\`\`
2610
+ ${prdContent}
2611
+ \`\`\`
2612
+ `;
2613
+ if (screenshots.length > 0) {
2614
+ prompt += `
2615
+ Screenshots available (${screenshots.length} files):
2616
+ `;
2617
+ for (const screenshot of screenshots) {
2618
+ prompt += ` - ${screenshot}
2619
+ `;
2620
+ }
2621
+ prompt += `You can use the Read tool to view these screenshots for visual reference.
2622
+ `;
2623
+ }
2624
+ if (demoRepos.length > 0) {
2625
+ prompt += `
2626
+ Demo Code Repos available:
2627
+ `;
2628
+ for (const repo of demoRepos) {
2629
+ prompt += ` - ${repo}
2630
+ `;
2631
+ }
2632
+ prompt += `You can explore these repos to understand existing implementation patterns.
2633
+ `;
2634
+ }
2635
+ prompt += `
2636
+ Output Requirements:
2637
+ 1. Split the PRD into multiple feature specs based on functionality
2638
+ 2. Each spec should follow the template format (see docs/specs/template.md)
2639
+ 3. Create each spec as a separate markdown file in docs/specs/
2640
+ 4. Use kebab-case for filenames (e.g., user-authentication.md, data-export.md)
2641
+ 5. Each spec must include:
2642
+ - Feature Overview (\u529F\u80FD\u6982\u8FF0)
2643
+ - Background & Goals (\u80CC\u666F\u4E0E\u76EE\u6807)
2644
+ - Functional Requirements (\u529F\u80FD\u9700\u6C42)
2645
+ - Technical Design (\u6280\u672F\u8BBE\u8BA1)
2646
+ - Acceptance Criteria (\u9A8C\u6536\u6807\u51C6)
2647
+
2648
+ Spec Format Template:
2649
+ \`\`\`markdown
2650
+ # [\u529F\u80FD\u6807\u9898]
2651
+
2652
+ ## \u529F\u80FD\u6982\u8FF0
2653
+ **\u529F\u80FD\u540D\u79F0**: [\u529F\u80FD\u4E2D\u6587\u540D]
2654
+ **\u4F18\u5148\u7EA7**: P0/P1/P2
2655
+ **\u9884\u4F30\u5DE5\u65F6**: X \u5929
2656
+ **\u72B6\u6001**: \u5F85\u62C6\u5206
2657
+ **\u521B\u5EFA\u65E5\u671F**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
2658
+
2659
+ ## \u4F9D\u8D56\u5173\u7CFB
2660
+ **\u524D\u7F6E\u4F9D\u8D56**:
2661
+ - (\u5217\u51FA\u4F9D\u8D56\u7684\u5176\u4ED6 spec)
2662
+
2663
+ **\u88AB\u4F9D\u8D56\u4E8E**:
2664
+ - (\u81EA\u52A8\u751F\u6210)
2665
+
2666
+ ## \u80CC\u666F\u4E0E\u76EE\u6807
2667
+ [\u7B80\u660E\u627C\u8981\u5730\u8BF4\u660E\u529F\u80FD\u7684\u80CC\u666F\u548C\u8981\u89E3\u51B3\u7684\u95EE\u9898]
2668
+
2669
+ ## \u529F\u80FD\u9700\u6C42
2670
+
2671
+ ### \u7528\u6237\u6545\u4E8B
2672
+ \`\`\`
2673
+ \u4F5C\u4E3A [\u5177\u4F53\u89D2\u8272]
2674
+ \u6211\u5E0C\u671B [\u5177\u4F53\u529F\u80FD]
2675
+ \u4EE5\u4FBF [\u5B9E\u73B0\u7684\u4EF7\u503C]
2676
+ \`\`\`
2677
+
2678
+ ### \u529F\u80FD\u70B9
2679
+ \u5217\u51FA 3-8 \u4E2A\u4E3B\u8981\u529F\u80FD\u70B9
2680
+
2681
+ ## \u6280\u672F\u8BBE\u8BA1
2682
+
2683
+ ### API \u8BBE\u8BA1
2684
+ \u5217\u51FA\u4E3B\u8981\u7684 API \u7AEF\u70B9
2685
+
2686
+ ### \u6570\u636E\u6A21\u578B
2687
+ \u5217\u51FA\u9700\u8981\u7684\u6570\u636E\u8868
2688
+
2689
+ ## \u9A8C\u6536\u6807\u51C6
2690
+ - [ ] \u9A8C\u6536\u6807\u51C6 1
2691
+ - [ ] \u9A8C\u6536\u6807\u51C6 2
2692
+ - [ ] \u9A8C\u6536\u6807\u51C6 3
2693
+
2694
+ ----
2695
+ *\u751F\u6210\u4E8E: ${(/* @__PURE__ */ new Date()).toISOString()} by Claude*
2696
+ \`\`\`
2697
+
2698
+ IMPORTANT:
2699
+ - After generating all spec files, exit immediately without waiting for further input
2700
+ - Do not ask any questions
2701
+ - Each spec should be implementable in 1-3 days
2702
+ - Dependencies between features should be clearly noted in the \u4F9D\u8D56\u5173\u7CFB section
2703
+ `;
2704
+ return prompt;
2705
+ }
2706
+
2707
+ // src/commands/bugfix.ts
2708
+ import { Command as Command6 } from "commander";
2709
+ import inquirer5 from "inquirer";
2710
+ import path7 from "path";
2711
+ import { Listr as Listr5 } from "listr2";
2712
+ var bugfixCommand = new Command6("bugfix").description("\u521B\u5EFA Bugfix \u8BB0\u5F55").action(async () => {
2713
+ try {
2714
+ logger.header("\u521B\u5EFA Bugfix \u8BB0\u5F55");
2715
+ logger.newLine();
2716
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
2717
+ if (!hasTechStack) {
2718
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
2719
+ logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
2720
+ process.exit(1);
2721
+ }
2722
+ const answers = await inquirer5.prompt([
2723
+ {
2724
+ type: "list",
2725
+ name: "severity",
2726
+ message: "Bug \u4E25\u91CD\u7A0B\u5EA6:",
2727
+ choices: [
2728
+ { name: "\u9AD8 - \u963B\u585E\u529F\u80FD\u6216\u5F71\u54CD\u6838\u5FC3\u6D41\u7A0B", value: "\u9AD8" },
2729
+ { name: "\u4E2D - \u5F71\u54CD\u529F\u80FD\u4F46\u4E0D\u963B\u585E", value: "\u4E2D" },
2730
+ { name: "\u4F4E - \u5C0F\u95EE\u9898\u6216\u4F53\u9A8C\u95EE\u9898", value: "\u4F4E" }
2731
+ ],
2732
+ default: "\u4E2D"
2733
+ },
2734
+ {
2735
+ type: "input",
2736
+ name: "description",
2737
+ message: "Bug \u63CF\u8FF0:",
2738
+ validate: (input) => input.trim().length > 0 || "\u63CF\u8FF0\u4E0D\u80FD\u4E3A\u7A7A"
2739
+ },
2740
+ {
2741
+ type: "input",
2742
+ name: "reproduction",
2743
+ message: "\u590D\u73B0\u6B65\u9AA4:",
2744
+ default: "1. \u6B65\u9AA4\u4E00\n2. \u6B65\u9AA4\u4E8C\n3. \u6B65\u9AA4\u4E09"
2745
+ },
2746
+ {
2747
+ type: "list",
2748
+ name: "scope",
2749
+ message: "\u5F71\u54CD\u8303\u56F4:",
2750
+ choices: ["\u5168\u90E8\u7528\u6237", "\u90E8\u5206\u7528\u6237", "\u4E2A\u522B\u7528\u6237", "\u672A\u77E5"],
2751
+ default: "\u90E8\u5206\u7528\u6237"
2752
+ }
2753
+ ]);
2754
+ const bugId = generateBugId();
2755
+ const timestamp = DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD");
2756
+ const relatedSpec = await findRelatedSpec(answers.description);
2757
+ const bugfixDir = "docs/bugfixes";
2758
+ await FileUtils.ensureDir(bugfixDir);
2759
+ const bugfixFile = path7.join(bugfixDir, `${timestamp}_${bugId}.md`);
2760
+ const content = formatBugfixDocument({
2761
+ id: bugId,
2762
+ severity: answers.severity,
2763
+ description: answers.description,
2764
+ reproduction: answers.reproduction,
2765
+ scope: answers.scope,
2766
+ relatedSpec,
2767
+ timestamp
2768
+ });
2769
+ await FileUtils.write(bugfixFile, content);
2770
+ logger.success(`Bugfix \u8BB0\u5F55\u5DF2\u521B\u5EFA: ${bugfixFile}`);
2771
+ logger.newLine();
2772
+ logger.info("\u4E0B\u4E00\u6B65:");
2773
+ logger.step("1. \u5206\u6790 bug \u6839\u672C\u539F\u56E0");
2774
+ logger.step("2. \u8FD0\u884C 'team-cli dev' \u9009\u62E9\u5173\u8054\u7684 spec \u8FDB\u884C\u4FEE\u590D");
2775
+ logger.step("3. \u4FEE\u590D\u540E\u66F4\u65B0 bugfix \u6587\u6863\u72B6\u6001");
2776
+ logger.newLine();
2777
+ } catch (error) {
2778
+ logger.error(`\u521B\u5EFA bugfix \u5931\u8D25: ${error.message}`);
2779
+ if (process.env.DEBUG) {
2780
+ console.error(error);
2781
+ }
2782
+ process.exit(1);
2783
+ }
2784
+ });
2785
+ var hotfixCommand = new Command6("hotfix").description("\u7D27\u6025\u4FEE\u590D\u6D41\u7A0B").action(async () => {
2786
+ try {
2787
+ logger.header("\u7D27\u6025\u4FEE\u590D\u6D41\u7A0B");
2788
+ logger.newLine();
2789
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
2790
+ if (!hasTechStack) {
2791
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
2792
+ logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
2793
+ process.exit(1);
2794
+ }
2795
+ logger.warn("\u6CE8\u610F: hotfix \u7528\u4E8E\u7D27\u6025\u95EE\u9898\u4FEE\u590D");
2796
+ logger.warn("\u4FEE\u590D\u540E\u9700\u8981\u521B\u5EFA\u89C4\u8303 bugfix \u8BB0\u5F55");
2797
+ logger.newLine();
2798
+ const answers = await inquirer5.prompt([
2799
+ {
2800
+ type: "input",
2801
+ name: "description",
2802
+ message: "\u7D27\u6025\u95EE\u9898\u63CF\u8FF0:",
2803
+ validate: (input) => input.trim().length > 0 || "\u63CF\u8FF0\u4E0D\u80FD\u4E3A\u7A7A"
2804
+ },
2805
+ {
2806
+ type: "list",
2807
+ name: "scope",
2808
+ message: "\u5F71\u54CD\u8303\u56F4:",
2809
+ choices: ["\u6240\u6709\u7528\u6237", "\u90E8\u5206\u7528\u6237", "\u7279\u5B9A\u7528\u6237"],
2810
+ default: "\u6240\u6709\u7528\u6237"
2811
+ },
2812
+ {
2813
+ type: "input",
2814
+ name: "solution",
2815
+ message: "\u4E34\u65F6\u89E3\u51B3\u65B9\u6848:",
2816
+ validate: (input) => input.trim().length > 0 || "\u65B9\u6848\u4E0D\u80FD\u4E3A\u7A7A"
2817
+ }
2818
+ ]);
2819
+ const hotfixId = generateHotfixId();
2820
+ const branchName = `hotfix/${hotfixId}`;
2821
+ const tasks = new Listr5([
2822
+ {
2823
+ title: "\u521B\u5EFA hotfix \u5206\u652F",
2824
+ task: async () => {
2825
+ const { execa: execa3 } = await import("execa");
2826
+ await execa3("git", ["checkout", "-b", branchName], { stdio: "inherit" });
2827
+ }
2828
+ },
2829
+ {
2830
+ title: "\u521B\u5EFA hotfix \u8BB0\u5F55",
2831
+ task: async () => {
2832
+ const timestamp = DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD HH:mm:ss");
2833
+ const hotfixDir = "docs/hotfixes";
2834
+ await FileUtils.ensureDir(hotfixDir);
2835
+ const hotfixFile = path7.join(hotfixDir, `${timestamp}_${hotfixId}.md`);
2836
+ const content = formatHotfixDocument({
2837
+ id: hotfixId,
2838
+ description: answers.description,
2839
+ scope: answers.scope,
2840
+ solution: answers.solution,
2841
+ branch: branchName,
2842
+ timestamp
2843
+ });
2844
+ await FileUtils.write(hotfixFile, content);
2845
+ }
2846
+ },
2847
+ {
2848
+ title: "\u521B\u5EFA\u521D\u59CB commit",
2849
+ task: async () => {
2850
+ const { execa: execa3 } = await import("execa");
2851
+ await execa3("git", ["add", "docs/hotfixes"], { stdio: "pipe" });
2852
+ await execa3(
2853
+ "git",
2854
+ [
2855
+ "commit",
2856
+ "-m",
2857
+ `hotfix: ${answers.description}
2858
+
2859
+ Temporary solution: ${answers.solution}`
2860
+ ],
2861
+ { stdio: "pipe" }
2862
+ );
2863
+ }
2864
+ }
2865
+ ]);
2866
+ await tasks.run();
2867
+ logger.newLine();
2868
+ logger.success(`Hotfix \u5206\u652F\u5DF2\u521B\u5EFA: ${branchName}`);
2869
+ logger.newLine();
2870
+ logger.info("\u4E0B\u4E00\u6B65:");
2871
+ logger.step("1. \u5B9E\u65BD\u4FEE\u590D");
2872
+ logger.step("2. \u8FD0\u884C 'team-cli lint' \u68C0\u67E5\u4EE3\u7801");
2873
+ logger.step("3. \u63D0\u4EA4\u5E76\u63A8\u9001\u5230\u8FDC\u7A0B");
2874
+ logger.step("4. \u521B\u5EFA PR \u8FDB\u884C code review");
2875
+ logger.step("5. \u5408\u5E76\u540E\u521B\u5EFA\u89C4\u8303 bugfix \u8BB0\u5F55");
2876
+ logger.newLine();
2877
+ } catch (error) {
2878
+ logger.error(`Hotfix \u6D41\u7A0B\u5931\u8D25: ${error.message}`);
2879
+ if (process.env.DEBUG) {
2880
+ console.error(error);
2881
+ }
2882
+ process.exit(1);
2883
+ }
2884
+ });
2885
+ function generateBugId() {
2886
+ const date = /* @__PURE__ */ new Date();
2887
+ const year = date.getFullYear();
2888
+ const month = String(date.getMonth() + 1).padStart(2, "0");
2889
+ const day = String(date.getDate()).padStart(2, "0");
2890
+ return `BUG-${year}${month}${day}-001`;
2891
+ }
2892
+ function generateHotfixId() {
2893
+ const date = /* @__PURE__ */ new Date();
2894
+ const timestamp = date.getTime().toString().slice(-6);
2895
+ return `HF-${timestamp}`;
2896
+ }
2897
+ async function findRelatedSpec(description) {
2898
+ const specDir = "docs/specs";
2899
+ const exists = await FileUtils.exists(specDir);
2900
+ if (!exists) {
2901
+ return "";
2902
+ }
2903
+ const files = await FileUtils.findFiles("*.md", specDir);
2904
+ const specs = files.filter((f) => !f.includes("template"));
2905
+ if (specs.length === 0) {
2906
+ return "";
2907
+ }
2908
+ const keywords = extractKeywords(description);
2909
+ for (const file of specs) {
2910
+ const filePath = path7.join(specDir, file);
2911
+ const content = await FileUtils.read(filePath);
2912
+ for (const keyword of keywords) {
2913
+ if (content.toLowerCase().includes(keyword.toLowerCase())) {
2914
+ return file.replace(".md", "");
2915
+ }
2916
+ }
2917
+ }
2918
+ return "";
2919
+ }
2920
+ function extractKeywords(text) {
2921
+ const stopWords = ["\u7684", "\u662F", "\u5728", "\u6709", "\u548C", "\u6216", "\u4E86", "\u4E0D", "\u5417", "\u5462"];
2922
+ const words = text.toLowerCase().replace(/[^\w\s\u4e00-\u9fa5]/g, "").split(/\s+/).filter((w) => w.length > 1 && !stopWords.includes(w));
2923
+ return words.slice(0, 5);
2924
+ }
2925
+ function formatBugfixDocument(data) {
2926
+ return `# Bugfix: ${data.description}
2927
+
2928
+ ## Bug \u4FE1\u606F
2929
+ - **ID**: ${data.id}
2930
+ - **\u4E25\u91CD\u7A0B\u5EA6**: ${data.severity}
2931
+ - **\u72B6\u6001**: \u5F85\u4FEE\u590D
2932
+ - **\u521B\u5EFA\u65F6\u95F4**: ${data.timestamp}
2933
+
2934
+ ## \u95EE\u9898\u63CF\u8FF0
2935
+ ${data.description}
2936
+
2937
+ ## \u590D\u73B0\u6B65\u9AA4
2938
+ ${data.reproduction}
2939
+
2940
+ ## \u5F71\u54CD\u8303\u56F4
2941
+ ${data.scope}
2942
+
2943
+ ## \u5173\u8054 Spec
2944
+ ${data.relatedSpec ? `- ${data.relatedSpec}` : "- (\u65E0)"}
2945
+
2946
+ ## \u4FEE\u590D\u65B9\u6848
2947
+ - [ ] \u5206\u6790\u6839\u672C\u539F\u56E0
2948
+ - [ ] \u8BBE\u8BA1\u4FEE\u590D\u65B9\u6848
2949
+ - [ ] \u5B9E\u65BD\u4FEE\u590D
2950
+ - [ ] \u7F16\u5199\u6D4B\u8BD5
2951
+ - [ ] \u9A8C\u8BC1\u4FEE\u590D
2952
+
2953
+ ## \u9A8C\u8BC1\u6E05\u5355
2954
+ - [ ] \u672C\u5730\u6D4B\u8BD5\u901A\u8FC7
2955
+ - [ ] \u5355\u5143\u6D4B\u8BD5\u8986\u76D6
2956
+ - [ ] \u56DE\u5F52\u6D4B\u8BD5\u901A\u8FC7
2957
+ - [ ] \u66F4\u65B0\u76F8\u5173\u6587\u6863
2958
+
2959
+ ---
2960
+ *\u521B\u5EFA\u4E8E: ${data.timestamp} by team-cli*
2961
+ `;
2962
+ }
2963
+ function formatHotfixDocument(data) {
2964
+ return `# Hotfix: ${data.description}
2965
+
2966
+ ## Hotfix \u4FE1\u606F
2967
+ - **ID**: ${data.id}
2968
+ - **\u521B\u5EFA\u65F6\u95F4**: ${data.timestamp}
2969
+
2970
+ ## \u95EE\u9898\u63CF\u8FF0
2971
+ ${data.description}
2972
+
2973
+ ## \u5F71\u54CD\u8303\u56F4
2974
+ ${data.scope}
2975
+
2976
+ ## \u4E34\u65F6\u89E3\u51B3\u65B9\u6848
2977
+ ${data.solution}
2978
+
2979
+ ## \u4FEE\u590D\u5206\u652F
2980
+ \`\`\`
2981
+ git checkout ${data.branch}
2982
+ \`\`\`
2983
+
2984
+ ## \u540E\u7EED\u6B65\u9AA4
2985
+ - [ ] \u5B9E\u65BD\u7D27\u6025\u4FEE\u590D
2986
+ - [ ] \u63A8\u9001\u5230\u8FDC\u7A0B
2987
+ - [ ] \u521B\u5EFA PR \u8FDB\u884C code review
2988
+ - [ ] \u7D27\u6025\u5408\u5E76\u5230\u4E3B\u5206\u652F
2989
+ - [ ] \u521B\u5EFA\u89C4\u8303 bugfix \u8BB0\u5F55
2990
+ - [ ] \u8BA1\u5212\u5F7B\u5E95\u4FEE\u590D\u65B9\u6848
2991
+
2992
+ ---
2993
+ *\u521B\u5EFA\u4E8E: ${data.timestamp} by team-cli*
2994
+ `;
2995
+ }
2996
+
2997
+ // src/commands/lint.ts
2998
+ import { Command as Command7 } from "commander";
2999
+ import { execa as execa2 } from "execa";
3000
+ var lintCommand = new Command7("lint").option("--fix", "\u81EA\u52A8\u4FEE\u590D\u95EE\u9898").description("\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5 (\u524D\u7AEF + \u540E\u7AEF)").action(async (options) => {
3001
+ try {
3002
+ logger.header("\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5");
3003
+ logger.newLine();
3004
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
3005
+ if (!hasTechStack) {
3006
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
3007
+ logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
3008
+ process.exit(1);
3009
+ }
3010
+ const results = {
3011
+ frontend: { exists: false, passed: false, errors: [] },
3012
+ backend: { exists: false, passed: false, errors: [] }
3013
+ };
3014
+ const frontendExists = await FileUtils.exists("frontend/package.json");
3015
+ if (frontendExists) {
3016
+ results.frontend.exists = true;
3017
+ try {
3018
+ logger.step("\u68C0\u67E5\u524D\u7AEF\u4EE3\u7801...");
3019
+ if (options.fix) {
3020
+ await execa2("npm", ["run", "lint", "--", "--fix"], {
3021
+ cwd: "frontend",
3022
+ stdio: "inherit"
3023
+ });
3024
+ } else {
3025
+ await execa2("npm", ["run", "lint"], {
3026
+ cwd: "frontend",
3027
+ stdio: "inherit"
3028
+ });
3029
+ }
3030
+ await execa2("npx", ["tsc", "--noEmit"], {
3031
+ cwd: "frontend",
3032
+ stdio: "pipe"
3033
+ });
3034
+ results.frontend.passed = true;
3035
+ logger.success("\u524D\u7AEF\u4EE3\u7801\u68C0\u67E5\u901A\u8FC7");
3036
+ } catch (error) {
3037
+ results.frontend.errors.push(error.message);
3038
+ logger.error("\u524D\u7AEF\u4EE3\u7801\u68C0\u67E5\u5931\u8D25");
3039
+ }
3040
+ } else {
3041
+ logger.info("\u672A\u627E\u5230\u524D\u7AEF\u9879\u76EE (frontend/package.json)");
3042
+ }
3043
+ logger.newLine();
3044
+ const backendExists = await FileUtils.exists("backend/build.gradle") || await FileUtils.exists("backend/pom.xml");
3045
+ if (backendExists) {
3046
+ results.backend.exists = true;
3047
+ const hasGradle = await FileUtils.exists("backend/build.gradle") || await FileUtils.exists("backend/build.gradle.kts");
3048
+ const hasMaven = await FileUtils.exists("backend/pom.xml");
3049
+ try {
3050
+ logger.step("\u68C0\u67E5\u540E\u7AEF\u4EE3\u7801...");
3051
+ if (hasGradle) {
3052
+ if (options.fix) {
3053
+ await execa2("./gradlew", ["spotlessApply"], {
3054
+ cwd: "backend",
3055
+ stdio: "inherit"
3056
+ });
3057
+ }
3058
+ await execa2("./gradlew", ["checkstyleMain", "compileJava"], {
3059
+ cwd: "backend",
3060
+ stdio: "inherit"
3061
+ });
3062
+ } else if (hasMaven) {
3063
+ if (options.fix) {
3064
+ await execa2("./mvnw", ["spotless:apply"], {
3065
+ cwd: "backend",
3066
+ stdio: "inherit"
3067
+ });
3068
+ }
3069
+ await execa2("./mvnw", ["checkstyle:check", "compile"], {
3070
+ cwd: "backend",
3071
+ stdio: "inherit"
3072
+ });
3073
+ }
3074
+ results.backend.passed = true;
3075
+ logger.success("\u540E\u7AEF\u4EE3\u7801\u68C0\u67E5\u901A\u8FC7");
3076
+ } catch (error) {
3077
+ results.backend.errors.push(error.message);
3078
+ logger.error("\u540E\u7AEF\u4EE3\u7801\u68C0\u67E5\u5931\u8D25");
3079
+ }
3080
+ } else {
3081
+ logger.info("\u672A\u627E\u5230\u540E\u7AEF\u9879\u76EE (build.gradle \u6216 pom.xml)");
3082
+ }
3083
+ logger.newLine();
3084
+ logger.separator("=", 50);
3085
+ logger.newLine();
3086
+ let hasErrors = false;
3087
+ if (results.frontend.exists) {
3088
+ if (results.frontend.passed) {
3089
+ logger.success("\u524D\u7AEF: \u2713 \u901A\u8FC7");
3090
+ } else {
3091
+ logger.error("\u524D\u7AEF: \u2717 \u5931\u8D25");
3092
+ hasErrors = true;
3093
+ }
3094
+ }
3095
+ if (results.backend.exists) {
3096
+ if (results.backend.passed) {
3097
+ logger.success("\u540E\u7AEF: \u2713 \u901A\u8FC7");
3098
+ } else {
3099
+ logger.error("\u540E\u7AEF: \u2717 \u5931\u8D25");
3100
+ hasErrors = true;
3101
+ }
3102
+ }
3103
+ logger.newLine();
3104
+ if (hasErrors) {
3105
+ logger.error("\u4EE3\u7801\u68C0\u67E5\u5931\u8D25");
3106
+ logger.info("\u8FD0\u884C 'team-cli lint --fix' \u5C1D\u8BD5\u81EA\u52A8\u4FEE\u590D");
3107
+ process.exit(1);
3108
+ } else {
3109
+ logger.success("\u4EE3\u7801\u68C0\u67E5\u5168\u90E8\u901A\u8FC7");
3110
+ }
3111
+ } catch (error) {
3112
+ logger.error(`\u4EE3\u7801\u68C0\u67E5\u5931\u8D25: ${error.message}`);
3113
+ if (process.env.DEBUG) {
3114
+ console.error(error);
3115
+ }
3116
+ process.exit(1);
3117
+ }
3118
+ });
3119
+
3120
+ // src/commands/status.ts
3121
+ import { Command as Command8 } from "commander";
3122
+ import path8 from "path";
3123
+ var statusCommand = new Command8("status").description("\u67E5\u770B\u9879\u76EE\u72B6\u6001").action(async () => {
3124
+ try {
3125
+ logger.header("\u9879\u76EE\u72B6\u6001");
3126
+ logger.newLine();
3127
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
3128
+ if (!hasTechStack) {
3129
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
3130
+ logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
3131
+ process.exit(1);
3132
+ }
3133
+ await displayProjectInfo();
3134
+ await displayFeatureInventory();
3135
+ await displayGitStatus();
3136
+ await displayRecentActivity();
3137
+ } catch (error) {
3138
+ logger.error(`\u83B7\u53D6\u72B6\u6001\u5931\u8D25: ${error.message}`);
3139
+ if (process.env.DEBUG) {
3140
+ console.error(error);
3141
+ }
3142
+ process.exit(1);
3143
+ }
3144
+ });
3145
+ async function displayProjectInfo() {
3146
+ logger.info("\u9879\u76EE\u4FE1\u606F:");
3147
+ logger.newLine();
3148
+ if (await FileUtils.exists("AI_MEMORY.md")) {
3149
+ const content = await FileUtils.read("AI_MEMORY.md");
3150
+ const projectName = content.match(/项目名称.*[::]\s*(.+)/);
3151
+ const currentPhase = content.match(/当前阶段.*[::]\s*(.+)/);
3152
+ const lastUpdated = content.match(/最后更新.*[::]\s*(.+)/);
3153
+ if (projectName) logger.step(`\u540D\u79F0: ${projectName[1].trim()}`);
3154
+ if (currentPhase) logger.step(`\u9636\u6BB5: ${currentPhase[1].trim()}`);
3155
+ if (lastUpdated) logger.step(`\u66F4\u65B0: ${lastUpdated[1].trim()}`);
3156
+ } else {
3157
+ logger.step("AI_MEMORY.md \u4E0D\u5B58\u5728");
3158
+ }
3159
+ logger.newLine();
3160
+ }
3161
+ async function displayFeatureInventory() {
3162
+ logger.info("\u529F\u80FD\u6E05\u5355:");
3163
+ logger.newLine();
3164
+ const specDir = "docs/specs";
3165
+ const exists = await FileUtils.exists(specDir);
3166
+ if (!exists) {
3167
+ logger.info(" (\u65E0 spec \u6587\u4EF6)");
3168
+ logger.newLine();
3169
+ return;
3170
+ }
3171
+ const files = await FileUtils.findFiles("*.md", specDir);
3172
+ const specs = files.filter((f) => !f.includes("template"));
3173
+ if (specs.length === 0) {
3174
+ logger.info(" (\u65E0 spec \u6587\u4EF6)");
3175
+ logger.newLine();
3176
+ return;
3177
+ }
3178
+ const inventory = [];
3179
+ for (const file of specs) {
3180
+ const filePath = path8.join(specDir, file);
3181
+ const content = await FileUtils.read(filePath);
3182
+ const status = parseSpecStatus2(content);
3183
+ inventory.push({
3184
+ name: file.replace(".md", ""),
3185
+ status,
3186
+ progress: getProgress(content)
3187
+ });
3188
+ }
3189
+ const tableData = inventory.map((item) => [
3190
+ item.name,
3191
+ item.status,
3192
+ item.progress
3193
+ ]);
3194
+ logger.table(["\u529F\u80FD", "\u72B6\u6001", "\u8FDB\u5EA6"], tableData);
3195
+ logger.newLine();
3196
+ const completed = inventory.filter((i) => i.status === "\u5DF2\u5B8C\u6210").length;
3197
+ const inProgress = inventory.filter((i) => i.status === "\u8FDB\u884C\u4E2D").length;
3198
+ const pending = inventory.filter((i) => i.status === "\u672A\u5F00\u59CB").length;
3199
+ logger.info(`\u603B\u8BA1: ${inventory.length} | \u5DF2\u5B8C\u6210: ${completed} | \u8FDB\u884C\u4E2D: ${inProgress} | \u672A\u5F00\u59CB: ${pending}`);
3200
+ logger.newLine();
3201
+ }
3202
+ async function displayGitStatus() {
3203
+ const isRepo = await GitUtils.isGitRepo();
3204
+ if (!isRepo) {
3205
+ logger.info("Git \u72B6\u6001: \u975E Git \u4ED3\u5E93");
3206
+ logger.newLine();
3207
+ return;
3208
+ }
3209
+ try {
3210
+ const branch = await GitUtils.getCurrentBranch();
3211
+ const commit = await GitUtils.getCurrentCommit();
3212
+ logger.info("Git \u72B6\u6001:");
3213
+ logger.step(`\u5206\u652F: ${branch}`);
3214
+ logger.step(`\u63D0\u4EA4: ${commit}`);
3215
+ logger.newLine();
3216
+ } catch {
3217
+ logger.info("Git \u72B6\u6001: \u65E0\u6CD5\u83B7\u53D6");
3218
+ logger.newLine();
3219
+ }
3220
+ }
3221
+ async function displayRecentActivity() {
3222
+ logger.info("\u6700\u8FD1\u6D3B\u52A8:");
3223
+ logger.newLine();
3224
+ const sessionDir = "docs/sessions";
3225
+ const exists = await FileUtils.exists(sessionDir);
3226
+ if (!exists) {
3227
+ logger.info(" (\u65E0\u4F1A\u8BDD\u8BB0\u5F55)");
3228
+ logger.newLine();
3229
+ return;
3230
+ }
3231
+ const files = await FileUtils.findFiles("*.md", sessionDir);
3232
+ if (files.length === 0) {
3233
+ logger.info(" (\u65E0\u4F1A\u8BDD\u8BB0\u5F55)");
3234
+ logger.newLine();
3235
+ return;
3236
+ }
3237
+ const sorted = files.sort().reverse().slice(0, 5);
3238
+ for (const file of sorted) {
3239
+ const filePath = path8.join(sessionDir, file);
3240
+ const stat = await FileUtils.read(filePath);
3241
+ const specMatch = stat.match(/\*\*Spec\*\*:\s*(.+)/);
3242
+ const spec = specMatch ? specMatch[1].trim() : "\u672A\u77E5";
3243
+ const match = file.match(/(\d{4}-\d{2}-\d{2})/);
3244
+ const date = match ? match[1] : "\u672A\u77E5";
3245
+ logger.step(`${date} - ${spec}`);
3246
+ }
3247
+ logger.newLine();
3248
+ if (files.length > 5) {
3249
+ logger.info(`(\u8FD8\u6709 ${files.length - 5} \u4E2A\u5386\u53F2\u4F1A\u8BDD\u8BB0\u5F55)`);
3250
+ logger.newLine();
3251
+ }
3252
+ }
3253
+ function parseSpecStatus2(spec) {
3254
+ const statusMatch = spec.match(/状态.*[::]\s*(.+)/);
3255
+ if (statusMatch) {
3256
+ const status = statusMatch[1].replace(/\*\*/g, "").trim();
3257
+ if (status.includes("\u5DF2\u5B8C\u6210")) return "\u2713 \u5DF2\u5B8C\u6210";
3258
+ if (status.includes("\u8FDB\u884C\u4E2D")) return "\u27F3 \u8FDB\u884C\u4E2D";
3259
+ if (status.includes("\u5DF2\u62C6\u5206")) return "\u25C9 \u5DF2\u62C6\u5206";
3260
+ }
3261
+ return "\u25CB \u672A\u5F00\u59CB";
3262
+ }
3263
+ function getProgress(spec) {
3264
+ const milestoneMatches = spec.match(/###\s+Milestone\s+\d+:/g);
3265
+ const milestones = milestoneMatches ? milestoneMatches.length : 0;
3266
+ const todoMatches = spec.match(/-\s+\[[ x ]\]/g);
3267
+ const totalTodos = todoMatches ? todoMatches.length : 0;
3268
+ const completedMatches = spec.match(/-\s+\[x\]/g);
3269
+ const completedTodos = completedMatches ? completedMatches.length : 0;
3270
+ if (totalTodos === 0) {
3271
+ return "-";
3272
+ }
3273
+ return `${completedTodos}/${totalTodos}`;
3274
+ }
3275
+
3276
+ // src/commands/detect-deps.ts
3277
+ import { Command as Command9 } from "commander";
3278
+ import path9 from "path";
3279
+ import inquirer6 from "inquirer";
3280
+ var detectDepsCommand = new Command9("detect-deps").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB").action(async (specFile) => {
3281
+ try {
3282
+ logger.header("\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB");
3283
+ logger.newLine();
3284
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
3285
+ if (!hasTechStack) {
3286
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
3287
+ logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
3288
+ process.exit(1);
3289
+ }
3290
+ if (!specFile) {
3291
+ logger.info("\u672A\u6307\u5B9A spec \u6587\u4EF6\uFF0C\u5C06\u626B\u63CF\u6240\u6709 specs...");
3292
+ logger.newLine();
3293
+ const specsDir = "docs/specs";
3294
+ const exists = await FileUtils.exists(specsDir);
3295
+ if (!exists) {
3296
+ logger.error("\u672A\u627E\u5230 spec \u6587\u4EF6");
3297
+ process.exit(1);
3298
+ }
3299
+ const files = await FileUtils.findFiles("*.md", specsDir);
3300
+ const specs = files.filter((f) => f !== "template.md");
3301
+ if (specs.length === 0) {
3302
+ logger.error("\u672A\u627E\u5230 spec \u6587\u4EF6");
3303
+ process.exit(1);
3304
+ }
3305
+ for (const spec of specs) {
3306
+ const specPath = path9.join(specsDir, spec);
3307
+ logger.step(`\u5904\u7406: ${spec}`);
3308
+ await detectDependencies(specPath);
3309
+ logger.newLine();
3310
+ }
3311
+ } else {
3312
+ const exists = await FileUtils.exists(specFile);
3313
+ if (!exists) {
3314
+ logger.error(`Spec \u6587\u4EF6\u4E0D\u5B58\u5728: ${specFile}`);
3315
+ process.exit(1);
3316
+ }
3317
+ await detectDependencies(specFile);
3318
+ }
3319
+ logger.header("\u4F9D\u8D56\u68C0\u6D4B\u5B8C\u6210");
3320
+ } catch (error) {
3321
+ logger.error(`\u4F9D\u8D56\u68C0\u6D4B\u5931\u8D25: ${error.message}`);
3322
+ if (process.env.DEBUG) {
3323
+ console.error(error);
3324
+ }
3325
+ process.exit(1);
3326
+ }
3327
+ });
3328
+ async function detectDependencies(specFile) {
3329
+ logger.step("\u81EA\u52A8\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB...");
3330
+ const projectDir = ".";
3331
+ const allDeps = /* @__PURE__ */ new Set();
3332
+ const backendDir = path9.join(projectDir, "backend");
3333
+ const backendExists = await FileUtils.exists(backendDir);
3334
+ if (backendExists) {
3335
+ const apiDeps = await scanBackendApiCalls(backendDir);
3336
+ apiDeps.forEach((d) => allDeps.add(d));
3337
+ const entityDeps = await scanBackendEntityRelations(backendDir);
3338
+ entityDeps.forEach((d) => allDeps.add(d));
3339
+ const serviceDeps = await scanBackendServiceRefs(backendDir);
3340
+ serviceDeps.forEach((d) => allDeps.add(d));
3341
+ }
3342
+ const frontendDir = path9.join(projectDir, "frontend");
3343
+ const frontendExists = await FileUtils.exists(frontendDir);
3344
+ if (frontendExists) {
3345
+ const frontendDeps = await scanFrontendApiCalls(frontendDir);
3346
+ frontendDeps.forEach((d) => allDeps.add(d));
3347
+ }
3348
+ if (allDeps.size === 0) {
3349
+ logger.info("\u672A\u68C0\u6D4B\u5230\u660E\u786E\u7684\u4F9D\u8D56\u5173\u7CFB");
3350
+ return;
3351
+ }
3352
+ const detectedSpecs = [];
3353
+ for (const dep of allDeps) {
3354
+ const matchedSpec = await findSpecByKeyword(dep, "docs/specs");
3355
+ if (matchedSpec && !detectedSpecs.includes(matchedSpec)) {
3356
+ detectedSpecs.push(matchedSpec);
3357
+ }
3358
+ }
3359
+ if (detectedSpecs.length === 0) {
3360
+ logger.info("\u68C0\u6D4B\u5230\u4F9D\u8D56\uFF0C\u4F46\u672A\u627E\u5230\u5BF9\u5E94\u7684 spec \u6587\u4EF6");
3361
+ return;
3362
+ }
3363
+ logger.success(`\u68C0\u6D4B\u5230 ${detectedSpecs.length} \u4E2A\u6F5C\u5728\u4F9D\u8D56:`);
3364
+ for (const spec of detectedSpecs) {
3365
+ logger.step(`- ${spec}`);
3366
+ }
3367
+ logger.newLine();
3368
+ const answers = await inquirer6.prompt([
3369
+ {
3370
+ type: "confirm",
3371
+ name: "autoUpdate",
3372
+ message: "\u662F\u5426\u81EA\u52A8\u66F4\u65B0\u4F9D\u8D56\u5173\u7CFB\u5230 spec \u6587\u4EF6?",
3373
+ default: true
3374
+ }
3375
+ ]);
3376
+ if (answers.autoUpdate) {
3377
+ await updateSpecDependencies(specFile, detectedSpecs);
3378
+ logger.success("\u4F9D\u8D56\u5173\u7CFB\u5DF2\u66F4\u65B0");
3379
+ }
3380
+ }
3381
+ async function scanBackendApiCalls(backendDir) {
3382
+ const deps = [];
3383
+ const srcDir = path9.join(backendDir, "src");
3384
+ try {
3385
+ const javaFiles = await FileUtils.findFiles("*.java", srcDir);
3386
+ for (const file of javaFiles) {
3387
+ const filePath = path9.join(srcDir, file);
3388
+ const content = await FileUtils.read(filePath);
3389
+ const pathRegex = /"(\/api\/[^"]+)"/g;
3390
+ let match;
3391
+ while ((match = pathRegex.exec(content)) !== null) {
3392
+ const fullPath = match[1];
3393
+ const parts = fullPath.split("/").filter(Boolean);
3394
+ if (parts.length > 1) {
3395
+ deps.push(parts[1]);
3396
+ }
3397
+ }
3398
+ }
3399
+ } catch (error) {
3400
+ }
3401
+ return deps;
3402
+ }
3403
+ async function scanBackendEntityRelations(backendDir) {
3404
+ const deps = [];
3405
+ const srcDir = path9.join(backendDir, "src");
3406
+ try {
3407
+ const javaFiles = await FileUtils.findFiles("*.java", srcDir);
3408
+ for (const file of javaFiles) {
3409
+ const filePath = path9.join(srcDir, file);
3410
+ const content = await FileUtils.read(filePath);
3411
+ if (content.includes("@JoinColumn") || content.includes("@ManyToOne") || content.includes("@OneToMany")) {
3412
+ const typeRegex = /type\s*=\s*(\w+)/g;
3413
+ let match;
3414
+ while ((match = typeRegex.exec(content)) !== null) {
3415
+ deps.push(match[1].toLowerCase());
3416
+ }
3417
+ }
3418
+ }
3419
+ } catch (error) {
3420
+ }
3421
+ return deps;
3422
+ }
3423
+ async function scanBackendServiceRefs(backendDir) {
3424
+ const deps = [];
3425
+ const srcDir = path9.join(backendDir, "src");
3426
+ try {
3427
+ const javaFiles = await FileUtils.findFiles("*.java", srcDir);
3428
+ for (const file of javaFiles) {
3429
+ const filePath = path9.join(srcDir, file);
3430
+ const content = await FileUtils.read(filePath);
3431
+ const serviceRegex = /private\s+(\w+)Service/g;
3432
+ let match;
3433
+ while ((match = serviceRegex.exec(content)) !== null) {
3434
+ const serviceName = match[1];
3435
+ const moduleName = serviceName.replace(/Service$/, "").toLowerCase();
3436
+ deps.push(moduleName);
3437
+ }
3438
+ }
3439
+ } catch (error) {
3440
+ }
3441
+ return deps;
3442
+ }
3443
+ async function scanFrontendApiCalls(frontendDir) {
3444
+ const deps = [];
3445
+ const srcDir = path9.join(frontendDir, "src");
3446
+ try {
3447
+ const tsFiles = await FileUtils.findFiles("*.{ts,tsx,js,jsx}", srcDir);
3448
+ for (const file of tsFiles) {
3449
+ const filePath = path9.join(srcDir, file);
3450
+ const content = await FileUtils.read(filePath);
3451
+ const pathRegex = /"(\/api\/[^"]+)"/g;
3452
+ let match;
3453
+ while ((match = pathRegex.exec(content)) !== null) {
3454
+ const fullPath = match[1];
3455
+ const parts = fullPath.split("/").filter(Boolean);
3456
+ if (parts.length > 1) {
3457
+ deps.push(parts[1]);
3458
+ }
3459
+ }
3460
+ }
3461
+ } catch (error) {
3462
+ }
3463
+ return deps;
3464
+ }
3465
+ async function findSpecByKeyword(keyword, specsDir) {
3466
+ const exists = await FileUtils.exists(specsDir);
3467
+ if (!exists) {
3468
+ return null;
3469
+ }
3470
+ try {
3471
+ const files = await FileUtils.findFiles("*.md", specsDir);
3472
+ for (const file of files) {
3473
+ if (file === "template.md") continue;
3474
+ const name = file.replace(".md", "");
3475
+ if (name.includes(keyword) || keyword.includes(name)) {
3476
+ return name;
3477
+ }
3478
+ }
3479
+ } catch (error) {
3480
+ }
3481
+ return null;
3482
+ }
3483
+ async function updateSpecDependencies(specFile, deps) {
3484
+ let content = await FileUtils.read(specFile);
3485
+ if (!content.includes("## \u4F9D\u8D56\u5173\u7CFB")) {
3486
+ const targetSection = "## \u80CC\u666F\u4E0E\u76EE\u6807";
3487
+ const insertIndex = content.indexOf(targetSection);
3488
+ if (insertIndex !== -1) {
3489
+ const depsSection = `
3490
+ ## \u4F9D\u8D56\u5173\u7CFB
3491
+
3492
+ **\u524D\u7F6E\u4F9D\u8D56**:
3493
+ ${deps.map((d) => ` - [x] ${d}`).join("\n")}
3494
+
3495
+ **\u88AB\u4F9D\u8D56\u4E8E**:
3496
+ - (\u81EA\u52A8\u751F\u6210\uFF0C\u8868\u793A\u54EA\u4E9B spec \u4F9D\u8D56\u672C\u529F\u80FD)
3497
+
3498
+ `;
3499
+ content = content.slice(0, insertIndex) + depsSection + content.slice(insertIndex);
3500
+ }
3501
+ } else {
3502
+ const lines = content.split("\n");
3503
+ let inDeps = false;
3504
+ const result = [];
3505
+ for (let i = 0; i < lines.length; i++) {
3506
+ const line = lines[i];
3507
+ if (line.startsWith("## \u4F9D\u8D56\u5173\u7CFB")) {
3508
+ inDeps = true;
3509
+ result.push(line);
3510
+ result.push("");
3511
+ result.push("**\u524D\u7F6E\u4F9D\u8D56**:");
3512
+ for (const dep of deps) {
3513
+ result.push(` - [x] ${dep}`);
3514
+ }
3515
+ result.push("");
3516
+ result.push("**\u88AB\u4F9D\u8D56\u4E8E**:");
3517
+ result.push(" - (\u81EA\u52A8\u751F\u6210\uFF0C\u8868\u793A\u54EA\u4E9B spec \u4F9D\u8D56\u672C\u529F\u80FD)");
3518
+ continue;
3519
+ }
3520
+ if (inDeps) {
3521
+ if (line.startsWith("## ") && !line.startsWith("## \u4F9D\u8D56\u5173\u7CFB")) {
3522
+ inDeps = false;
3523
+ result.push(line);
3524
+ }
3525
+ continue;
3526
+ }
3527
+ result.push(line);
3528
+ }
3529
+ content = result.join("\n");
3530
+ }
3531
+ await FileUtils.write(specFile, content);
3532
+ }
3533
+
3534
+ // src/commands/sync-memory.ts
3535
+ import { Command as Command10 } from "commander";
3536
+ import path10 from "path";
3537
+ var syncMemoryCommand = new Command10("sync-memory").description("\u540C\u6B65 AI_MEMORY.md").action(async () => {
3538
+ try {
3539
+ logger.header("\u540C\u6B65 AI_MEMORY.md");
3540
+ logger.newLine();
3541
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
3542
+ if (!hasTechStack) {
3543
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
3544
+ logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
3545
+ process.exit(1);
3546
+ }
3547
+ const aiMemoryFile = "AI_MEMORY.md";
3548
+ const exists = await FileUtils.exists(aiMemoryFile);
3549
+ if (!exists) {
3550
+ logger.info("AI_MEMORY.md \u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u540C\u6B65");
3551
+ process.exit(0);
3552
+ }
3553
+ await syncFeatureInventory(aiMemoryFile, ".");
3554
+ await syncApiInventory(aiMemoryFile, ".");
3555
+ await syncDataModels(aiMemoryFile, ".");
3556
+ await updateSyncTime(aiMemoryFile);
3557
+ logger.success("AI_MEMORY.md \u5DF2\u540C\u6B65");
3558
+ } catch (error) {
3559
+ logger.error(`\u540C\u6B65\u5931\u8D25: ${error.message}`);
3560
+ if (process.env.DEBUG) {
3561
+ console.error(error);
3562
+ }
3563
+ process.exit(1);
3564
+ }
3565
+ });
3566
+ async function syncFeatureInventory(aiMemoryFile, projectDir) {
3567
+ logger.step("\u540C\u6B65\u529F\u80FD\u6E05\u5355...");
3568
+ const specsDir = path10.join(projectDir, "docs/specs");
3569
+ const exists = await FileUtils.exists(specsDir);
3570
+ if (!exists) {
3571
+ return;
3572
+ }
3573
+ const files = await FileUtils.findFiles("*.md", specsDir);
3574
+ const specs = files.filter((f) => f !== "template.md");
3575
+ const lines = [];
3576
+ lines.push("## \u529F\u80FD\u6E05\u5355 (Feature Inventory)");
3577
+ lines.push("");
3578
+ lines.push("| \u529F\u80FD | Spec \u6587\u4EF6 | \u72B6\u6001 | \u8FDB\u5EA6 | \u5B8C\u6210\u65E5\u671F | \u5907\u6CE8 |");
3579
+ lines.push("|------|----------|------|------|---------|------|");
3580
+ for (const specFile of specs) {
3581
+ const name = specFile.replace(".md", "");
3582
+ const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
3583
+ const specPath = path10.join(specsDir, specFile);
3584
+ const content = await FileUtils.read(specPath);
3585
+ const status = parseSpecStatus3(content);
3586
+ const progress = getSpecProgress(content);
3587
+ let completionDate = "-";
3588
+ if (status === "\u2705 \u5DF2\u5B8C\u6210") {
3589
+ const dateMatch = content.match(/完成日期.*[::]\s*(.+)/);
3590
+ if (dateMatch) {
3591
+ completionDate = dateMatch[1].trim();
3592
+ }
3593
+ }
3594
+ lines.push(`| ${displayName} | ${name}.md | ${status} | ${progress} | ${completionDate} | |`);
3595
+ }
3596
+ const newContent = lines.join("\n") + "\n";
3597
+ await replaceOrInsertSection(aiMemoryFile, "## \u529F\u80FD\u6E05\u5355", newContent);
3598
+ }
3599
+ async function syncApiInventory(aiMemoryFile, projectDir) {
3600
+ logger.step("\u540C\u6B65 API \u5217\u8868...");
3601
+ const backendDir = path10.join(projectDir, "backend");
3602
+ const exists = await FileUtils.exists(backendDir);
3603
+ if (!exists) {
3604
+ return;
3605
+ }
3606
+ const lines = [];
3607
+ lines.push("## API \u5217\u8868 (API Inventory)");
3608
+ lines.push("");
3609
+ lines.push("> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u626B\u63CF\u540E\u7AEF Controller \u751F\u6210");
3610
+ lines.push("");
3611
+ const srcDir = path10.join(backendDir, "src");
3612
+ const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
3613
+ if (controllers.length === 0) {
3614
+ lines.push("\u6682\u65E0 API");
3615
+ } else {
3616
+ for (const controllerFile of controllers) {
3617
+ const controllerPath = path10.join(srcDir, controllerFile);
3618
+ const controllerName = controllerFile.replace(".java", "");
3619
+ const module = controllerName.replace(/Controller$/, "").toLowerCase();
3620
+ lines.push(`### ${module} \u6A21\u5757`);
3621
+ lines.push("");
3622
+ lines.push("| \u65B9\u6CD5 | \u8DEF\u5F84 | \u8BF4\u660E | \u72B6\u6001 | \u65E5\u671F |");
3623
+ lines.push("|------|------|------|------|------|");
3624
+ const apis = await scanControllerApis(controllerPath);
3625
+ for (const api of apis) {
3626
+ lines.push(`| ${api.method} | ${api.path} | ${api.description} | \u2705 | ${api.date} |`);
3627
+ }
3628
+ lines.push("");
3629
+ }
3630
+ }
3631
+ const newContent = lines.join("\n");
3632
+ await replaceOrInsertSection(aiMemoryFile, "## API \u5217\u8868", newContent);
3633
+ }
3634
+ async function scanControllerApis(controllerPath) {
3635
+ const apis = [];
3636
+ const content = await FileUtils.read(controllerPath);
3637
+ let classPath = "";
3638
+ const classRequestMappingMatch = content.match(/@RequestMapping\("([^"]+)"\)/);
3639
+ if (classRequestMappingMatch) {
3640
+ classPath = classRequestMappingMatch[1];
3641
+ }
3642
+ const methodRegex = /@(GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping)\("([^"]+)"\)\s*\n\s*public\s+(\w+)\s*\(([^)]*)\)/g;
3643
+ let match;
3644
+ while ((match = methodRegex.exec(content)) !== null) {
3645
+ const mappingType = match[1];
3646
+ const methodPath = match[2];
3647
+ const methodName = match[3];
3648
+ let httpMethod = "";
3649
+ switch (mappingType) {
3650
+ case "GetMapping":
3651
+ httpMethod = "GET";
3652
+ break;
3653
+ case "PostMapping":
3654
+ httpMethod = "POST";
3655
+ break;
3656
+ case "PutMapping":
3657
+ httpMethod = "PUT";
3658
+ break;
3659
+ case "DeleteMapping":
3660
+ httpMethod = "DELETE";
3661
+ break;
3662
+ case "PatchMapping":
3663
+ httpMethod = "PATCH";
3664
+ break;
3665
+ }
3666
+ let fullPath = methodPath;
3667
+ if (classPath && !methodPath.startsWith("/api")) {
3668
+ fullPath = `${classPath}${methodPath}`;
3669
+ }
3670
+ const description = extractMethodComment(content, methodName);
3671
+ apis.push({
3672
+ method: httpMethod,
3673
+ path: fullPath,
3674
+ description,
3675
+ date: DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD")
3676
+ });
3677
+ }
3678
+ return apis;
3679
+ }
3680
+ function extractMethodComment(content, methodName) {
3681
+ const methodIndex = content.indexOf(`${methodName}(`);
3682
+ if (methodIndex === -1) {
3683
+ return "";
3684
+ }
3685
+ const beforeMethod = content.substring(Math.max(0, methodIndex - 500), methodIndex);
3686
+ const commentMatch = beforeMethod.match(/\*\s*([^\n*]+)/g);
3687
+ if (commentMatch && commentMatch.length > 0) {
3688
+ return commentMatch[0].replace(/\*\s?/, "").trim();
3689
+ }
3690
+ return "";
3691
+ }
3692
+ async function syncDataModels(aiMemoryFile, projectDir) {
3693
+ logger.step("\u540C\u6B65\u6570\u636E\u6A21\u578B...");
3694
+ const backendDir = path10.join(projectDir, "backend");
3695
+ const exists = await FileUtils.exists(backendDir);
3696
+ if (!exists) {
3697
+ return;
3698
+ }
3699
+ const lines = [];
3700
+ lines.push("## \u6570\u636E\u6A21\u578B (Data Models)");
3701
+ lines.push("");
3702
+ lines.push("> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u626B\u63CF\u540E\u7AEF Entity \u751F\u6210");
3703
+ lines.push("");
3704
+ const srcDir = path10.join(backendDir, "src");
3705
+ const entities = await FileUtils.findFiles("*Entity.java", srcDir);
3706
+ if (entities.length === 0) {
3707
+ lines.push("\u6682\u65E0\u6570\u636E\u6A21\u578B");
3708
+ } else {
3709
+ lines.push("| \u6A21\u578B | \u8BF4\u660E | \u5B57\u6BB5 | \u5173\u8054 |");
3710
+ lines.push("|------|------|------|------|");
3711
+ for (const entityFile of entities) {
3712
+ const entityPath = path10.join(srcDir, entityFile);
3713
+ const entityName = entityFile.replace(".java", "").replace(/Entity$/, "");
3714
+ const displayName = entityName.split(/(?=[A-Z])/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
3715
+ const content = await FileUtils.read(entityPath);
3716
+ const classCommentMatch = content.match(/\/\*\*\s*\n([^*]|\*[^/])*\*\//);
3717
+ const description = classCommentMatch ? classCommentMatch[0].replace(/\/\*\*|\*\/|\*/g, "").trim() : "";
3718
+ const fieldCount = (content.match(/private\s+\w+/g) || []).length;
3719
+ const relations = [];
3720
+ if (content.includes("@ManyToOne")) relations.push("Many-to-One");
3721
+ if (content.includes("@OneToMany")) relations.push("One-to-Many");
3722
+ if (content.includes("@OneToOne")) relations.push("One-to-One");
3723
+ if (content.includes("@ManyToMany")) relations.push("Many-to-Many");
3724
+ lines.push(`| ${displayName} | ${description} | ${fieldCount} | ${relations.join(", ") || "-"} |`);
3725
+ }
3726
+ }
3727
+ const newContent = lines.join("\n");
3728
+ await replaceOrInsertSection(aiMemoryFile, "## \u6570\u636E\u6A21\u578B", newContent);
3729
+ }
3730
+ async function updateSyncTime(aiMemoryFile) {
3731
+ const content = await FileUtils.read(aiMemoryFile);
3732
+ const timestamp = DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD HH:mm:ss");
3733
+ let updated = content;
3734
+ if (content.includes("\u6700\u540E\u540C\u6B65")) {
3735
+ updated = content.replace(
3736
+ /最后同步.*[::]\s*.+/,
3737
+ `\u6700\u540E\u540C\u6B65: ${timestamp}`
3738
+ );
3739
+ } else {
3740
+ updated = content.trimEnd() + `
3741
+
3742
+ ---
3743
+
3744
+ *\u6700\u540E\u540C\u6B65: ${timestamp} by team-cli*
3745
+ `;
3746
+ }
3747
+ await FileUtils.write(aiMemoryFile, updated);
3748
+ }
3749
+ async function replaceOrInsertSection(aiMemoryFile, sectionTitle, newContent) {
3750
+ const content = await FileUtils.read(aiMemoryFile);
3751
+ if (content.includes(sectionTitle)) {
3752
+ const lines = content.split("\n");
3753
+ const result = [];
3754
+ let inSection = false;
3755
+ let skip = false;
3756
+ for (let i = 0; i < lines.length; i++) {
3757
+ const line = lines[i];
3758
+ if (line.startsWith(sectionTitle)) {
3759
+ inSection = true;
3760
+ result.push(newContent);
3761
+ skip = true;
3762
+ continue;
3763
+ }
3764
+ if (inSection) {
3765
+ if (line.startsWith("## ") && !line.startsWith(sectionTitle)) {
3766
+ inSection = false;
3767
+ skip = false;
3768
+ result.push(line);
3769
+ }
3770
+ continue;
3771
+ }
3772
+ if (!skip) {
3773
+ result.push(line);
3774
+ }
3775
+ }
3776
+ await FileUtils.write(aiMemoryFile, result.join("\n"));
3777
+ } else {
3778
+ await FileUtils.write(aiMemoryFile, content.trimEnd() + "\n\n" + newContent + "\n");
3779
+ }
3780
+ }
3781
+ function parseSpecStatus3(spec) {
3782
+ const statusMatch = spec.match(/状态.*[::]\s*(.+)/);
3783
+ if (statusMatch) {
3784
+ const status = statusMatch[1].replace(/\*\*/g, "").trim();
3785
+ if (status.includes("\u5DF2\u5B8C\u6210")) return "\u2705 \u5DF2\u5B8C\u6210";
3786
+ if (status.includes("\u8FDB\u884C\u4E2D")) return "\u27F3 \u8FDB\u884C\u4E2D";
3787
+ if (status.includes("\u5DF2\u62C6\u5206")) return "\u25C9 \u5DF2\u62C6\u5206";
3788
+ }
3789
+ return "\u25CB \u672A\u5F00\u59CB";
3790
+ }
3791
+ function getSpecProgress(spec) {
3792
+ const todoMatches = spec.match(/-\s+\[[ x ]\]/g);
3793
+ const totalTodos = todoMatches ? todoMatches.length : 0;
3794
+ const completedMatches = spec.match(/-\s+\[x\]/g);
3795
+ const completedTodos = completedMatches ? completedMatches.length : 0;
3796
+ if (totalTodos === 0) {
3797
+ return "-";
3798
+ }
3799
+ return `${completedTodos}/${totalTodos}`;
3800
+ }
3801
+
3802
+ // src/commands/check-api.ts
3803
+ import { Command as Command11 } from "commander";
3804
+ import path11 from "path";
3805
+ import inquirer7 from "inquirer";
3806
+ import { Listr as Listr6 } from "listr2";
3807
+ var checkApiCommand = new Command11("check-api").description("API \u68C0\u67E5\uFF08\u51B2\u7A81/\u53D8\u66F4/Registry\uFF09").action(async () => {
3808
+ try {
3809
+ logger.header("API \u68C0\u67E5");
3810
+ logger.newLine();
3811
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
3812
+ if (!hasTechStack) {
3813
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
3814
+ logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
3815
+ process.exit(1);
3816
+ }
3817
+ const answers = await inquirer7.prompt([
3818
+ {
3819
+ type: "list",
3820
+ name: "checkType",
3821
+ message: "\u9009\u62E9\u68C0\u67E5\u7C7B\u578B:",
3822
+ choices: [
3823
+ { name: "\u68C0\u6D4B API \u51B2\u7A81", value: "conflicts" },
3824
+ { name: "\u68C0\u6D4B API \u53D8\u66F4", value: "changes" },
3825
+ { name: "\u751F\u6210 API Registry", value: "registry" },
3826
+ { name: "\u5168\u90E8\u6267\u884C", value: "all" }
3827
+ ],
3828
+ default: "all"
3829
+ }
3830
+ ]);
3831
+ const tasks = new Listr6([]);
3832
+ if (answers.checkType === "conflicts" || answers.checkType === "all") {
3833
+ tasks.add({
3834
+ title: "\u68C0\u6D4B API \u51B2\u7A81",
3835
+ task: async () => {
3836
+ await checkApiConflicts(".");
3837
+ }
3838
+ });
3839
+ }
3840
+ if (answers.checkType === "changes" || answers.checkType === "all") {
3841
+ tasks.add({
3842
+ title: "\u68C0\u6D4B API \u53D8\u66F4",
3843
+ task: async () => {
3844
+ await detectApiChanges(".");
3845
+ }
3846
+ });
3847
+ }
3848
+ if (answers.checkType === "registry" || answers.checkType === "all") {
3849
+ tasks.add({
3850
+ title: "\u751F\u6210 API Registry",
3851
+ task: async () => {
3852
+ await generateApiRegistry(".");
3853
+ }
3854
+ });
3855
+ }
3856
+ await tasks.run();
3857
+ logger.newLine();
3858
+ logger.header("API \u68C0\u67E5\u5B8C\u6210");
3859
+ } catch (error) {
3860
+ logger.error(`API \u68C0\u67E5\u5931\u8D25: ${error.message}`);
3861
+ if (process.env.DEBUG) {
3862
+ console.error(error);
3863
+ }
3864
+ process.exit(1);
3865
+ }
3866
+ });
3867
+ async function checkApiConflicts(projectDir) {
3868
+ const backendDir = path11.join(projectDir, "backend");
3869
+ const exists = await FileUtils.exists(backendDir);
3870
+ if (!exists) {
3871
+ logger.info("\u672A\u627E\u5230\u540E\u7AEF\u9879\u76EE");
3872
+ return;
3873
+ }
3874
+ logger.step("\u626B\u63CF\u540E\u7AEF API...");
3875
+ logger.newLine();
3876
+ const apiMap = /* @__PURE__ */ new Map();
3877
+ const srcDir = path11.join(backendDir, "src");
3878
+ const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
3879
+ for (const controllerFile of controllers) {
3880
+ const controllerPath = path11.join(srcDir, controllerFile);
3881
+ const apis = await extractApisFromController(controllerPath);
3882
+ for (const api of apis) {
3883
+ const key = `${api.method}:${api.path}`;
3884
+ if (!apiMap.has(key)) {
3885
+ apiMap.set(key, []);
3886
+ }
3887
+ apiMap.get(key).push(controllerFile);
3888
+ }
3889
+ }
3890
+ const conflicts = [];
3891
+ for (const [key, controllers2] of apiMap.entries()) {
3892
+ if (controllers2.length > 1) {
3893
+ conflicts.push({ key, controllers: controllers2 });
3894
+ }
3895
+ }
3896
+ if (conflicts.length === 0) {
3897
+ logger.success("\u672A\u53D1\u73B0 API \u51B2\u7A81");
3898
+ } else {
3899
+ logger.error(`\u53D1\u73B0 ${conflicts.length} \u4E2A API \u51B2\u7A81:`);
3900
+ logger.newLine();
3901
+ for (const conflict of conflicts) {
3902
+ logger.error(`\u51B2\u7A81: ${conflict.key}`);
3903
+ for (const controller of conflict.controllers) {
3904
+ logger.step(` - ${controller}`);
3905
+ }
3906
+ logger.newLine();
3907
+ }
3908
+ }
3909
+ }
3910
+ async function detectApiChanges(projectDir) {
3911
+ const backendDir = path11.join(projectDir, "backend");
3912
+ const registryFile = path11.join(projectDir, "docs/api-registry.md");
3913
+ const registryExists = await FileUtils.exists(registryFile);
3914
+ if (!registryExists) {
3915
+ logger.info("API Registry \u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u53D8\u66F4\u68C0\u6D4B");
3916
+ logger.info("\u8FD0\u884C 'team-cli check-api' \u9009\u62E9 '\u751F\u6210 API Registry'");
3917
+ return;
3918
+ }
3919
+ logger.step("\u68C0\u6D4B API \u53D8\u66F4...");
3920
+ logger.newLine();
3921
+ const registryContent = await FileUtils.read(registryFile);
3922
+ const existingApis = extractApisFromRegistry(registryContent);
3923
+ const currentApis = /* @__PURE__ */ new Map();
3924
+ const srcDir = path11.join(backendDir, "src");
3925
+ const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
3926
+ for (const controllerFile of controllers) {
3927
+ const controllerPath = path11.join(srcDir, controllerFile);
3928
+ const apis = await extractApisFromController(controllerPath);
3929
+ for (const api of apis) {
3930
+ const key = `${api.method}:${api.path}`;
3931
+ currentApis.set(key, api);
3932
+ }
3933
+ }
3934
+ const added = [];
3935
+ const removed = [];
3936
+ const modified = [];
3937
+ for (const [key, api] of currentApis.entries()) {
3938
+ if (!existingApis.has(key)) {
3939
+ added.push({ method: api.method, path: api.path });
3940
+ } else {
3941
+ const existingApi = existingApis.get(key);
3942
+ if (existingApi.description !== api.description) {
3943
+ modified.push({
3944
+ method: api.method,
3945
+ path: api.path,
3946
+ oldDesc: existingApi.description,
3947
+ newDesc: api.description
3948
+ });
3949
+ }
3950
+ }
3951
+ }
3952
+ for (const [key, api] of existingApis.entries()) {
3953
+ if (!currentApis.has(key)) {
3954
+ removed.push({ method: api.method, path: api.path });
3955
+ }
3956
+ }
3957
+ let hasChanges = false;
3958
+ if (added.length > 0) {
3959
+ hasChanges = true;
3960
+ logger.success(`\u65B0\u589E API (${added.length}):`);
3961
+ for (const api of added) {
3962
+ logger.step(` + ${api.method} ${api.path}`);
3963
+ }
3964
+ logger.newLine();
3965
+ }
3966
+ if (removed.length > 0) {
3967
+ hasChanges = true;
3968
+ logger.error(`\u5220\u9664 API (${removed.length}):`);
3969
+ for (const api of removed) {
3970
+ logger.step(` - ${api.method} ${api.path}`);
3971
+ }
3972
+ logger.newLine();
3973
+ }
3974
+ if (modified.length > 0) {
3975
+ hasChanges = true;
3976
+ logger.warn(`\u4FEE\u6539 API (${modified.length}):`);
3977
+ for (const api of modified) {
3978
+ logger.step(` ~ ${api.method} ${api.path}`);
3979
+ logger.step(` \u65E7: ${api.oldDesc}`);
3980
+ logger.step(` \u65B0: ${api.newDesc}`);
3981
+ }
3982
+ logger.newLine();
3983
+ }
3984
+ if (!hasChanges) {
3985
+ logger.success("\u672A\u68C0\u6D4B\u5230 API \u53D8\u66F4");
3986
+ }
3987
+ }
3988
+ async function generateApiRegistry(projectDir) {
3989
+ const registryFile = path11.join(projectDir, "docs/api-registry.md");
3990
+ logger.step("\u626B\u63CF\u5E76\u751F\u6210 API Registry...");
3991
+ await FileUtils.ensureDir(path11.dirname(registryFile));
3992
+ const header = `# API Registry
3993
+
3994
+ > \u672C\u6587\u4EF6\u8BB0\u5F55\u6240\u6709 API \u7684\u5B9A\u4E49\u3001\u7248\u672C\u548C\u53D8\u66F4\u5386\u53F2
3995
+
3996
+ ## API \u89C4\u8303
3997
+
3998
+ ### \u57FA\u7840\u4FE1\u606F
3999
+ - **Base URL**: \`/api\`
4000
+ - **\u8BA4\u8BC1\u65B9\u5F0F**: JWT Bearer Token
4001
+ - **\u6570\u636E\u683C\u5F0F**: JSON
4002
+ - **\u5B57\u7B26\u7F16\u7801**: UTF-8
4003
+
4004
+ ### \u54CD\u5E94\u7801\u89C4\u8303
4005
+ | \u72B6\u6001\u7801 | \u8BF4\u660E |
4006
+ |--------|------|
4007
+ | 200 | \u6210\u529F |
4008
+ | 201 | \u521B\u5EFA\u6210\u529F |
4009
+ | 400 | \u8BF7\u6C42\u53C2\u6570\u9519\u8BEF |
4010
+ | 401 | \u672A\u8BA4\u8BC1 |
4011
+ | 403 | \u65E0\u6743\u9650 |
4012
+ | 404 | \u8D44\u6E90\u4E0D\u5B58\u5728 |
4013
+ | 500 | \u670D\u52A1\u5668\u9519\u8BEF |
4014
+
4015
+ ---
4016
+
4017
+ *\u6700\u540E\u66F4\u65B0: ${DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD HH:mm:ss")}*
4018
+ `;
4019
+ let content = header;
4020
+ const backendDir = path11.join(projectDir, "backend");
4021
+ const exists = await FileUtils.exists(backendDir);
4022
+ if (exists) {
4023
+ const srcDir = path11.join(backendDir, "src");
4024
+ const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
4025
+ const moduleMap = /* @__PURE__ */ new Map();
4026
+ for (const controllerFile of controllers) {
4027
+ const controllerPath = path11.join(srcDir, controllerFile);
4028
+ const controllerName = controllerFile.replace(".java", "");
4029
+ const module = controllerName.replace(/Controller$/, "").toLowerCase();
4030
+ if (!moduleMap.has(module)) {
4031
+ moduleMap.set(module, []);
4032
+ }
4033
+ const apis = await extractApisFromController(controllerPath);
4034
+ moduleMap.get(module).push(...apis);
4035
+ }
4036
+ for (const [module, apis] of moduleMap.entries()) {
4037
+ content += `
4038
+ ## ${module.charAt(0).toUpperCase() + module.slice(1)} \u6A21\u5757
4039
+
4040
+ `;
4041
+ for (const api of apis) {
4042
+ content += `### ${api.method} ${api.path}
4043
+
4044
+ `;
4045
+ content += `**\u7248\u672C**: v1.0
4046
+
4047
+ `;
4048
+ content += `**\u8BF4\u660E**: ${api.description || "-"}
4049
+
4050
+ `;
4051
+ content += `---
4052
+
4053
+ `;
4054
+ }
4055
+ }
4056
+ }
4057
+ await FileUtils.write(registryFile, content);
4058
+ logger.success(`API Registry \u5DF2\u751F\u6210: ${registryFile}`);
4059
+ }
4060
+ async function extractApisFromController(controllerPath) {
4061
+ const apis = [];
4062
+ const content = await FileUtils.read(controllerPath);
4063
+ let classPath = "";
4064
+ const classRequestMappingMatch = content.match(/@RequestMapping\("([^"]+)"\)/);
4065
+ if (classRequestMappingMatch) {
4066
+ classPath = classRequestMappingMatch[1];
4067
+ }
4068
+ const methodRegex = /@(GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping)\("([^"]+)"\)\s*\n\s*public\s+(\w+)\s*\(([^)]*)\)/g;
4069
+ let match;
4070
+ while ((match = methodRegex.exec(content)) !== null) {
4071
+ const mappingType = match[1];
4072
+ const methodPath = match[2];
4073
+ const methodName = match[3];
4074
+ let httpMethod = "";
4075
+ switch (mappingType) {
4076
+ case "GetMapping":
4077
+ httpMethod = "GET";
4078
+ break;
4079
+ case "PostMapping":
4080
+ httpMethod = "POST";
4081
+ break;
4082
+ case "PutMapping":
4083
+ httpMethod = "PUT";
4084
+ break;
4085
+ case "DeleteMapping":
4086
+ httpMethod = "DELETE";
4087
+ break;
4088
+ case "PatchMapping":
4089
+ httpMethod = "PATCH";
4090
+ break;
4091
+ }
4092
+ let fullPath = methodPath;
4093
+ if (classPath && !methodPath.startsWith("/api")) {
4094
+ fullPath = `${classPath}${methodPath}`;
4095
+ }
4096
+ const description = extractMethodComment2(content, methodName);
4097
+ apis.push({
4098
+ method: httpMethod,
4099
+ path: fullPath,
4100
+ description
4101
+ });
4102
+ }
4103
+ return apis;
4104
+ }
4105
+ function extractApisFromRegistry(registryContent) {
4106
+ const apis = /* @__PURE__ */ new Map();
4107
+ const apiRegex = /### (GET|POST|PUT|DELETE|PATCH) ([^\n]+)\n\n\*\*版本\*\*:.+?\n\n\*\*说明\*\*:\s*([^\n-]+)/g;
4108
+ let match;
4109
+ while ((match = apiRegex.exec(registryContent)) !== null) {
4110
+ const method = match[1];
4111
+ const path13 = match[2].trim();
4112
+ const description = match[3].trim();
4113
+ const key = `${method}:${path13}`;
4114
+ apis.set(key, { method, path: path13, description });
4115
+ }
4116
+ return apis;
4117
+ }
4118
+ function extractMethodComment2(content, methodName) {
4119
+ const methodIndex = content.indexOf(`${methodName}(`);
4120
+ if (methodIndex === -1) {
4121
+ return "";
4122
+ }
4123
+ const beforeMethod = content.substring(Math.max(0, methodIndex - 500), methodIndex);
4124
+ const commentMatch = beforeMethod.match(/\*\s*([^\n*]+)/g);
4125
+ if (commentMatch && commentMatch.length > 0) {
4126
+ return commentMatch[0].replace(/\*\s?/, "").trim();
4127
+ }
4128
+ return "";
4129
+ }
4130
+
4131
+ // src/commands/logs.ts
4132
+ import { Command as Command12 } from "commander";
4133
+ import path12 from "path";
4134
+ import inquirer8 from "inquirer";
4135
+ var logsCommand = new Command12("logs").argument("[filter]", "\u8FC7\u6EE4\u5668 (today, --all, \u6216\u65E5\u671F YYYY-MM-DD)").description("\u67E5\u770B\u4F1A\u8BDD\u65E5\u5FD7").action(async (filter = "today") => {
4136
+ try {
4137
+ logger.header("\u4F1A\u8BDD\u65E5\u5FD7");
4138
+ logger.newLine();
4139
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
4140
+ if (!hasTechStack) {
4141
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
4142
+ logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
4143
+ process.exit(1);
4144
+ }
4145
+ const sessionsDir = "docs/sessions";
4146
+ const dirExists = await FileUtils.exists(sessionsDir);
4147
+ if (!dirExists) {
4148
+ logger.info("\u6682\u65E0\u4F1A\u8BDD\u65E5\u5FD7");
4149
+ logger.info("\u8FD0\u884C 'team-cli dev' \u540E\u4F1A\u81EA\u52A8\u751F\u6210\u65E5\u5FD7");
4150
+ process.exit(0);
4151
+ }
4152
+ let targetDir = "";
4153
+ let displayTitle = "";
4154
+ switch (filter) {
4155
+ case "":
4156
+ case "today": {
4157
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4158
+ targetDir = path12.join(sessionsDir, today);
4159
+ const todayExists = await FileUtils.exists(targetDir);
4160
+ if (!todayExists) {
4161
+ logger.info("\u4ECA\u65E5\u6682\u65E0\u4F1A\u8BDD\u65E5\u5FD7");
4162
+ process.exit(0);
4163
+ }
4164
+ displayTitle = "\u663E\u793A\u4ECA\u65E5\u4F1A\u8BDD\u65E5\u5FD7:";
4165
+ break;
4166
+ }
4167
+ case "--all":
4168
+ case "-a": {
4169
+ targetDir = sessionsDir;
4170
+ displayTitle = "\u663E\u793A\u6240\u6709\u4F1A\u8BDD\u65E5\u5FD7:";
4171
+ break;
4172
+ }
4173
+ default: {
4174
+ targetDir = path12.join(sessionsDir, filter);
4175
+ const dateExists = await FileUtils.exists(targetDir);
4176
+ if (!dateExists) {
4177
+ logger.error(`\u672A\u627E\u5230\u65E5\u671F '${filter}' \u7684\u65E5\u5FD7`);
4178
+ logger.info("\u53EF\u7528\u65E5\u671F:");
4179
+ const entries = await FileUtils.findFiles("*/", sessionsDir);
4180
+ const dates = entries.slice(0, 10);
4181
+ for (const date of dates) {
4182
+ logger.info(` ${date.replace("/", "")}`);
4183
+ }
4184
+ process.exit(1);
4185
+ }
4186
+ displayTitle = `\u663E\u793A ${filter} \u7684\u4F1A\u8BDD\u65E5\u5FD7:`;
4187
+ break;
4188
+ }
4189
+ }
4190
+ logger.info(displayTitle);
4191
+ logger.newLine();
4192
+ const logs = await collectLogFiles(targetDir);
4193
+ if (logs.length === 0) {
4194
+ logger.info("\u65E0\u65E5\u5FD7\u6587\u4EF6");
4195
+ process.exit(0);
4196
+ }
4197
+ for (let i = 0; i < logs.length; i++) {
4198
+ const relPath = path12.relative(sessionsDir, logs[i]);
4199
+ logger.step(`${i + 1}) ${relPath}`);
4200
+ }
4201
+ logger.newLine();
4202
+ const answers = await inquirer8.prompt([
4203
+ {
4204
+ type: "input",
4205
+ name: "selection",
4206
+ message: "\u8F93\u5165\u7F16\u53F7\u67E5\u770B\u8BE6\u60C5 (\u6216 Enter \u9000\u51FA):",
4207
+ default: ""
4208
+ }
4209
+ ]);
4210
+ const selection = answers.selection.trim();
4211
+ if (selection === "") {
4212
+ process.exit(0);
4213
+ }
4214
+ const selectionNum = parseInt(selection, 10);
4215
+ if (isNaN(selectionNum) || selectionNum < 1 || selectionNum > logs.length) {
4216
+ logger.error("\u65E0\u6548\u7684\u9009\u62E9");
4217
+ process.exit(1);
4218
+ }
4219
+ const selectedLog = logs[selectionNum - 1];
4220
+ logger.newLine();
4221
+ logger.header("\u65E5\u5FD7\u8BE6\u60C5");
4222
+ logger.newLine();
4223
+ const content = await FileUtils.read(selectedLog);
4224
+ console.log(content);
4225
+ } catch (error) {
4226
+ logger.error(`\u67E5\u770B\u65E5\u5FD7\u5931\u8D25: ${error.message}`);
4227
+ if (process.env.DEBUG) {
4228
+ console.error(error);
4229
+ }
4230
+ process.exit(1);
4231
+ }
4232
+ });
4233
+ async function collectLogFiles(targetDir) {
4234
+ const logs = [];
4235
+ try {
4236
+ const allFiles = await FileUtils.findFiles("*.md", targetDir);
4237
+ const filtered = allFiles.filter((f) => f !== "index.md");
4238
+ for (const file of filtered) {
4239
+ const filePath = path12.join(targetDir, file);
4240
+ const stat = await FileUtils.exists(filePath);
4241
+ if (stat) {
4242
+ logs.push(filePath);
4243
+ }
4244
+ }
4245
+ } catch (error) {
4246
+ }
4247
+ return logs;
4248
+ }
4249
+
4250
+ // src/index.ts
4251
+ var program = new Command13();
4252
+ program.name("team-cli").description("AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6").version("2.0.0");
4253
+ program.option("-v, --verbose", "\u8BE6\u7EC6\u8F93\u51FA\u6A21\u5F0F").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F");
4254
+ program.addCommand(initCommand);
4255
+ program.addCommand(splitPrdCommand);
4256
+ program.addCommand(breakdownCommand);
4257
+ program.addCommand(devCommand);
4258
+ program.addCommand(addFeatureCommand);
4259
+ program.addCommand(bugfixCommand);
4260
+ program.addCommand(hotfixCommand);
4261
+ program.addCommand(lintCommand);
4262
+ program.addCommand(statusCommand);
4263
+ program.addCommand(detectDepsCommand);
4264
+ program.addCommand(syncMemoryCommand);
4265
+ program.addCommand(checkApiCommand);
4266
+ program.addCommand(logsCommand);
4267
+ program.action(() => {
4268
+ showHelp();
4269
+ });
4270
+ function showHelp() {
4271
+ console.log("");
4272
+ logger.header("team-cli - AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6");
4273
+ console.log("");
4274
+ console.log(chalk2.bold("\u4F7F\u7528\u65B9\u6CD5:"));
4275
+ console.log(" team-cli init [project-name] \u521D\u59CB\u5316\u65B0\u9879\u76EE");
4276
+ console.log(" team-cli split-prd <prd-folder> \u5C06 PRD \u62C6\u5206\u6210\u591A\u4E2A specs");
4277
+ console.log(" team-cli breakdown [spec-file] \u5C06 spec \u62C6\u5206\u4E3A milestones \u548C todos");
4278
+ console.log(" team-cli dev \u5F00\u53D1\u6A21\u5F0F\uFF0C\u6267\u884C\u5177\u4F53\u4EFB\u52A1");
4279
+ console.log(" team-cli add-feature <name> \u6DFB\u52A0\u65B0\u529F\u80FD");
4280
+ console.log(" team-cli bugfix \u521B\u5EFA Bugfix \u8BB0\u5F55");
4281
+ console.log(" team-cli hotfix \u521B\u5EFA Hotfix");
4282
+ console.log(" team-cli detect-deps [spec] \u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB");
4283
+ console.log(" team-cli sync-memory \u540C\u6B65 AI_MEMORY.md");
4284
+ console.log(" team-cli check-api API \u68C0\u67E5\uFF08\u51B2\u7A81/\u53D8\u66F4/Registry\uFF09");
4285
+ console.log(" team-cli status \u67E5\u770B\u9879\u76EE\u72B6\u6001");
4286
+ console.log(" team-cli lint \u4EE3\u7801\u8D28\u91CF\u68C0\u67E5 (\u524D\u7AEF+\u540E\u7AEF)");
4287
+ console.log(" team-cli logs [date] \u67E5\u770B\u4F1A\u8BDD\u65E5\u5FD7");
4288
+ console.log(" team-cli --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F");
4289
+ console.log("");
4290
+ console.log(chalk2.bold("\u793A\u4F8B:"));
4291
+ console.log(" team-cli init my-project");
4292
+ console.log(" cd my-project");
4293
+ console.log(" team-cli add-feature payment-system");
4294
+ console.log(" team-cli breakdown docs/specs/xxx.md");
4295
+ console.log(" team-cli dev");
4296
+ console.log("");
4297
+ console.log(chalk2.bold("\u5F00\u53D1\u6D41\u7A0B:"));
4298
+ console.log(" 1. PRD \u2192 specs (split-prd)");
4299
+ console.log(" 2. spec \u2192 milestones + todos (breakdown)");
4300
+ console.log(" 3. \u9009\u62E9 milestone/todo \u2192 \u5B9E\u73B0 (dev)");
4301
+ console.log("");
4302
+ console.log(chalk2.bold("\u8FED\u4EE3\u6D41\u7A0B:"));
4303
+ console.log(" team-cli add-feature <name> # \u6DFB\u52A0\u65B0\u529F\u80FD");
4304
+ console.log(" team-cli detect-deps [spec] # \u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB");
4305
+ console.log(" team-cli sync-memory # \u540C\u6B65 AI_MEMORY");
4306
+ console.log(" team-cli check-api # API \u68C0\u67E5");
4307
+ console.log(" team-cli bugfix # \u521B\u5EFA bugfix");
4308
+ console.log(" team-cli hotfix # \u7D27\u6025\u4FEE\u590D");
4309
+ console.log(" team-cli status # \u67E5\u770B\u9879\u76EE\u72B6\u6001");
4310
+ console.log("");
4311
+ console.log(chalk2.gray("\u66F4\u591A\u4FE1\u606F: https://github.com/yungu/team-cli"));
4312
+ console.log("");
4313
+ }
4314
+ process.on("uncaughtException", (error) => {
4315
+ logger.error("\u672A\u6355\u83B7\u7684\u5F02\u5E38");
4316
+ if (process.env.DEBUG) {
4317
+ console.error(error);
4318
+ }
4319
+ process.exit(1);
4320
+ });
4321
+ process.on("unhandledRejection", (reason) => {
4322
+ logger.error("\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD");
4323
+ if (process.env.DEBUG) {
4324
+ console.error(reason);
4325
+ }
4326
+ process.exit(1);
4327
+ });
4328
+ program.parse(process.argv);
4329
+ //# sourceMappingURL=index.js.map