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