yg-team-cli 2.6.4 → 2.7.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
@@ -212,6 +212,12 @@ var init_utils = __esm({
212
212
  static async read(file) {
213
213
  return await fs.readFile(file, "utf-8");
214
214
  }
215
+ /**
216
+ * 同步读取文件内容
217
+ */
218
+ static readSync(file) {
219
+ return fs.readFileSync(file, "utf-8");
220
+ }
215
221
  /**
216
222
  * 写入文件内容(自动创建父目录)
217
223
  */
@@ -1332,27 +1338,164 @@ var init_module_registry = __esm({
1332
1338
  } else if (module.type === "remote") {
1333
1339
  await this.generateRemoteSpecPlaceholder(targetPath, module);
1334
1340
  addedFiles.push(path5.relative(projectPath, targetPath));
1341
+ } else {
1342
+ await this.generateLocalSpecPlaceholder(targetPath, module);
1343
+ addedFiles.push(path5.relative(projectPath, targetPath));
1335
1344
  }
1336
1345
  }
1337
- if (module.type === "local" && module.backendFragments) {
1346
+ if (module.backendFragments) {
1338
1347
  const backendJavaDir = path5.join(projectPath, "backend/src/main/java");
1339
- const commonBasePath = path5.join(backendJavaDir, "org/yungu/common");
1348
+ const useTemplates = await FileUtils.exists(path5.join(templatesDir, "modules"));
1340
1349
  for (const fragment of module.backendFragments) {
1350
+ let sourcePath = null;
1351
+ let targetPath;
1352
+ let content;
1353
+ if (useTemplates) {
1354
+ sourcePath = path5.join(templatesDir, "modules", fragment.source);
1355
+ if (await FileUtils.exists(sourcePath)) {
1356
+ content = await FileUtils.read(sourcePath);
1357
+ }
1358
+ }
1359
+ if (!content) {
1360
+ content = this.generatePlaceholderCode(module, fragment);
1361
+ }
1362
+ if (fragment.target.startsWith("common/")) {
1363
+ const relativePath = fragment.target.replace(/^common\//, "org/yungu/common/");
1364
+ targetPath = path5.join(backendJavaDir, relativePath);
1365
+ } else if (fragment.target.startsWith("config/")) {
1366
+ const mainModule = this.findMainModule(projectPath);
1367
+ if (mainModule) {
1368
+ const relativePath = fragment.target.replace(/^config\//, `${mainModule}/config/`);
1369
+ targetPath = path5.join(backendJavaDir, relativePath);
1370
+ } else {
1371
+ targetPath = path5.join(backendJavaDir, fragment.target);
1372
+ }
1373
+ } else {
1374
+ targetPath = path5.join(backendJavaDir, fragment.target);
1375
+ }
1376
+ await FileUtils.ensureDir(path5.dirname(targetPath));
1377
+ await FileUtils.write(targetPath, content);
1378
+ addedFiles.push(path5.relative(projectPath, targetPath));
1379
+ }
1380
+ }
1381
+ if (module.frontendFragments) {
1382
+ const frontendSrcDir = path5.join(projectPath, "frontend/src");
1383
+ for (const fragment of module.frontendFragments) {
1341
1384
  const sourcePath = path5.join(templatesDir, "modules", fragment.source);
1342
- const targetPath = path5.join(commonBasePath, fragment.target);
1385
+ const targetPath = path5.join(frontendSrcDir, fragment.target);
1343
1386
  if (await FileUtils.exists(sourcePath)) {
1344
- await FileUtils.ensureDir(path5.dirname(targetPath));
1345
- let content = await FileUtils.read(sourcePath);
1346
- const targetPackage = "org.yungu.common." + path5.dirname(fragment.target).replace(/\//g, ".");
1347
- content = content.replace(/^package\s+[^;]+;/m, `package ${targetPackage};`);
1348
- await FileUtils.write(targetPath, content);
1387
+ await FileUtils.copy(sourcePath, targetPath);
1349
1388
  addedFiles.push(path5.relative(projectPath, targetPath));
1350
1389
  }
1351
1390
  }
1352
- } else if (module.type === "remote") {
1353
1391
  }
1354
1392
  return addedFiles;
1355
1393
  }
1394
+ /**
1395
+ * 查找主模块名 (第一个 service 类型的模块)
1396
+ */
1397
+ static findMainModule(projectPath) {
1398
+ const settingsPath = path5.join(projectPath, "backend/settings.gradle");
1399
+ const content = FileUtils.readSync(settingsPath);
1400
+ const matches = content.match(/include\s+'([^']+)'/g);
1401
+ if (matches) {
1402
+ for (const match of matches) {
1403
+ const moduleName = match.replace(/include\s+'/, "").replace(/'/, "");
1404
+ if (moduleName !== "common") {
1405
+ return moduleName;
1406
+ }
1407
+ }
1408
+ }
1409
+ return null;
1410
+ }
1411
+ /**
1412
+ * 生成占位符代码
1413
+ */
1414
+ static generatePlaceholderCode(module, fragment) {
1415
+ const targetPackage = path5.dirname(fragment.target).replace(/\//g, ".");
1416
+ const className = path5.basename(fragment.target, ".java");
1417
+ const moduleName = module.name;
1418
+ const moduleId = module.id;
1419
+ return `package ${targetPackage};
1420
+
1421
+ import lombok.extern.slf4j.Slf4j;
1422
+
1423
+ /**
1424
+ * ${moduleName} - \u5360\u4F4D\u7B26
1425
+ *
1426
+ * \u6B64\u6587\u4EF6\u7531 team-cli \u81EA\u52A8\u751F\u6210\u3002
1427
+ * \u6A21\u5757 ID: ${moduleId}
1428
+ *
1429
+ * \u4F7F\u7528\u8BF4\u660E:
1430
+ * 1. \u8BF7\u6839\u636E\u9879\u76EE\u5B9E\u9645\u9700\u6C42\u5B8C\u5584\u6B64\u6587\u4EF6
1431
+ * 2. \u6216\u53C2\u8003 docs/specs/${moduleId}/spec.md \u83B7\u53D6\u8BE6\u7EC6\u9700\u6C42
1432
+ */
1433
+ @Slf4j
1434
+ public class ${className} {
1435
+
1436
+ /**
1437
+ * TODO: \u5B9E\u73B0 ${moduleName} \u529F\u80FD
1438
+ */
1439
+ public void execute() {
1440
+ log.info("${className} - \u6267\u884C\u4E2D...");
1441
+ // \u5728\u6B64\u5B9E\u73B0\u5177\u4F53\u903B\u8F91
1442
+ }
1443
+ }
1444
+ `;
1445
+ }
1446
+ /**
1447
+ * 生成本地模块的 Spec 占位符
1448
+ */
1449
+ static async generateLocalSpecPlaceholder(targetPath, module) {
1450
+ const content = `# ${module.name}
1451
+
1452
+ ## \u529F\u80FD\u6982\u8FF0
1453
+ ${module.description}
1454
+
1455
+ ## \u4F7F\u7528\u8BF4\u660E
1456
+
1457
+ \u6B64\u6A21\u5757\u4E3A\u4E91\u8C37\u901A\u7528\u6A21\u5757\u7684\u5360\u4F4D\u7B26\u914D\u7F6E\u3002
1458
+
1459
+ ### \u914D\u7F6E\u6B65\u9AA4
1460
+
1461
+ 1. **\u6DFB\u52A0\u4F9D\u8D56**
1462
+
1463
+ \u5728 \`backend/build.gradle\` \u4E2D\u6DFB\u52A0:
1464
+
1465
+ \`\`\`groovy
1466
+ ${module.dependencies ? module.dependencies.map((d) => `implementation '${d}'`).join("\n") : ""}
1467
+ \`\`\`
1468
+
1469
+ 2. **\u914D\u7F6E\u6587\u4EF6**
1470
+
1471
+ \u6839\u636E\u9700\u6C42\u914D\u7F6E application.yml:
1472
+
1473
+ \`\`\`yaml
1474
+ # ${module.name} \u914D\u7F6E
1475
+ \`\`\`
1476
+
1477
+ 3. **\u5BFC\u5165\u914D\u7F6E\u7C7B**
1478
+
1479
+ \u5728\u4E3B Application \u7C7B\u4E0A\u6DFB\u52A0\u6CE8\u89E3:
1480
+
1481
+ \`\`\`java
1482
+ @SpringBootApplication
1483
+ @ComponentScan("${module.id}")
1484
+ public class Application {
1485
+ // ...
1486
+ }
1487
+ \`\`\`
1488
+
1489
+ 4. **\u53C2\u8003\u6587\u6863**
1490
+
1491
+ \u67E5\u770B docs/specs/${module.id}/spec.md \u83B7\u53D6\u8BE6\u7EC6\u5B9E\u73B0\u8BF4\u660E\u3002
1492
+
1493
+ ## \u4F9D\u8D56\u5173\u7CFB
1494
+
1495
+ ${module.requires ? `\u9700\u8981\u5148\u542F\u7528: ${module.requires.join(", ")}` : "\u65E0\u524D\u7F6E\u4F9D\u8D56"}
1496
+ `;
1497
+ await FileUtils.write(targetPath, content);
1498
+ }
1356
1499
  /**
1357
1500
  * 生成远程模块的 Spec 占位符
1358
1501
  */
@@ -4074,9 +4217,30 @@ Generate a complete Spec document with the following sections:
4074
4217
  \u5217\u51FA\u9700\u8981\u7684\u540E\u7AEF\u6A21\u5757\u7ED3\u6784
4075
4218
 
4076
4219
  ## \u9A8C\u6536\u6807\u51C6
4077
- - [ ] \u9A8C\u6536\u6807\u51C6 1
4078
- - [ ] \u9A8C\u6536\u6807\u51C6 2
4079
- - [ ] \u9A8C\u6536\u6807\u51C6 3
4220
+
4221
+ ### BDD \u573A\u666F\uFF08\u884C\u4E3A\u9A71\u52A8\u89C6\u89D2\uFF09
4222
+
4223
+ \u8BF7\u4E3A\u6BCF\u4E2A\u4E3B\u8981\u529F\u80FD\u70B9\u751F\u6210 2-4 \u4E2A BDD \u573A\u666F\uFF0C\u683C\u5F0F\u5982\u4E0B\uFF1A
4224
+
4225
+ \`\`\`markdown
4226
+ #### \u573A\u666F 1: [\u573A\u666F\u540D\u79F0]
4227
+ **\u4F5C\u4E3A** [\u7528\u6237\u89D2\u8272]
4228
+ **\u6211\u5E0C\u671B** [\u5B8C\u6210\u7684\u52A8\u4F5C]
4229
+ **\u4EE5\u4FBF** [\u5B9E\u73B0\u7684\u4EF7\u503C]
4230
+
4231
+ - [GIVEN] \u524D\u7F6E\u6761\u4EF6\u63CF\u8FF0
4232
+ - [WHEN] \u7528\u6237\u52A8\u4F5C\u6216 API \u8C03\u7528
4233
+ - [THEN] \u9884\u671F\u7ED3\u679C
4234
+
4235
+ **\u72B6\u6001\u6D41\u8F6C**:
4236
+ - \`\u672A\u767B\u5F55\` \u2192 \`\u767B\u5F55\u4E2D\` \u2192 \`\u5DF2\u767B\u5F55\`
4237
+ \`\`\`
4238
+
4239
+ \u8981\u6C42\uFF1A
4240
+ 1. \u6BCF\u4E2A\u573A\u666F\u8986\u76D6\u4E00\u4E2A\u5B8C\u6574\u7684\u7528\u6237\u6D41\u7A0B
4241
+ 2. Given/When/Then \u7ED3\u6784\u6E05\u6670
4242
+ 3. \u72B6\u6001\u6D41\u8F6C\u6807\u6CE8\u660E\u786E
4243
+ 4. \u573A\u666F\u6570\u91CF\u6839\u636E\u529F\u80FD\u590D\u6742\u5EA6\u51B3\u5B9A\uFF08\u7B80\u5355\u529F\u80FD 2-3 \u4E2A\uFF0C\u590D\u6742\u529F\u80FD 4-6 \u4E2A\uFF09
4080
4244
 
4081
4245
  ## \u91CC\u7A0B\u7891 (Milestones)
4082
4246
  > \u6CE8: \u4F7F\u7528 \`team-cli breakdown ${featureSlug}.md\` \u62C6\u5206\u6B64 spec \u4E3A milestones \u548C todos
@@ -4091,6 +4255,7 @@ IMPORTANT:
4091
4255
  3. Priority should be justified based on feature importance
4092
4256
  4. Work estimation should be realistic
4093
4257
  5. Extract key requirements from the PRD accurately
4258
+ 6. BDD scenarios should cover all user flows and edge cases
4094
4259
  `;
4095
4260
  }
4096
4261
  function buildSplitPrdPrompt(prdContent, screenshots, demoRepos) {
@@ -4545,10 +4710,366 @@ Temporary solution: ${answers.solution}`
4545
4710
  }
4546
4711
  });
4547
4712
 
4713
+ // src/lib/bdd-parser.ts
4714
+ var BDDParser;
4715
+ var init_bdd_parser = __esm({
4716
+ "src/lib/bdd-parser.ts"() {
4717
+ "use strict";
4718
+ init_esm_shims();
4719
+ BDDParser = class {
4720
+ static SCENARIO_PATTERN = /^####?\s*场景\s*(\d+)[::]?\s*(.+)$/;
4721
+ static GIVEN_PATTERN = /^\*?\s*-\s*\[GIVEN\]\s*(.+)$/;
4722
+ static WHEN_PATTERN = /^\*?\s*-\s*\[WHEN\]\s*(.+)$/;
4723
+ static THEN_PATTERN = /^\*?\s*-\s*\[THEN\]\s*(.+)$/;
4724
+ static STATE_PATTERN = /^\*?\s*-\s*\[STATE\]\s*(.+)$/;
4725
+ static ROLE_PATTERN = /^\*\*作为\*\*[::]?\s*(.+)$/i;
4726
+ static WANT_PATTERN = /^\*\*我希望\*\*[::]?\s*(.+)$/i;
4727
+ static SO_THAT_PATTERN = /^\*\*以便\*\*[::]?\s*(.+)$/i;
4728
+ static TRANSITION_PATTERN = /`([^`]+)`\s*→\s*`([^`]+)`/;
4729
+ /**
4730
+ * 解析 BDD 场景
4731
+ */
4732
+ static parseScenarios(content) {
4733
+ const scenarios = [];
4734
+ const lines = content.split("\n");
4735
+ let currentScenario = null;
4736
+ let section = null;
4737
+ let scenarioOrder = 0;
4738
+ for (let i = 0; i < lines.length; i++) {
4739
+ const line = lines[i].trim();
4740
+ const lineNum = i + 1;
4741
+ const scenarioMatch = line.match(this.SCENARIO_PATTERN);
4742
+ if (scenarioMatch) {
4743
+ if (currentScenario && currentScenario.title) {
4744
+ scenarios.push(this.finalizeScenario(currentScenario, scenarioOrder));
4745
+ }
4746
+ scenarioOrder++;
4747
+ currentScenario = {
4748
+ id: `scenario-${scenarioOrder}`,
4749
+ title: scenarioMatch[2].trim(),
4750
+ order: scenarioOrder,
4751
+ given: [],
4752
+ when: [],
4753
+ then: [],
4754
+ stateTransitions: [],
4755
+ relatedApis: [],
4756
+ relatedFeatures: []
4757
+ };
4758
+ section = "title";
4759
+ continue;
4760
+ }
4761
+ if (line.match(/^\*\*作为\*\*[::]?/i)) {
4762
+ const match = line.match(this.ROLE_PATTERN);
4763
+ if (match && currentScenario) {
4764
+ currentScenario.asA = match[1].trim();
4765
+ }
4766
+ continue;
4767
+ }
4768
+ if (line.match(/^\*\*我希望\*\*[::]?/i)) {
4769
+ const match = line.match(this.WANT_PATTERN);
4770
+ if (match && currentScenario) {
4771
+ currentScenario.iWant = match[1].trim();
4772
+ }
4773
+ continue;
4774
+ }
4775
+ if (line.match(/^\*\*以便\*\*[::]?/i)) {
4776
+ const match = line.match(this.SO_THAT_PATTERN);
4777
+ if (match && currentScenario) {
4778
+ currentScenario.soThat = match[1].trim();
4779
+ }
4780
+ continue;
4781
+ }
4782
+ const givenMatch = line.match(this.GIVEN_PATTERN);
4783
+ const whenMatch = line.match(this.WHEN_PATTERN);
4784
+ const thenMatch = line.match(this.THEN_PATTERN);
4785
+ const stateMatch = line.match(this.STATE_PATTERN);
4786
+ if (givenMatch && currentScenario) {
4787
+ currentScenario.given.push({
4788
+ description: givenMatch[1].trim(),
4789
+ type: this.detectConditionType(givenMatch[1]),
4790
+ validation: this.extractValidation(givenMatch[1])
4791
+ });
4792
+ section = "given";
4793
+ continue;
4794
+ }
4795
+ if (whenMatch && currentScenario) {
4796
+ const action = this.parseAction(whenMatch[1].trim());
4797
+ currentScenario.when.push(action);
4798
+ if (action.api) {
4799
+ currentScenario.relatedApis.push(action.api);
4800
+ }
4801
+ section = "when";
4802
+ continue;
4803
+ }
4804
+ if (thenMatch && currentScenario) {
4805
+ currentScenario.then.push({
4806
+ description: thenMatch[1].trim(),
4807
+ type: this.detectResultType(thenMatch[1]),
4808
+ expectedBehavior: this.extractExpectedBehavior(thenMatch[1])
4809
+ });
4810
+ section = "then";
4811
+ continue;
4812
+ }
4813
+ if (stateMatch && currentScenario) {
4814
+ const transition = this.parseStateTransition(stateMatch[1].trim());
4815
+ if (transition) {
4816
+ currentScenario.stateTransitions.push(transition);
4817
+ }
4818
+ section = "state";
4819
+ continue;
4820
+ }
4821
+ if (line && !line.startsWith("#") && !line.startsWith("---") && currentScenario) {
4822
+ const trimmed = line.replace(/^[\s-]*/, "");
4823
+ if (trimmed && section === "given" && currentScenario.given.length > 0) {
4824
+ currentScenario.given[currentScenario.given.length - 1].description += " " + trimmed;
4825
+ } else if (trimmed && section === "when" && currentScenario.when.length > 0) {
4826
+ currentScenario.when[currentScenario.when.length - 1].description += " " + trimmed;
4827
+ } else if (trimmed && section === "then" && currentScenario.then.length > 0) {
4828
+ currentScenario.then[currentScenario.then.length - 1].description += " " + trimmed;
4829
+ }
4830
+ }
4831
+ }
4832
+ if (currentScenario && currentScenario.title) {
4833
+ scenarios.push(this.finalizeScenario(currentScenario, scenarioOrder));
4834
+ }
4835
+ return scenarios;
4836
+ }
4837
+ /**
4838
+ * 从验收标准部分提取 BDD 场景
4839
+ */
4840
+ static parseFromAcceptanceSection(acceptanceSection) {
4841
+ const bddSectionMatch = acceptanceSection.match(
4842
+ /(?:###\s*BDD[^\n]*|###\s*场景|###\s*行为驱动)[^]*(?=##|$)/i
4843
+ );
4844
+ if (bddSectionMatch) {
4845
+ return this.parseScenarios(bddSectionMatch[0]);
4846
+ }
4847
+ return this.convertFromLegacyCriteria(acceptanceSection);
4848
+ }
4849
+ /**
4850
+ * 从传统验收标准转换
4851
+ */
4852
+ static convertFromLegacyCriteria(criteria) {
4853
+ const scenarios = [];
4854
+ const lines = criteria.split("\n");
4855
+ let scenarioId = 0;
4856
+ let currentScenario = null;
4857
+ for (const line of lines) {
4858
+ const criteriaMatch = line.match(/^-\s*\[\s*\]\s*(.+)$/);
4859
+ if (criteriaMatch) {
4860
+ if (!currentScenario) {
4861
+ scenarioId++;
4862
+ currentScenario = {
4863
+ id: `scenario-${scenarioId}`,
4864
+ title: `\u573A\u666F ${scenarioId}`,
4865
+ order: scenarioId,
4866
+ given: [],
4867
+ when: [],
4868
+ then: [],
4869
+ stateTransitions: []
4870
+ };
4871
+ }
4872
+ const text = criteriaMatch[1];
4873
+ const normalized = text.toLowerCase();
4874
+ if (normalized.includes("\u5DF2") || normalized.includes("\u6709") || normalized.includes("\u5B58\u5728")) {
4875
+ currentScenario.given.push({
4876
+ description: text,
4877
+ type: "data",
4878
+ validation: "\u68C0\u67E5\u6570\u636E\u5B58\u5728"
4879
+ });
4880
+ } else if (normalized.includes("\u5F53") || normalized.includes("\u63D0\u4EA4") || normalized.includes("\u70B9\u51FB")) {
4881
+ currentScenario.when.push({
4882
+ description: text,
4883
+ trigger: "user"
4884
+ });
4885
+ } else if (normalized.includes("\u5E94") || normalized.includes("\u8FD4\u56DE") || normalized.includes("\u663E\u793A")) {
4886
+ currentScenario.then.push({
4887
+ description: text,
4888
+ type: "response",
4889
+ expectedBehavior: text
4890
+ });
4891
+ }
4892
+ } else if (line.trim() === "" && currentScenario) {
4893
+ scenarios.push(this.finalizeScenario(currentScenario, scenarioId));
4894
+ currentScenario = null;
4895
+ }
4896
+ }
4897
+ if (currentScenario && currentScenario.title) {
4898
+ scenarios.push(this.finalizeScenario(currentScenario, scenarioId));
4899
+ }
4900
+ return scenarios;
4901
+ }
4902
+ /**
4903
+ * 检测条件类型
4904
+ */
4905
+ static detectConditionType(description) {
4906
+ const lower = description.toLowerCase();
4907
+ if (lower.includes("\u6570\u636E") || lower.includes("\u8BB0\u5F55") || lower.includes("\u8868")) return "data";
4908
+ if (lower.includes("\u72B6\u6001") || lower.includes("\u5DF2") || lower.includes("\u672A")) return "state";
4909
+ return "user";
4910
+ }
4911
+ /**
4912
+ * 检测结果类型
4913
+ */
4914
+ static detectResultType(description) {
4915
+ const lower = description.toLowerCase();
4916
+ if (lower.includes("\u8FD4\u56DE") || lower.includes("\u54CD\u5E94") || lower.includes("\u72B6\u6001\u7801")) return "response";
4917
+ if (lower.includes("\u72B6\u6001") || lower.includes("\u53D8\u6210") || lower.includes("\u66F4\u65B0")) return "state";
4918
+ if (lower.includes("\u663E\u793A") || lower.includes("\u9875\u9762") || lower.includes("\u754C\u9762")) return "ui";
4919
+ return "data";
4920
+ }
4921
+ /**
4922
+ * 提取验证方式
4923
+ */
4924
+ static extractValidation(description) {
4925
+ const match = description.match(/\(([^)]+)\)/);
4926
+ return match ? match[1] : "\u68C0\u67E5\u6761\u4EF6\u6EE1\u8DB3";
4927
+ }
4928
+ /**
4929
+ * 提取预期行为
4930
+ */
4931
+ static extractExpectedBehavior(description) {
4932
+ return description.replace(/^(应该|应|应当|应该会|会)/, "").trim();
4933
+ }
4934
+ /**
4935
+ * 解析动作
4936
+ */
4937
+ static parseAction(description) {
4938
+ const apiMatch = description.match(/(?:API|POST|GET|PUT|DELETE|PATCH)\s*[::]?\s*`([^`]+)`/i);
4939
+ const uiMatch = description.match(/(?:点击|输入|选择|提交|拖拽)[::]?\s*(.+)/);
4940
+ return {
4941
+ description,
4942
+ api: apiMatch?.[1],
4943
+ uiAction: uiMatch?.[1],
4944
+ trigger: apiMatch ? "api" : "user"
4945
+ };
4946
+ }
4947
+ /**
4948
+ * 解析状态流转
4949
+ */
4950
+ static parseStateTransition(description) {
4951
+ const arrowMatch = description.match(/`([^`]+)`\s*(?:→|->)\s*`([^`]+)`/);
4952
+ if (arrowMatch) {
4953
+ return {
4954
+ from: arrowMatch[1],
4955
+ trigger: description.split(/→|->/)[1]?.trim() || "\u5B8C\u6210",
4956
+ to: arrowMatch[2]
4957
+ };
4958
+ }
4959
+ const fromMatch = description.match(/(?:从|起始于|开始于)\s*`([^`]+)`/);
4960
+ const toMatch = description.match(/(?:到|变为|结束于)\s*`([^`]+)`/);
4961
+ const triggerMatch = description.match(/通过|当|使用\s*[::]?\s*(.+?)(?:,|$)/);
4962
+ if (fromMatch && toMatch) {
4963
+ return {
4964
+ from: fromMatch[1],
4965
+ trigger: triggerMatch?.[1] || "\u5B8C\u6210",
4966
+ to: toMatch[1]
4967
+ };
4968
+ }
4969
+ return null;
4970
+ }
4971
+ /**
4972
+ * 完成场景构建
4973
+ */
4974
+ static finalizeScenario(scenario, order) {
4975
+ return {
4976
+ id: scenario.id || `scenario-${order}`,
4977
+ title: scenario.title || `\u573A\u666F ${order}`,
4978
+ order: scenario.order || order,
4979
+ asA: scenario.asA || "\u7528\u6237",
4980
+ iWant: scenario.iWant || "",
4981
+ soThat: scenario.soThat || "",
4982
+ given: scenario.given || [],
4983
+ when: scenario.when || [],
4984
+ then: scenario.then || [],
4985
+ stateTransitions: scenario.stateTransitions || [],
4986
+ relatedApis: scenario.relatedApis || [],
4987
+ relatedFeatures: scenario.relatedFeatures || []
4988
+ };
4989
+ }
4990
+ /**
4991
+ * 生成场景的 Markdown
4992
+ */
4993
+ static generateMarkdown(scenario) {
4994
+ const lines = [];
4995
+ lines.push(`#### \u573A\u666F ${scenario.order}: ${scenario.title}`);
4996
+ lines.push("");
4997
+ if (scenario.asA) {
4998
+ lines.push(`**\u4F5C\u4E3A** ${scenario.asA}`);
4999
+ }
5000
+ if (scenario.iWant) {
5001
+ lines.push(`**\u6211\u5E0C\u671B** ${scenario.iWant}`);
5002
+ }
5003
+ if (scenario.soThat) {
5004
+ lines.push(`**\u4EE5\u4FBF** ${scenario.soThat}`);
5005
+ }
5006
+ lines.push("");
5007
+ for (const given of scenario.given) {
5008
+ lines.push(`- [GIVEN] ${given.description}`);
5009
+ }
5010
+ lines.push("");
5011
+ for (const when of scenario.when) {
5012
+ lines.push(`- [WHEN] ${when.description}`);
5013
+ }
5014
+ lines.push("");
5015
+ for (const then of scenario.then) {
5016
+ lines.push(`- [THEN] ${then.description}`);
5017
+ }
5018
+ lines.push("");
5019
+ if (scenario.stateTransitions.length > 0) {
5020
+ lines.push("**\u72B6\u6001\u6D41\u8F6C**:");
5021
+ for (const trans of scenario.stateTransitions) {
5022
+ lines.push(`- \`${trans.from}\` \u2192 \`${trans.to}\` (\u89E6\u53D1: ${trans.trigger})`);
5023
+ }
5024
+ lines.push("");
5025
+ }
5026
+ return lines.join("\n");
5027
+ }
5028
+ };
5029
+ }
5030
+ });
5031
+
4548
5032
  // src/commands/accept.ts
4549
5033
  import { Command as Command7 } from "commander";
4550
5034
  import inquirer6 from "inquirer";
4551
5035
  import path12 from "path";
5036
+ async function runTraditionalAcceptance(defaultSpec) {
5037
+ const selectedSpec = await selectSpec2(defaultSpec);
5038
+ const selectedMilestone = await selectMilestone2(selectedSpec);
5039
+ const result = await runAcceptanceCheck(selectedSpec, selectedMilestone);
5040
+ await generateAcceptanceReport(result);
5041
+ if (result.issues.length > 0) {
5042
+ await handleIssues(result);
5043
+ }
5044
+ await syncSpecStatus(selectedSpec, result);
5045
+ }
5046
+ async function runBDDAcceptance(defaultSpec, scenarioId) {
5047
+ const selectedSpec = await selectSpec2(defaultSpec);
5048
+ const specContent = await FileUtils.read(selectedSpec);
5049
+ const scenarios = BDDParser.parseFromAcceptanceSection(specContent);
5050
+ if (scenarios.length === 0) {
5051
+ logger.warn("\u672A\u627E\u5230 BDD \u573A\u666F\uFF0C\u4F7F\u7528\u4F20\u7EDF\u9A8C\u6536\u6A21\u5F0F");
5052
+ logger.info('\u8BF7\u5148\u8FD0\u884C "team-cli split-prd" \u751F\u6210\u5305\u542B BDD \u573A\u666F\u7684 spec');
5053
+ await runTraditionalAcceptance(selectedSpec);
5054
+ return;
5055
+ }
5056
+ const targetScenarios = scenarioId ? scenarios.filter((s) => s.id === scenarioId || s.title.toLowerCase().includes(scenarioId.toLowerCase())) : scenarios;
5057
+ if (targetScenarios.length === 0) {
5058
+ throw new Error(`\u672A\u627E\u5230\u573A\u666F: ${scenarioId}`);
5059
+ }
5060
+ logger.newLine();
5061
+ logger.info(`\u627E\u5230 ${scenarios.length} \u4E2A BDD \u573A\u666F\uFF0C\u5C06\u9A8C\u6536 ${targetScenarios.length} \u4E2A`);
5062
+ const results = [];
5063
+ for (const scenario of targetScenarios) {
5064
+ logger.newLine();
5065
+ logger.info(`\u9A8C\u6536\u573A\u666F: ${scenario.title}`);
5066
+ const result = await checkBDDScenario(scenario, selectedSpec);
5067
+ results.push(result);
5068
+ printScenarioResult(result);
5069
+ }
5070
+ await generateBDDReport(selectedSpec, results);
5071
+ await syncBDDStatus(selectedSpec, results);
5072
+ }
4552
5073
  async function selectSpec2(defaultSpec) {
4553
5074
  logger.step("\u6B65\u9AA4 1/4: \u9009\u62E9 spec \u6587\u4EF6...");
4554
5075
  logger.newLine();
@@ -5095,6 +5616,377 @@ async function syncSpecStatus(specFile, result) {
5095
5616
  }
5096
5617
  }
5097
5618
  }
