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