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/README.md +10 -1
- package/dist/cli.js +363 -240
- package/dist/cli.js.map +1 -1
- package/dist/index.js +362 -239
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,
|
|
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 (
|
|
3198
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
3219
|
-
|
|
3220
|
+
task: async (ctx) => {
|
|
3221
|
+
ctx.projectContext = await buildProjectContext();
|
|
3220
3222
|
}
|
|
3221
3223
|
},
|
|
3222
3224
|
{
|
|
3223
|
-
title: "\u8C03\u7528 Claude \u751F\u6210
|
|
3224
|
-
task: async (
|
|
3225
|
+
title: "\u8C03\u7528 Claude \u751F\u6210 PRD",
|
|
3226
|
+
task: async (ctx) => {
|
|
3225
3227
|
const prompt = buildPrdPrompt(
|
|
3226
3228
|
featureName,
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
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
|
-
|
|
3236
|
+
ctx.generatedPrd = result;
|
|
3235
3237
|
}
|
|
3236
3238
|
},
|
|
3237
3239
|
{
|
|
3238
|
-
title: "\u4FDD\u5B58
|
|
3239
|
-
task: async (
|
|
3240
|
-
await FileUtils.write(
|
|
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
|
-
|
|
3246
|
+
await tasks.run();
|
|
3251
3247
|
logger.newLine();
|
|
3252
3248
|
logger.separator("\u2500", 60);
|
|
3253
3249
|
logger.newLine();
|
|
3254
|
-
logger.success(`
|
|
3255
|
-
await
|
|
3256
|
-
await askToAdjust(
|
|
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,
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
3286
|
-
if (
|
|
3287
|
-
|
|
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:
|
|
3291
|
+
choices: ctx.completedSpecs
|
|
3296
3292
|
}
|
|
3297
3293
|
]);
|
|
3298
|
-
|
|
3294
|
+
ctx.selectedDeps = dependencies;
|
|
3299
3295
|
}
|
|
3300
3296
|
},
|
|
3301
3297
|
{
|
|
3302
3298
|
title: "\u6784\u5EFA\u9879\u76EE\u4E0A\u4E0B\u6587",
|
|
3303
|
-
task: async (
|
|
3304
|
-
|
|
3299
|
+
task: async (ctx) => {
|
|
3300
|
+
ctx.projectContext = await buildProjectContext();
|
|
3305
3301
|
}
|
|
3306
3302
|
},
|
|
3307
3303
|
{
|
|
3308
|
-
title: "\u8C03\u7528 Claude \u751F\u6210
|
|
3309
|
-
task: async (
|
|
3304
|
+
title: "\u8C03\u7528 Claude \u751F\u6210 PRD",
|
|
3305
|
+
task: async (ctx) => {
|
|
3310
3306
|
const prompt = buildSimplePrompt(
|
|
3311
3307
|
featureName,
|
|
3312
3308
|
description,
|
|
3313
|
-
|
|
3314
|
-
|
|
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
|
-
|
|
3315
|
+
ctx.generatedPrd = result;
|
|
3320
3316
|
}
|
|
3321
3317
|
},
|
|
3322
3318
|
{
|
|
3323
|
-
title: "\u4FDD\u5B58
|
|
3324
|
-
task: async (
|
|
3325
|
-
await FileUtils.write(
|
|
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
|
-
|
|
3325
|
+
await tasks.run();
|
|
3336
3326
|
logger.newLine();
|
|
3337
3327
|
logger.separator("\u2500", 60);
|
|
3338
3328
|
logger.newLine();
|
|
3339
|
-
logger.success(`
|
|
3340
|
-
await
|
|
3341
|
-
await askToAdjust(
|
|
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
|
|
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
|
|
3534
|
+
logger.header("\u751F\u6210\u7684 PRD \u9884\u89C8:");
|
|
3605
3535
|
logger.newLine();
|
|
3606
3536
|
try {
|
|
3607
|
-
const content = await FileUtils.read(
|
|
3537
|
+
const content = await FileUtils.read(prdFile);
|
|
3608
3538
|
if (!content || content.trim().length === 0) {
|
|
3609
|
-
logger.warn("
|
|
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("
|
|
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
|
|
3555
|
+
logger.error(`\u8BFB\u53D6 PRD \u9884\u89C8\u5931\u8D25: ${error.message}`);
|
|
3626
3556
|
}
|
|
3627
3557
|
}
|
|
3628
|
-
async function askToAdjust(
|
|
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:
|
|
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} ${
|
|
3572
|
+
await execaCommand(`${editor} ${file}`, { stdio: "inherit" });
|
|
3642
3573
|
}
|
|
3643
3574
|
logger.newLine();
|
|
3644
3575
|
logger.info("\u4E0B\u4E00\u6B65:");
|
|
3645
|
-
|
|
3646
|
-
|
|
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
|
|
3675
|
-
const
|
|
3676
|
-
if (
|
|
3677
|
-
logger.error(`
|
|
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 ${
|
|
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,
|
|
3630
|
+
await addFeatureFromPrd(featureName, featureSlug, prdFile);
|
|
3695
3631
|
} else {
|
|
3696
|
-
await addFeatureSimple(featureName, featureSlug,
|
|
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-
|
|
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 \
|
|
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
|
|
3840
|
-
if (!
|
|
3841
|
-
throw new Error(`PRD \
|
|
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
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
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 \
|
|
4072
|
+
logger.error(`PRD \u8F6C\u6362\u5931\u8D25: ${error.message}`);
|
|
3950
4073
|
if (process.env.DEBUG) {
|
|
3951
4074
|
console.error(error);
|
|
3952
4075
|
}
|