yg-team-cli 2.7.1 → 2.8.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
@@ -4217,30 +4217,9 @@ Generate a complete Spec document with the following sections:
4217
4217
  \u5217\u51FA\u9700\u8981\u7684\u540E\u7AEF\u6A21\u5757\u7ED3\u6784
4218
4218
 
4219
4219
  ## \u9A8C\u6536\u6807\u51C6
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
4220
+ - [ ] \u9A8C\u6536\u6807\u51C6 1
4221
+ - [ ] \u9A8C\u6536\u6807\u51C6 2
4222
+ - [ ] \u9A8C\u6536\u6807\u51C6 3
4244
4223
 
4245
4224
  ## \u91CC\u7A0B\u7891 (Milestones)
4246
4225
  > \u6CE8: \u4F7F\u7528 \`team-cli breakdown ${featureSlug}.md\` \u62C6\u5206\u6B64 spec \u4E3A milestones \u548C todos
@@ -4255,7 +4234,6 @@ IMPORTANT:
4255
4234
  3. Priority should be justified based on feature importance
4256
4235
  4. Work estimation should be realistic
4257
4236
  5. Extract key requirements from the PRD accurately
4258
- 6. BDD scenarios should cover all user flows and edge cases
4259
4237
  `;
4260
4238
  }