5619
+ async function checkBDDScenario(scenario, specFile) {
5620
+ const result = {
5621
+ scenarioId: scenario.id,
5622
+ scenarioTitle: scenario.title,
5623
+ overallStatus: "pending",
5624
+ givenResults: [],
5625
+ whenResults: [],
5626
+ thenResults: [],
5627
+ stateTransitionResults: []
5628
+ };
5629
+ for (const given of scenario.given) {
5630
+ const checkResult = await verifyGivenCondition(given, specFile);
5631
+ result.givenResults.push(checkResult);
5632
+ }
5633
+ for (const when of scenario.when) {
5634
+ const checkResult = await verifyWhenAction(when, specFile);
5635
+ result.whenResults.push(checkResult);
5636
+ }
5637
+ for (const then of scenario.then) {
5638
+ const checkResult = await verifyThenResult(then, specFile);
5639
+ result.thenResults.push(checkResult);
5640
+ }
5641
+ for (const transition of scenario.stateTransitions) {
5642
+ const checkResult = await verifyStateTransition(transition, specFile);
5643
+ result.stateTransitionResults.push(checkResult);
5644
+ }
5645
+ const allResults = [
5646
+ ...result.givenResults,
5647
+ ...result.whenResults,
5648
+ ...result.thenResults,
5649
+ ...result.stateTransitionResults
5650
+ ];
5651
+ if (allResults.every((r) => r.status === "pass")) {
5652
+ result.overallStatus = "pass";
5653
+ } else if (allResults.some((r) => r.status === "fail")) {
5654
+ result.overallStatus = "fail";
5655
+ } else {
5656
+ result.overallStatus = "pending";
5657
+ }
5658
+ return result;
5659
+ }
5660
+ async function verifyGivenCondition(given, specFile) {
5661
+ switch (given.type) {
5662
+ case "data":
5663
+ return verifyDataCondition(given);
5664
+ case "state":
5665
+ return verifyStateCondition(given);
5666
+ case "user":
5667
+ return verifyUserCondition(given);
5668
+ default:
5669
+ return { condition: given.description, status: "pending" };
5670
+ }
5671
+ }
5672
+ async function verifyDataCondition(given) {
5673
+ const tableMatch = given.description.match(/`?([a-z_]+)`?表/);
5674
+ const fieldMatch = given.description.match(/`?([a-z_]+)`?字段/);
5675
+ if (tableMatch) {
5676
+ const tableName = tableMatch[1];
5677
+ const tableFiles = await FileUtils.findFiles(`**/${tableName}.sql`, "database");
5678
+ const entityFiles = await FileUtils.findFiles(`**/${StringUtils.toPascalCase(tableName)}.java`, "backend/src");
5679
+ if (tableFiles.length > 0 || entityFiles.length > 0) {
5680
+ return {
5681
+ condition: given.description,
5682
+ status: "pass",
5683
+ evidence: tableFiles[0] || entityFiles[0]
5684
+ };
5685
+ }
5686
+ return {
5687
+ condition: given.description,
5688
+ status: "fail",
5689
+ evidence: `\u8868 ${tableName} \u672A\u627E\u5230`
5690
+ };
5691
+ }
5692
+ return { condition: given.description, status: "pending" };
5693
+ }
5694
+ async function verifyStateCondition(given) {
5695
+ const statusMatch = given.description.match(/`([^`]+)`状态/);
5696
+ const stateMatch = given.description.match(/`([^`]+)`/);
5697
+ const statusName = statusMatch?.[1] || stateMatch?.[1];
5698
+ if (statusName) {
5699
+ const enumFiles = [
5700
+ ...await FileUtils.findFiles("**/*Status*.java", "backend/src"),
5701
+ ...await FileUtils.findFiles("**/*Status*.ts", "frontend/src")
5702
+ ];
5703
+ for (const ef of enumFiles) {
5704
+ const content = await FileUtils.read(ef);
5705
+ if (content.includes(statusName)) {
5706
+ return {
5707
+ condition: given.description,
5708
+ status: "pass",
5709
+ evidence: ef
5710
+ };
5711
+ }
5712
+ }
5713
+ return {
5714
+ condition: given.description,
5715
+ status: "fail",
5716
+ evidence: `\u72B6\u6001 ${statusName} \u672A\u627E\u5230`
5717
+ };
5718
+ }
5719
+ return { condition: given.description, status: "pending" };
5720
+ }
5721
+ async function verifyUserCondition(given) {
5722
+ const userMatch = given.description.match(/用户.*?`([^`]+)`/);
5723
+ if (userMatch) {
5724
+ return {
5725
+ condition: given.description,
5726
+ status: "pending",
5727
+ evidence: "\u9700\u8981\u4EBA\u5DE5\u9A8C\u8BC1\u7528\u6237\u72B6\u6001"
5728
+ };
5729
+ }
5730
+ return { condition: given.description, status: "pending" };
5731
+ }
5732
+ async function verifyWhenAction(when, specFile) {
5733
+ if (when.api) {
5734
+ const apiCheck = await verifyApiImplementationByPath(when.api);
5735
+ return {
5736
+ action: when.description,
5737
+ status: apiCheck.status,
5738
+ apiCalled: when.api,
5739
+ responseCode: apiCheck.responseCode
5740
+ };
5741
+ }
5742
+ if (when.uiAction) {
5743
+ const uiCheck = await verifyUIComponent(when.uiAction);
5744
+ return {
5745
+ action: when.description,
5746
+ status: uiCheck.status
5747
+ };
5748
+ }
5749
+ return { action: when.description, status: "pending" };
5750
+ }
5751
+ async function verifyApiImplementationByPath(apiPath) {
5752
+ const apiMatch = apiPath.match(/(GET|POST|PUT|DELETE|PATCH)\s+\/?(.+)/i);
5753
+ if (!apiMatch) {
5754
+ return { status: "pending" };
5755
+ }
5756
+ const method = apiMatch[1].toUpperCase();
5757
+ const path22 = "/" + apiMatch[2];
5758
+ const controllerFiles = await FileUtils.findFiles("**/*Controller.java", "backend/src");
5759
+ for (const cf of controllerFiles) {
5760
+ const content = await FileUtils.read(cf);
5761
+ if (content.includes(`"${path22}"`) || content.includes(`"${path22}/"`)) {
5762
+ const annotationMap = {
5763
+ GET: ["@GetMapping", "@RequestMapping(method = RequestMethod.GET)"],
5764
+ POST: ["@PostMapping", "@RequestMapping(method = RequestMethod.POST)"],
5765
+ PUT: ["@PutMapping", "@RequestMapping(method = RequestMethod.PUT)"],
5766
+ DELETE: ["@DeleteMapping", "@RequestMapping(method = RequestMethod.DELETE)"]
5767
+ };
5768
+ for (const annotation of annotationMap[method] || []) {
5769
+ if (content.includes(annotation)) {
5770
+ return { status: "pass", responseCode: 200 };
5771
+ }
5772
+ }
5773
+ }
5774
+ }
5775
+ return { status: "fail" };
5776
+ }
5777
+ async function verifyUIComponent(uiAction) {
5778
+ const componentFiles = await FileUtils.findFiles("**/*.tsx", "frontend/src");
5779
+ const viewFiles = await FileUtils.findFiles("**/*.vue", "frontend/src");
5780
+ const allFiles = [...componentFiles, ...viewFiles];
5781
+ for (const cf of allFiles) {
5782
+ const content = await FileUtils.read(cf);
5783
+ if (content.includes(uiAction) || content.toLowerCase().includes(uiAction.toLowerCase())) {
5784
+ return { status: "pass" };
5785
+ }
5786
+ }
5787
+ return { status: "pending" };
5788
+ }
5789
+ async function verifyThenResult(then, specFile) {
5790
+ switch (then.type) {
5791
+ case "response":
5792
+ return verifyResponseResult(then);
5793
+ case "state":
5794
+ return verifyStateResult(then);
5795
+ case "data":
5796
+ return verifyDataResult(then);
5797
+ case "ui":
5798
+ return verifyUIResult(then);
5799
+ default:
5800
+ return { result: then.description, status: "pending" };
5801
+ }
5802
+ }
5803
+ async function verifyResponseResult(then) {
5804
+ const codeMatch = then.description.match(/状态码\s*(\d+)/);
5805
+ const tokenMatch = then.description.match(/token/i);
5806
+ if (tokenMatch) {
5807
+ const tokenFiles = await FileUtils.findFiles("**/*Token*.java", "backend/src");
5808
+ if (tokenFiles.length > 0) {
5809
+ return { result: then.description, status: "pass", expectedValue: "JWT Token" };
5810
+ }
5811
+ return { result: then.description, status: "pending" };
5812
+ }
5813
+ if (codeMatch) {
5814
+ return { result: then.description, status: "pending", expectedValue: `HTTP ${codeMatch[1]}` };
5815
+ }
5816
+ return { result: then.description, status: "pending" };
5817
+ }
5818
+ async function verifyStateResult(then) {
5819
+ const stateMatch = then.description.match(/`([^`]+)`/);
5820
+ if (stateMatch) {
5821
+ const stateName = stateMatch[1];
5822
+ const enumFiles = [
5823
+ ...await FileUtils.findFiles("**/*Status*.java", "backend/src"),
5824
+ ...await FileUtils.findFiles("**/*Status*.ts", "frontend/src")
5825
+ ];
5826
+ for (const ef of enumFiles) {
5827
+ const content = await FileUtils.read(ef);
5828
+ if (content.includes(stateName)) {
5829
+ return { result: then.description, status: "pass", expectedValue: stateName };
5830
+ }
5831
+ }
5832
+ return { result: then.description, status: "fail", expectedValue: stateName };
5833
+ }
5834
+ return { result: then.description, status: "pending" };
5835
+ }
5836
+ async function verifyDataResult(then) {
5837
+ return { result: then.description, status: "pending" };
5838
+ }
5839
+ async function verifyUIResult(then) {
5840
+ return { result: then.description, status: "pending" };
5841
+ }
5842
+ async function verifyStateTransition(transition, specFile) {
5843
+ const { from, trigger, to } = transition;
5844
+ const enumFiles = [
5845
+ ...await FileUtils.findFiles("**/*Status*.java", "backend/src"),
5846
+ ...await FileUtils.findFiles("**/*Status*.ts", "frontend/src"),
5847
+ ...await FileUtils.findFiles("**/*State*.ts", "frontend/src")
5848
+ ];
5849
+ const fromExists = await checkStateExists(from, enumFiles);
5850
+ const toExists = await checkStateExists(to, enumFiles);
5851
+ if (!fromExists || !toExists) {
5852
+ return {
5853
+ transition: `\`${from}\` \u2192 \`${to}\``,
5854
+ status: "fail",
5855
+ fromState: fromExists ? "\u2713" : "\u2717",
5856
+ toState: toExists ? "\u2713" : "\u2717"
5857
+ };
5858
+ }
5859
+ const serviceFiles = await FileUtils.findFiles("**/*Service*.java", "backend/src");
5860
+ for (const sf of serviceFiles) {
5861
+ const content = await FileUtils.read(sf);
5862
+ const methodPatterns = [
5863
+ new RegExp(`${from}\\s*\u2192\\s*${to}`, "i"),
5864
+ new RegExp(`from\\s*${from}\\s*to\\s*${to}`, "i")
5865
+ ];
5866
+ for (const pattern of methodPatterns) {
5867
+ if (pattern.test(content)) {
5868
+ return {
5869
+ transition: `\`${from}\` \u2192 \`${to}\` (${trigger})`,
5870
+ status: "pass",
5871
+ fromState: from,
5872
+ toState: to
5873
+ };
5874
+ }
5875
+ }
5876
+ }
5877
+ return {
5878
+ transition: `\`${from}\` \u2192 \`${to}\` (${trigger})`,
5879
+ status: "pending",
5880
+ fromState: from,
5881
+ toState: to
5882
+ };
5883
+ }
5884
+ async function checkStateExists(state, files) {
5885
+ for (const file of files) {
5886
+ try {
5887
+ const content = await FileUtils.read(file);
5888
+ if (content.includes(state) || content.includes(`"${state}"`)) {
5889
+ return true;
5890
+ }
5891
+ } catch {
5892
+ continue;
5893
+ }
5894
+ }
5895
+ return false;
5896
+ }
5897
+ function printScenarioResult(result) {
5898
+ const icon = result.overallStatus === "pass" ? "\u2713" : result.overallStatus === "fail" ? "\u2717" : "\u25CB";
5899
+ console.log("");
5900
+ console.log(` ${icon} \u573A\u666F: ${result.scenarioTitle}`);
5901
+ const passGiven = result.givenResults.filter((r) => r.status === "pass").length;
5902
+ const passWhen = result.whenResults.filter((r) => r.status === "pass").length;
5903
+ const passThen = result.thenResults.filter((r) => r.status === "pass").length;
5904
+ const passTrans = result.stateTransitionResults.filter((r) => r.status === "pass").length;
5905
+ console.log(` Given: ${passGiven}/${result.givenResults.length}`);
5906
+ console.log(` When: ${passWhen}/${result.whenResults.length}`);
5907
+ console.log(` Then: ${passThen}/${result.thenResults.length}`);
5908
+ console.log(` State: ${passTrans}/${result.stateTransitionResults.length}`);
5909
+ }
5910
+ async function generateBDDReport(specFile, results) {
5911
+ const reportDir = "docs/bdd-reports";
5912
+ await FileUtils.ensureDir(reportDir);
5913
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5914
+ const specName = path12.basename(specFile, ".md");
5915
+ const reportFile = path12.join(reportDir, `${timestamp}_${specName}_bdd.md`);
5916
+ const lines = [];
5917
+ lines.push("# BDD \u9A8C\u6536\u62A5\u544A");
5918
+ lines.push("");
5919
+ lines.push(`**Spec \u6587\u4EF6**: ${specFile}`);
5920
+ lines.push(`**\u9A8C\u6536\u65F6\u95F4**: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}`);
5921
+ lines.push(`**\u573A\u666F\u6570\u91CF**: ${results.length}`);
5922
+ lines.push("");
5923
+ lines.push("## \u9A8C\u6536\u6C47\u603B");
5924
+ lines.push("");
5925
+ let totalPass = 0;
5926
+ let totalFail = 0;
5927
+ for (const r of results) {
5928
+ const statusIcon = r.overallStatus === "pass" ? "\u2713" : r.overallStatus === "fail" ? "\u2717" : "\u25CB";
5929
+ const givenIcon = r.givenResults.every((g) => g.status === "pass") ? "\u2713" : r.givenResults.some((g) => g.status === "fail") ? "\u2717" : "\u25CB";
5930
+ const whenIcon = r.whenResults.every((w) => w.status === "pass") ? "\u2713" : r.whenResults.some((w) => w.status === "fail") ? "\u2717" : "\u25CB";
5931
+ const thenIcon = r.thenResults.every((t) => t.status === "pass") ? "\u2713" : r.thenResults.some((t) => t.status === "fail") ? "\u2717" : "\u25CB";
5932
+ const transIcon = r.stateTransitionResults.every((t) => t.status === "pass") ? "\u2713" : r.stateTransitionResults.some((t) => t.status === "fail") ? "\u2717" : "\u25CB";
5933
+ lines.push(`- ${statusIcon} ${r.scenarioTitle} | Given:${givenIcon} When:${whenIcon} Then:${thenIcon} State:${transIcon}`);
5934
+ if (r.overallStatus === "pass") totalPass++;
5935
+ if (r.overallStatus === "fail") totalFail++;
5936
+ }
5937
+ lines.push("");
5938
+ lines.push(`**\u603B\u8BA1**: ${totalPass} \u4E2A\u901A\u8FC7, ${totalFail} \u4E2A\u5931\u8D25, ${results.length - totalPass - totalFail} \u4E2A\u5F85\u9A8C\u8BC1`);
5939
+ lines.push("");
5940
+ lines.push("## \u8BE6\u7EC6\u9A8C\u6536\u7ED3\u679C");
5941
+ lines.push("");
5942
+ for (const r of results) {
5943
+ lines.push(`### \u573A\u666F: ${r.scenarioTitle}`);
5944
+ lines.push("");
5945
+ lines.push("#### GIVEN (\u524D\u7F6E\u6761\u4EF6)");
5946
+ for (const g of r.givenResults) {
5947
+ const icon = g.status === "pass" ? "\u2713" : g.status === "fail" ? "\u2717" : "\u25CB";
5948
+ lines.push(`- ${icon} ${g.condition}`);
5949
+ if (g.evidence) {
5950
+ lines.push(` - \u8BC1\u636E: ${g.evidence}`);
5951
+ }
5952
+ }
5953
+ lines.push("");
5954
+ lines.push("#### WHEN (\u89E6\u53D1\u52A8\u4F5C)");
5955
+ for (const w of r.whenResults) {
5956
+ const icon = w.status === "pass" ? "\u2713" : w.status === "fail" ? "\u2717" : "\u25CB";
5957
+ lines.push(`- ${icon} ${w.action}`);
5958
+ if (w.apiCalled) {
5959
+ lines.push(` - API: ${w.apiCalled}`);
5960
+ }
5961
+ }
5962
+ lines.push("");
5963
+ lines.push("#### THEN (\u9884\u671F\u7ED3\u679C)");
5964
+ for (const t of r.thenResults) {
5965
+ const icon = t.status === "pass" ? "\u2713" : t.status === "fail" ? "\u2717" : "\u25CB";
5966
+ lines.push(`- ${icon} ${t.result}`);
5967
+ }
5968
+ lines.push("");
5969
+ lines.push("#### \u72B6\u6001\u6D41\u8F6C");
5970
+ for (const s of r.stateTransitionResults) {
5971
+ const icon = s.status === "pass" ? "\u2713" : s.status === "fail" ? "\u2717" : "\u25CB";
5972
+ lines.push(`- ${icon} ${s.transition}`);
5973
+ }
5974
+ lines.push("");
5975
+ lines.push("---");
5976
+ lines.push("");
5977
+ }
5978
+ await FileUtils.write(reportFile, lines.join("\n"));
5979
+ logger.success(`BDD \u9A8C\u6536\u62A5\u544A\u5DF2\u751F\u6210: ${reportFile}`);
5980
+ }
5981
+ async function syncBDDStatus(specFile, results) {
5982
+ const allPassed = results.every((r) => r.overallStatus === "pass");
5983
+ if (allPassed) {
5984
+ logger.success("\u6240\u6709 BDD \u573A\u666F\u9A8C\u6536\u901A\u8FC7");
5985
+ } else {
5986
+ const failedCount = results.filter((r) => r.overallStatus === "fail").length;
5987
+ logger.warn(`${failedCount} \u4E2A\u573A\u666F\u9A8C\u6536\u672A\u901A\u8FC7\uFF0C\u9700\u8981\u4FEE\u590D`);
5988
+ }
5989
+ }
5098
5990
  var acceptCommand;
