@nick848/sf-cli 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -3605,12 +3605,20 @@ function getScheduleRuleDescription(step) {
3605
3605
  var DEFAULT_CONFIRMATION_POINTS = [
3606
3606
  {
3607
3607
  type: "spec-review",
3608
- name: "\u89C4\u8303\u62C6\u89E3\u786E\u8BA4",
3609
- description: "\u89C4\u8303\u62C6\u89E3\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u662F\u5426\u7EE7\u7EED\u8FDB\u5165\u8BBE\u8BA1\u9636\u6BB5",
3608
+ name: "\u89C4\u683C\u786E\u8BA4",
3609
+ description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
3610
3610
  triggerStep: "explore",
3611
3611
  targetStep: "new",
3612
3612
  required: true
3613
3613
  },
3614
+ {
3615
+ type: "spec-review",
3616
+ name: "\u89C4\u683C\u786E\u8BA4",
3617
+ description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
3618
+ triggerStep: "propose",
3619
+ targetStep: "apply",
3620
+ required: true
3621
+ },
3614
3622
  {
3615
3623
  type: "architecture",
3616
3624
  name: "\u67B6\u6784\u8C03\u6574\u786E\u8BA4",
@@ -3646,19 +3654,15 @@ var ConfirmationManager = class {
3646
3654
  confirmationPoints;
3647
3655
  confirmations = /* @__PURE__ */ new Map();
3648
3656
  constructor(customPoints) {
3649
- const points = customPoints || DEFAULT_CONFIRMATION_POINTS;
3650
- this.confirmationPoints = new Map(points.map((p) => [p.type, p]));
3657
+ this.confirmationPoints = customPoints || DEFAULT_CONFIRMATION_POINTS;
3651
3658
  }
3652
3659
  /**
3653
3660
  * 获取指定阶段的确认点
3654
3661
  */
3655
3662
  getConfirmationPointForTransition(from, to) {
3656
- for (const point of this.confirmationPoints.values()) {
3657
- if (point.triggerStep === from && point.targetStep === to) {
3658
- return point;
3659
- }
3660
- }
3661
- return void 0;
3663
+ return this.confirmationPoints.find(
3664
+ (point) => point.triggerStep === from && point.targetStep === to
3665
+ );
3662
3666
  }
3663
3667
  /**
3664
3668
  * 检查是否需要确认
@@ -3671,7 +3675,7 @@ var ConfirmationManager = class {
3671
3675
  * 获取确认点详情
3672
3676
  */
3673
3677
  getConfirmationPoint(type) {
3674
- return this.confirmationPoints.get(type);
3678
+ return this.confirmationPoints.find((point) => point.type === type);
3675
3679
  }
3676
3680
  /**
3677
3681
  * 记录确认
@@ -3715,14 +3719,14 @@ var ConfirmationManager = class {
3715
3719
  * 获取所有确认点
3716
3720
  */
3717
3721
  getAllConfirmationPoints() {
3718
- return Array.from(this.confirmationPoints.values());
3722
+ return [...this.confirmationPoints];
3719
3723
  }
3720
3724
  /**
3721
3725
  * 获取指定阶段需要清除的确认点
3722
3726
  */
3723
3727
  getConfirmationsToClear(targetStep) {
3724
3728
  const types = [];
3725
- for (const point of this.confirmationPoints.values()) {
3729
+ for (const point of this.confirmationPoints) {
3726
3730
  const stepOrder = ["explore", "new", "continue", "propose", "apply", "archive"];
3727
3731
  const targetIndex = stepOrder.indexOf(targetStep);
3728
3732
  const triggerIndex = stepOrder.indexOf(point.triggerStep);
@@ -3845,6 +3849,26 @@ var WorkflowEngine = class {
3845
3849
  devStandards: this.devStandards
3846
3850
  };
3847
3851
  }
3852
+ /**
3853
+ * 获取规格文件路径
3854
+ */
3855
+ getSpecFilePath() {
3856
+ if (!this.state) return null;
3857
+ return path5.join(this.openspecPath, "changes", `${this.state.id}-spec.md`);
3858
+ }
3859
+ /**
3860
+ * 检查规格文件是否存在
3861
+ */
3862
+ async hasSpecFile() {
3863
+ const specPath = this.getSpecFilePath();
3864
+ if (!specPath) return false;
3865
+ try {
3866
+ await fs4.access(specPath);
3867
+ return true;
3868
+ } catch {
3869
+ return false;
3870
+ }
3871
+ }
3848
3872
  /**
3849
3873
  * 启动新工作流
3850
3874
  */
@@ -6708,16 +6732,23 @@ async function handleNew(args, ctx) {
6708
6732
  if (workflowEngine) {
6709
6733
  const existingState = workflowEngine.getState();
6710
6734
  if (existingState && existingState.status === "running") {
6735
+ if (existingState.currentStep === "explore" || existingState.currentStep === "propose") {
6736
+ const specPath = path5.join(workingDir, "openspec", "changes", `${existingState.id}-spec.md`);
6737
+ if (fs10.existsSync(specPath)) {
6738
+ return {
6739
+ output: chalk9.yellow("\u5F53\u524D\u5DE5\u4F5C\u6D41\u6B63\u5728\u7B49\u5F85\u89C4\u683C\u786E\u8BA4") + chalk9.gray(`
6740
+
6741
+ \u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9.gray(`
6742
+ \u53D8\u66F4ID: ${existingState.id}`) + chalk9.cyan("\n\n\u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210:") + chalk9.white(`
6743
+ ${specPath}`) + chalk9.yellow("\n\n\u8BF7\u786E\u8BA4\u89C4\u683C\u540E\u7EE7\u7EED:") + chalk9.gray("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C") + chalk9.gray("\n /opsx:status - \u67E5\u770B\u8BE6\u60C5")
6744
+ };
6745
+ }
6746
+ }
6711
6747
  return {
6712
6748
  output: chalk9.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9.gray(`
6713
6749
 
6714
6750
  \u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9.gray(`
6715
- \u5F53\u524D\u9636\u6BB5: ${existingState.currentStep}`) + chalk9.gray(`
6716
-
6717
- \u9009\u9879:`) + chalk9.gray(`
6718
- 1. \u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:${existingState.currentStep}`) + chalk9.gray(`
6719
- 2. \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:cancel`) + chalk9.gray(`
6720
- 3. \u67E5\u770B\u5DE5\u4F5C\u6D41\u72B6\u6001: /opsx:status`)
6751
+ \u5F53\u524D\u9636\u6BB5: ${existingState.currentStep}`) + chalk9.gray("\n\n\u9009\u9879:") + chalk9.gray("\n 1. \u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:status") + chalk9.gray("\n 2. \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:cancel")
6721
6752
  };
6722
6753
  }
6723
6754
  }
@@ -6732,6 +6763,7 @@ async function handleNew(args, ctx) {
6732
6763
  async function newFeature(options, workingDir, workflowEngine) {
6733
6764
  const cwd = workingDir || process.cwd();
6734
6765
  const { requirement, forceComplexity } = options;
6766
+ const lines = [];
6735
6767
  try {
6736
6768
  const stats = await fs4.stat(cwd);
6737
6769
  if (!stats.isDirectory()) {
@@ -6739,51 +6771,283 @@ async function newFeature(options, workingDir, workflowEngine) {
6739
6771
  output: chalk9.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
6740
6772
  };
6741
6773
  }
6742
- } catch (e) {
6774
+ } catch {
6743
6775
  return {
6744
6776
  output: chalk9.red(`\u9519\u8BEF: \u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6743\u9650\u8BBF\u95EE ${cwd}`)
6745
6777
  };
6746
6778
  }
6747
- try {
6748
- const context = await readProjectContext(cwd);
6749
- const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
6750
- const workflow = workflowEngine || new WorkflowEngine();
6751
- if (!workflowEngine) {
6752
- await workflow.initialize(cwd);
6753
- }
6754
- const state = await workflow.start(requirement, analysis.score, {
6755
- title: extractTitle(requirement)
6779
+ lines.push(chalk9.cyan("\u{1F50D} \u5206\u6790\u9879\u76EE..."));
6780
+ const context = await readProjectContext(cwd);
6781
+ lines.push(chalk9.gray(` \u9879\u76EE: ${context.name}`));
6782
+ lines.push(chalk9.gray(` \u7C7B\u578B: ${context.type}`));
6783
+ lines.push(chalk9.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`));
6784
+ lines.push("");
6785
+ lines.push(chalk9.cyan("\u{1F4CA} \u5206\u6790\u9700\u6C42\u590D\u6742\u5EA6..."));
6786
+ const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
6787
+ lines.push(chalk9.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`));
6788
+ lines.push(chalk9.gray(` \u6D41\u7A0B\u7C7B\u578B: ${analysis.recommendation === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`));
6789
+ for (const factor of analysis.factors) {
6790
+ lines.push(chalk9.gray(` - ${factor}`));
6791
+ }
6792
+ lines.push("");
6793
+ lines.push(chalk9.cyan("\u{1F4CB} \u521D\u59CB\u5316\u5DE5\u4F5C\u6D41..."));
6794
+ const workflow = workflowEngine || new WorkflowEngine();
6795
+ if (!workflowEngine) {
6796
+ await workflow.initialize(cwd);
6797
+ }
6798
+ const state = await workflow.start(requirement, analysis.score, {
6799
+ title: extractTitle(requirement)
6800
+ });
6801
+ lines.push(chalk9.gray(` \u53D8\u66F4ID: ${state.id}`));
6802
+ lines.push(chalk9.gray(` \u5DE5\u4F5C\u6D41: ${state.type}`));
6803
+ lines.push("");
6804
+ lines.push(chalk9.cyan("\u{1F4DD} \u751F\u6210\u89C4\u683C\u62C6\u5206..."));
6805
+ const spec = await generateSpec(requirement, context, analysis, state.id);
6806
+ const specPath = await saveSpecFile(cwd, spec);
6807
+ lines.push(chalk9.green(" \u2713 \u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210"));
6808
+ lines.push(chalk9.gray(` \u8DEF\u5F84: ${specPath}`));
6809
+ lines.push("");
6810
+ lines.push(chalk9.cyan.bold("\u{1F4CB} \u89C4\u683C\u6982\u89C8:"));
6811
+ lines.push(chalk9.white(`
6812
+ ${spec.summary}`));
6813
+ if (spec.items.length > 0) {
6814
+ lines.push("");
6815
+ lines.push(chalk9.cyan(" \u4EFB\u52A1\u62C6\u5206:"));
6816
+ for (const item of spec.items) {
6817
+ const priorityIcon = item.priority === "high" ? "\u{1F534}" : item.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
6818
+ lines.push(chalk9.gray(` ${priorityIcon} [${item.id}] ${item.title}`));
6819
+ }
6820
+ }
6821
+ if (spec.risks.length > 0) {
6822
+ lines.push("");
6823
+ lines.push(chalk9.yellow(" \u26A0\uFE0F \u98CE\u9669\u63D0\u793A:"));
6824
+ for (const risk of spec.risks) {
6825
+ lines.push(chalk9.gray(` - ${risk}`));
6826
+ }
6827
+ }
6828
+ lines.push("");
6829
+ lines.push(chalk9.yellow.bold("\u23F3 \u7B49\u5F85\u89C4\u683C\u786E\u8BA4"));
6830
+ lines.push(chalk9.gray("\n\u8BF7\u68C0\u67E5\u751F\u6210\u7684\u89C4\u683C\u6587\u4EF6\uFF0C\u786E\u8BA4\u540E\u7EE7\u7EED:"));
6831
+ lines.push(chalk9.white("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C\uFF0C\u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5"));
6832
+ lines.push(chalk9.white(" /opsx:rollback explore - \u89C4\u683C\u4E0D\u7B26\uFF0C\u91CD\u65B0\u62C6\u5206"));
6833
+ lines.push(chalk9.white(" /opsx:status - \u67E5\u770B\u5DE5\u4F5C\u6D41\u72B6\u6001"));
6834
+ return { output: lines.join("\n") };
6835
+ }
6836
+ async function generateSpec(requirement, context, analysis, changeId) {
6837
+ const spec = {
6838
+ changeId,
6839
+ requirement,
6840
+ summary: "",
6841
+ items: [],
6842
+ architectureNotes: [],
6843
+ risks: [],
6844
+ suggestions: []
6845
+ };
6846
+ spec.summary = generateSummary(requirement);
6847
+ if (analysis.recommendation === "complex") {
6848
+ spec.items = generateComplexTasks(requirement, context, analysis);
6849
+ spec.architectureNotes = generateArchitectureNotes(requirement, context);
6850
+ } else {
6851
+ spec.items = generateSimpleTasks(requirement);
6852
+ }
6853
+ spec.risks = generateRisks(requirement, context, analysis);
6854
+ spec.suggestions = generateSuggestions(requirement, context, analysis);
6855
+ return spec;
6856
+ }
6857
+ function generateSummary(requirement) {
6858
+ const firstSentence = requirement.split(/[。!?\n]/)[0];
6859
+ return firstSentence.length > 100 ? firstSentence.slice(0, 97) + "..." : firstSentence;
6860
+ }
6861
+ function generateComplexTasks(requirement, context, analysis) {
6862
+ const items = [];
6863
+ let itemId = 1;
6864
+ const featurePatterns = [
6865
+ { pattern: /用户|登录|注册|认证|权限/, title: "\u7528\u6237\u8BA4\u8BC1\u6A21\u5757", priority: "high" },
6866
+ { pattern: /数据|存储|缓存|数据库/, title: "\u6570\u636E\u5C42\u5B9E\u73B0", priority: "high" },
6867
+ { pattern: /接口|API|请求|响应/, title: "API \u63A5\u53E3\u5F00\u53D1", priority: "high" },
6868
+ { pattern: /界面|页面|组件|UI/, title: "\u754C\u9762\u5F00\u53D1", priority: "medium" },
6869
+ { pattern: /测试|单测|覆盖/, title: "\u6D4B\u8BD5\u7528\u4F8B\u7F16\u5199", priority: "medium" },
6870
+ { pattern: /文档|说明/, title: "\u6587\u6863\u7F16\u5199", priority: "low" },
6871
+ { pattern: /配置|设置/, title: "\u914D\u7F6E\u7BA1\u7406", priority: "low" },
6872
+ { pattern: /优化|性能/, title: "\u6027\u80FD\u4F18\u5316", priority: "medium" },
6873
+ { pattern: /安全|加密/, title: "\u5B89\u5168\u5B9E\u73B0", priority: "high" },
6874
+ { pattern: /日志|监控/, title: "\u65E5\u5FD7\u76D1\u63A7", priority: "low" }
6875
+ ];
6876
+ for (const { pattern, title, priority } of featurePatterns) {
6877
+ if (pattern.test(requirement)) {
6878
+ items.push({
6879
+ id: `T${itemId.toString().padStart(3, "0")}`,
6880
+ title,
6881
+ description: `${title}\u76F8\u5173\u7684\u529F\u80FD\u5B9E\u73B0`,
6882
+ priority,
6883
+ dependencies: itemId > 1 ? [`T${(itemId - 1).toString().padStart(3, "0")}`] : [],
6884
+ estimatedComplexity: priority === "high" ? 3 : priority === "medium" ? 2 : 1
6885
+ });
6886
+ itemId++;
6887
+ }
6888
+ }
6889
+ if (items.length === 0) {
6890
+ items.push({
6891
+ id: "T001",
6892
+ title: "\u9700\u6C42\u5206\u6790\u4E0E\u8BBE\u8BA1",
6893
+ description: "\u5206\u6790\u9700\u6C42\u7EC6\u8282\uFF0C\u8BBE\u8BA1\u5B9E\u73B0\u65B9\u6848",
6894
+ priority: "high",
6895
+ dependencies: [],
6896
+ estimatedComplexity: 2
6756
6897
  });
6757
- const lines = [
6758
- chalk9.green("\u2713 \u9700\u6C42\u5206\u6790\u5B8C\u6210\n"),
6759
- chalk9.cyan("\u9879\u76EE\u4FE1\u606F:"),
6760
- chalk9.gray(` \u540D\u79F0: ${context.name}`),
6761
- chalk9.gray(` \u7C7B\u578B: ${context.type}`),
6762
- chalk9.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`),
6763
- "",
6764
- chalk9.cyan("\u9700\u6C42\u5206\u6790:"),
6765
- chalk9.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`),
6766
- chalk9.gray(` \u5DE5\u4F5C\u6D41: ${state.type === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`),
6767
- chalk9.gray(` \u53D8\u66F4ID: ${state.id}`),
6768
- "",
6769
- chalk9.cyan("\u590D\u6742\u5EA6\u56E0\u7D20:"),
6770
- ...analysis.factors.map((f) => chalk9.gray(` - ${f}`)),
6771
- "",
6772
- chalk9.yellow("\u4E0B\u4E00\u6B65:"),
6773
- state.type === "complex" ? chalk9.gray(" \u6267\u884C /opsx:explore \u5F00\u59CB\u63A2\u7D22\u9636\u6BB5") : chalk9.gray(" \u6267\u884C /opsx:propose \u63D0\u4EA4\u53D8\u66F4\u63D0\u6848")
6774
- ];
6775
- return { output: lines.join("\n") };
6776
- } catch (error) {
6777
- const err = error;
6778
- if (err.code === "EACCES") {
6779
- return {
6780
- output: chalk9.red(`\u9519\u8BEF: \u65E0\u6743\u9650\u8BBF\u95EE\u76EE\u5F55 ${cwd}`)
6781
- };
6898
+ items.push({
6899
+ id: "T002",
6900
+ title: "\u6838\u5FC3\u529F\u80FD\u5B9E\u73B0",
6901
+ description: requirement,
6902
+ priority: "high",
6903
+ dependencies: ["T001"],
6904
+ estimatedComplexity: analysis.score
6905
+ });
6906
+ items.push({
6907
+ id: "T003",
6908
+ title: "\u6D4B\u8BD5\u4E0E\u9A8C\u8BC1",
6909
+ description: "\u7F16\u5199\u6D4B\u8BD5\u7528\u4F8B\uFF0C\u9A8C\u8BC1\u529F\u80FD\u6B63\u786E\u6027",
6910
+ priority: "medium",
6911
+ dependencies: ["T002"],
6912
+ estimatedComplexity: 2
6913
+ });
6914
+ }
6915
+ return items;
6916
+ }
6917
+ function generateSimpleTasks(requirement, context) {
6918
+ return [
6919
+ {
6920
+ id: "T001",
6921
+ title: "\u5B9E\u73B0\u53D8\u66F4",
6922
+ description: requirement,
6923
+ priority: "high",
6924
+ dependencies: [],
6925
+ estimatedComplexity: 3
6926
+ },
6927
+ {
6928
+ id: "T002",
6929
+ title: "\u6D4B\u8BD5\u9A8C\u8BC1",
6930
+ description: "\u9A8C\u8BC1\u53D8\u66F4\u6B63\u786E\u6027",
6931
+ priority: "medium",
6932
+ dependencies: ["T001"],
6933
+ estimatedComplexity: 1
6782
6934
  }
6783
- return {
6784
- output: chalk9.red(`\u542F\u52A8\u9700\u6C42\u5931\u8D25: ${error.message}`)
6785
- };
6935
+ ];
6936
+ }
6937
+ function generateArchitectureNotes(requirement, context) {
6938
+ const notes = [];
6939
+ if (context.framework) {
6940
+ notes.push(`\u9879\u76EE\u4F7F\u7528 ${context.framework} \u6846\u67B6\uFF0C\u9700\u9075\u5FAA\u5176\u6700\u4F73\u5B9E\u8DF5`);
6786
6941
  }
6942
+ if (requirement.includes("\u6A21\u5757") || requirement.includes("\u7EC4\u4EF6")) {
6943
+ notes.push("\u5EFA\u8BAE\u91C7\u7528\u6A21\u5757\u5316\u8BBE\u8BA1\uFF0C\u4FDD\u6301\u7EC4\u4EF6\u804C\u8D23\u5355\u4E00");
6944
+ }
6945
+ if (requirement.includes("API") || requirement.includes("\u63A5\u53E3")) {
6946
+ notes.push("API \u8BBE\u8BA1\u9700\u8003\u8651\u7248\u672C\u63A7\u5236\u548C\u5411\u540E\u517C\u5BB9");
6947
+ }
6948
+ if (context.structure.srcStructure) {
6949
+ notes.push(`\u73B0\u6709\u6E90\u7801\u7ED3\u6784: ${context.structure.srcStructure}`);
6950
+ }
6951
+ return notes;
6952
+ }
6953
+ function generateRisks(requirement, context, analysis) {
6954
+ const risks = [];
6955
+ if (!context.framework) {
6956
+ risks.push("\u9879\u76EE\u6846\u67B6\u672A\u8BC6\u522B\uFF0C\u53EF\u80FD\u5F71\u54CD\u4EE3\u7801\u98CE\u683C\u4E00\u81F4\u6027");
6957
+ }
6958
+ if (analysis.score >= 7) {
6959
+ risks.push("\u9700\u6C42\u590D\u6742\u5EA6\u8F83\u9AD8\uFF0C\u5EFA\u8BAE\u5206\u9636\u6BB5\u5B9E\u73B0");
6960
+ }
6961
+ if (requirement.includes("\u8FC1\u79FB") || requirement.includes("\u91CD\u6784")) {
6962
+ risks.push("\u6D89\u53CA\u73B0\u6709\u4EE3\u7801\u4FEE\u6539\uFF0C\u9700\u6CE8\u610F\u56DE\u5F52\u6D4B\u8BD5");
6963
+ }
6964
+ if (requirement.includes("\u6743\u9650") || requirement.includes("\u5B89\u5168")) {
6965
+ risks.push("\u6D89\u53CA\u5B89\u5168\u654F\u611F\u529F\u80FD\uFF0C\u9700\u8981\u989D\u5916\u5BA1\u67E5");
6966
+ }
6967
+ return risks;
6968
+ }
6969
+ function generateSuggestions(requirement, context, analysis) {
6970
+ const suggestions = [];
6971
+ if (context.norms.devStandards) {
6972
+ suggestions.push("\u9879\u76EE\u5DF2\u6709\u5F00\u53D1\u89C4\u8303\uFF0C\u8BF7\u9075\u5FAA\u73B0\u6709\u89C4\u8303");
6973
+ }
6974
+ if (analysis.recommendation === "complex") {
6975
+ suggestions.push("\u590D\u6742\u9700\u6C42\u5EFA\u8BAE\u5148\u8FDB\u884C\u6280\u672F\u8BC4\u5BA1");
6976
+ suggestions.push("\u5EFA\u8BAE\u8C03\u7528 $architect \u83B7\u53D6\u67B6\u6784\u5EFA\u8BAE");
6977
+ }
6978
+ if (context.techStack.length > 0) {
6979
+ suggestions.push(`\u6280\u672F\u6808: ${context.techStack.join(", ")}`);
6980
+ }
6981
+ return suggestions;
6982
+ }
6983
+ async function saveSpecFile(cwd, spec) {
6984
+ const changesDir = path5.join(cwd, "openspec", "changes");
6985
+ await fs4.mkdir(changesDir, { recursive: true });
6986
+ const specPath = path5.join(changesDir, `${spec.changeId}-spec.md`);
6987
+ const content = formatSpecFile(spec);
6988
+ await fs4.writeFile(specPath, content, "utf-8");
6989
+ return specPath;
6990
+ }
6991
+ function formatSpecFile(spec) {
6992
+ const lines = [];
6993
+ lines.push(`# Spec: ${spec.summary}`);
6994
+ lines.push("");
6995
+ lines.push(`> \u53D8\u66F4ID: ${spec.changeId}`);
6996
+ lines.push(`> \u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}`);
6997
+ lines.push("");
6998
+ lines.push("---");
6999
+ lines.push("");
7000
+ lines.push("## \u9700\u6C42\u6982\u8FF0");
7001
+ lines.push("");
7002
+ lines.push(spec.requirement);
7003
+ lines.push("");
7004
+ lines.push("## \u4EFB\u52A1\u62C6\u5206");
7005
+ lines.push("");
7006
+ for (const item of spec.items) {
7007
+ const priorityLabel = item.priority === "high" ? "\u{1F534} \u9AD8" : item.priority === "medium" ? "\u{1F7E1} \u4E2D" : "\u{1F7E2} \u4F4E";
7008
+ lines.push(`### ${item.id}: ${item.title}`);
7009
+ lines.push("");
7010
+ lines.push(`- **\u4F18\u5148\u7EA7**: ${priorityLabel}`);
7011
+ lines.push(`- **\u63CF\u8FF0**: ${item.description}`);
7012
+ lines.push(`- **\u9884\u4F30\u590D\u6742\u5EA6**: ${item.estimatedComplexity}/5`);
7013
+ if (item.dependencies.length > 0) {
7014
+ lines.push(`- **\u4F9D\u8D56**: ${item.dependencies.join(", ")}`);
7015
+ }
7016
+ lines.push("");
7017
+ }
7018
+ if (spec.architectureNotes.length > 0) {
7019
+ lines.push("## \u67B6\u6784\u8BF4\u660E");
7020
+ lines.push("");
7021
+ for (const note of spec.architectureNotes) {
7022
+ lines.push(`- ${note}`);
7023
+ }
7024
+ lines.push("");
7025
+ }
7026
+ if (spec.risks.length > 0) {
7027
+ lines.push("## \u26A0\uFE0F \u98CE\u9669\u8BC4\u4F30");
7028
+ lines.push("");
7029
+ for (const risk of spec.risks) {
7030
+ lines.push(`- ${risk}`);
7031
+ }
7032
+ lines.push("");
7033
+ }
7034
+ if (spec.suggestions.length > 0) {
7035
+ lines.push("## \u{1F4A1} \u5EFA\u8BAE");
7036
+ lines.push("");
7037
+ for (const suggestion of spec.suggestions) {
7038
+ lines.push(`- ${suggestion}`);
7039
+ }
7040
+ lines.push("");
7041
+ }
7042
+ lines.push("---");
7043
+ lines.push("");
7044
+ lines.push("## \u786E\u8BA4\u72B6\u6001");
7045
+ lines.push("");
7046
+ lines.push("- [ ] \u89C4\u683C\u5DF2\u5BA1\u9605");
7047
+ lines.push("- [ ] \u4EFB\u52A1\u62C6\u5206\u5DF2\u786E\u8BA4");
7048
+ lines.push("");
7049
+ lines.push("**\u786E\u8BA4\u540E\u6267\u884C**: `/opsx:confirm spec-review`");
7050
+ return lines.join("\n");
6787
7051
  }
6788
7052
  function parseArgs(args) {
6789
7053
  let forceComplexity;
@@ -6808,42 +7072,52 @@ async function readProjectContext(cwd) {
6808
7072
  type: "unknown",
6809
7073
  framework: null,
6810
7074
  techStack: [],
6811
- description: ""
7075
+ description: "",
7076
+ structure: {
7077
+ directories: [],
7078
+ keyFiles: [],
7079
+ srcStructure: ""
7080
+ },
7081
+ norms: {
7082
+ devStandards: "",
7083
+ patterns: "",
7084
+ weights: ""
7085
+ }
6812
7086
  };
7087
+ const [agentsContext, configContext, normsContext, structureContext] = await Promise.all([
7088
+ readAgentsMd(cwd),
7089
+ readConfigYaml(cwd),
7090
+ readNorms(cwd),
7091
+ analyzeStructure(cwd)
7092
+ ]);
7093
+ return {
7094
+ ...defaultContext,
7095
+ ...agentsContext,
7096
+ ...configContext,
7097
+ norms: normsContext,
7098
+ structure: structureContext
7099
+ };
7100
+ }
7101
+ async function readAgentsMd(cwd) {
6813
7102
  const agentsPath = path5.join(cwd, "AGENTS.md");
6814
7103
  try {
6815
7104
  const stats = await fs4.stat(agentsPath);
6816
7105
  if (stats.size > MAX_FILE_SIZE2) {
6817
7106
  console.warn(`\u8B66\u544A: AGENTS.md \u6587\u4EF6\u8FC7\u5927 (${stats.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
6818
- return defaultContext;
7107
+ return {};
6819
7108
  }
6820
7109
  const content = await fs4.readFile(agentsPath, "utf-8");
6821
- return parseAgentsMd(content, defaultContext);
7110
+ return parseAgentsMd(content);
6822
7111
  } catch (e) {
6823
7112
  const err = e;
6824
7113
  if (err.code !== "ENOENT") {
6825
7114
  console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 AGENTS.md - ${err.message}`);
6826
7115
  }
7116
+ return {};
6827
7117
  }
6828
- const configPath = path5.join(cwd, "openspec", "config.yaml");
6829
- try {
6830
- const stats = await fs4.stat(configPath);
6831
- if (stats.size > MAX_FILE_SIZE2) {
6832
- console.warn(`\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
6833
- return defaultContext;
6834
- }
6835
- const content = await fs4.readFile(configPath, "utf-8");
6836
- return parseConfigYaml(content, defaultContext);
6837
- } catch (e) {
6838
- const err = e;
6839
- if (err.code !== "ENOENT") {
6840
- console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
6841
- }
6842
- }
6843
- return defaultContext;
6844
7118
  }
6845
- function parseAgentsMd(content, defaults) {
6846
- const context = { ...defaults };
7119
+ function parseAgentsMd(content) {
7120
+ const context = {};
6847
7121
  const nameMatch = content.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
6848
7122
  if (nameMatch) {
6849
7123
  context.name = nameMatch[1];
@@ -6860,10 +7134,32 @@ function parseAgentsMd(content, defaults) {
6860
7134
  if (descMatch) {
6861
7135
  context.description = descMatch[1].trim();
6862
7136
  }
7137
+ const techStackMatch = content.match(/技术栈[::]\s*([^\n]+)/);
7138
+ if (techStackMatch) {
7139
+ context.techStack = techStackMatch[1].split(/[,,、]/).map((s) => s.trim()).filter(Boolean);
7140
+ }
6863
7141
  return context;
6864
7142
  }
6865
- function parseConfigYaml(content, defaults) {
6866
- const context = { ...defaults };
7143
+ async function readConfigYaml(cwd) {
7144
+ const configPath = path5.join(cwd, "openspec", "config.yaml");
7145
+ try {
7146
+ const stats = await fs4.stat(configPath);
7147
+ if (stats.size > MAX_FILE_SIZE2) {
7148
+ console.warn("\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6");
7149
+ return {};
7150
+ }
7151
+ const content = await fs4.readFile(configPath, "utf-8");
7152
+ return parseConfigYaml(content);
7153
+ } catch (e) {
7154
+ const err = e;
7155
+ if (err.code !== "ENOENT") {
7156
+ console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
7157
+ }
7158
+ return {};
7159
+ }
7160
+ }
7161
+ function parseConfigYaml(content) {
7162
+ const context = {};
6867
7163
  const nameMatch = content.match(/name:\s*(.+)/);
6868
7164
  if (nameMatch) {
6869
7165
  context.name = nameMatch[1].trim();
@@ -6878,6 +7174,67 @@ function parseConfigYaml(content, defaults) {
6878
7174
  }
6879
7175
  return context;
6880
7176
  }
7177
+ async function readNorms(cwd) {
7178
+ const normsDir = path5.join(cwd, ".sf-cli", "norms");
7179
+ const norms = {
7180
+ devStandards: "",
7181
+ patterns: "",
7182
+ weights: ""
7183
+ };
7184
+ try {
7185
+ const devStandardsPath = path5.join(normsDir, "devstanded.md");
7186
+ norms.devStandards = await fs4.readFile(devStandardsPath, "utf-8").catch(() => "");
7187
+ } catch {
7188
+ }
7189
+ try {
7190
+ const patternsPath = path5.join(normsDir, "patterns.json");
7191
+ norms.patterns = await fs4.readFile(patternsPath, "utf-8").catch(() => "");
7192
+ } catch {
7193
+ }
7194
+ try {
7195
+ const weightsPath = path5.join(normsDir, "weights.json");
7196
+ norms.weights = await fs4.readFile(weightsPath, "utf-8").catch(() => "");
7197
+ } catch {
7198
+ }
7199
+ return norms;
7200
+ }
7201
+ async function analyzeStructure(cwd) {
7202
+ const structure = {
7203
+ directories: [],
7204
+ keyFiles: [],
7205
+ srcStructure: ""
7206
+ };
7207
+ try {
7208
+ const entries = await fs4.readdir(cwd, { withFileTypes: true });
7209
+ for (const entry of entries) {
7210
+ if (entry.isDirectory() && !["node_modules", "dist", ".git", "build"].includes(entry.name)) {
7211
+ structure.directories.push(entry.name);
7212
+ }
7213
+ }
7214
+ const keyFiles = [
7215
+ "package.json",
7216
+ "tsconfig.json",
7217
+ "AGENTS.md",
7218
+ "README.md"
7219
+ ];
7220
+ for (const file of keyFiles) {
7221
+ const filePath = path5.join(cwd, file);
7222
+ try {
7223
+ await fs4.access(filePath);
7224
+ structure.keyFiles.push(file);
7225
+ } catch {
7226
+ }
7227
+ }
7228
+ const srcDir = path5.join(cwd, "src");
7229
+ try {
7230
+ const srcEntries = await fs4.readdir(srcDir, { withFileTypes: true });
7231
+ structure.srcStructure = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name).join("/");
7232
+ } catch {
7233
+ }
7234
+ } catch (e) {
7235
+ }
7236
+ return { structure };
7237
+ }
6881
7238
  function analyzeComplexity(requirement, context) {
6882
7239
  let score = 3;
6883
7240
  const factors = [];
@@ -7041,7 +7398,7 @@ async function handleOpsx(command, args, ctx) {
7041
7398
  case "rollback":
7042
7399
  return handleRollback(workflow, args);
7043
7400
  case "confirm":
7044
- return handleConfirm(workflow, args);
7401
+ return handleConfirm(workflow, args, ctx);
7045
7402
  case "next":
7046
7403
  return handleNext(workflow);
7047
7404
  case "auto":
@@ -7409,6 +7766,16 @@ async function handleConfirm(workflow, args, ctx) {
7409
7766
  const type = args[0];
7410
7767
  if (!type) {
7411
7768
  const pendingPoint = workflow.getCurrentConfirmationPoint();
7769
+ const specPath = await checkPendingSpec(ctx.options.workingDirectory, state.id);
7770
+ if (specPath) {
7771
+ return {
7772
+ output: chalk9.cyan("\u{1F4CB} \u89C4\u683C\u6587\u4EF6\u5F85\u786E\u8BA4") + chalk9.gray(`
7773
+
7774
+ \u89C4\u683C\u6587\u4EF6: ${specPath}`) + chalk9.gray(`
7775
+
7776
+ \u8BF7\u68C0\u67E5\u89C4\u683C\u6587\u4EF6\u540E\u786E\u8BA4:`) + chalk9.white("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C") + chalk9.white("\n /opsx:rollback explore - \u91CD\u65B0\u62C6\u5206")
7777
+ };
7778
+ }
7412
7779
  if (pendingPoint) {
7413
7780
  return {
7414
7781
  output: chalk9.cyan("\u5F85\u786E\u8BA4\u7684\u68C0\u67E5\u70B9:") + chalk9.white(`
@@ -7430,12 +7797,54 @@ ${generateConfirmationPrompt(pendingPoint)}`) + chalk9.gray(`
7430
7797
  }
7431
7798
  const comment = args.slice(1).join(" ");
7432
7799
  workflow.confirm(type, comment);
7433
- return {
7434
- output: chalk9.green("\u2713 \u5DF2\u786E\u8BA4") + chalk9.white(`
7800
+ const lines = [];
7801
+ lines.push(chalk9.green("\u2713 \u5DF2\u786E\u8BA4"));
7802
+ lines.push(chalk9.white(`
7803
+ ${point.name}`));
7804
+ if (comment) {
7805
+ lines.push(chalk9.gray(`
7806
+ \u5907\u6CE8: ${comment}`));
7807
+ }
7808
+ if (type === "spec-review") {
7809
+ const allowed = workflow.getAllowedTransitions();
7810
+ if (allowed.length > 0) {
7811
+ const nextStep = allowed[0];
7812
+ try {
7813
+ const transition = await workflow.transition(nextStep);
7814
+ lines.push("");
7815
+ lines.push(chalk9.cyan(`\u2713 \u5DF2\u81EA\u52A8\u8FDB\u5165 ${nextStep} \u9636\u6BB5`));
7816
+ lines.push(chalk9.gray(`\u8F6C\u6362: ${transition.from} \u2192 ${transition.to}`));
7817
+ if (nextStep === "new") {
7818
+ lines.push(chalk9.yellow("\n\u4E0B\u4E00\u6B65: \u8BBE\u8BA1\u65B9\u6848"));
7819
+ lines.push(chalk9.gray(" \u53EF\u8C03\u7528 $architect \u6216 $frontend-dev \u8FDB\u884C\u8BBE\u8BA1"));
7820
+ } else if (nextStep === "apply") {
7821
+ lines.push(chalk9.yellow("\n\u4E0B\u4E00\u6B65: \u6267\u884C\u53D8\u66F4"));
7822
+ lines.push(chalk9.gray(" \u53EF\u8C03\u7528 $frontend-dev \u6267\u884C\u4EE3\u7801\u4FEE\u6539"));
7823
+ }
7824
+ return { output: lines.join("\n") };
7825
+ } catch (e) {
7826
+ if (e instanceof ConfirmationRequiredError) {
7827
+ lines.push(chalk9.yellow("\n\u26A0 \u8FD8\u9700\u8981\u786E\u8BA4:") + chalk9.white(`
7828
+ ${generateConfirmationPrompt(e.point)}`) + chalk9.cyan(`
7435
7829
 
7436
- ${point.name}`) + (comment ? chalk9.gray(`
7437
- \u5907\u6CE8: ${comment}`) : "") + chalk9.yellow("\n\n\u4F7F\u7528 /opsx:next \u7EE7\u7EED\u4E0B\u4E00\u9636\u6BB5")
7438
- };
7830
+ \u4F7F\u7528 /opsx:confirm ${e.point.type}`));
7831
+ return { output: lines.join("\n") };
7832
+ }
7833
+ throw e;
7834
+ }
7835
+ }
7836
+ }
7837
+ lines.push(chalk9.yellow("\n\u4F7F\u7528 /opsx:next \u7EE7\u7EED\u4E0B\u4E00\u9636\u6BB5"));
7838
+ return { output: lines.join("\n") };
7839
+ }
7840
+ async function checkPendingSpec(workingDirectory, changeId) {
7841
+ const specPath = path5.join(workingDirectory, "openspec", "changes", `${changeId}-spec.md`);
7842
+ try {
7843
+ await fs10.promises.access(specPath);
7844
+ return specPath;
7845
+ } catch {
7846
+ return null;
7847
+ }
7439
7848
  }
7440
7849
  async function handleNext(workflow, args, ctx) {
7441
7850
  const state = workflow.getState();