4261
4239
  function buildSplitPrdPrompt(prdContent, screenshots, demoRepos) {
@@ -4710,366 +4688,10 @@ Temporary solution: ${answers.solution}`
4710
4688
  }
4711
4689
  });
4712
4690
 
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
-
5032
4691
  // src/commands/accept.ts
5033
4692
  import { Command as Command7 } from "commander";
5034
4693
  import inquirer6 from "inquirer";
5035
4694
  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
- }
5073
4695
  async function selectSpec2(defaultSpec) {
5074
4696
  logger.step("\u6B65\u9AA4 1/4: \u9009\u62E9 spec \u6587\u4EF6...");
5075
4697
  logger.newLine();
@@ -5616,377 +5238,6 @@ async function syncSpecStatus(specFile, result) {
5616
5238
  }
5617
5239
  }
5618
5240
  }
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
- }
5990
5241
  var acceptCommand;
5991
5242
  var init_accept = __esm({
5992
5243
  "src/commands/accept.ts"() {
@@ -5994,8 +5245,7 @@ var init_accept = __esm({
5994
5245
  init_esm_shims();
5995
5246
  init_utils();
5996
5247
  init_logger();
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) => {
5248
+ 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) => {
5999
5249
  try {
6000
5250
  logger.header("\u9A8C\u6536\u6A21\u5F0F");
6001
5251
  logger.newLine();
@@ -6006,11 +5256,14 @@ var init_accept = __esm({
6006
5256
  process.exit(1);
6007
5257
  }
6008
5258
  logger.success("\u68C0\u6D4B\u5230\u9879\u76EE\u4E0A\u4E0B\u6587");
6009
- if (options.mode === "bdd") {
6010
- await runBDDAcceptance(specFile, options.scenario);
6011
- } else {
6012
- await runTraditionalAcceptance(specFile);
6013
- }
5259
+ const selectedSpec = await selectSpec2(specFile);
5260
+ const selectedMilestone = await selectMilestone2(selectedSpec);
5261
+ const result = await runAcceptanceCheck(selectedSpec, selectedMilestone);
5262
+ await generateAcceptanceReport(result);
5263
+ if (result.issues.length > 0) {
5264
+ await handleIssues(result);
5265
+ }
5266
+ await syncSpecStatus(selectedSpec, result);
6014
5267
  logger.header("\u9A8C\u6536\u5B8C\u6210!");
6015
5268
  logger.newLine();
6016
5269
  } catch (error) {
@@ -8850,9 +8103,479 @@ var init_diff = __esm({
8850
8103
  }
8851
8104
  });
8852
8105
 
8106
+ // src/lib/fe-git.ts
8107
+ async function ensureGitRepo() {
8108
+ const isRepo = await GitUtils.isGitRepo();
8109
+ if (!isRepo) {
8110
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A Git \u4ED3\u5E93");
8111
+ process.exit(1);
8112
+ }
8113
+ }
8114
+ async function ensureCleanWorkingTree() {
8115
+ const { execa: execa5 } = await import("execa");
8116
+ const { stdout } = await execa5("git", ["status", "--porcelain"]);
8117
+ if (stdout.trim()) {
8118
+ logger.error("\u5DE5\u4F5C\u533A\u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF1A");
8119
+ stdout.trim().split("\n").forEach((line) => logger.info(` ${line.trim()}`));
8120
+ process.exit(1);
8121
+ }
8122
+ }
8123
+ async function ensureOnBranch(prefixes = [...VALID_BRANCH_PREFIXES]) {
8124
+ const branch = await getCurrentBranch();
8125
+ const prefix = getBranchPrefix(branch);
8126
+ if (!prefix || !prefixes.includes(prefix)) {
8127
+ logger.error(`\u5F53\u524D\u5206\u652F "${branch}" \u4E0D\u662F\u5408\u6CD5\u7684\u5DE5\u4F5C\u5206\u652F`);
8128
+ logger.info(`\u5408\u6CD5\u524D\u7F00: ${prefixes.join(", ")}`);
8129
+ process.exit(1);
8130
+ }
8131
+ return branch;
8132
+ }
8133
+ async function ensureBranchExists(branch) {
8134
+ if (await branchExistsLocal(branch)) {
8135
+ return;
8136
+ }
8137
+ const { execa: execa5 } = await import("execa");
8138
+ if (await branchExistsRemote(branch)) {
8139
+ try {
8140
+ await execa5("git", ["checkout", "-b", branch, `origin/${branch}`]);
8141
+ const current = await getCurrentBranch();
8142
+ if (current === branch) {
8143
+ await execa5("git", ["checkout", "-"]).catch(() => {
8144
+ });
8145
+ }
8146
+ return;
8147
+ } catch {
8148
+ }
8149
+ }
8150
+ if (branch === "develop") {
8151
+ logger.warn("develop \u5206\u652F\u4E0D\u5B58\u5728\uFF0C\u5C06\u4ECE master \u81EA\u52A8\u521B\u5EFA");
8152
+ try {
8153
+ await execa5("git", ["checkout", "-b", "develop", "master"]);
8154
+ await execa5("git", ["push", "-u", "origin", "develop"]);
8155
+ await execa5("git", ["checkout", "-"]).catch(() => {
8156
+ });
8157
+ return;
8158
+ } catch (err) {
8159
+ logger.error(`\u521B\u5EFA develop \u5206\u652F\u5931\u8D25: ${err.message}`);
8160
+ process.exit(1);
8161
+ }
8162
+ }
8163
+ logger.error(`\u5206\u652F "${branch}" \u4E0D\u5B58\u5728\uFF08\u672C\u5730\u548C\u8FDC\u7AEF\u5747\u672A\u627E\u5230\uFF09`);
8164
+ process.exit(1);
8165
+ }
8166
+ async function getCurrentBranch() {
8167
+ return GitUtils.getCurrentBranch();
8168
+ }
8169
+ function getBranchPrefix(branch) {
8170
+ const match = branch.match(/^(feat|fix|hotfix|refactor)\//);
8171
+ return match ? match[1] : null;
8172
+ }
8173
+ async function branchExistsLocal(branch) {
8174
+ const { execa: execa5 } = await import("execa");
8175
+ try {
8176
+ const { stdout } = await execa5("git", ["branch", "--list", branch]);
8177
+ return stdout.trim().length > 0;
8178
+ } catch {
8179
+ return false;
8180
+ }
8181
+ }
8182
+ async function branchExistsRemote(branch) {
8183
+ const { execa: execa5 } = await import("execa");
8184
+ try {
8185
+ const { stdout } = await execa5("git", ["ls-remote", "--heads", "origin", branch]);
8186
+ return stdout.trim().length > 0;
8187
+ } catch {
8188
+ return false;
8189
+ }
8190
+ }
8191
+ async function scanBranchDiffForDangerousFiles(baseBranch, targetBranch) {
8192
+ const { execa: execa5 } = await import("execa");
8193
+ let diffFiles;
8194
+ try {
8195
+ const { stdout } = await execa5("git", [
8196
+ "diff",
8197
+ "--name-only",
8198
+ `${baseBranch}...${targetBranch}`
8199
+ ]);
8200
+ diffFiles = stdout.trim().split("\n").filter(Boolean);
8201
+ } catch {
8202
+ return [];
8203
+ }
8204
+ const result = [];
8205
+ for (const rule of DANGEROUS_FILE_RULES) {
8206
+ const matched = diffFiles.filter(
8207
+ (file) => rule.patterns.some((pattern) => pattern.test(file))
8208
+ );
8209
+ if (matched.length > 0) {
8210
+ result.push({ category: rule.category, files: matched });
8211
+ }
8212
+ }
8213
+ return result;
8214
+ }
8215
+ function reportDangerousFilesAndExit(dangerous) {
8216
+ logger.error("\u68C0\u6D4B\u5230\u5371\u9669\u6587\u4EF6\uFF0C\u5DF2\u62E6\u622A\u64CD\u4F5C\uFF1A");
8217
+ for (const group of dangerous) {
8218
+ logger.warn(` [${group.category}]`);
8219
+ group.files.forEach((f) => logger.info(` ${f}`));
8220
+ }
8221
+ logger.info("\u8BF7\u4ECE\u5206\u652F\u4E2D\u79FB\u9664\u4EE5\u4E0A\u6587\u4EF6\u540E\u91CD\u8BD5");
8222
+ process.exit(1);
8223
+ }
8224
+ async function checkUnstableDependencies() {
8225
+ const pkgPath = "package.json";
8226
+ if (!await FileUtils.exists(pkgPath)) {
8227
+ return [];
8228
+ }
8229
+ const pkg2 = JSON.parse(await FileUtils.read(pkgPath));
8230
+ const allDeps = {
8231
+ ...pkg2.dependencies || {},
8232
+ ...pkg2.devDependencies || {}
8233
+ };
8234
+ const unstable = [];
8235
+ for (const [name, version] of Object.entries(allDeps)) {
8236
+ if (UNSTABLE_SUFFIXES.some((suffix) => version.includes(suffix))) {
8237
+ unstable.push({ name, version });
8238
+ }
8239
+ }
8240
+ return unstable;
8241
+ }
8242
+ async function readVersion(pkgPath = "package.json") {
8243
+ if (!await FileUtils.exists(pkgPath)) {
8244
+ logger.error("package.json \u4E0D\u5B58\u5728");
8245
+ process.exit(1);
8246
+ }
8247
+ const pkg2 = JSON.parse(await FileUtils.read(pkgPath));
8248
+ return pkg2.version || "0.0.0";
8249
+ }
8250
+ function bumpVersion(currentVersion, type) {
8251
+ const parts = currentVersion.split(".").map(Number);
8252
+ if (parts.length !== 3 || parts.some(isNaN)) {
8253
+ logger.error(`\u65E0\u6548\u7684\u7248\u672C\u53F7: ${currentVersion}`);
8254
+ process.exit(1);
8255
+ }
8256
+ switch (type) {
8257
+ case "major":
8258
+ return `${parts[0] + 1}.0.0`;
8259
+ case "minor":
8260
+ return `${parts[0]}.${parts[1] + 1}.0`;
8261
+ case "patch":
8262
+ return `${parts[0]}.${parts[1]}.${parts[2] + 1}`;
8263
+ }
8264
+ }
8265
+ async function setVersion(version, pkgPath = "package.json") {
8266
+ const content = await FileUtils.read(pkgPath);
8267
+ const pkg2 = JSON.parse(content);
8268
+ pkg2.version = version;
8269
+ await FileUtils.write(pkgPath, JSON.stringify(pkg2, null, 2) + "\n");
8270
+ }
8271
+ var VALID_BRANCH_PREFIXES, DANGEROUS_FILE_RULES, UNSTABLE_SUFFIXES;
8272
+ var init_fe_git = __esm({
8273
+ "src/lib/fe-git.ts"() {
8274
+ "use strict";
8275
+ init_esm_shims();
8276
+ init_utils();
8277
+ init_logger();
8278
+ VALID_BRANCH_PREFIXES = ["feat", "fix", "hotfix", "refactor"];
8279
+ DANGEROUS_FILE_RULES = [
8280
+ {
8281
+ category: "\u6784\u5EFA\u4EA7\u7269",
8282
+ patterns: [/^dist\//, /^build\//, /^\.output\//]
8283
+ },
8284
+ {
8285
+ category: "\u73AF\u5883\u53D8\u91CF",
8286
+ patterns: [/^\.env$/, /^\.env\./]
8287
+ },
8288
+ {
8289
+ category: "\u4F9D\u8D56\u76EE\u5F55",
8290
+ patterns: [/^node_modules\//]
8291
+ },
8292
+ {
8293
+ category: "\u79D8\u94A5/\u8BC1\u4E66",
8294
+ patterns: [
8295
+ /\.pem$/,
8296
+ /\.key$/,
8297
+ /\.p12$/,
8298
+ /\.pfx$/,
8299
+ /\.cert$/,
8300
+ /credentials\.json$/,
8301
+ /_rsa$/,
8302
+ /id_ed25519$/
8303
+ ]
8304
+ },
8305
+ {
8306
+ category: "\u654F\u611F\u914D\u7F6E",
8307
+ patterns: [/^\.npmrc$/, /^\.docker\/config\.json$/]
8308
+ }
8309
+ ];
8310
+ UNSTABLE_SUFFIXES = ["-alpha", "-beta", "-dev", "-rc", "-canary", "-next"];
8311
+ }
8312
+ });
8313
+
8314
+ // src/commands/fe-start.ts
8315
+ import { Command as Command18 } from "commander";
8316
+ import inquirer13 from "inquirer";
8317
+ var feStartCommand;
8318
+ var init_fe_start = __esm({
8319
+ "src/commands/fe-start.ts"() {
8320
+ "use strict";
8321
+ init_esm_shims();
8322
+ init_logger();
8323
+ init_fe_git();
8324
+ feStartCommand = new Command18("fe-start").description("\u521B\u5EFA\u524D\u7AEF\u5DE5\u4F5C\u5206\u652F (\u4ECE master \u5207\u51FA)").argument("<name>", "\u5206\u652F\u540D\u79F0").option("--type <type>", "\u5206\u652F\u7C7B\u578B (feat|fix|hotfix|refactor)").action(async (name, opts) => {
8325
+ try {
8326
+ const { execa: execa5 } = await import("execa");
8327
+ await ensureGitRepo();
8328
+ let branchType = opts.type;
8329
+ if (!branchType) {
8330
+ const answer = await inquirer13.prompt([
8331
+ {
8332
+ type: "list",
8333
+ name: "type",
8334
+ message: "\u8BF7\u9009\u62E9\u5206\u652F\u7C7B\u578B:",
8335
+ choices: [...VALID_BRANCH_PREFIXES]
8336
+ }
8337
+ ]);
8338
+ branchType = answer.type;
8339
+ }
8340
+ const branchFullName = `${branchType}/${name}`;
8341
+ let didStash = false;
8342
+ const { stdout: statusOutput } = await execa5("git", ["status", "--porcelain"]);
8343
+ if (statusOutput.trim()) {
8344
+ logger.warn("\u68C0\u6D4B\u5230\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u6682\u5B58\u5230 stash...");
8345
+ await execa5("git", ["stash"]);
8346
+ didStash = true;
8347
+ }
8348
+ await ensureBranchExists("master");
8349
+ await execa5("git", ["checkout", "master"]);
8350
+ logger.step("\u62C9\u53D6 master \u6700\u65B0\u4EE3\u7801...");
8351
+ await execa5("git", ["pull", "origin", "master"]);
8352
+ const localExists = await branchExistsLocal(branchFullName);
8353
+ const remoteExists = await branchExistsRemote(branchFullName);
8354
+ if (localExists || remoteExists) {
8355
+ const { overwrite } = await inquirer13.prompt([
8356
+ {
8357
+ type: "confirm",
8358
+ name: "overwrite",
8359
+ message: `\u5206\u652F "${branchFullName}" \u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6?`,
8360
+ default: false
8361
+ }
8362
+ ]);
8363
+ if (!overwrite) {
8364
+ logger.warn("\u5DF2\u53D6\u6D88\u64CD\u4F5C");
8365
+ if (didStash) {
8366
+ await execa5("git", ["stash", "pop"]);
8367
+ }
8368
+ return;
8369
+ }
8370
+ if (localExists) {
8371
+ await execa5("git", ["branch", "-D", branchFullName]);
8372
+ }
8373
+ }
8374
+ await execa5("git", ["checkout", "-b", branchFullName]);
8375
+ if (didStash) {
8376
+ try {
8377
+ await execa5("git", ["stash", "pop"]);
8378
+ } catch (e) {
8379
+ logger.warn(`\u6062\u590D stash \u65F6\u51FA\u73B0\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u5904\u7406: ${e.message}`);
8380
+ }
8381
+ }
8382
+ logger.success(`\u5DE5\u4F5C\u5206\u652F "${branchFullName}" \u521B\u5EFA\u6210\u529F (\u57FA\u4E8E master)`);
8383
+ } catch (error) {
8384
+ logger.error(`\u521B\u5EFA\u5DE5\u4F5C\u5206\u652F\u5931\u8D25: ${error.message}`);
8385
+ process.exit(1);
8386
+ }
8387
+ });
8388
+ }
8389
+ });
8390
+
8391
+ // src/commands/fe-dev.ts
8392
+ import { Command as Command19 } from "commander";
8393
+ var feDevCommand;
8394
+ var init_fe_dev = __esm({
8395
+ "src/commands/fe-dev.ts"() {
8396
+ "use strict";
8397
+ init_esm_shims();
8398
+ init_logger();
8399
+ init_fe_git();
8400
+ feDevCommand = new Command19("fe-dev").description("\u5408\u5165 develop \u5206\u652F\u63D0\u6D4B").action(async () => {
8401
+ try {
8402
+ const { execa: execa5 } = await import("execa");
8403
+ await ensureGitRepo();
8404
+ await ensureCleanWorkingTree();
8405
+ const currentBranch = await ensureOnBranch();
8406
+ logger.step("\u626B\u63CF\u5371\u9669\u6587\u4EF6...");
8407
+ const dangerous = await scanBranchDiffForDangerousFiles("develop", currentBranch);
8408
+ if (dangerous.length > 0) {
8409
+ reportDangerousFilesAndExit(dangerous);
8410
+ }
8411
+ await ensureBranchExists("develop");
8412
+ logger.step("\u5207\u6362\u5230 develop \u5206\u652F...");
8413
+ await execa5("git", ["checkout", "develop"]);
8414
+ logger.step("\u62C9\u53D6\u6700\u65B0 develop...");
8415
+ await execa5("git", ["pull", "origin", "develop"]);
8416
+ logger.step(`\u5408\u5E76 ${currentBranch} \u5230 develop...`);
8417
+ try {
8418
+ await execa5("git", ["merge", currentBranch, "--no-ff"]);
8419
+ } catch (mergeErr) {
8420
+ await execa5("git", ["merge", "--abort"]).catch(() => {
8421
+ });
8422
+ await execa5("git", ["checkout", currentBranch]);
8423
+ logger.error(`\u5408\u5E76 ${currentBranch} \u5230 develop \u5931\u8D25: ${mergeErr.stderr || mergeErr.message}`);
8424
+ logger.info("\u8BF7\u624B\u52A8\u89E3\u51B3\u540E\u91CD\u8BD5");
8425
+ process.exit(1);
8426
+ }
8427
+ logger.step("\u63A8\u9001 develop \u5230\u8FDC\u7AEF...");
8428
+ await execa5("git", ["push", "origin", "develop"]);
8429
+ logger.step(`\u5207\u56DE ${currentBranch}...`);
8430
+ await execa5("git", ["checkout", currentBranch]);
8431
+ logger.success(`\u5DF2\u5408\u5165 develop \u5E76\u63A8\u9001\uFF0C\u5DF2\u5207\u56DE ${currentBranch}`);
8432
+ } catch (error) {
8433
+ logger.error(`fe-dev \u6267\u884C\u5931\u8D25: ${error.message}`);
8434
+ process.exit(1);
8435
+ }
8436
+ });
8437
+ }
8438
+ });
8439
+
8440
+ // src/commands/fe-release.ts
8441
+ import { Command as Command20 } from "commander";
8442
+ import inquirer14 from "inquirer";
8443
+ var feReleaseCommand;
8444
+ var init_fe_release = __esm({
8445
+ "src/commands/fe-release.ts"() {
8446
+ "use strict";
8447
+ init_esm_shims();
8448
+ init_logger();
8449
+ init_fe_git();
8450
+ feReleaseCommand = new Command20("fe-release").argument("[version]", "\u6307\u5B9A\u7248\u672C\u53F7").option("--minor", "\u9012\u589E minor \u7248\u672C").option("--major", "\u9012\u589E major \u7248\u672C").description("\u521B\u5EFA release \u5206\u652F (\u4ECE master \u5207\u51FA\u5E76\u5408\u5165\u5F53\u524D\u7279\u6027)").action(async (versionArg, opts) => {
8451
+ try {
8452
+ const { execa: execa5 } = await import("execa");
8453
+ logger.step("\u68C0\u67E5 Git \u4ED3\u5E93\u72B6\u6001...");
8454
+ await ensureGitRepo();
8455
+ await ensureCleanWorkingTree();
8456
+ const currentBranch = await ensureOnBranch();
8457
+ logger.step("\u68C0\u67E5\u4E0D\u7A33\u5B9A\u4F9D\u8D56...");
8458
+ const unstable = await checkUnstableDependencies();
8459
+ if (unstable.length > 0) {
8460
+ unstable.forEach((dep) => {
8461
+ logger.warn(` ${dep.name}: ${dep.version}`);
8462
+ });
8463
+ const { shouldContinue } = await inquirer14.prompt([
8464
+ {
8465
+ type: "confirm",
8466
+ name: "shouldContinue",
8467
+ message: "\u68C0\u6D4B\u5230\u4E0D\u7A33\u5B9A\u4F9D\u8D56\uFF0C\u662F\u5426\u7EE7\u7EED\uFF1F",
8468
+ default: false
8469
+ }
8470
+ ]);
8471
+ if (!shouldContinue) {
8472
+ process.exit(1);
8473
+ }
8474
+ }
8475
+ logger.step("\u626B\u63CF\u5371\u9669\u6587\u4EF6...");
8476
+ const dangerous = await scanBranchDiffForDangerousFiles("master", currentBranch);
8477
+ if (dangerous.length > 0) {
8478
+ reportDangerousFilesAndExit(dangerous);
8479
+ }
8480
+ logger.step("\u786E\u4FDD master \u5206\u652F\u5B58\u5728...");
8481
+ await ensureBranchExists("master");
8482
+ logger.step("\u5207\u6362\u5230 master \u5E76\u62C9\u53D6\u6700\u65B0\u4EE3\u7801...");
8483
+ await execa5("git", ["checkout", "master"]);
8484
+ await execa5("git", ["pull", "origin", "master"]);
8485
+ let version;
8486
+ if (versionArg) {
8487
+ version = versionArg;
8488
+ } else {
8489
+ const currentVersion = await readVersion();
8490
+ if (opts.major) {
8491
+ version = bumpVersion(currentVersion, "major");
8492
+ } else if (opts.minor) {
8493
+ version = bumpVersion(currentVersion, "minor");
8494
+ } else {
8495
+ version = bumpVersion(currentVersion, "patch");
8496
+ }
8497
+ }
8498
+ const releaseBranch = `release/${version}`;
8499
+ logger.step(`\u68C0\u67E5\u8FDC\u7AEF\u662F\u5426\u5DF2\u5B58\u5728 ${releaseBranch}...`);
8500
+ if (await branchExistsRemote(releaseBranch)) {
8501
+ logger.error(`\u8FDC\u7AEF\u5DF2\u5B58\u5728 ${releaseBranch} \u5206\u652F`);
8502
+ await execa5("git", ["checkout", currentBranch]);
8503
+ process.exit(1);
8504
+ }
8505
+ logger.step(`\u521B\u5EFA\u5206\u652F ${releaseBranch}...`);
8506
+ await execa5("git", ["checkout", "-b", releaseBranch]);
8507
+ logger.step(`\u5408\u5E76 ${currentBranch} \u5230 ${releaseBranch}...`);
8508
+ try {
8509
+ await execa5("git", ["merge", currentBranch, "--no-ff"]);
8510
+ } catch (mergeErr) {
8511
+ logger.error(`\u5408\u5E76 ${currentBranch} \u5931\u8D25: ${mergeErr.stderr || mergeErr.message}`);
8512
+ await execa5("git", ["merge", "--abort"]).catch(() => {
8513
+ });
8514
+ await execa5("git", ["checkout", currentBranch]);
8515
+ await execa5("git", ["branch", "-D", releaseBranch]).catch(() => {
8516
+ });
8517
+ process.exit(1);
8518
+ }
8519
+ logger.step(`\u5199\u5165\u7248\u672C\u53F7 ${version}...`);
8520
+ await setVersion(version);
8521
+ await execa5("git", ["add", "package.json"]);
8522
+ await execa5("git", ["commit", "-m", `chore: bump version to ${version}`]);
8523
+ logger.step(`\u63A8\u9001 ${releaseBranch} \u5230\u8FDC\u7AEF...`);
8524
+ await execa5("git", ["push", "origin", releaseBranch]);
8525
+ logger.success(`${releaseBranch} \u521B\u5EFA\u5E76\u63A8\u9001\u6210\u529F`);
8526
+ } catch (error) {
8527
+ logger.error(`\u521B\u5EFA release \u5206\u652F\u5931\u8D25: ${error.message}`);
8528
+ if (process.env.DEBUG) {
8529
+ console.error(error);
8530
+ }
8531
+ process.exit(1);
8532
+ }
8533
+ });
8534
+ }
8535
+ });
8536
+
8537
+ // src/commands/fe-sync.ts
8538
+ import { Command as Command21 } from "commander";
8539
+ var feSyncCommand;
8540
+ var init_fe_sync = __esm({
8541
+ "src/commands/fe-sync.ts"() {
8542
+ "use strict";
8543
+ init_esm_shims();
8544
+ init_logger();
8545
+ init_fe_git();
8546
+ feSyncCommand = new Command21("fe-sync").description("\u540C\u6B65 master \u6700\u65B0\u4EE3\u7801\u5230\u5F53\u524D\u5206\u652F").action(async () => {
8547
+ try {
8548
+ await ensureGitRepo();
8549
+ await ensureCleanWorkingTree();
8550
+ const currentBranch = await ensureOnBranch();
8551
+ const { execa: execa5 } = await import("execa");
8552
+ logger.step("\u6B63\u5728\u62C9\u53D6 origin/master \u6700\u65B0\u4EE3\u7801...");
8553
+ await execa5("git", ["fetch", "origin", "master"]);
8554
+ logger.step("\u6B63\u5728\u5408\u5E76 origin/master \u5230\u5F53\u524D\u5206\u652F...");
8555
+ try {
8556
+ await execa5("git", ["merge", "origin/master", "--no-ff"]);
8557
+ } catch (mergeErr) {
8558
+ await execa5("git", ["merge", "--abort"]).catch(() => {
8559
+ });
8560
+ logger.error(`\u5408\u5E76\u5931\u8D25: ${mergeErr.stderr || mergeErr.message}`);
8561
+ logger.info("\u8BF7\u624B\u52A8\u89E3\u51B3\u540E\u91CD\u8BD5");
8562
+ process.exit(1);
8563
+ }
8564
+ logger.success("\u5DF2\u540C\u6B65 master \u6700\u65B0\u4EE3\u7801\u5230 " + currentBranch);
8565
+ } catch (error) {
8566
+ logger.error(`\u540C\u6B65\u5931\u8D25: ${error.message}`);
8567
+ if (process.env.DEBUG) {
8568
+ console.error(error);
8569
+ }
8570
+ process.exit(1);
8571
+ }
8572
+ });
8573
+ }
8574
+ });
8575
+
8853
8576
  // src/index.ts
8854
8577
  var index_exports = {};
8855
- import { Command as Command18 } from "commander";
8578
+ import { Command as Command22 } from "commander";
8856
8579
  import chalk4 from "chalk";
8857
8580
  import fs6 from "fs-extra";
8858
8581
  import path21 from "path";
@@ -8880,6 +8603,12 @@ function showHelp() {
8880
8603
  console.log(" team-cli update \u68C0\u67E5\u5E76\u66F4\u65B0\u6A21\u677F\u7248\u672C");
8881
8604
  console.log(" team-cli config \u7BA1\u7406 GitLab \u914D\u7F6E");
8882
8605
  console.log(" team-cli diff \u5BF9\u6BD4\u672C\u5730\u4E0E\u8FDC\u7A0B\u6A21\u677F\u5DEE\u5F02");
8606
+ console.log("");
8607
+ console.log(chalk4.bold("\u524D\u7AEF Git \u5DE5\u4F5C\u6D41:"));
8608
+ console.log(" team-cli fe-start <name> \u521B\u5EFA\u524D\u7AEF\u5DE5\u4F5C\u5206\u652F (\u4ECE master \u5207\u51FA)");
8609
+ console.log(" team-cli fe-dev \u5408\u5165 develop \u5206\u652F\u63D0\u6D4B");
8610
+ console.log(" team-cli fe-release [version] \u521B\u5EFA release \u5206\u652F");
8611
+ console.log(" team-cli fe-sync \u540C\u6B65 master \u6700\u65B0\u4EE3\u7801\u5230\u5F53\u524D\u5206\u652F");
8883
8612
  console.log(" team-cli --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F");
8884
8613
  console.log("");
8885
8614
  console.log(chalk4.bold("\u793A\u4F8B:"));
@@ -8938,9 +8667,13 @@ var init_index = __esm({
8938
8667
  init_update();
8939
8668
  init_config();
8940
8669
  init_diff();
8670
+ init_fe_start();
8671
+ init_fe_dev();
8672
+ init_fe_release();
8673
+ init_fe_sync();
8941
8674
  __dirname2 = path21.dirname(fileURLToPath2(import.meta.url));
8942
8675
  pkg = fs6.readJsonSync(path21.join(__dirname2, "../package.json"));
8943
- program = new Command18();
8676
+ program = new Command22();
8944
8677
  program.name("team-cli").description("AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6").version(pkg.version);
8945
8678
  program.option("-v, --verbose", "\u8BE6\u7EC6\u8F93\u51FA\u6A21\u5F0F").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F");
8946
8679
  program.addCommand(initCommand);
@@ -8961,6 +8694,10 @@ var init_index = __esm({
8961
8694
  program.addCommand(updateCommand);
8962
8695
  program.addCommand(configCommand);
8963
8696
  program.addCommand(diffCommand);
8697
+ program.addCommand(feStartCommand);
8698
+ program.addCommand(feDevCommand);
8699
+ program.addCommand(feReleaseCommand);
8700
+ program.addCommand(feSyncCommand);
8964
8701
  program.action(() => {
8965
8702
  showHelp();
8966
8703
  });