@nick848/sf-cli 1.0.3 → 1.0.7

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
  */
@@ -4066,6 +4090,92 @@ var WorkflowEngine = class {
4066
4090
  getState() {
4067
4091
  return this.state;
4068
4092
  }
4093
+ /**
4094
+ * 获取所有活跃工作流
4095
+ */
4096
+ async getAllActiveWorkflows() {
4097
+ const workflows = [];
4098
+ const changesDir = path5.join(this.openspecPath, "changes");
4099
+ try {
4100
+ const files = await fs4.readdir(changesDir);
4101
+ for (const file of files) {
4102
+ if (!file.endsWith(".md") || file.includes("-spec.md")) continue;
4103
+ const filePath = path5.join(changesDir, file);
4104
+ const content = await fs4.readFile(filePath, "utf-8");
4105
+ const state = this.parseChangeRecord(content);
4106
+ if (state && state.status === "running") {
4107
+ workflows.push(state);
4108
+ }
4109
+ }
4110
+ } catch {
4111
+ }
4112
+ if (this.state && !workflows.find((w) => w.id === this.state?.id)) {
4113
+ workflows.unshift(this.state);
4114
+ }
4115
+ return workflows;
4116
+ }
4117
+ /**
4118
+ * 解析变更记录
4119
+ */
4120
+ parseChangeRecord(content) {
4121
+ try {
4122
+ const idMatch = content.match(/^id:\s*(.+)$/m);
4123
+ const titleMatch = content.match(/^title:\s*(.+)$/m);
4124
+ const statusMatch = content.match(/^status:\s*(.+)$/m);
4125
+ const complexityMatch = content.match(/^complexity:\s*(\d+)/m);
4126
+ const workflowMatch = content.match(/^workflow:\s*(.+)$/m);
4127
+ const requirementMatch = content.match(/## 变更概述\s*\n+([\s\S]+?)(?=\n##|$)/);
4128
+ if (!idMatch || !titleMatch) return null;
4129
+ return {
4130
+ id: idMatch[1].trim(),
4131
+ title: titleMatch[1].trim(),
4132
+ status: statusMatch?.[1].trim() || "running",
4133
+ requirement: requirementMatch?.[1].trim() || "",
4134
+ complexity: parseInt(complexityMatch?.[1] || "5", 10),
4135
+ type: workflowMatch?.[1].trim() || "simple",
4136
+ currentStep: "propose",
4137
+ // 默认值,实际值需要从状态文件读取
4138
+ steps: [],
4139
+ artifacts: [],
4140
+ createdAt: /* @__PURE__ */ new Date()
4141
+ };
4142
+ } catch {
4143
+ return null;
4144
+ }
4145
+ }
4146
+ /**
4147
+ * 切换到指定工作流
4148
+ */
4149
+ async switchTo(changeId) {
4150
+ if (this.state) {
4151
+ await this.saveState();
4152
+ }
4153
+ const statePath = path5.join(this.openspecPath, ".workflow-states", `${changeId}.json`);
4154
+ try {
4155
+ const content = await fs4.readFile(statePath, "utf-8");
4156
+ this.state = JSON.parse(content, (key, value) => {
4157
+ if (key.endsWith("At") && typeof value === "string") {
4158
+ return new Date(value);
4159
+ }
4160
+ return value;
4161
+ });
4162
+ await this.restoreSnapshots();
4163
+ return true;
4164
+ } catch {
4165
+ const changesDir = path5.join(this.openspecPath, "changes");
4166
+ const changeFile = path5.join(changesDir, `${changeId}.md`);
4167
+ try {
4168
+ const content = await fs4.readFile(changeFile, "utf-8");
4169
+ const parsed = this.parseChangeRecord(content);
4170
+ if (parsed && parsed.status === "running") {
4171
+ this.state = parsed;
4172
+ return true;
4173
+ }
4174
+ } catch {
4175
+ }
4176
+ return false;
4177
+ }
4178
+ }
4069
4179
  /**
4070
4180
  * 获取允许的下一步
4071
4181
  */
@@ -4146,32 +4256,64 @@ var WorkflowEngine = class {
4146
4256
  const changesDir = path5.join(this.openspecPath, "changes");
4147
4257
  const archiveDir = path5.join(changesDir, "archive");
4148
4258
  const specDir = path5.join(this.openspecPath, "spec");
4259
+ const statesDir = path5.join(this.openspecPath, ".workflow-states");
4149
4260
  await fs4.mkdir(archiveDir, { recursive: true });
4150
4261
  await fs4.mkdir(specDir, { recursive: true });
4262
+ await fs4.mkdir(statesDir, { recursive: true });
4151
4263
  }
4152
4264
  async restoreState() {
4153
- const statePath = path5.join(this.openspecPath, ".workflow-state.json");
4265
+ const activePath = path5.join(this.openspecPath, ".workflow-active.json");
4266
+ let activeId = null;
4154
4267
  try {
4155
- const content = await fs4.readFile(statePath, "utf-8");
4268
+ const activeContent = await fs4.readFile(activePath, "utf-8");
4269
+ const activeData = JSON.parse(activeContent);
4270
+ activeId = activeData.activeId;
4271
+ } catch {
4272
+ }
4273
+ if (activeId) {
4274
+ const statePath = path5.join(this.openspecPath, ".workflow-states", `${activeId}.json`);
4275
+ try {
4276
+ const content = await fs4.readFile(statePath, "utf-8");
4277
+ this.state = JSON.parse(content, (key, value) => {
4278
+ if (key.endsWith("At") && typeof value === "string") {
4279
+ return new Date(value);
4280
+ }
4281
+ return value;
4282
+ });
4283
+ return;
4284
+ } catch {
4285
+ }
4286
+ }
4287
+ const oldStatePath = path5.join(this.openspecPath, ".workflow-state.json");
4288
+ try {
4289
+ const content = await fs4.readFile(oldStatePath, "utf-8");
4156
4290
  this.state = JSON.parse(content, (key, value) => {
4157
4291
  if (key.endsWith("At") && typeof value === "string") {
4158
4292
  return new Date(value);
4159
4293
  }
4160
4294
  return value;
4161
4295
  });
4296
+ if (this.state) {
4297
+ await this.saveState();
4298
+ await fs4.unlink(oldStatePath).catch(() => {
4299
+ });
4300
+ }
4162
4301
  } catch (e) {
4163
4302
  const err = e;
4164
4303
  if (err.code !== "ENOENT") {
4165
4304
  console.warn("\u8B66\u544A: \u5DE5\u4F5C\u6D41\u72B6\u6001\u6587\u4EF6\u5DF2\u635F\u574F\uFF0C\u5C06\u91CD\u65B0\u5F00\u59CB");
4166
- await fs4.unlink(statePath).catch(() => {
4167
- });
4168
4305
  }
4169
4306
  this.state = null;
4170
4307
  }
4171
4308
  }
4172
4309
  async saveState() {
4173
- const statePath = path5.join(this.openspecPath, ".workflow-state.json");
4310
+ if (!this.state) return;
4311
+ const statesDir = path5.join(this.openspecPath, ".workflow-states");
4312
+ await fs4.mkdir(statesDir, { recursive: true });
4313
+ const statePath = path5.join(statesDir, `${this.state.id}.json`);
4174
4314
  await fs4.writeFile(statePath, JSON.stringify(this.state, null, 2));
4315
+ const activePath = path5.join(this.openspecPath, ".workflow-active.json");
4316
+ await fs4.writeFile(activePath, JSON.stringify({ activeId: this.state.id }, null, 2));
4175
4317
  }
4176
4318
  async restoreSnapshots() {
4177
4319
  const snapshotsPath = path5.join(this.openspecPath, ".workflow-snapshots.json");
@@ -5542,18 +5684,38 @@ var CommandType = /* @__PURE__ */ ((CommandType3) => {
5542
5684
  var CommandParser = class {
5543
5685
  slashCommands = [
5544
5686
  "help",
5687
+ "h",
5688
+ "?",
5545
5689
  "init",
5690
+ "i",
5546
5691
  "new",
5692
+ "n",
5547
5693
  "model",
5694
+ "m",
5548
5695
  "update",
5696
+ "u",
5549
5697
  "clear",
5698
+ "c",
5550
5699
  "exit",
5700
+ "e",
5701
+ "q",
5702
+ "quit",
5703
+ "version",
5704
+ "v",
5705
+ // OpenSpec 工作流命令
5551
5706
  "opsx:explore",
5552
5707
  "opsx:new",
5553
5708
  "opsx:continue",
5554
5709
  "opsx:apply",
5555
5710
  "opsx:archive",
5556
- "opsx:propose"
5711
+ "opsx:propose",
5712
+ "opsx:status",
5713
+ "opsx:cancel",
5714
+ "opsx:rollback",
5715
+ "opsx:confirm",
5716
+ "opsx:next",
5717
+ "opsx:auto",
5718
+ "opsx:test"
5557
5719
  ];
5558
5720
  builtInAgents = [
5559
5721
  "frontend-dev",
@@ -5602,8 +5764,19 @@ var CommandParser = class {
5602
5764
  if (!command) {
5603
5765
  return { success: false, error: "\u65E0\u6548\u7684\u547D\u4EE4\u683C\u5F0F" };
5604
5766
  }
5767
+ if (command.startsWith("opsx:")) {
5768
+ return {
5769
+ success: true,
5770
+ command: {
5771
+ type: "slash" /* SLASH */,
5772
+ raw: input,
5773
+ command,
5774
+ args
5775
+ }
5776
+ };
5777
+ }
5605
5778
  const isValidCommand = this.slashCommands.some(
5606
- (cmd) => cmd === command || cmd.startsWith(command)
5779
+ (cmd) => cmd === command
5607
5780
  );
5608
5781
  if (!isValidCommand) {
5609
5782
  return { success: false, error: `\u672A\u77E5\u547D\u4EE4: /${command}` };
@@ -6708,16 +6881,28 @@ async function handleNew(args, ctx) {
6708
6881
  if (workflowEngine) {
6709
6882
  const existingState = workflowEngine.getState();
6710
6883
  if (existingState && existingState.status === "running") {
6711
- return {
6712
- output: chalk9.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9.gray(`
6884
+ if (existingState.currentStep === "explore" || existingState.currentStep === "propose") {
6885
+ const specPath = path5.join(workingDir, "openspec", "changes", `${existingState.id}-spec.md`);
6886
+ if (fs10.existsSync(specPath)) {
6887
+ return {
6888
+ output: chalk9.yellow("\u5F53\u524D\u5DE5\u4F5C\u6D41\u6B63\u5728\u7B49\u5F85\u89C4\u683C\u786E\u8BA4") + chalk9.gray(`
6713
6889
 
6714
6890
  \u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9.gray(`
6715
- \u5F53\u524D\u9636\u6BB5: ${existingState.currentStep}`) + chalk9.gray(`
6891
+ \u53D8\u66F4ID: ${existingState.id}`) + chalk9.cyan("\n\n\u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210:") + chalk9.white(`
6892
+ ${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")
6893
+ };
6894
+ }
6895
+ }
6896
+ return {
6897
+ output: chalk9.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9.white(`
6716
6898
 
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`)
6899
+ \u{1F4CB} ${existingState.title || existingState.id}`) + chalk9.gray(`
6900
+ \u7C7B\u578B: ${existingState.type} | \u590D\u6742\u5EA6: ${existingState.complexity}/10`) + chalk9.cyan(`
6901
+
6902
+ \u8FDB\u5EA6: ${existingState.steps.map((s) => {
6903
+ const icon = s.status === "completed" ? "\u2713" : s.status === "running" ? "\u25CF" : "\u25CB";
6904
+ return `${icon} ${s.step}`;
6905
+ }).join(" \u2192 ")}`) + chalk9.yellow("\n\n\u53EF\u7528\u547D\u4EE4:") + chalk9.white("\n /opsx:status - \u67E5\u770B\u5DE5\u4F5C\u6D41\u8BE6\u60C5") + chalk9.white("\n /opsx:cancel - \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41")
6721
6906
  };
6722
6907
  }
6723
6908
  }
@@ -6732,6 +6917,7 @@ async function handleNew(args, ctx) {
6732
6917
  async function newFeature(options, workingDir, workflowEngine) {
6733
6918
  const cwd = workingDir || process.cwd();
6734
6919
  const { requirement, forceComplexity } = options;
6920
+ const lines = [];
6735
6921
  try {
6736
6922
  const stats = await fs4.stat(cwd);
6737
6923
  if (!stats.isDirectory()) {
@@ -6739,51 +6925,283 @@ async function newFeature(options, workingDir, workflowEngine) {
6739
6925
  output: chalk9.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
6740
6926
  };
6741
6927
  }
6742
- } catch (e) {
6928
+ } catch {
6743
6929
  return {
6744
6930
  output: chalk9.red(`\u9519\u8BEF: \u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6743\u9650\u8BBF\u95EE ${cwd}`)
6745
6931
  };
6746
6932
  }
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)
6933
+ lines.push(chalk9.cyan("\u{1F50D} \u5206\u6790\u9879\u76EE..."));
6934
+ const context = await readProjectContext(cwd);
6935
+ lines.push(chalk9.gray(` \u9879\u76EE: ${context.name}`));
6936
+ lines.push(chalk9.gray(` \u7C7B\u578B: ${context.type}`));
6937
+ lines.push(chalk9.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`));
6938
+ lines.push("");
6939
+ lines.push(chalk9.cyan("\u{1F4CA} \u5206\u6790\u9700\u6C42\u590D\u6742\u5EA6..."));
6940
+ const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
6941
+ lines.push(chalk9.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`));
6942
+ lines.push(chalk9.gray(` \u6D41\u7A0B\u7C7B\u578B: ${analysis.recommendation === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`));
6943
+ for (const factor of analysis.factors) {
6944
+ lines.push(chalk9.gray(` - ${factor}`));
6945
+ }
6946
+ lines.push("");
6947
+ lines.push(chalk9.cyan("\u{1F4CB} \u521D\u59CB\u5316\u5DE5\u4F5C\u6D41..."));
6948
+ const workflow = workflowEngine || new WorkflowEngine();
6949
+ if (!workflowEngine) {
6950
+ await workflow.initialize(cwd);
6951
+ }
6952
+ const state = await workflow.start(requirement, analysis.score, {
6953
+ title: extractTitle(requirement)
6954
+ });
6955
+ lines.push(chalk9.gray(` \u53D8\u66F4ID: ${state.id}`));
6956
+ lines.push(chalk9.gray(` \u5DE5\u4F5C\u6D41: ${state.type}`));
6957
+ lines.push("");
6958
+ lines.push(chalk9.cyan("\u{1F4DD} \u751F\u6210\u89C4\u683C\u62C6\u5206..."));
6959
+ const spec = await generateSpec(requirement, context, analysis, state.id);
6960
+ const specPath = await saveSpecFile(cwd, spec);
6961
+ lines.push(chalk9.green(" \u2713 \u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210"));
6962
+ lines.push(chalk9.gray(` \u8DEF\u5F84: ${specPath}`));
6963
+ lines.push("");
6964
+ lines.push(chalk9.cyan.bold("\u{1F4CB} \u89C4\u683C\u6982\u89C8:"));
6965
+ lines.push(chalk9.white(`
6966
+ ${spec.summary}`));
6967
+ if (spec.items.length > 0) {
6968
+ lines.push("");
6969
+ lines.push(chalk9.cyan(" \u4EFB\u52A1\u62C6\u5206:"));
6970
+ for (const item of spec.items) {
6971
+ const priorityIcon = item.priority === "high" ? "\u{1F534}" : item.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
6972
+ lines.push(chalk9.gray(` ${priorityIcon} [${item.id}] ${item.title}`));
6973
+ }
6974
+ }
6975
+ if (spec.risks.length > 0) {
6976
+ lines.push("");
6977
+ lines.push(chalk9.yellow(" \u26A0\uFE0F \u98CE\u9669\u63D0\u793A:"));
6978
+ for (const risk of spec.risks) {
6979
+ lines.push(chalk9.gray(` - ${risk}`));
6980
+ }
6981
+ }
6982
+ lines.push("");
6983
+ lines.push(chalk9.yellow.bold("\u23F3 \u7B49\u5F85\u89C4\u683C\u786E\u8BA4"));
6984
+ lines.push(chalk9.gray("\n\u8BF7\u68C0\u67E5\u751F\u6210\u7684\u89C4\u683C\u6587\u4EF6\uFF0C\u786E\u8BA4\u540E\u7EE7\u7EED:"));
6985
+ lines.push(chalk9.white("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C\uFF0C\u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5"));
6986
+ lines.push(chalk9.white(" /opsx:rollback explore - \u89C4\u683C\u4E0D\u7B26\uFF0C\u91CD\u65B0\u62C6\u5206"));
6987
+ lines.push(chalk9.white(" /opsx:status - \u67E5\u770B\u5DE5\u4F5C\u6D41\u72B6\u6001"));
6988
+ return { output: lines.join("\n") };
6989
+ }
6990
+ async function generateSpec(requirement, context, analysis, changeId) {
6991
+ const spec = {
6992
+ changeId,
6993
+ requirement,
6994
+ summary: "",
6995
+ items: [],
6996
+ architectureNotes: [],
6997
+ risks: [],
6998
+ suggestions: []
6999
+ };
7000
+ spec.summary = generateSummary(requirement);
7001
+ if (analysis.recommendation === "complex") {
7002
+ spec.items = generateComplexTasks(requirement, context, analysis);
7003
+ spec.architectureNotes = generateArchitectureNotes(requirement, context);
7004
+ } else {
7005
+ spec.items = generateSimpleTasks(requirement);
7006
+ }
7007
+ spec.risks = generateRisks(requirement, context, analysis);
7008
+ spec.suggestions = generateSuggestions(requirement, context, analysis);
7009
+ return spec;
7010
+ }
7011
+ function generateSummary(requirement) {
7012
+ const firstSentence = requirement.split(/[。!?\n]/)[0];
7013
+ return firstSentence.length > 100 ? firstSentence.slice(0, 97) + "..." : firstSentence;
7014
+ }
7015
+ function generateComplexTasks(requirement, context, analysis) {
7016
+ const items = [];
7017
+ let itemId = 1;
7018
+ const featurePatterns = [
7019
+ { pattern: /用户|登录|注册|认证|权限/, title: "\u7528\u6237\u8BA4\u8BC1\u6A21\u5757", priority: "high" },
7020
+ { pattern: /数据|存储|缓存|数据库/, title: "\u6570\u636E\u5C42\u5B9E\u73B0", priority: "high" },
7021
+ { pattern: /接口|API|请求|响应/, title: "API \u63A5\u53E3\u5F00\u53D1", priority: "high" },
7022
+ { pattern: /界面|页面|组件|UI/, title: "\u754C\u9762\u5F00\u53D1", priority: "medium" },
7023
+ { pattern: /测试|单测|覆盖/, title: "\u6D4B\u8BD5\u7528\u4F8B\u7F16\u5199", priority: "medium" },
7024
+ { pattern: /文档|说明/, title: "\u6587\u6863\u7F16\u5199", priority: "low" },
7025
+ { pattern: /配置|设置/, title: "\u914D\u7F6E\u7BA1\u7406", priority: "low" },
7026
+ { pattern: /优化|性能/, title: "\u6027\u80FD\u4F18\u5316", priority: "medium" },
7027
+ { pattern: /安全|加密/, title: "\u5B89\u5168\u5B9E\u73B0", priority: "high" },
7028
+ { pattern: /日志|监控/, title: "\u65E5\u5FD7\u76D1\u63A7", priority: "low" }
7029
+ ];
7030
+ for (const { pattern, title, priority } of featurePatterns) {
7031
+ if (pattern.test(requirement)) {
7032
+ items.push({
7033
+ id: `T${itemId.toString().padStart(3, "0")}`,
7034
+ title,
7035
+ description: `${title}\u76F8\u5173\u7684\u529F\u80FD\u5B9E\u73B0`,
7036
+ priority,
7037
+ dependencies: itemId > 1 ? [`T${(itemId - 1).toString().padStart(3, "0")}`] : [],
7038
+ estimatedComplexity: priority === "high" ? 3 : priority === "medium" ? 2 : 1
7039
+ });
7040
+ itemId++;
7041
+ }
7042
+ }
7043
+ if (items.length === 0) {
7044
+ items.push({
7045
+ id: "T001",
7046
+ title: "\u9700\u6C42\u5206\u6790\u4E0E\u8BBE\u8BA1",
7047
+ description: "\u5206\u6790\u9700\u6C42\u7EC6\u8282\uFF0C\u8BBE\u8BA1\u5B9E\u73B0\u65B9\u6848",
7048
+ priority: "high",
7049
+ dependencies: [],
7050
+ estimatedComplexity: 2
6756
7051
  });
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
- };
7052
+ items.push({
7053
+ id: "T002",
7054
+ title: "\u6838\u5FC3\u529F\u80FD\u5B9E\u73B0",
7055
+ description: requirement,
7056
+ priority: "high",
7057
+ dependencies: ["T001"],
7058
+ estimatedComplexity: analysis.score
7059
+ });
7060
+ items.push({
7061
+ id: "T003",
7062
+ title: "\u6D4B\u8BD5\u4E0E\u9A8C\u8BC1",
7063
+ description: "\u7F16\u5199\u6D4B\u8BD5\u7528\u4F8B\uFF0C\u9A8C\u8BC1\u529F\u80FD\u6B63\u786E\u6027",
7064
+ priority: "medium",
7065
+ dependencies: ["T002"],
7066
+ estimatedComplexity: 2
7067
+ });
7068
+ }
7069
+ return items;
7070
+ }
7071
+ function generateSimpleTasks(requirement, context) {
7072
+ return [
7073
+ {
7074
+ id: "T001",
7075
+ title: "\u5B9E\u73B0\u53D8\u66F4",
7076
+ description: requirement,
7077
+ priority: "high",
7078
+ dependencies: [],
7079
+ estimatedComplexity: 3
7080
+ },
7081
+ {
7082
+ id: "T002",
7083
+ title: "\u6D4B\u8BD5\u9A8C\u8BC1",
7084
+ description: "\u9A8C\u8BC1\u53D8\u66F4\u6B63\u786E\u6027",
7085
+ priority: "medium",
7086
+ dependencies: ["T001"],
7087
+ estimatedComplexity: 1
6782
7088
  }
6783
- return {
6784
- output: chalk9.red(`\u542F\u52A8\u9700\u6C42\u5931\u8D25: ${error.message}`)
6785
- };
7089
+ ];
7090
+ }
7091
+ function generateArchitectureNotes(requirement, context) {
7092
+ const notes = [];
7093
+ if (context.framework) {
7094
+ notes.push(`\u9879\u76EE\u4F7F\u7528 ${context.framework} \u6846\u67B6\uFF0C\u9700\u9075\u5FAA\u5176\u6700\u4F73\u5B9E\u8DF5`);
7095
+ }
7096
+ if (requirement.includes("\u6A21\u5757") || requirement.includes("\u7EC4\u4EF6")) {
7097
+ notes.push("\u5EFA\u8BAE\u91C7\u7528\u6A21\u5757\u5316\u8BBE\u8BA1\uFF0C\u4FDD\u6301\u7EC4\u4EF6\u804C\u8D23\u5355\u4E00");
7098
+ }
7099
+ if (requirement.includes("API") || requirement.includes("\u63A5\u53E3")) {
7100
+ notes.push("API \u8BBE\u8BA1\u9700\u8003\u8651\u7248\u672C\u63A7\u5236\u548C\u5411\u540E\u517C\u5BB9");
7101
+ }
7102
+ if (context.structure.srcStructure) {
7103
+ notes.push(`\u73B0\u6709\u6E90\u7801\u7ED3\u6784: ${context.structure.srcStructure}`);
7104
+ }
7105
+ return notes;
7106
+ }
7107
+ function generateRisks(requirement, context, analysis) {
7108
+ const risks = [];
7109
+ if (!context.framework) {
7110
+ risks.push("\u9879\u76EE\u6846\u67B6\u672A\u8BC6\u522B\uFF0C\u53EF\u80FD\u5F71\u54CD\u4EE3\u7801\u98CE\u683C\u4E00\u81F4\u6027");
7111
+ }
7112
+ if (analysis.score >= 7) {
7113
+ risks.push("\u9700\u6C42\u590D\u6742\u5EA6\u8F83\u9AD8\uFF0C\u5EFA\u8BAE\u5206\u9636\u6BB5\u5B9E\u73B0");
7114
+ }
7115
+ if (requirement.includes("\u8FC1\u79FB") || requirement.includes("\u91CD\u6784")) {
7116
+ risks.push("\u6D89\u53CA\u73B0\u6709\u4EE3\u7801\u4FEE\u6539\uFF0C\u9700\u6CE8\u610F\u56DE\u5F52\u6D4B\u8BD5");
7117
+ }
7118
+ if (requirement.includes("\u6743\u9650") || requirement.includes("\u5B89\u5168")) {
7119
+ risks.push("\u6D89\u53CA\u5B89\u5168\u654F\u611F\u529F\u80FD\uFF0C\u9700\u8981\u989D\u5916\u5BA1\u67E5");
7120
+ }
7121
+ return risks;
7122
+ }
7123
+ function generateSuggestions(requirement, context, analysis) {
7124
+ const suggestions = [];
7125
+ if (context.norms.devStandards) {
7126
+ suggestions.push("\u9879\u76EE\u5DF2\u6709\u5F00\u53D1\u89C4\u8303\uFF0C\u8BF7\u9075\u5FAA\u73B0\u6709\u89C4\u8303");
7127
+ }
7128
+ if (analysis.recommendation === "complex") {
7129
+ suggestions.push("\u590D\u6742\u9700\u6C42\u5EFA\u8BAE\u5148\u8FDB\u884C\u6280\u672F\u8BC4\u5BA1");
7130
+ suggestions.push("\u5EFA\u8BAE\u8C03\u7528 $architect \u83B7\u53D6\u67B6\u6784\u5EFA\u8BAE");
7131
+ }
7132
+ if (context.techStack.length > 0) {
7133
+ suggestions.push(`\u6280\u672F\u6808: ${context.techStack.join(", ")}`);
6786
7134
  }
7135
+ return suggestions;
7136
+ }
7137
+ async function saveSpecFile(cwd, spec) {
7138
+ const changesDir = path5.join(cwd, "openspec", "changes");
7139
+ await fs4.mkdir(changesDir, { recursive: true });
7140
+ const specPath = path5.join(changesDir, `${spec.changeId}-spec.md`);
7141
+ const content = formatSpecFile(spec);
7142
+ await fs4.writeFile(specPath, content, "utf-8");
7143
+ return specPath;
7144
+ }
7145
+ function formatSpecFile(spec) {
7146
+ const lines = [];
7147
+ lines.push(`# Spec: ${spec.summary}`);
7148
+ lines.push("");
7149
+ lines.push(`> \u53D8\u66F4ID: ${spec.changeId}`);
7150
+ lines.push(`> \u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}`);
7151
+ lines.push("");
7152
+ lines.push("---");
7153
+ lines.push("");
7154
+ lines.push("## \u9700\u6C42\u6982\u8FF0");
7155
+ lines.push("");
7156
+ lines.push(spec.requirement);
7157
+ lines.push("");
7158
+ lines.push("## \u4EFB\u52A1\u62C6\u5206");
7159
+ lines.push("");
7160
+ for (const item of spec.items) {
7161
+ const priorityLabel = item.priority === "high" ? "\u{1F534} \u9AD8" : item.priority === "medium" ? "\u{1F7E1} \u4E2D" : "\u{1F7E2} \u4F4E";
7162
+ lines.push(`### ${item.id}: ${item.title}`);
7163
+ lines.push("");
7164
+ lines.push(`- **\u4F18\u5148\u7EA7**: ${priorityLabel}`);
7165
+ lines.push(`- **\u63CF\u8FF0**: ${item.description}`);
7166
+ lines.push(`- **\u9884\u4F30\u590D\u6742\u5EA6**: ${item.estimatedComplexity}/5`);
7167
+ if (item.dependencies.length > 0) {
7168
+ lines.push(`- **\u4F9D\u8D56**: ${item.dependencies.join(", ")}`);
7169
+ }
7170
+ lines.push("");
7171
+ }
7172
+ if (spec.architectureNotes.length > 0) {
7173
+ lines.push("## \u67B6\u6784\u8BF4\u660E");
7174
+ lines.push("");
7175
+ for (const note of spec.architectureNotes) {
7176
+ lines.push(`- ${note}`);
7177
+ }
7178
+ lines.push("");
7179
+ }
7180
+ if (spec.risks.length > 0) {
7181
+ lines.push("## \u26A0\uFE0F \u98CE\u9669\u8BC4\u4F30");
7182
+ lines.push("");
7183
+ for (const risk of spec.risks) {
7184
+ lines.push(`- ${risk}`);
7185
+ }
7186
+ lines.push("");
7187
+ }
7188
+ if (spec.suggestions.length > 0) {
7189
+ lines.push("## \u{1F4A1} \u5EFA\u8BAE");
7190
+ lines.push("");
7191
+ for (const suggestion of spec.suggestions) {
7192
+ lines.push(`- ${suggestion}`);
7193
+ }
7194
+ lines.push("");
7195
+ }
7196
+ lines.push("---");
7197
+ lines.push("");
7198
+ lines.push("## \u786E\u8BA4\u72B6\u6001");
7199
+ lines.push("");
7200
+ lines.push("- [ ] \u89C4\u683C\u5DF2\u5BA1\u9605");
7201
+ lines.push("- [ ] \u4EFB\u52A1\u62C6\u5206\u5DF2\u786E\u8BA4");
7202
+ lines.push("");
7203
+ lines.push("**\u786E\u8BA4\u540E\u6267\u884C**: `/opsx:confirm spec-review`");
7204
+ return lines.join("\n");
6787
7205
  }
6788
7206
  function parseArgs(args) {
6789
7207
  let forceComplexity;
@@ -6808,42 +7226,52 @@ async function readProjectContext(cwd) {
6808
7226
  type: "unknown",
6809
7227
  framework: null,
6810
7228
  techStack: [],
6811
- description: ""
7229
+ description: "",
7230
+ structure: {
7231
+ directories: [],
7232
+ keyFiles: [],
7233
+ srcStructure: ""
7234
+ },
7235
+ norms: {
7236
+ devStandards: "",
7237
+ patterns: "",
7238
+ weights: ""
7239
+ }
7240
+ };
7241
+ const [agentsContext, configContext, normsContext, structureContext] = await Promise.all([
7242
+ readAgentsMd(cwd),
7243
+ readConfigYaml(cwd),
7244
+ readNorms(cwd),
7245
+ analyzeStructure(cwd)
7246
+ ]);
7247
+ return {
7248
+ ...defaultContext,
7249
+ ...agentsContext,
7250
+ ...configContext,
7251
+ norms: normsContext,
7252
+ structure: structureContext
6812
7253
  };
7254
+ }
7255
+ async function readAgentsMd(cwd) {
6813
7256
  const agentsPath = path5.join(cwd, "AGENTS.md");
6814
7257
  try {
6815
7258
  const stats = await fs4.stat(agentsPath);
6816
7259
  if (stats.size > MAX_FILE_SIZE2) {
6817
7260
  console.warn(`\u8B66\u544A: AGENTS.md \u6587\u4EF6\u8FC7\u5927 (${stats.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
6818
- return defaultContext;
7261
+ return {};
6819
7262
  }
6820
7263
  const content = await fs4.readFile(agentsPath, "utf-8");
6821
- return parseAgentsMd(content, defaultContext);
7264
+ return parseAgentsMd(content);
6822
7265
  } catch (e) {
6823
7266
  const err = e;
6824
7267
  if (err.code !== "ENOENT") {
6825
7268
  console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 AGENTS.md - ${err.message}`);
6826
7269
  }
7270
+ return {};
6827
7271
  }
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
7272
  }
6845
- function parseAgentsMd(content, defaults) {
6846
- const context = { ...defaults };
7273
+ function parseAgentsMd(content) {
7274
+ const context = {};
6847
7275
  const nameMatch = content.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
6848
7276
  if (nameMatch) {
6849
7277
  context.name = nameMatch[1];
@@ -6860,10 +7288,32 @@ function parseAgentsMd(content, defaults) {
6860
7288
  if (descMatch) {
6861
7289
  context.description = descMatch[1].trim();
6862
7290
  }
7291
+ const techStackMatch = content.match(/技术栈[::]\s*([^\n]+)/);
7292
+ if (techStackMatch) {
7293
+ context.techStack = techStackMatch[1].split(/[,,、]/).map((s) => s.trim()).filter(Boolean);
7294
+ }
6863
7295
  return context;
6864
7296
  }
6865
- function parseConfigYaml(content, defaults) {
6866
- const context = { ...defaults };
7297
+ async function readConfigYaml(cwd) {
7298
+ const configPath = path5.join(cwd, "openspec", "config.yaml");
7299
+ try {
7300
+ const stats = await fs4.stat(configPath);
7301
+ if (stats.size > MAX_FILE_SIZE2) {
7302
+ console.warn("\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6");
7303
+ return {};
7304
+ }
7305
+ const content = await fs4.readFile(configPath, "utf-8");
7306
+ return parseConfigYaml(content);
7307
+ } catch (e) {
7308
+ const err = e;
7309
+ if (err.code !== "ENOENT") {
7310
+ console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
7311
+ }
7312
+ return {};
7313
+ }
7314
+ }
7315
+ function parseConfigYaml(content) {
7316
+ const context = {};
6867
7317
  const nameMatch = content.match(/name:\s*(.+)/);
6868
7318
  if (nameMatch) {
6869
7319
  context.name = nameMatch[1].trim();
@@ -6878,6 +7328,67 @@ function parseConfigYaml(content, defaults) {
6878
7328
  }
6879
7329
  return context;
6880
7330
  }
7331
+ async function readNorms(cwd) {
7332
+ const normsDir = path5.join(cwd, ".sf-cli", "norms");
7333
+ const norms = {
7334
+ devStandards: "",
7335
+ patterns: "",
7336
+ weights: ""
7337
+ };
7338
+ try {
7339
+ const devStandardsPath = path5.join(normsDir, "devstanded.md");
7340
+ norms.devStandards = await fs4.readFile(devStandardsPath, "utf-8").catch(() => "");
7341
+ } catch {
7342
+ }
7343
+ try {
7344
+ const patternsPath = path5.join(normsDir, "patterns.json");
7345
+ norms.patterns = await fs4.readFile(patternsPath, "utf-8").catch(() => "");
7346
+ } catch {
7347
+ }
7348
+ try {
7349
+ const weightsPath = path5.join(normsDir, "weights.json");
7350
+ norms.weights = await fs4.readFile(weightsPath, "utf-8").catch(() => "");
7351
+ } catch {
7352
+ }
7353
+ return norms;
7354
+ }
7355
+ async function analyzeStructure(cwd) {
7356
+ const structure = {
7357
+ directories: [],
7358
+ keyFiles: [],
7359
+ srcStructure: ""
7360
+ };
7361
+ try {
7362
+ const entries = await fs4.readdir(cwd, { withFileTypes: true });
7363
+ for (const entry of entries) {
7364
+ if (entry.isDirectory() && !["node_modules", "dist", ".git", "build"].includes(entry.name)) {
7365
+ structure.directories.push(entry.name);
7366
+ }
7367
+ }
7368
+ const keyFiles = [
7369
+ "package.json",
7370
+ "tsconfig.json",
7371
+ "AGENTS.md",
7372
+ "README.md"
7373
+ ];
7374
+ for (const file of keyFiles) {
7375
+ const filePath = path5.join(cwd, file);
7376
+ try {
7377
+ await fs4.access(filePath);
7378
+ structure.keyFiles.push(file);
7379
+ } catch {
7380
+ }
7381
+ }
7382
+ const srcDir = path5.join(cwd, "src");
7383
+ try {
7384
+ const srcEntries = await fs4.readdir(srcDir, { withFileTypes: true });
7385
+ structure.srcStructure = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name).join("/");
7386
+ } catch {
7387
+ }
7388
+ } catch (e) {
7389
+ }
7390
+ return { structure };
7391
+ }
6881
7392
  function analyzeComplexity(requirement, context) {
6882
7393
  let score = 3;
6883
7394
  const factors = [];
@@ -7041,16 +7552,20 @@ async function handleOpsx(command, args, ctx) {
7041
7552
  case "rollback":
7042
7553
  return handleRollback(workflow, args);
7043
7554
  case "confirm":
7044
- return handleConfirm(workflow, args);
7555
+ return handleConfirm(workflow, args, ctx);
7045
7556
  case "next":
7046
7557
  return handleNext(workflow);
7047
7558
  case "auto":
7048
7559
  return handleAutoSchedule(args);
7049
7560
  case "test":
7050
7561
  return handleRegressionTest(ctx);
7562
+ case "list":
7563
+ return handleList(workflow);
7564
+ case "switch":
7565
+ return handleSwitch(workflow, args);
7051
7566
  default:
7052
7567
  return {
7053
- output: chalk9.red(`\u672A\u77E5\u7684OpenSpec\u547D\u4EE4: /${command}`)
7568
+ output: chalk9.red(`\u672A\u77E5\u7684OpenSpec\u547D\u4EE4: /${command}`) + chalk9.gray("\n\u53EF\u7528\u547D\u4EE4: /opsx:list, /opsx:status, /opsx:confirm, /opsx:next, /opsx:archive")
7054
7569
  };
7055
7570
  }
7056
7571
  }
@@ -7409,6 +7924,16 @@ async function handleConfirm(workflow, args, ctx) {
7409
7924
  const type = args[0];
7410
7925
  if (!type) {
7411
7926
  const pendingPoint = workflow.getCurrentConfirmationPoint();
7927
+ const specPath = await checkPendingSpec(ctx.options.workingDirectory, state.id);
7928
+ if (specPath) {
7929
+ return {
7930
+ output: chalk9.cyan("\u{1F4CB} \u89C4\u683C\u6587\u4EF6\u5F85\u786E\u8BA4") + chalk9.gray(`
7931
+
7932
+ \u89C4\u683C\u6587\u4EF6: ${specPath}`) + chalk9.gray(`
7933
+
7934
+ \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")
7935
+ };
7936
+ }
7412
7937
  if (pendingPoint) {
7413
7938
  return {
7414
7939
  output: chalk9.cyan("\u5F85\u786E\u8BA4\u7684\u68C0\u67E5\u70B9:") + chalk9.white(`
@@ -7430,12 +7955,54 @@ ${generateConfirmationPrompt(pendingPoint)}`) + chalk9.gray(`
7430
7955
  }
7431
7956
  const comment = args.slice(1).join(" ");
7432
7957
  workflow.confirm(type, comment);
7433
- return {
7434
- output: chalk9.green("\u2713 \u5DF2\u786E\u8BA4") + chalk9.white(`
7958
+ const lines = [];
7959
+ lines.push(chalk9.green("\u2713 \u5DF2\u786E\u8BA4"));
7960
+ lines.push(chalk9.white(`
7961
+ ${point.name}`));
7962
+ if (comment) {
7963
+ lines.push(chalk9.gray(`
7964
+ \u5907\u6CE8: ${comment}`));
7965
+ }
7966
+ if (type === "spec-review") {
7967
+ const allowed = workflow.getAllowedTransitions();
7968
+ if (allowed.length > 0) {
7969
+ const nextStep = allowed[0];
7970
+ try {
7971
+ const transition = await workflow.transition(nextStep);
7972
+ lines.push("");
7973
+ lines.push(chalk9.cyan(`\u2713 \u5DF2\u81EA\u52A8\u8FDB\u5165 ${nextStep} \u9636\u6BB5`));
7974
+ lines.push(chalk9.gray(`\u8F6C\u6362: ${transition.from} \u2192 ${transition.to}`));
7975
+ if (nextStep === "new") {
7976
+ lines.push(chalk9.yellow("\n\u4E0B\u4E00\u6B65: \u8BBE\u8BA1\u65B9\u6848"));
7977
+ lines.push(chalk9.gray(" \u53EF\u8C03\u7528 $architect \u6216 $frontend-dev \u8FDB\u884C\u8BBE\u8BA1"));
7978
+ } else if (nextStep === "apply") {
7979
+ lines.push(chalk9.yellow("\n\u4E0B\u4E00\u6B65: \u6267\u884C\u53D8\u66F4"));
7980
+ lines.push(chalk9.gray(" \u53EF\u8C03\u7528 $frontend-dev \u6267\u884C\u4EE3\u7801\u4FEE\u6539"));
7981
+ }
7982
+ return { output: lines.join("\n") };
7983
+ } catch (e) {
7984
+ if (e instanceof ConfirmationRequiredError) {
7985
+ lines.push(chalk9.yellow("\n\u26A0 \u8FD8\u9700\u8981\u786E\u8BA4:") + chalk9.white(`
7986
+ ${generateConfirmationPrompt(e.point)}`) + chalk9.cyan(`
7435
7987
 
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
- };
7988
+ \u4F7F\u7528 /opsx:confirm ${e.point.type}`));
7989
+ return { output: lines.join("\n") };
7990
+ }
7991
+ throw e;
7992
+ }
7993
+ }
7994
+ }
7995
+ lines.push(chalk9.yellow("\n\u4F7F\u7528 /opsx:next \u7EE7\u7EED\u4E0B\u4E00\u9636\u6BB5"));
7996
+ return { output: lines.join("\n") };
7997
+ }
7998
+ async function checkPendingSpec(workingDirectory, changeId) {
7999
+ const specPath = path5.join(workingDirectory, "openspec", "changes", `${changeId}-spec.md`);
8000
+ try {
8001
+ await fs10.promises.access(specPath);
8002
+ return specPath;
8003
+ } catch {
8004
+ return null;
8005
+ }
7439
8006
  }
7440
8007
  async function handleNext(workflow, args, ctx) {
7441
8008
  const state = workflow.getState();
@@ -7471,6 +8038,76 @@ ${generateConfirmationPrompt(e.point)}`) + chalk9.cyan(`
7471
8038
  throw e;
7472
8039
  }
7473
8040
  }
8041
+ async function handleList(workflow, ctx) {
8042
+ const lines = [];
8043
+ const activeWorkflows = await workflow.getAllActiveWorkflows();
8044
+ const currentState = workflow.getState();
8045
+ if (activeWorkflows.length === 0) {
8046
+ return {
8047
+ output: chalk9.gray("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9.yellow("\n\n\u4F7F\u7528 /new <\u9700\u6C42\u63CF\u8FF0> \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
8048
+ };
8049
+ }
8050
+ lines.push(chalk9.cyan.bold(`\u{1F4CB} \u6D3B\u8DC3\u5DE5\u4F5C\u6D41 (${activeWorkflows.length})`));
8051
+ lines.push("");
8052
+ for (const wf of activeWorkflows) {
8053
+ const isCurrent = currentState?.id === wf.id;
8054
+ const prefix = isCurrent ? chalk9.green("\u25B6 ") : " ";
8055
+ const title = isCurrent ? chalk9.white(wf.title) : chalk9.gray(wf.title);
8056
+ const stepIcon = wf.currentStep === "propose" || wf.currentStep === "explore" ? "\u23F3" : wf.currentStep === "apply" ? "\u{1F527}" : wf.currentStep === "archive" ? "\u{1F4E6}" : "\u{1F4DD}";
8057
+ lines.push(`${prefix}${title}`);
8058
+ lines.push(chalk9.gray(` ID: ${wf.id}`));
8059
+ lines.push(chalk9.gray(` \u9636\u6BB5: ${stepIcon} ${wf.currentStep} | \u590D\u6742\u5EA6: ${wf.complexity}/10`));
8060
+ if (isCurrent) {
8061
+ lines.push(chalk9.green(" (\u5F53\u524D)"));
8062
+ }
8063
+ lines.push("");
8064
+ }
8065
+ if (activeWorkflows.length > 1) {
8066
+ lines.push(chalk9.yellow("\u5207\u6362\u5DE5\u4F5C\u6D41:"));
8067
+ lines.push(chalk9.gray(" /opsx:switch <\u53D8\u66F4ID>"));
8068
+ }
8069
+ lines.push(chalk9.yellow("\n\u64CD\u4F5C\u547D\u4EE4:"));
8070
+ lines.push(chalk9.gray(" /opsx:status - \u67E5\u770B\u5F53\u524D\u5DE5\u4F5C\u6D41\u8BE6\u60C5"));
8071
+ lines.push(chalk9.gray(" /opsx:confirm - \u786E\u8BA4\u89C4\u683C"));
8072
+ lines.push(chalk9.gray(" /opsx:next - \u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5"));
8073
+ lines.push(chalk9.gray(" /opsx:cancel - \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41"));
8074
+ return { output: lines.join("\n") };
8075
+ }
8076
+ async function handleSwitch(workflow, args, ctx) {
8077
+ const targetId = args[0];
8078
+ if (!targetId) {
8079
+ const activeWorkflows = await workflow.getAllActiveWorkflows();
8080
+ if (activeWorkflows.length === 0) {
8081
+ return {
8082
+ output: chalk9.gray("\u6CA1\u6709\u53EF\u5207\u6362\u7684\u5DE5\u4F5C\u6D41")
8083
+ };
8084
+ }
8085
+ const lines = [
8086
+ chalk9.cyan("\u53EF\u7528\u5DE5\u4F5C\u6D41:"),
8087
+ ""
8088
+ ];
8089
+ for (const wf of activeWorkflows) {
8090
+ lines.push(chalk9.white(` ${wf.id}`) + chalk9.gray(` - ${wf.title.slice(0, 30)}...`));
8091
+ }
8092
+ lines.push("");
8093
+ lines.push(chalk9.gray("\u7528\u6CD5: /opsx:switch <\u53D8\u66F4ID>"));
8094
+ return { output: lines.join("\n") };
8095
+ }
8096
+ const success = await workflow.switchTo(targetId);
8097
+ if (success) {
8098
+ const state = workflow.getState();
8099
+ return {
8100
+ output: chalk9.green(`\u2713 \u5DF2\u5207\u6362\u5230\u5DE5\u4F5C\u6D41: ${targetId}`) + chalk9.gray(`
8101
+
8102
+ \u9700\u6C42: ${state?.title}`) + chalk9.cyan(`
8103
+ \u5F53\u524D\u9636\u6BB5: ${state?.currentStep}`) + chalk9.yellow("\n\n\u4F7F\u7528 /opsx:status \u67E5\u770B\u8BE6\u60C5")
8104
+ };
8105
+ } else {
8106
+ return {
8107
+ output: chalk9.red(`\u5207\u6362\u5931\u8D25: \u627E\u4E0D\u5230\u5DE5\u4F5C\u6D41 ${targetId}`) + chalk9.gray("\n\u4F7F\u7528 /opsx:list \u67E5\u770B\u6240\u6709\u6D3B\u8DC3\u5DE5\u4F5C\u6D41")
8108
+ };
8109
+ }
8110
+ }
7474
8111
 
7475
8112
  // src/commands/runner.ts
7476
8113
  function getVersion() {
@@ -7653,7 +8290,11 @@ var ALLOWED_COMMANDS_WITHOUT_WORKFLOW = [
7653
8290
  "update",
7654
8291
  "u",
7655
8292
  "version",
7656
- "v"
8293
+ "v",
8294
+ // OpenSpec 工作流管理命令(始终允许)
8295
+ "opsx:status",
8296
+ "opsx:cancel",
8297
+ "opsx:rollback"
7657
8298
  ];
7658
8299
  var STAGE_PERMISSIONS = {
7659
8300
  "explore": {
@@ -7726,6 +8367,12 @@ var CommandExecutor = class {
7726
8367
  checkWorkflowPermission(command, ctx) {
7727
8368
  const workflowEngine = ctx.workflowEngine;
7728
8369
  const workflowState = workflowEngine?.getState();
8370
+ if (command.type === "slash" /* SLASH */) {
8371
+ const cmd = command.command?.toLowerCase();
8372
+ if (cmd?.startsWith("opsx:")) {
8373
+ return { allowed: true };
8374
+ }
8375
+ }
7729
8376
  if (!workflowState) {
7730
8377
  if (command.type === "slash" /* SLASH */) {
7731
8378
  const cmd = command.command?.toLowerCase();