yg-team-cli 2.4.10 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -213,9 +213,11 @@ var init_utils = __esm({
213
213
  return await fs.readFile(file, "utf-8");
214
214
  }
215
215
  /**
216
- * 写入文件内容
216
+ * 写入文件内容(自动创建父目录)
217
217
  */
218
218
  static async write(file, content) {
219
+ const dir = path2.dirname(file);
220
+ await fs.ensureDir(dir);
219
221
  await fs.writeFile(file, content, "utf-8");
220
222
  }
221
223
  /**
@@ -3171,12 +3173,12 @@ var addFeatureCommand = new Command4("add-feature").argument("<feature-name>", "
3171
3173
  process.exit(1);
3172
3174
  }
3173
3175
  const featureSlug = StringUtils.toKebabCase(featureName);
3174
- const specFile = path10.join("docs/specs", `${featureSlug}.md`);
3175
- const specExists = await FileUtils.exists(specFile);
3176
- if (specExists) {
3177
- logger.error(`Spec \u6587\u4EF6\u5DF2\u5B58\u5728: ${specFile}`);
3176
+ const prdFile = path10.join("docs/prd-docs", `${featureSlug}.md`);
3177
+ const prdExists = await FileUtils.exists(prdFile);
3178
+ if (prdExists) {
3179
+ logger.error(`PRD \u6587\u4EF6\u5DF2\u5B58\u5728: ${prdFile}`);
3178
3180
  logger.info("\u5982\u9700\u91CD\u65B0\u751F\u6210\uFF0C\u8BF7\u5148\u5220\u9664\uFF1A");
3179
- logger.info(` rm ${specFile}`);
3181
+ logger.info(` rm ${prdFile}`);
3180
3182
  process.exit(1);
3181
3183
  }
3182
3184
  const { mode } = await inquirer4.prompt([
@@ -3191,9 +3193,9 @@ var addFeatureCommand = new Command4("add-feature").argument("<feature-name>", "
3191
3193
  }
3192
3194
  ]);
3193
3195
  if (mode === "prd") {
3194
- await addFeatureFromPrd(featureName, featureSlug, specFile);
3196
+ await addFeatureFromPrd(featureName, featureSlug, prdFile);
3195
3197
  } else {
3196
- await addFeatureSimple(featureName, featureSlug, specFile);
3198
+ await addFeatureSimple(featureName, featureSlug, prdFile);
3197
3199
  }
3198
3200
  } catch (error) {
3199
3201
  logger.error(`\u6DFB\u52A0\u529F\u80FD\u5931\u8D25: ${error.message}`);
@@ -3203,7 +3205,7 @@ var addFeatureCommand = new Command4("add-feature").argument("<feature-name>", "
3203
3205
  process.exit(1);
3204
3206
  }
3205
3207
  });
3206
- async function addFeatureFromPrd(featureName, featureSlug, specFile) {
3208
+ async function addFeatureFromPrd(featureName, _featureSlug, prdOutputFile) {
3207
3209
  const { prdPath } = await inquirer4.prompt([
3208
3210
  {
3209
3211
  type: "input",
@@ -3218,68 +3220,62 @@ async function addFeatureFromPrd(featureName, featureSlug, specFile) {
3218
3220
  const tasks = new Listr3([
3219
3221
  {
3220
3222
  title: "\u8BFB\u53D6 PRD \u6587\u6863",
3221
- task: async (ctx2) => {
3222
- ctx2.prdContent = await FileUtils.read(prdPath);
3223
+ task: async (ctx) => {
3224
+ ctx.prdContent = await FileUtils.read(prdPath);
3223
3225
  }
3224
3226
  },
3225
3227
  {
3226
3228
  title: "\u626B\u63CF\u5DF2\u5B8C\u6210\u529F\u80FD",
3227
- task: async (ctx2) => {
3229
+ task: async (ctx) => {
3228
3230
  const specDir = "docs/specs";
3229
3231
  const files = await FileUtils.findFiles("*.md", specDir);
3230
3232
  const specs = files.filter((f) => !f.includes("template"));
3231
- ctx2.completedSpecs = [];
3233
+ ctx.completedSpecs = [];
3232
3234
  for (const file of specs) {
3233
3235
  const status = await SpecUtils.getSpecStatus(path10.join(specDir, file));
3234
3236
  if (status === "\u5DF2\u5B8C\u6210") {
3235
- ctx2.completedSpecs.push(file.replace(".md", ""));
3237
+ ctx.completedSpecs.push(file.replace(".md", ""));
3236
3238
  }
3237
3239
  }
3238
3240
  }
3239
3241
  },
3240
3242
  {
3241
3243
  title: "\u6784\u5EFA\u9879\u76EE\u4E0A\u4E0B\u6587",
3242
- task: async (ctx2) => {
3243
- ctx2.projectContext = await buildProjectContext();
3244
+ task: async (ctx) => {
3245
+ ctx.projectContext = await buildProjectContext();
3244
3246
  }
3245
3247
  },
3246
3248
  {
3247
- title: "\u8C03\u7528 Claude \u751F\u6210 spec",
3248
- task: async (ctx2) => {
3249
+ title: "\u8C03\u7528 Claude \u751F\u6210 PRD",
3250
+ task: async (ctx) => {
3249
3251
  const prompt = buildPrdPrompt(
3250
3252
  featureName,
3251
- ctx2.prdContent,
3252
- ctx2.projectContext,
3253
- ctx2.completedSpecs
3253
+ ctx.prdContent,
3254
+ ctx.projectContext,
3255
+ ctx.completedSpecs
3254
3256
  );
3255
3257
  const result = await claudeAI.prompt(prompt, {
3256
3258
  contextFiles: ["TECH_STACK.md", "CONVENTIONS.md", "AI_MEMORY.md"]
3257
3259
  });
3258
- ctx2.generatedSpec = result;
3259
- }
3260
- },
3261
- {
3262
- title: "\u4FDD\u5B58 spec \u6587\u4EF6",
3263
- task: async (ctx2) => {
3264
- await FileUtils.write(specFile, ctx2.generatedSpec);
3260
+ ctx.generatedPrd = result;
3265
3261
  }
3266
3262
  },
3267
3263
  {
3268
- title: "\u66F4\u65B0 AI_MEMORY",
3269
- task: async () => {
3270
- await updateAiMemory(featureName, featureSlug);
3264
+ title: "\u4FDD\u5B58 PRD \u6587\u4EF6",
3265
+ task: async (ctx) => {
3266
+ await FileUtils.write(prdOutputFile, ctx.generatedPrd);
3271
3267
  }
3272
3268
  }
3273
3269
  ]);
3274
- const ctx = await tasks.run();
3270
+ await tasks.run();
3275
3271
  logger.newLine();
3276
3272
  logger.separator("\u2500", 60);
3277
3273
  logger.newLine();
3278
- logger.success(`Spec \u6587\u4EF6\u5DF2\u751F\u6210: ${specFile}`);
3279
- await showSpecPreview(specFile);
3280
- await askToAdjust(specFile);
3274
+ logger.success(`PRD \u6587\u4EF6\u5DF2\u751F\u6210: ${prdOutputFile}`);
3275
+ await showPrdPreview(prdOutputFile);
3276
+ await askToAdjust(prdOutputFile, "prd");
3281
3277
  }
3282
- async function addFeatureSimple(featureName, featureSlug, specFile) {
3278
+ async function addFeatureSimple(featureName, _featureSlug, prdOutputFile) {
3283
3279
  const { description } = await inquirer4.prompt([
3284
3280
  {
3285
3281
  type: "input",
@@ -3291,24 +3287,24 @@ async function addFeatureSimple(featureName, featureSlug, specFile) {
3291
3287
  const tasks = new Listr3([
3292
3288
  {
3293
3289
  title: "\u626B\u63CF\u5DF2\u5B8C\u6210\u529F\u80FD",
3294
- task: async (ctx2) => {
3290
+ task: async (ctx) => {
3295
3291
  const specDir = "docs/specs";
3296
3292
  const files = await FileUtils.findFiles("*.md", specDir);
3297
3293
  const specs = files.filter((f) => !f.includes("template"));
3298
- ctx2.completedSpecs = [];
3294
+ ctx.completedSpecs = [];
3299
3295
  for (const file of specs) {
3300
3296
  const status = await SpecUtils.getSpecStatus(path10.join(specDir, file));
3301
3297
  if (status === "\u5DF2\u5B8C\u6210") {
3302
- ctx2.completedSpecs.push(file.replace(".md", ""));
3298
+ ctx.completedSpecs.push(file.replace(".md", ""));
3303
3299
  }
3304
3300
  }
3305
3301
  }
3306
3302
  },
3307
3303
  {
3308
3304
  title: "\u9009\u62E9\u4F9D\u8D56\u529F\u80FD",
3309
- task: async (ctx2) => {
3310
- if (ctx2.completedSpecs.length === 0) {
3311
- ctx2.selectedDeps = [];
3305
+ task: async (ctx) => {
3306
+ if (ctx.completedSpecs.length === 0) {
3307
+ ctx.selectedDeps = [];
3312
3308
  return;
3313
3309
  }
3314
3310
  const { dependencies } = await inquirer4.prompt([
@@ -3316,53 +3312,47 @@ async function addFeatureSimple(featureName, featureSlug, specFile) {
3316
3312
  type: "checkbox",
3317
3313
  name: "dependencies",
3318
3314
  message: "\u9009\u62E9\u6B64\u529F\u80FD\u4F9D\u8D56\u7684\u5DF2\u6709\u529F\u80FD (\u53EF\u591A\u9009\uFF0C\u76F4\u63A5\u56DE\u8F66\u8DF3\u8FC7):",
3319
- choices: ctx2.completedSpecs
3315
+ choices: ctx.completedSpecs
3320
3316
  }
3321
3317
  ]);
3322
- ctx2.selectedDeps = dependencies;
3318
+ ctx.selectedDeps = dependencies;
3323
3319
  }
3324
3320
  },
3325
3321
  {
3326
3322
  title: "\u6784\u5EFA\u9879\u76EE\u4E0A\u4E0B\u6587",
3327
- task: async (ctx2) => {
3328
- ctx2.projectContext = await buildProjectContext();
3323
+ task: async (ctx) => {
3324
+ ctx.projectContext = await buildProjectContext();
3329
3325
  }
3330
3326
  },
3331
3327
  {
3332
- title: "\u8C03\u7528 Claude \u751F\u6210 spec",
3333
- task: async (ctx2) => {
3328
+ title: "\u8C03\u7528 Claude \u751F\u6210 PRD",
3329
+ task: async (ctx) => {
3334
3330
  const prompt = buildSimplePrompt(
3335
3331
  featureName,
3336
3332
  description,
3337
- ctx2.projectContext,
3338
- ctx2.selectedDeps
3333
+ ctx.projectContext,
3334
+ ctx.selectedDeps
3339
3335
  );
3340
3336
  const result = await claudeAI.prompt(prompt, {
3341
3337
  contextFiles: ["TECH_STACK.md", "CONVENTIONS.md", "AI_MEMORY.md"]
3342
3338
  });
3343
- ctx2.generatedSpec = result;
3344
- }
3345
- },
3346
- {
3347
- title: "\u4FDD\u5B58 spec \u6587\u4EF6",
3348
- task: async (ctx2) => {
3349
- await FileUtils.write(specFile, ctx2.generatedSpec);
3339
+ ctx.generatedPrd = result;
3350
3340
  }
3351
3341
  },
3352
3342
  {
3353
- title: "\u66F4\u65B0 AI_MEMORY",
3354
- task: async () => {
3355
- await updateAiMemory(featureName, featureSlug);
3343
+ title: "\u4FDD\u5B58 PRD \u6587\u4EF6",
3344
+ task: async (ctx) => {
3345
+ await FileUtils.write(prdOutputFile, ctx.generatedPrd);
3356
3346
  }
3357
3347
  }
3358
3348
  ]);
3359
- const ctx = await tasks.run();
3349
+ await tasks.run();
3360
3350
  logger.newLine();
3361
3351
  logger.separator("\u2500", 60);
3362
3352
  logger.newLine();
3363
- logger.success(`Spec \u6587\u4EF6\u5DF2\u751F\u6210: ${specFile}`);
3364
- await showSpecPreview(specFile);
3365
- await askToAdjust(specFile);
3353
+ logger.success(`PRD \u6587\u4EF6\u5DF2\u751F\u6210: ${prdOutputFile}`);
3354
+ await showPrdPreview(prdOutputFile);
3355
+ await askToAdjust(prdOutputFile, "prd");
3366
3356
  }
3367
3357
  async function buildProjectContext() {
3368
3358
  const context = [];
@@ -3563,79 +3553,19 @@ ${dependencies.map((d) => `- [x] ${d}`).join("\n") || "- (\u65E0)"}
3563
3553
  5. \u6839\u636E\u529F\u80FD\u63CF\u8FF0\u63A8\u65AD\u5408\u7406\u7684 API \u548C\u6570\u636E\u6A21\u578B
3564
3554
  6. \u53C2\u8003\u5DF2\u6709\u529F\u80FD\u7684\u6280\u672F\u98CE\u683C\u4FDD\u6301\u4E00\u81F4\u6027`;
3565
3555
  }
3566
- async function updateAiMemory(featureName, featureSlug) {
3567
- const aiMemoryFile = "AI_MEMORY.md";
3568
- const exists = await FileUtils.exists(aiMemoryFile);
3569
- if (!exists) {
3570
- logger.warn("AI_MEMORY.md \u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u66F4\u65B0");
3571
- return;
3572
- }
3573
- let content = await FileUtils.read(aiMemoryFile);
3574
- const featureDisplay = featureName.replace(/[-_]/g, " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
3575
- const newRow = `| ${featureDisplay} | ${featureSlug}.md | \u25CB \u672A\u5F00\u59CB | 0/0 | - | |`;
3576
- if (!content.includes("## \u529F\u80FD\u6E05\u5355")) {
3577
- content += `
3578
- ## \u529F\u80FD\u6E05\u5355 (Feature Inventory)
3579
-
3580
- | \u529F\u80FD | Spec \u6587\u4EF6 | \u72B6\u6001 | \u8FDB\u5EA6 | \u5B8C\u6210\u65E5\u671F | \u5907\u6CE8 |
3581
- |------|----------|------|------|---------|------|
3582
- ${newRow}
3583
- `;
3584
- } else {
3585
- const lines = content.split("\n");
3586
- let featureInventorySection = false;
3587
- let insertIndex = -1;
3588
- for (let i = 0; i < lines.length; i++) {
3589
- const line = lines[i];
3590
- if (line.includes("## \u529F\u80FD\u6E05\u5355")) {
3591
- featureInventorySection = true;
3592
- continue;
3593
- }
3594
- if (featureInventorySection && line.startsWith("## ") && !line.includes("\u529F\u80FD\u6E05\u5355")) {
3595
- break;
3596
- }
3597
- if (featureInventorySection && /^\|[-]+\|/.test(line.trim())) {
3598
- insertIndex = i + 1;
3599
- break;
3600
- }
3601
- }
3602
- if (insertIndex !== -1) {
3603
- lines.splice(insertIndex, 0, newRow);
3604
- content = lines.join("\n");
3605
- } else {
3606
- const sectionIndex = lines.findIndex((line) => line.includes("## \u529F\u80FD\u6E05\u5355"));
3607
- if (sectionIndex !== -1) {
3608
- const tableLines = [
3609
- "",
3610
- "| \u529F\u80FD | Spec \u6587\u4EF6 | \u72B6\u6001 | \u8FDB\u5EA6 | \u5B8C\u6210\u65E5\u671F | \u5907\u6CE8 |",
3611
- "|------|----------|------|------|---------|------|",
3612
- newRow,
3613
- ""
3614
- ];
3615
- lines.splice(sectionIndex + 1, 0, ...tableLines);
3616
- content = lines.join("\n");
3617
- } else {
3618
- content += `
3619
- ${newRow}
3620
- `;
3621
- }
3622
- }
3623
- }
3624
- await FileUtils.write(aiMemoryFile, content);
3625
- }
3626
- async function showSpecPreview(specFile) {
3556
+ async function showPrdPreview(prdFile) {
3627
3557
  logger.newLine();
3628
- logger.header("\u751F\u6210\u7684 Spec \u9884\u89C8:");
3558
+ logger.header("\u751F\u6210\u7684 PRD \u9884\u89C8:");
3629
3559
  logger.newLine();
3630
3560
  try {
3631
- const content = await FileUtils.read(specFile);
3561
+ const content = await FileUtils.read(prdFile);
3632
3562
  if (!content || content.trim().length === 0) {
3633
- logger.warn("Spec \u6587\u4EF6\u5185\u5BB9\u4E3A\u7A7A");
3563
+ logger.warn("PRD \u6587\u4EF6\u5185\u5BB9\u4E3A\u7A7A");
3634
3564
  return;
3635
3565
  }
3636
3566
  const lines = content.split("\n");
3637
3567
  if (lines.length === 0) {
3638
- logger.warn("Spec \u6587\u4EF6\u6CA1\u6709\u5185\u5BB9");
3568
+ logger.warn("PRD \u6587\u4EF6\u6CA1\u6709\u5185\u5BB9");
3639
3569
  return;
3640
3570
  }
3641
3571
  const preview = lines.slice(0, 40).join("\n");
@@ -3646,15 +3576,16 @@ async function showSpecPreview(specFile) {
3646
3576
  }
3647
3577
  console.log("");
3648
3578
  } catch (error) {
3649
- logger.error(`\u8BFB\u53D6 Spec \u9884\u89C8\u5931\u8D25: ${error.message}`);
3579
+ logger.error(`\u8BFB\u53D6 PRD \u9884\u89C8\u5931\u8D25: ${error.message}`);
3650
3580
  }
3651
3581
  }
3652
- async function askToAdjust(specFile) {
3582
+ async function askToAdjust(file, type = "spec") {
3583
+ const typeLabel = type === "prd" ? "PRD" : "Spec";
3653
3584
  const { needAdjust } = await inquirer4.prompt([
3654
3585
  {
3655
3586
  type: "confirm",
3656
3587
  name: "needAdjust",
3657
- message: "\u662F\u5426\u9700\u8981\u8C03\u6574 Spec \u5185\u5BB9?",
3588
+ message: `\u662F\u5426\u9700\u8981\u8C03\u6574 ${typeLabel} \u5185\u5BB9?`,
3658
3589
  default: false
3659
3590
  }
3660
3591
  ]);
@@ -3662,12 +3593,17 @@ async function askToAdjust(specFile) {
3662
3593
  logger.info("\u6B63\u5728\u6253\u5F00\u7F16\u8F91\u5668...");
3663
3594
  const editor = process.env.EDITOR || "vim";
3664
3595
  const { execaCommand } = await import("execa");
3665
- await execaCommand(`${editor} ${specFile}`, { stdio: "inherit" });
3596
+ await execaCommand(`${editor} ${file}`, { stdio: "inherit" });
3666
3597
  }
3667
3598
  logger.newLine();
3668
3599
  logger.info("\u4E0B\u4E00\u6B65:");
3669
- logger.step(`1. \u8FD0\u884C 'team-cli breakdown ${specFile}' \u62C6\u5206\u4E3A milestones`);
3670
- logger.step("2. \u8FD0\u884C 'team-cli dev' \u9009\u62E9 milestone \u8FDB\u884C\u5F00\u53D1");
3600
+ if (type === "prd") {
3601
+ logger.step(`1. \u8FD0\u884C 'team-cli split-prd ${file}' \u751F\u6210 Spec \u89C4\u683C\u6587\u6863`);
3602
+ logger.step("2. \u8FD0\u884C 'team-cli breakdown <spec-file>' \u62C6\u5206\u4E3A milestones");
3603
+ } else {
3604
+ logger.step(`1. \u8FD0\u884C 'team-cli breakdown ${file}' \u62C6\u5206\u4E3A milestones`);
3605
+ logger.step("2. \u8FD0\u884C 'team-cli dev' \u9009\u62E9 milestone \u8FDB\u884C\u5F00\u53D1");
3606
+ }
3671
3607
  logger.newLine();
3672
3608
  }
3673
3609
 
@@ -3678,9 +3614,9 @@ init_logger();
3678
3614
  import { Command as Command5 } from "commander";
3679
3615
  import path11 from "path";
3680
3616
  import { Listr as Listr4 } from "listr2";
3681
- var splitPrdCommand = new Command5("split-prd").argument("<prd-folder>", "PRD \u6587\u6863\u76EE\u5F55").description("\u5C06 PRD \u62C6\u5206\u6210\u591A\u4E2A specs").action(async (prdFolder) => {
3617
+ var splitPrdCommand = new Command5("split-prd").argument("<prd-path>", "PRD \u6587\u6863\u8DEF\u5F84 (\u652F\u6301\u5355\u6587\u4EF6\u6216\u76EE\u5F55)").description("\u5C06 PRD \u8F6C\u6362\u4E3A Spec \u89C4\u683C\u6587\u6863").action(async (prdPath) => {
3682
3618
  try {
3683
- logger.header("PRD \u62C6\u5206");
3619
+ logger.header("PRD \u2192 Spec \u8F6C\u6362");
3684
3620
  logger.newLine();
3685
3621
  const hasTechStack = await FileUtils.exists("TECH_STACK.md");
3686
3622
  if (!hasTechStack) {
@@ -3688,9 +3624,9 @@ var splitPrdCommand = new Command5("split-prd").argument("<prd-folder>", "PRD \u
3688
3624
  logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
3689
3625
  process.exit(1);
3690
3626
  }
3691
- const folderExists = await FileUtils.exists(prdFolder);
3692
- if (!folderExists) {
3693
- throw new Error(`PRD \u6587\u4EF6\u5939\u4E0D\u5B58\u5728: ${prdFolder}`);
3627
+ const pathExists = await FileUtils.exists(prdPath);
3628
+ if (!pathExists) {
3629
+ throw new Error(`PRD \u8DEF\u5F84\u4E0D\u5B58\u5728: ${prdPath}`);
3694
3630
  }
3695
3631
  const hasClaude = await claudeAI.checkInstalled();
3696
3632
  if (!hasClaude) {
@@ -3698,113 +3634,300 @@ var splitPrdCommand = new Command5("split-prd").argument("<prd-folder>", "PRD \u
3698
3634
  logger.info("\u8BF7\u5B89\u88C5 Claude CLI: npm install -g @anthropic-ai/claude-code");
3699
3635
  process.exit(1);
3700
3636
  }
3701
- const tasks = new Listr4([
3702
- {
3703
- title: "\u626B\u63CF PRD \u6587\u4EF6",
3704
- task: async (ctx2) => {
3705
- const supportedExtensions = ["md", "txt", "markdown"];
3706
- ctx2.prdFiles = [];
3707
- for (const ext of supportedExtensions) {
3708
- const files = await FileUtils.findFiles(`*.${ext}`, prdFolder);
3709
- ctx2.prdFiles.push(...files.map((f) => path11.join(prdFolder, f)));
3710
- }
3711
- if (ctx2.prdFiles.length === 0) {
3712
- throw new Error(
3713
- "\u672A\u627E\u5230 PRD \u6587\u6863 (\u652F\u6301 .md, .txt, .markdown)"
3714
- );
3715
- }
3716
- ctx2.prdFile = ctx2.prdFiles[0];
3717
- if (ctx2.prdFiles.length > 1) {
3718
- logger.info(`\u627E\u5230\u591A\u4E2A PRD \u6587\u6863\uFF0C\u4F7F\u7528: ${ctx2.prdFile}`);
3719
- } else {
3720
- logger.success(`\u627E\u5230 PRD \u6587\u6863: ${ctx2.prdFile}`);
3721
- }
3637
+ const isSingleFile = prdPath.endsWith(".md") || prdPath.endsWith(".txt") || prdPath.endsWith(".markdown");
3638
+ if (isSingleFile) {
3639
+ await processSinglePrd(prdPath);
3640
+ } else {
3641
+ await processMultiplePrds(prdPath);
3642
+ }
3643
+ } catch (error) {
3644
+ logger.error(`PRD \u8F6C\u6362\u5931\u8D25: ${error.message}`);
3645
+ if (process.env.DEBUG) {
3646
+ console.error(error);
3647
+ }
3648
+ process.exit(1);
3649
+ }
3650
+ });
3651
+ async function processSinglePrd(prdFile) {
3652
+ const baseName = path11.basename(prdFile, path11.extname(prdFile));
3653
+ const featureSlug = StringUtils.toKebabCase(baseName);
3654
+ const specFile = path11.join("docs/specs", `${featureSlug}.md`);
3655
+ const specExists = await FileUtils.exists(specFile);
3656
+ if (specExists) {
3657
+ logger.error(`Spec \u6587\u4EF6\u5DF2\u5B58\u5728: ${specFile}`);
3658
+ logger.info("\u5982\u9700\u91CD\u65B0\u751F\u6210\uFF0C\u8BF7\u5148\u5220\u9664\uFF1A");
3659
+ logger.info(` rm ${specFile}`);
3660
+ process.exit(1);
3661
+ }
3662
+ const tasks = new Listr4([
3663
+ {
3664
+ title: "\u8BFB\u53D6 PRD \u5185\u5BB9",
3665
+ task: async (ctx) => {
3666
+ ctx.prdContent = await FileUtils.read(prdFile);
3667
+ logger.success(`\u8BFB\u53D6 PRD: ${prdFile}`);
3668
+ }
3669
+ },
3670
+ {
3671
+ title: "\u8C03\u7528 Claude \u751F\u6210 Spec",
3672
+ task: async (ctx) => {
3673
+ const prompt = buildSinglePrdToSpecPrompt(ctx.prdContent, featureSlug);
3674
+ logger.newLine();
3675
+ logger.separator("\u2500", 60);
3676
+ logger.info("Claude \u6267\u884C\u4E2D...");
3677
+ logger.separator("\u2500", 60);
3678
+ logger.newLine();
3679
+ ctx.specContent = await claudeAI.prompt(prompt, {
3680
+ contextFiles: ["TECH_STACK.md", "CONVENTIONS.md", "AI_MEMORY.md"]
3681
+ });
3682
+ }
3683
+ },
3684
+ {
3685
+ title: "\u4FDD\u5B58 Spec \u6587\u4EF6",
3686
+ task: async (ctx) => {
3687
+ await FileUtils.write(specFile, ctx.specContent);
3688
+ }
3689
+ },
3690
+ {
3691
+ title: "\u66F4\u65B0 AI_MEMORY",
3692
+ task: async () => {
3693
+ await updateAiMemory(baseName, featureSlug);
3694
+ }
3695
+ }
3696
+ ]);
3697
+ await tasks.run();
3698
+ logger.newLine();
3699
+ logger.separator("\u2500", 60);
3700
+ logger.newLine();
3701
+ logger.header("PRD \u2192 Spec \u8F6C\u6362\u5B8C\u6210!");
3702
+ logger.success(`Spec \u6587\u4EF6\u5DF2\u751F\u6210: ${specFile}`);
3703
+ logger.newLine();
3704
+ logger.info("\u4E0B\u4E00\u6B65:");
3705
+ logger.step(`1. \u8FD0\u884C 'team-cli breakdown ${specFile}' \u62C6\u5206\u4E3A milestones`);
3706
+ logger.step("2. \u8FD0\u884C 'team-cli dev' \u9009\u62E9 milestone \u8FDB\u884C\u5F00\u53D1");
3707
+ logger.newLine();
3708
+ }
3709
+ async function processMultiplePrds(prdFolder) {
3710
+ const tasks = new Listr4([
3711
+ {
3712
+ title: "\u626B\u63CF PRD \u6587\u4EF6",
3713
+ task: async (ctx) => {
3714
+ const supportedExtensions = ["md", "txt", "markdown"];
3715
+ ctx.prdFiles = [];
3716
+ for (const ext of supportedExtensions) {
3717
+ const files = await FileUtils.findFiles(`*.${ext}`, prdFolder);
3718
+ ctx.prdFiles.push(...files.map((f) => path11.join(prdFolder, f)));
3722
3719
  }
3723
- },
3724
- {
3725
- title: "\u626B\u63CF\u622A\u56FE\u6587\u4EF6",
3726
- task: async (ctx2) => {
3727
- const screenshotDir = path11.join(prdFolder, "screenshots");
3728
- const dirExists = await FileUtils.exists(screenshotDir);
3729
- if (!dirExists) {
3730
- logger.info("\u672A\u627E\u5230 screenshots \u76EE\u5F55\uFF0C\u8DF3\u8FC7\u622A\u56FE");
3731
- ctx2.screenshots = [];
3732
- return;
3733
- }
3734
- const imageExtensions = ["png", "jpg", "jpeg", "gif", "webp"];
3735
- ctx2.screenshots = [];
3736
- for (const ext of imageExtensions) {
3737
- const files = await FileUtils.findFiles(`*.${ext}`, screenshotDir);
3738
- ctx2.screenshots.push(...files.map((f) => path11.join(screenshotDir, f)));
3739
- }
3740
- logger.success(`\u627E\u5230 ${ctx2.screenshots.length} \u4E2A\u622A\u56FE\u6587\u4EF6`);
3720
+ if (ctx.prdFiles.length === 0) {
3721
+ throw new Error(
3722
+ "\u672A\u627E\u5230 PRD \u6587\u6863 (\u652F\u6301 .md, .txt, .markdown)"
3723
+ );
3741
3724
  }
3742
- },
3743
- {
3744
- title: "\u626B\u63CF demo \u4EE3\u7801\u4ED3\u5E93",
3745
- task: async (ctx2) => {
3746
- const entries = await FileUtils.findFiles("*/", prdFolder);
3747
- ctx2.demoRepos = [];
3748
- for (const entry of entries) {
3749
- const dirPath = path11.join(prdFolder, entry);
3750
- const gitDir = path11.join(dirPath, ".git");
3751
- const hasGit = await FileUtils.exists(gitDir);
3752
- if (hasGit) {
3753
- ctx2.demoRepos.push(dirPath);
3754
- }
3755
- }
3756
- if (ctx2.demoRepos.length > 0) {
3757
- logger.success(`\u627E\u5230 demo \u4ED3\u5E93: ${ctx2.demoRepos.join(", ")}`);
3758
- } else {
3759
- logger.info("\u672A\u627E\u5230 demo \u4EE3\u7801\u4ED3\u5E93\uFF0C\u8DF3\u8FC7");
3760
- }
3725
+ ctx.prdFile = ctx.prdFiles[0];
3726
+ if (ctx.prdFiles.length > 1) {
3727
+ logger.info(`\u627E\u5230\u591A\u4E2A PRD \u6587\u6863\uFF0C\u4F7F\u7528: ${ctx.prdFile}`);
3728
+ } else {
3729
+ logger.success(`\u627E\u5230 PRD \u6587\u6863: ${ctx.prdFile}`);
3761
3730
  }
3762
- },
3763
- {
3764
- title: "\u8BFB\u53D6 PRD \u5185\u5BB9",
3765
- task: async (ctx2) => {
3766
- ctx2.prdContent = await FileUtils.read(ctx2.prdFile);
3731
+ }
3732
+ },
3733
+ {
3734
+ title: "\u626B\u63CF\u622A\u56FE\u6587\u4EF6",
3735
+ task: async (ctx) => {
3736
+ const screenshotDir = path11.join(prdFolder, "screenshots");
3737
+ const dirExists = await FileUtils.exists(screenshotDir);
3738
+ if (!dirExists) {
3739
+ logger.info("\u672A\u627E\u5230 screenshots \u76EE\u5F55\uFF0C\u8DF3\u8FC7\u622A\u56FE");
3740
+ ctx.screenshots = [];
3741
+ return;
3767
3742
  }
3768
- },
3769
- {
3770
- title: "\u8C03\u7528 Claude \u62C6\u5206 PRD",
3771
- task: async (ctx2) => {
3772
- const prompt = buildSplitPrdPrompt(
3773
- ctx2.prdContent,
3774
- ctx2.screenshots,
3775
- ctx2.demoRepos
3776
- );
3777
- logger.newLine();
3778
- logger.separator("\u2500", 60);
3779
- logger.info("Claude \u6267\u884C\u4E2D...");
3780
- logger.separator("\u2500", 60);
3781
- logger.newLine();
3782
- return await claudeAI.prompt(prompt, {
3783
- contextFiles: ["TECH_STACK.md", "CONVENTIONS.md"]
3784
- });
3743
+ const imageExtensions = ["png", "jpg", "jpeg", "gif", "webp"];
3744
+ ctx.screenshots = [];
3745
+ for (const ext of imageExtensions) {
3746
+ const files = await FileUtils.findFiles(`*.${ext}`, screenshotDir);
3747
+ ctx.screenshots.push(...files.map((f) => path11.join(screenshotDir, f)));
3785
3748
  }
3749
+ logger.success(`\u627E\u5230 ${ctx.screenshots.length} \u4E2A\u622A\u56FE\u6587\u4EF6`);
3750
+ }
3751
+ },
3752
+ {
3753
+ title: "\u626B\u63CF demo \u4EE3\u7801\u4ED3\u5E93",
3754
+ task: async (ctx) => {
3755
+ const entries = await FileUtils.findFiles("*/", prdFolder);
3756
+ ctx.demoRepos = [];
3757
+ for (const entry of entries) {
3758
+ const dirPath = path11.join(prdFolder, entry);
3759
+ const gitDir = path11.join(dirPath, ".git");
3760
+ const hasGit = await FileUtils.exists(gitDir);
3761
+ if (hasGit) {
3762
+ ctx.demoRepos.push(dirPath);
3763
+ }
3764
+ }
3765
+ if (ctx.demoRepos.length > 0) {
3766
+ logger.success(`\u627E\u5230 demo \u4ED3\u5E93: ${ctx.demoRepos.join(", ")}`);
3767
+ } else {
3768
+ logger.info("\u672A\u627E\u5230 demo \u4EE3\u7801\u4ED3\u5E93\uFF0C\u8DF3\u8FC7");
3769
+ }
3770
+ }
3771
+ },
3772
+ {
3773
+ title: "\u8BFB\u53D6 PRD \u5185\u5BB9",
3774
+ task: async (ctx) => {
3775
+ ctx.prdContent = await FileUtils.read(ctx.prdFile);
3776
+ }
3777
+ },
3778
+ {
3779
+ title: "\u8C03\u7528 Claude \u62C6\u5206 PRD",
3780
+ task: async (ctx) => {
3781
+ const prompt = buildSplitPrdPrompt(
3782
+ ctx.prdContent,
3783
+ ctx.screenshots,
3784
+ ctx.demoRepos
3785
+ );
3786
+ logger.newLine();
3787
+ logger.separator("\u2500", 60);
3788
+ logger.info("Claude \u6267\u884C\u4E2D...");
3789
+ logger.separator("\u2500", 60);
3790
+ logger.newLine();
3791
+ return await claudeAI.prompt(prompt, {
3792
+ contextFiles: ["TECH_STACK.md", "CONVENTIONS.md"]
3793
+ });
3786
3794
  }
3787
- ]);
3788
- const ctx = await tasks.run();
3789
- logger.newLine();
3790
- logger.separator("\u2500", 60);
3791
- logger.newLine();
3792
- logger.header("PRD \u62C6\u5206\u5B8C\u6210!");
3793
- logger.success("Spec \u6587\u4EF6\u5DF2\u751F\u6210\u5230 docs/specs/ \u76EE\u5F55");
3794
- logger.newLine();
3795
- logger.info("\u4E0B\u4E00\u6B65:");
3796
- logger.step("1. \u68C0\u67E5\u751F\u6210\u7684 spec \u6587\u4EF6");
3797
- logger.step("2. \u8FD0\u884C 'team-cli breakdown <spec-file>' \u62C6\u5206 milestones");
3798
- logger.step("3. \u8FD0\u884C 'team-cli dev' \u5F00\u59CB\u5F00\u53D1");
3799
- logger.newLine();
3800
- } catch (error) {
3801
- logger.error(`PRD \u62C6\u5206\u5931\u8D25: ${error.message}`);
3802
- if (process.env.DEBUG) {
3803
- console.error(error);
3804
3795
  }
3805
- process.exit(1);
3796
+ ]);
3797
+ await tasks.run();
3798
+ logger.newLine();
3799
+ logger.separator("\u2500", 60);
3800
+ logger.newLine();
3801
+ logger.header("PRD \u62C6\u5206\u5B8C\u6210!");
3802
+ logger.success("Spec \u6587\u4EF6\u5DF2\u751F\u6210\u5230 docs/specs/ \u76EE\u5F55");
3803
+ logger.newLine();
3804
+ logger.info("\u4E0B\u4E00\u6B65:");
3805
+ logger.step("1. \u68C0\u67E5\u751F\u6210\u7684 spec \u6587\u4EF6");
3806
+ logger.step("2. \u8FD0\u884C 'team-cli breakdown <spec-file>' \u62C6\u5206 milestones");
3807
+ logger.step("3. \u8FD0\u884C 'team-cli dev' \u5F00\u59CB\u5F00\u53D1");
3808
+ logger.newLine();
3809
+ }
3810
+ async function updateAiMemory(featureName, featureSlug) {
3811
+ const aiMemoryFile = "AI_MEMORY.md";
3812
+ const memoryExists = await FileUtils.exists(aiMemoryFile);
3813
+ if (!memoryExists) {
3814
+ logger.warn("AI_MEMORY.md \u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u66F4\u65B0");
3815
+ return;
3806
3816
  }
3807
- });
3817
+ let content = await FileUtils.read(aiMemoryFile);
3818
+ const featureDisplay = featureName.replace(/[-_]/g, " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
3819
+ const newRow = `| ${featureDisplay} | ${featureSlug}.md | \u25CB \u672A\u5F00\u59CB | 0/0 | - | |`;
3820
+ if (content.includes(`| ${featureDisplay} |`) || content.includes(`${featureSlug}.md`)) {
3821
+ logger.info(`\u529F\u80FD ${featureDisplay} \u5DF2\u5B58\u5728\u4E8E AI_MEMORY\uFF0C\u8DF3\u8FC7`);
3822
+ return;
3823
+ }
3824
+ const lines = content.split("\n");
3825
+ let insertIndex = -1;
3826
+ let featureInventorySection = false;
3827
+ for (let i = 0; i < lines.length; i++) {
3828
+ const line = lines[i];
3829
+ if (line.includes("\u529F\u80FD\u6E05\u5355") || line.includes("Feature Inventory")) {
3830
+ featureInventorySection = true;
3831
+ }
3832
+ if (featureInventorySection && /^\|[-]+\|/.test(line.trim())) {
3833
+ insertIndex = i + 1;
3834
+ break;
3835
+ }
3836
+ }
3837
+ if (insertIndex > 0) {
3838
+ lines.splice(insertIndex, 0, newRow);
3839
+ content = lines.join("\n");
3840
+ } else {
3841
+ content += `
3842
+ ${newRow}
3843
+ `;
3844
+ }
3845
+ await FileUtils.write(aiMemoryFile, content);
3846
+ }
3847
+ function buildSinglePrdToSpecPrompt(prdContent, featureSlug) {
3848
+ return `Role: Senior Technical Architect
3849
+
3850
+ Task: Convert the following PRD (Product Requirements Document) into a detailed technical Spec.
3851
+
3852
+ Context:
3853
+ - Read TECH_STACK.md for technology constraints
3854
+ - Read CONVENTIONS.md for coding standards
3855
+ - Read AI_MEMORY.md for project status
3856
+
3857
+ PRD Content:
3858
+ \`\`\`
3859
+ ${prdContent}
3860
+ \`\`\`
3861
+
3862
+ Output Requirements:
3863
+ Generate a complete Spec document with the following sections:
3864
+
3865
+ \`\`\`markdown
3866
+ # [\u529F\u80FD\u6807\u9898]
3867
+
3868
+ ## \u529F\u80FD\u6982\u8FF0
3869
+ **\u529F\u80FD\u540D\u79F0**: [\u529F\u80FD\u4E2D\u6587\u540D]
3870
+ **\u4F18\u5148\u7EA7**: P0/P1/P2 (\u6839\u636E\u529F\u80FD\u91CD\u8981\u6027\u5224\u65AD)
3871
+ **\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)
3872
+ **\u72B6\u6001**: \u5F85\u62C6\u5206
3873
+ **\u521B\u5EFA\u65E5\u671F**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
3874
+
3875
+ ## \u4F9D\u8D56\u5173\u7CFB
3876
+ **\u524D\u7F6E\u4F9D\u8D56**:
3877
+ - (\u5217\u51FA\u4F9D\u8D56\u7684\u5176\u4ED6\u529F\u80FD)
3878
+
3879
+ **\u88AB\u4F9D\u8D56\u4E8E**:
3880
+ - (\u81EA\u52A8\u751F\u6210)
3881
+
3882
+ ## \u80CC\u666F\u4E0E\u76EE\u6807
3883
+ [\u6839\u636E PRD \u63D0\u53D6\u80CC\u666F\u548C\u76EE\u6807]
3884
+
3885
+ ## \u529F\u80FD\u9700\u6C42
3886
+ ### \u7528\u6237\u6545\u4E8B
3887
+ \`\`\`
3888
+ \u4F5C\u4E3A [\u5177\u4F53\u89D2\u8272]
3889
+ \u6211\u5E0C\u671B [\u5177\u4F53\u529F\u80FD]
3890
+ \u4EE5\u4FBF [\u5B9E\u73B0\u7684\u4EF7\u503C]
3891
+ \`\`\`
3892
+
3893
+ ### \u529F\u80FD\u70B9
3894
+ [\u4ECE PRD \u63D0\u53D6 3-8 \u4E2A\u4E3B\u8981\u529F\u80FD\u70B9]
3895
+
3896
+ ## \u6280\u672F\u8BBE\u8BA1
3897
+ ### API \u8BBE\u8BA1
3898
+ \u5217\u51FA\u4E3B\u8981\u7684 API \u7AEF\u70B9\uFF0C\u683C\u5F0F\uFF1A
3899
+ - \`METHOD /api/path\` - \u7B80\u77ED\u8BF4\u660E
3900
+
3901
+ ### \u6570\u636E\u6A21\u578B
3902
+ \u5217\u51FA\u9700\u8981\u7684\u6570\u636E\u8868\uFF0C\u683C\u5F0F\uFF1A
3903
+ - \`table_name\` (\u8868\u8BF4\u660E) - \u5B57\u6BB5\u8BF4\u660E
3904
+
3905
+ ### \u524D\u7AEF\u7EC4\u4EF6
3906
+ \u5217\u51FA\u9700\u8981\u7684\u4E3B\u8981\u524D\u7AEF\u7EC4\u4EF6
3907
+
3908
+ ### \u540E\u7AEF\u6A21\u5757
3909
+ \u5217\u51FA\u9700\u8981\u7684\u540E\u7AEF\u6A21\u5757\u7ED3\u6784
3910
+
3911
+ ## \u9A8C\u6536\u6807\u51C6
3912
+ - [ ] \u9A8C\u6536\u6807\u51C6 1
3913
+ - [ ] \u9A8C\u6536\u6807\u51C6 2
3914
+ - [ ] \u9A8C\u6536\u6807\u51C6 3
3915
+
3916
+ ## \u91CC\u7A0B\u7891 (Milestones)
3917
+ > \u6CE8: \u4F7F\u7528 \`team-cli breakdown ${featureSlug}.md\` \u62C6\u5206\u6B64 spec \u4E3A milestones \u548C todos
3918
+
3919
+ ----
3920
+ *\u751F\u6210\u4E8E: ${(/* @__PURE__ */ new Date()).toISOString()} by Claude*
3921
+ \`\`\`
3922
+
3923
+ IMPORTANT:
3924
+ 1. Only output the Spec document content, no additional text
3925
+ 2. Make sure to include comprehensive API and data model design
3926
+ 3. Priority should be justified based on feature importance
3927
+ 4. Work estimation should be realistic
3928
+ 5. Extract key requirements from the PRD accurately
3929
+ `;
3930
+ }
3808
3931
  function buildSplitPrdPrompt(prdContent, screenshots, demoRepos) {
3809
3932
  let prompt = `Role: Senior Product Manager and Technical Architect
3810
3933