5099
5991
  var init_accept = __esm({
5100
5992
  "src/commands/accept.ts"() {
@@ -5102,7 +5994,8 @@ var init_accept = __esm({
5102
5994
  init_esm_shims();
5103
5995
  init_utils();
5104
5996
  init_logger();
5105
- acceptCommand = new Command7("accept").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u9A8C\u6536\u529F\u80FD - \u8D70\u67E5\u6240\u6709\u9700\u6C42\uFF0C\u9A8C\u8BC1\u8054\u8C03\u662F\u5426\u5B8C\u6210").action(async (specFile) => {
5997
+ init_bdd_parser();
5998
+ acceptCommand = new Command7("accept").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u9A8C\u6536\u529F\u80FD - \u8D70\u67E5\u6240\u6709\u9700\u6C42\uFF0C\u9A8C\u8BC1\u8054\u8C03\u662F\u5426\u5B8C\u6210").option("--mode <mode>", "\u9A8C\u6536\u6A21\u5F0F: traditional (\u4F20\u7EDF) \u6216 bdd (\u884C\u4E3A\u9A71\u52A8)", "traditional").option("--scenario <id>", "\u6307\u5B9A\u9A8C\u6536\u7684 BDD \u573A\u666F ID").action(async (specFile, options) => {
5106
5999
  try {
5107
6000
  logger.header("\u9A8C\u6536\u6A21\u5F0F");
5108
6001
  logger.newLine();
@@ -5113,14 +6006,11 @@ var init_accept = __esm({
5113
6006
  process.exit(1);
5114
6007
  }
5115
6008
  logger.success("\u68C0\u6D4B\u5230\u9879\u76EE\u4E0A\u4E0B\u6587");
5116
- const selectedSpec = await selectSpec2(specFile);
5117
- const selectedMilestone = await selectMilestone2(selectedSpec);
5118
- const result = await runAcceptanceCheck(selectedSpec, selectedMilestone);
5119
- await generateAcceptanceReport(result);
5120
- if (result.issues.length > 0) {
5121
- await handleIssues(result);
5122
- }
5123
- await syncSpecStatus(selectedSpec, result);
6009
+ if (options.mode === "bdd") {
6010
+ await runBDDAcceptance(specFile, options.scenario);
6011
+ } else {
6012
+ await runTraditionalAcceptance(specFile);
6013
+ }
5124
6014
  logger.header("\u9A8C\u6536\u5B8C\u6210!");
5125
6015
  logger.newLine();
5126
6016
  } catch (error) {