@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/cli/index.js CHANGED
@@ -48,18 +48,38 @@ var os__namespace = /*#__PURE__*/_interopNamespace(os);
48
48
  var CommandParser = class {
49
49
  slashCommands = [
50
50
  "help",
51
+ "h",
52
+ "?",
51
53
  "init",
54
+ "i",
52
55
  "new",
56
+ "n",
53
57
  "model",
58
+ "m",
54
59
  "update",
60
+ "u",
55
61
  "clear",
62
+ "c",
56
63
  "exit",
64
+ "e",
65
+ "q",
66
+ "quit",
67
+ "version",
68
+ "v",
69
+ // OpenSpec 工作流命令
57
70
  "opsx:explore",
58
71
  "opsx:new",
59
72
  "opsx:continue",
60
73
  "opsx:apply",
61
74
  "opsx:archive",
62
- "opsx:propose"
75
+ "opsx:propose",
76
+ "opsx:status",
77
+ "opsx:cancel",
78
+ "opsx:rollback",
79
+ "opsx:confirm",
80
+ "opsx:next",
81
+ "opsx:auto",
82
+ "opsx:test"
63
83
  ];
64
84
  builtInAgents = [
65
85
  "frontend-dev",
@@ -108,8 +128,19 @@ var CommandParser = class {
108
128
  if (!command) {
109
129
  return { success: false, error: "\u65E0\u6548\u7684\u547D\u4EE4\u683C\u5F0F" };
110
130
  }
131
+ if (command.startsWith("opsx:")) {
132
+ return {
133
+ success: true,
134
+ command: {
135
+ type: "slash" /* SLASH */,
136
+ raw: input,
137
+ command,
138
+ args
139
+ }
140
+ };
141
+ }
111
142
  const isValidCommand = this.slashCommands.some(
112
- (cmd) => cmd === command || cmd.startsWith(command)
143
+ (cmd) => cmd === command
113
144
  );
114
145
  if (!isValidCommand) {
115
146
  return { success: false, error: `\u672A\u77E5\u547D\u4EE4: /${command}` };
@@ -3314,12 +3345,20 @@ async function exitCLI(ctx, options = {}) {
3314
3345
  var DEFAULT_CONFIRMATION_POINTS = [
3315
3346
  {
3316
3347
  type: "spec-review",
3317
- name: "\u89C4\u8303\u62C6\u89E3\u786E\u8BA4",
3318
- description: "\u89C4\u8303\u62C6\u89E3\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u662F\u5426\u7EE7\u7EED\u8FDB\u5165\u8BBE\u8BA1\u9636\u6BB5",
3348
+ name: "\u89C4\u683C\u786E\u8BA4",
3349
+ description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
3319
3350
  triggerStep: "explore",
3320
3351
  targetStep: "new",
3321
3352
  required: true
3322
3353
  },
3354
+ {
3355
+ type: "spec-review",
3356
+ name: "\u89C4\u683C\u786E\u8BA4",
3357
+ description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
3358
+ triggerStep: "propose",
3359
+ targetStep: "apply",
3360
+ required: true
3361
+ },
3323
3362
  {
3324
3363
  type: "architecture",
3325
3364
  name: "\u67B6\u6784\u8C03\u6574\u786E\u8BA4",
@@ -3355,19 +3394,15 @@ var ConfirmationManager = class {
3355
3394
  confirmationPoints;
3356
3395
  confirmations = /* @__PURE__ */ new Map();
3357
3396
  constructor(customPoints) {
3358
- const points = customPoints || DEFAULT_CONFIRMATION_POINTS;
3359
- this.confirmationPoints = new Map(points.map((p) => [p.type, p]));
3397
+ this.confirmationPoints = customPoints || DEFAULT_CONFIRMATION_POINTS;
3360
3398
  }
3361
3399
  /**
3362
3400
  * 获取指定阶段的确认点
3363
3401
  */
3364
3402
  getConfirmationPointForTransition(from, to) {
3365
- for (const point of this.confirmationPoints.values()) {
3366
- if (point.triggerStep === from && point.targetStep === to) {
3367
- return point;
3368
- }
3369
- }
3370
- return void 0;
3403
+ return this.confirmationPoints.find(
3404
+ (point) => point.triggerStep === from && point.targetStep === to
3405
+ );
3371
3406
  }
3372
3407
  /**
3373
3408
  * 检查是否需要确认
@@ -3380,7 +3415,7 @@ var ConfirmationManager = class {
3380
3415
  * 获取确认点详情
3381
3416
  */
3382
3417
  getConfirmationPoint(type) {
3383
- return this.confirmationPoints.get(type);
3418
+ return this.confirmationPoints.find((point) => point.type === type);
3384
3419
  }
3385
3420
  /**
3386
3421
  * 记录确认
@@ -3424,14 +3459,14 @@ var ConfirmationManager = class {
3424
3459
  * 获取所有确认点
3425
3460
  */
3426
3461
  getAllConfirmationPoints() {
3427
- return Array.from(this.confirmationPoints.values());
3462
+ return [...this.confirmationPoints];
3428
3463
  }
3429
3464
  /**
3430
3465
  * 获取指定阶段需要清除的确认点
3431
3466
  */
3432
3467
  getConfirmationsToClear(targetStep) {
3433
3468
  const types = [];
3434
- for (const point of this.confirmationPoints.values()) {
3469
+ for (const point of this.confirmationPoints) {
3435
3470
  const stepOrder = ["explore", "new", "continue", "propose", "apply", "archive"];
3436
3471
  const targetIndex = stepOrder.indexOf(targetStep);
3437
3472
  const triggerIndex = stepOrder.indexOf(point.triggerStep);
@@ -3554,6 +3589,26 @@ var WorkflowEngine = class {
3554
3589
  devStandards: this.devStandards
3555
3590
  };
3556
3591
  }
3592
+ /**
3593
+ * 获取规格文件路径
3594
+ */
3595
+ getSpecFilePath() {
3596
+ if (!this.state) return null;
3597
+ return path6__namespace.join(this.openspecPath, "changes", `${this.state.id}-spec.md`);
3598
+ }
3599
+ /**
3600
+ * 检查规格文件是否存在
3601
+ */
3602
+ async hasSpecFile() {
3603
+ const specPath = this.getSpecFilePath();
3604
+ if (!specPath) return false;
3605
+ try {
3606
+ await fs6__namespace.access(specPath);
3607
+ return true;
3608
+ } catch {
3609
+ return false;
3610
+ }
3611
+ }
3557
3612
  /**
3558
3613
  * 启动新工作流
3559
3614
  */
@@ -3775,6 +3830,92 @@ var WorkflowEngine = class {
3775
3830
  getState() {
3776
3831
  return this.state;
3777
3832
  }
3833
+ /**
3834
+ * 获取所有活跃工作流
3835
+ */
3836
+ async getAllActiveWorkflows() {
3837
+ const workflows = [];
3838
+ const changesDir = path6__namespace.join(this.openspecPath, "changes");
3839
+ try {
3840
+ const files = await fs6__namespace.readdir(changesDir);
3841
+ for (const file of files) {
3842
+ if (!file.endsWith(".md") || file.includes("-spec.md")) continue;
3843
+ const filePath = path6__namespace.join(changesDir, file);
3844
+ const content = await fs6__namespace.readFile(filePath, "utf-8");
3845
+ const state = this.parseChangeRecord(content);
3846
+ if (state && state.status === "running") {
3847
+ workflows.push(state);
3848
+ }
3849
+ }
3850
+ } catch {
3851
+ }
3852
+ if (this.state && !workflows.find((w) => w.id === this.state?.id)) {
3853
+ workflows.unshift(this.state);
3854
+ }
3855
+ return workflows;
3856
+ }
3857
+ /**
3858
+ * 解析变更记录
3859
+ */
3860
+ parseChangeRecord(content) {
3861
+ try {
3862
+ const idMatch = content.match(/^id:\s*(.+)$/m);
3863
+ const titleMatch = content.match(/^title:\s*(.+)$/m);
3864
+ const statusMatch = content.match(/^status:\s*(.+)$/m);
3865
+ const complexityMatch = content.match(/^complexity:\s*(\d+)/m);
3866
+ const workflowMatch = content.match(/^workflow:\s*(.+)$/m);
3867
+ const requirementMatch = content.match(/## 变更概述\s*\n+([\s\S]+?)(?=\n##|$)/);
3868
+ if (!idMatch || !titleMatch) return null;
3869
+ return {
3870
+ id: idMatch[1].trim(),
3871
+ title: titleMatch[1].trim(),
3872
+ status: statusMatch?.[1].trim() || "running",
3873
+ requirement: requirementMatch?.[1].trim() || "",
3874
+ complexity: parseInt(complexityMatch?.[1] || "5", 10),
3875
+ type: workflowMatch?.[1].trim() || "simple",
3876
+ currentStep: "propose",
3877
+ // 默认值,实际值需要从状态文件读取
3878
+ steps: [],
3879
+ artifacts: [],
3880
+ createdAt: /* @__PURE__ */ new Date()
3881
+ };
3882
+ } catch {
3883
+ return null;
3884
+ }
3885
+ }
3886
+ /**
3887
+ * 切换到指定工作流
3888
+ */
3889
+ async switchTo(changeId) {
3890
+ if (this.state) {
3891
+ await this.saveState();
3892
+ }
3893
+ const statePath = path6__namespace.join(this.openspecPath, ".workflow-states", `${changeId}.json`);
3894
+ try {
3895
+ const content = await fs6__namespace.readFile(statePath, "utf-8");
3896
+ this.state = JSON.parse(content, (key, value) => {
3897
+ if (key.endsWith("At") && typeof value === "string") {
3898
+ return new Date(value);
3899
+ }
3900
+ return value;
3901
+ });
3902
+ await this.restoreSnapshots();
3903
+ return true;
3904
+ } catch {
3905
+ const changesDir = path6__namespace.join(this.openspecPath, "changes");
3906
+ const changeFile = path6__namespace.join(changesDir, `${changeId}.md`);
3907
+ try {
3908
+ const content = await fs6__namespace.readFile(changeFile, "utf-8");
3909
+ const parsed = this.parseChangeRecord(content);
3910
+ if (parsed && parsed.status === "running") {
3911
+ this.state = parsed;
3912
+ return true;
3913
+ }
3914
+ } catch {
3915
+ }
3916
+ return false;
3917
+ }
3918
+ }
3778
3919
  /**
3779
3920
  * 获取允许的下一步
3780
3921
  */
@@ -3855,32 +3996,64 @@ var WorkflowEngine = class {
3855
3996
  const changesDir = path6__namespace.join(this.openspecPath, "changes");
3856
3997
  const archiveDir = path6__namespace.join(changesDir, "archive");
3857
3998
  const specDir = path6__namespace.join(this.openspecPath, "spec");
3999
+ const statesDir = path6__namespace.join(this.openspecPath, ".workflow-states");
3858
4000
  await fs6__namespace.mkdir(archiveDir, { recursive: true });
3859
4001
  await fs6__namespace.mkdir(specDir, { recursive: true });
4002
+ await fs6__namespace.mkdir(statesDir, { recursive: true });
3860
4003
  }
3861
4004
  async restoreState() {
3862
- const statePath = path6__namespace.join(this.openspecPath, ".workflow-state.json");
4005
+ const activePath = path6__namespace.join(this.openspecPath, ".workflow-active.json");
4006
+ let activeId = null;
3863
4007
  try {
3864
- const content = await fs6__namespace.readFile(statePath, "utf-8");
4008
+ const activeContent = await fs6__namespace.readFile(activePath, "utf-8");
4009
+ const activeData = JSON.parse(activeContent);
4010
+ activeId = activeData.activeId;
4011
+ } catch {
4012
+ }
4013
+ if (activeId) {
4014
+ const statePath = path6__namespace.join(this.openspecPath, ".workflow-states", `${activeId}.json`);
4015
+ try {
4016
+ const content = await fs6__namespace.readFile(statePath, "utf-8");
4017
+ this.state = JSON.parse(content, (key, value) => {
4018
+ if (key.endsWith("At") && typeof value === "string") {
4019
+ return new Date(value);
4020
+ }
4021
+ return value;
4022
+ });
4023
+ return;
4024
+ } catch {
4025
+ }
4026
+ }
4027
+ const oldStatePath = path6__namespace.join(this.openspecPath, ".workflow-state.json");
4028
+ try {
4029
+ const content = await fs6__namespace.readFile(oldStatePath, "utf-8");
3865
4030
  this.state = JSON.parse(content, (key, value) => {
3866
4031
  if (key.endsWith("At") && typeof value === "string") {
3867
4032
  return new Date(value);
3868
4033
  }
3869
4034
  return value;
3870
4035
  });
4036
+ if (this.state) {
4037
+ await this.saveState();
4038
+ await fs6__namespace.unlink(oldStatePath).catch(() => {
4039
+ });
4040
+ }
3871
4041
  } catch (e) {
3872
4042
  const err = e;
3873
4043
  if (err.code !== "ENOENT") {
3874
4044
  console.warn("\u8B66\u544A: \u5DE5\u4F5C\u6D41\u72B6\u6001\u6587\u4EF6\u5DF2\u635F\u574F\uFF0C\u5C06\u91CD\u65B0\u5F00\u59CB");
3875
- await fs6__namespace.unlink(statePath).catch(() => {
3876
- });
3877
4045
  }
3878
4046
  this.state = null;
3879
4047
  }
3880
4048
  }
3881
4049
  async saveState() {
3882
- const statePath = path6__namespace.join(this.openspecPath, ".workflow-state.json");
4050
+ if (!this.state) return;
4051
+ const statesDir = path6__namespace.join(this.openspecPath, ".workflow-states");
4052
+ await fs6__namespace.mkdir(statesDir, { recursive: true });
4053
+ const statePath = path6__namespace.join(statesDir, `${this.state.id}.json`);
3883
4054
  await fs6__namespace.writeFile(statePath, JSON.stringify(this.state, null, 2));
4055
+ const activePath = path6__namespace.join(this.openspecPath, ".workflow-active.json");
4056
+ await fs6__namespace.writeFile(activePath, JSON.stringify({ activeId: this.state.id }, null, 2));
3884
4057
  }
3885
4058
  async restoreSnapshots() {
3886
4059
  const snapshotsPath = path6__namespace.join(this.openspecPath, ".workflow-snapshots.json");
@@ -4015,16 +4188,28 @@ async function handleNew(args, ctx) {
4015
4188
  if (workflowEngine) {
4016
4189
  const existingState = workflowEngine.getState();
4017
4190
  if (existingState && existingState.status === "running") {
4018
- return {
4019
- output: chalk9__default.default.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9__default.default.gray(`
4191
+ if (existingState.currentStep === "explore" || existingState.currentStep === "propose") {
4192
+ const specPath = path6__namespace.join(workingDir, "openspec", "changes", `${existingState.id}-spec.md`);
4193
+ if (fs9__namespace.existsSync(specPath)) {
4194
+ return {
4195
+ output: chalk9__default.default.yellow("\u5F53\u524D\u5DE5\u4F5C\u6D41\u6B63\u5728\u7B49\u5F85\u89C4\u683C\u786E\u8BA4") + chalk9__default.default.gray(`
4020
4196
 
4021
4197
  \u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9__default.default.gray(`
4022
- \u5F53\u524D\u9636\u6BB5: ${existingState.currentStep}`) + chalk9__default.default.gray(`
4198
+ \u53D8\u66F4ID: ${existingState.id}`) + chalk9__default.default.cyan("\n\n\u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210:") + chalk9__default.default.white(`
4199
+ ${specPath}`) + chalk9__default.default.yellow("\n\n\u8BF7\u786E\u8BA4\u89C4\u683C\u540E\u7EE7\u7EED:") + chalk9__default.default.gray("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C") + chalk9__default.default.gray("\n /opsx:status - \u67E5\u770B\u8BE6\u60C5")
4200
+ };
4201
+ }
4202
+ }
4203
+ return {
4204
+ output: chalk9__default.default.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9__default.default.white(`
4205
+
4206
+ \u{1F4CB} ${existingState.title || existingState.id}`) + chalk9__default.default.gray(`
4207
+ \u7C7B\u578B: ${existingState.type} | \u590D\u6742\u5EA6: ${existingState.complexity}/10`) + chalk9__default.default.cyan(`
4023
4208
 
4024
- \u9009\u9879:`) + chalk9__default.default.gray(`
4025
- 1. \u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:${existingState.currentStep}`) + chalk9__default.default.gray(`
4026
- 2. \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:cancel`) + chalk9__default.default.gray(`
4027
- 3. \u67E5\u770B\u5DE5\u4F5C\u6D41\u72B6\u6001: /opsx:status`)
4209
+ \u8FDB\u5EA6: ${existingState.steps.map((s) => {
4210
+ const icon = s.status === "completed" ? "\u2713" : s.status === "running" ? "\u25CF" : "\u25CB";
4211
+ return `${icon} ${s.step}`;
4212
+ }).join(" \u2192 ")}`) + chalk9__default.default.yellow("\n\n\u53EF\u7528\u547D\u4EE4:") + chalk9__default.default.white("\n /opsx:status - \u67E5\u770B\u5DE5\u4F5C\u6D41\u8BE6\u60C5") + chalk9__default.default.white("\n /opsx:cancel - \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41")
4028
4213
  };
4029
4214
  }
4030
4215
  }
@@ -4039,6 +4224,7 @@ async function handleNew(args, ctx) {
4039
4224
  async function newFeature(options, workingDir, workflowEngine) {
4040
4225
  const cwd = workingDir || process.cwd();
4041
4226
  const { requirement, forceComplexity } = options;
4227
+ const lines = [];
4042
4228
  try {
4043
4229
  const stats = await fs6__namespace.stat(cwd);
4044
4230
  if (!stats.isDirectory()) {
@@ -4046,51 +4232,283 @@ async function newFeature(options, workingDir, workflowEngine) {
4046
4232
  output: chalk9__default.default.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
4047
4233
  };
4048
4234
  }
4049
- } catch (e) {
4235
+ } catch {
4050
4236
  return {
4051
4237
  output: chalk9__default.default.red(`\u9519\u8BEF: \u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6743\u9650\u8BBF\u95EE ${cwd}`)
4052
4238
  };
4053
4239
  }
4054
- try {
4055
- const context = await readProjectContext(cwd);
4056
- const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
4057
- const workflow = workflowEngine || new WorkflowEngine();
4058
- if (!workflowEngine) {
4059
- await workflow.initialize(cwd);
4060
- }
4061
- const state = await workflow.start(requirement, analysis.score, {
4062
- title: extractTitle(requirement)
4240
+ lines.push(chalk9__default.default.cyan("\u{1F50D} \u5206\u6790\u9879\u76EE..."));
4241
+ const context = await readProjectContext(cwd);
4242
+ lines.push(chalk9__default.default.gray(` \u9879\u76EE: ${context.name}`));
4243
+ lines.push(chalk9__default.default.gray(` \u7C7B\u578B: ${context.type}`));
4244
+ lines.push(chalk9__default.default.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`));
4245
+ lines.push("");
4246
+ lines.push(chalk9__default.default.cyan("\u{1F4CA} \u5206\u6790\u9700\u6C42\u590D\u6742\u5EA6..."));
4247
+ const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
4248
+ lines.push(chalk9__default.default.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`));
4249
+ lines.push(chalk9__default.default.gray(` \u6D41\u7A0B\u7C7B\u578B: ${analysis.recommendation === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`));
4250
+ for (const factor of analysis.factors) {
4251
+ lines.push(chalk9__default.default.gray(` - ${factor}`));
4252
+ }
4253
+ lines.push("");
4254
+ lines.push(chalk9__default.default.cyan("\u{1F4CB} \u521D\u59CB\u5316\u5DE5\u4F5C\u6D41..."));
4255
+ const workflow = workflowEngine || new WorkflowEngine();
4256
+ if (!workflowEngine) {
4257
+ await workflow.initialize(cwd);
4258
+ }
4259
+ const state = await workflow.start(requirement, analysis.score, {
4260
+ title: extractTitle(requirement)
4261
+ });
4262
+ lines.push(chalk9__default.default.gray(` \u53D8\u66F4ID: ${state.id}`));
4263
+ lines.push(chalk9__default.default.gray(` \u5DE5\u4F5C\u6D41: ${state.type}`));
4264
+ lines.push("");
4265
+ lines.push(chalk9__default.default.cyan("\u{1F4DD} \u751F\u6210\u89C4\u683C\u62C6\u5206..."));
4266
+ const spec = await generateSpec(requirement, context, analysis, state.id);
4267
+ const specPath = await saveSpecFile(cwd, spec);
4268
+ lines.push(chalk9__default.default.green(" \u2713 \u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210"));
4269
+ lines.push(chalk9__default.default.gray(` \u8DEF\u5F84: ${specPath}`));
4270
+ lines.push("");
4271
+ lines.push(chalk9__default.default.cyan.bold("\u{1F4CB} \u89C4\u683C\u6982\u89C8:"));
4272
+ lines.push(chalk9__default.default.white(`
4273
+ ${spec.summary}`));
4274
+ if (spec.items.length > 0) {
4275
+ lines.push("");
4276
+ lines.push(chalk9__default.default.cyan(" \u4EFB\u52A1\u62C6\u5206:"));
4277
+ for (const item of spec.items) {
4278
+ const priorityIcon = item.priority === "high" ? "\u{1F534}" : item.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
4279
+ lines.push(chalk9__default.default.gray(` ${priorityIcon} [${item.id}] ${item.title}`));
4280
+ }
4281
+ }
4282
+ if (spec.risks.length > 0) {
4283
+ lines.push("");
4284
+ lines.push(chalk9__default.default.yellow(" \u26A0\uFE0F \u98CE\u9669\u63D0\u793A:"));
4285
+ for (const risk of spec.risks) {
4286
+ lines.push(chalk9__default.default.gray(` - ${risk}`));
4287
+ }
4288
+ }
4289
+ lines.push("");
4290
+ lines.push(chalk9__default.default.yellow.bold("\u23F3 \u7B49\u5F85\u89C4\u683C\u786E\u8BA4"));
4291
+ lines.push(chalk9__default.default.gray("\n\u8BF7\u68C0\u67E5\u751F\u6210\u7684\u89C4\u683C\u6587\u4EF6\uFF0C\u786E\u8BA4\u540E\u7EE7\u7EED:"));
4292
+ lines.push(chalk9__default.default.white("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C\uFF0C\u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5"));
4293
+ lines.push(chalk9__default.default.white(" /opsx:rollback explore - \u89C4\u683C\u4E0D\u7B26\uFF0C\u91CD\u65B0\u62C6\u5206"));
4294
+ lines.push(chalk9__default.default.white(" /opsx:status - \u67E5\u770B\u5DE5\u4F5C\u6D41\u72B6\u6001"));
4295
+ return { output: lines.join("\n") };
4296
+ }
4297
+ async function generateSpec(requirement, context, analysis, changeId) {
4298
+ const spec = {
4299
+ changeId,
4300
+ requirement,
4301
+ summary: "",
4302
+ items: [],
4303
+ architectureNotes: [],
4304
+ risks: [],
4305
+ suggestions: []
4306
+ };
4307
+ spec.summary = generateSummary(requirement);
4308
+ if (analysis.recommendation === "complex") {
4309
+ spec.items = generateComplexTasks(requirement, context, analysis);
4310
+ spec.architectureNotes = generateArchitectureNotes(requirement, context);
4311
+ } else {
4312
+ spec.items = generateSimpleTasks(requirement);
4313
+ }
4314
+ spec.risks = generateRisks(requirement, context, analysis);
4315
+ spec.suggestions = generateSuggestions(requirement, context, analysis);
4316
+ return spec;
4317
+ }
4318
+ function generateSummary(requirement) {
4319
+ const firstSentence = requirement.split(/[。!?\n]/)[0];
4320
+ return firstSentence.length > 100 ? firstSentence.slice(0, 97) + "..." : firstSentence;
4321
+ }
4322
+ function generateComplexTasks(requirement, context, analysis) {
4323
+ const items = [];
4324
+ let itemId = 1;
4325
+ const featurePatterns = [
4326
+ { pattern: /用户|登录|注册|认证|权限/, title: "\u7528\u6237\u8BA4\u8BC1\u6A21\u5757", priority: "high" },
4327
+ { pattern: /数据|存储|缓存|数据库/, title: "\u6570\u636E\u5C42\u5B9E\u73B0", priority: "high" },
4328
+ { pattern: /接口|API|请求|响应/, title: "API \u63A5\u53E3\u5F00\u53D1", priority: "high" },
4329
+ { pattern: /界面|页面|组件|UI/, title: "\u754C\u9762\u5F00\u53D1", priority: "medium" },
4330
+ { pattern: /测试|单测|覆盖/, title: "\u6D4B\u8BD5\u7528\u4F8B\u7F16\u5199", priority: "medium" },
4331
+ { pattern: /文档|说明/, title: "\u6587\u6863\u7F16\u5199", priority: "low" },
4332
+ { pattern: /配置|设置/, title: "\u914D\u7F6E\u7BA1\u7406", priority: "low" },
4333
+ { pattern: /优化|性能/, title: "\u6027\u80FD\u4F18\u5316", priority: "medium" },
4334
+ { pattern: /安全|加密/, title: "\u5B89\u5168\u5B9E\u73B0", priority: "high" },
4335
+ { pattern: /日志|监控/, title: "\u65E5\u5FD7\u76D1\u63A7", priority: "low" }
4336
+ ];
4337
+ for (const { pattern, title, priority } of featurePatterns) {
4338
+ if (pattern.test(requirement)) {
4339
+ items.push({
4340
+ id: `T${itemId.toString().padStart(3, "0")}`,
4341
+ title,
4342
+ description: `${title}\u76F8\u5173\u7684\u529F\u80FD\u5B9E\u73B0`,
4343
+ priority,
4344
+ dependencies: itemId > 1 ? [`T${(itemId - 1).toString().padStart(3, "0")}`] : [],
4345
+ estimatedComplexity: priority === "high" ? 3 : priority === "medium" ? 2 : 1
4346
+ });
4347
+ itemId++;
4348
+ }
4349
+ }
4350
+ if (items.length === 0) {
4351
+ items.push({
4352
+ id: "T001",
4353
+ title: "\u9700\u6C42\u5206\u6790\u4E0E\u8BBE\u8BA1",
4354
+ description: "\u5206\u6790\u9700\u6C42\u7EC6\u8282\uFF0C\u8BBE\u8BA1\u5B9E\u73B0\u65B9\u6848",
4355
+ priority: "high",
4356
+ dependencies: [],
4357
+ estimatedComplexity: 2
4063
4358
  });
4064
- const lines = [
4065
- chalk9__default.default.green("\u2713 \u9700\u6C42\u5206\u6790\u5B8C\u6210\n"),
4066
- chalk9__default.default.cyan("\u9879\u76EE\u4FE1\u606F:"),
4067
- chalk9__default.default.gray(` \u540D\u79F0: ${context.name}`),
4068
- chalk9__default.default.gray(` \u7C7B\u578B: ${context.type}`),
4069
- chalk9__default.default.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`),
4070
- "",
4071
- chalk9__default.default.cyan("\u9700\u6C42\u5206\u6790:"),
4072
- chalk9__default.default.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`),
4073
- chalk9__default.default.gray(` \u5DE5\u4F5C\u6D41: ${state.type === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`),
4074
- chalk9__default.default.gray(` \u53D8\u66F4ID: ${state.id}`),
4075
- "",
4076
- chalk9__default.default.cyan("\u590D\u6742\u5EA6\u56E0\u7D20:"),
4077
- ...analysis.factors.map((f) => chalk9__default.default.gray(` - ${f}`)),
4078
- "",
4079
- chalk9__default.default.yellow("\u4E0B\u4E00\u6B65:"),
4080
- state.type === "complex" ? chalk9__default.default.gray(" \u6267\u884C /opsx:explore \u5F00\u59CB\u63A2\u7D22\u9636\u6BB5") : chalk9__default.default.gray(" \u6267\u884C /opsx:propose \u63D0\u4EA4\u53D8\u66F4\u63D0\u6848")
4081
- ];
4082
- return { output: lines.join("\n") };
4083
- } catch (error) {
4084
- const err = error;
4085
- if (err.code === "EACCES") {
4086
- return {
4087
- output: chalk9__default.default.red(`\u9519\u8BEF: \u65E0\u6743\u9650\u8BBF\u95EE\u76EE\u5F55 ${cwd}`)
4088
- };
4359
+ items.push({
4360
+ id: "T002",
4361
+ title: "\u6838\u5FC3\u529F\u80FD\u5B9E\u73B0",
4362
+ description: requirement,
4363
+ priority: "high",
4364
+ dependencies: ["T001"],
4365
+ estimatedComplexity: analysis.score
4366
+ });
4367
+ items.push({
4368
+ id: "T003",
4369
+ title: "\u6D4B\u8BD5\u4E0E\u9A8C\u8BC1",
4370
+ description: "\u7F16\u5199\u6D4B\u8BD5\u7528\u4F8B\uFF0C\u9A8C\u8BC1\u529F\u80FD\u6B63\u786E\u6027",
4371
+ priority: "medium",
4372
+ dependencies: ["T002"],
4373
+ estimatedComplexity: 2
4374
+ });
4375
+ }
4376
+ return items;
4377
+ }
4378
+ function generateSimpleTasks(requirement, context) {
4379
+ return [
4380
+ {
4381
+ id: "T001",
4382
+ title: "\u5B9E\u73B0\u53D8\u66F4",
4383
+ description: requirement,
4384
+ priority: "high",
4385
+ dependencies: [],
4386
+ estimatedComplexity: 3
4387
+ },
4388
+ {
4389
+ id: "T002",
4390
+ title: "\u6D4B\u8BD5\u9A8C\u8BC1",
4391
+ description: "\u9A8C\u8BC1\u53D8\u66F4\u6B63\u786E\u6027",
4392
+ priority: "medium",
4393
+ dependencies: ["T001"],
4394
+ estimatedComplexity: 1
4089
4395
  }
4090
- return {
4091
- output: chalk9__default.default.red(`\u542F\u52A8\u9700\u6C42\u5931\u8D25: ${error.message}`)
4092
- };
4396
+ ];
4397
+ }
4398
+ function generateArchitectureNotes(requirement, context) {
4399
+ const notes = [];
4400
+ if (context.framework) {
4401
+ notes.push(`\u9879\u76EE\u4F7F\u7528 ${context.framework} \u6846\u67B6\uFF0C\u9700\u9075\u5FAA\u5176\u6700\u4F73\u5B9E\u8DF5`);
4402
+ }
4403
+ if (requirement.includes("\u6A21\u5757") || requirement.includes("\u7EC4\u4EF6")) {
4404
+ notes.push("\u5EFA\u8BAE\u91C7\u7528\u6A21\u5757\u5316\u8BBE\u8BA1\uFF0C\u4FDD\u6301\u7EC4\u4EF6\u804C\u8D23\u5355\u4E00");
4405
+ }
4406
+ if (requirement.includes("API") || requirement.includes("\u63A5\u53E3")) {
4407
+ notes.push("API \u8BBE\u8BA1\u9700\u8003\u8651\u7248\u672C\u63A7\u5236\u548C\u5411\u540E\u517C\u5BB9");
4093
4408
  }
4409
+ if (context.structure.srcStructure) {
4410
+ notes.push(`\u73B0\u6709\u6E90\u7801\u7ED3\u6784: ${context.structure.srcStructure}`);
4411
+ }
4412
+ return notes;
4413
+ }
4414
+ function generateRisks(requirement, context, analysis) {
4415
+ const risks = [];
4416
+ if (!context.framework) {
4417
+ risks.push("\u9879\u76EE\u6846\u67B6\u672A\u8BC6\u522B\uFF0C\u53EF\u80FD\u5F71\u54CD\u4EE3\u7801\u98CE\u683C\u4E00\u81F4\u6027");
4418
+ }
4419
+ if (analysis.score >= 7) {
4420
+ risks.push("\u9700\u6C42\u590D\u6742\u5EA6\u8F83\u9AD8\uFF0C\u5EFA\u8BAE\u5206\u9636\u6BB5\u5B9E\u73B0");
4421
+ }
4422
+ if (requirement.includes("\u8FC1\u79FB") || requirement.includes("\u91CD\u6784")) {
4423
+ risks.push("\u6D89\u53CA\u73B0\u6709\u4EE3\u7801\u4FEE\u6539\uFF0C\u9700\u6CE8\u610F\u56DE\u5F52\u6D4B\u8BD5");
4424
+ }
4425
+ if (requirement.includes("\u6743\u9650") || requirement.includes("\u5B89\u5168")) {
4426
+ risks.push("\u6D89\u53CA\u5B89\u5168\u654F\u611F\u529F\u80FD\uFF0C\u9700\u8981\u989D\u5916\u5BA1\u67E5");
4427
+ }
4428
+ return risks;
4429
+ }
4430
+ function generateSuggestions(requirement, context, analysis) {
4431
+ const suggestions = [];
4432
+ if (context.norms.devStandards) {
4433
+ suggestions.push("\u9879\u76EE\u5DF2\u6709\u5F00\u53D1\u89C4\u8303\uFF0C\u8BF7\u9075\u5FAA\u73B0\u6709\u89C4\u8303");
4434
+ }
4435
+ if (analysis.recommendation === "complex") {
4436
+ suggestions.push("\u590D\u6742\u9700\u6C42\u5EFA\u8BAE\u5148\u8FDB\u884C\u6280\u672F\u8BC4\u5BA1");
4437
+ suggestions.push("\u5EFA\u8BAE\u8C03\u7528 $architect \u83B7\u53D6\u67B6\u6784\u5EFA\u8BAE");
4438
+ }
4439
+ if (context.techStack.length > 0) {
4440
+ suggestions.push(`\u6280\u672F\u6808: ${context.techStack.join(", ")}`);
4441
+ }
4442
+ return suggestions;
4443
+ }
4444
+ async function saveSpecFile(cwd, spec) {
4445
+ const changesDir = path6__namespace.join(cwd, "openspec", "changes");
4446
+ await fs6__namespace.mkdir(changesDir, { recursive: true });
4447
+ const specPath = path6__namespace.join(changesDir, `${spec.changeId}-spec.md`);
4448
+ const content = formatSpecFile(spec);
4449
+ await fs6__namespace.writeFile(specPath, content, "utf-8");
4450
+ return specPath;
4451
+ }
4452
+ function formatSpecFile(spec) {
4453
+ const lines = [];
4454
+ lines.push(`# Spec: ${spec.summary}`);
4455
+ lines.push("");
4456
+ lines.push(`> \u53D8\u66F4ID: ${spec.changeId}`);
4457
+ lines.push(`> \u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}`);
4458
+ lines.push("");
4459
+ lines.push("---");
4460
+ lines.push("");
4461
+ lines.push("## \u9700\u6C42\u6982\u8FF0");
4462
+ lines.push("");
4463
+ lines.push(spec.requirement);
4464
+ lines.push("");
4465
+ lines.push("## \u4EFB\u52A1\u62C6\u5206");
4466
+ lines.push("");
4467
+ for (const item of spec.items) {
4468
+ const priorityLabel = item.priority === "high" ? "\u{1F534} \u9AD8" : item.priority === "medium" ? "\u{1F7E1} \u4E2D" : "\u{1F7E2} \u4F4E";
4469
+ lines.push(`### ${item.id}: ${item.title}`);
4470
+ lines.push("");
4471
+ lines.push(`- **\u4F18\u5148\u7EA7**: ${priorityLabel}`);
4472
+ lines.push(`- **\u63CF\u8FF0**: ${item.description}`);
4473
+ lines.push(`- **\u9884\u4F30\u590D\u6742\u5EA6**: ${item.estimatedComplexity}/5`);
4474
+ if (item.dependencies.length > 0) {
4475
+ lines.push(`- **\u4F9D\u8D56**: ${item.dependencies.join(", ")}`);
4476
+ }
4477
+ lines.push("");
4478
+ }
4479
+ if (spec.architectureNotes.length > 0) {
4480
+ lines.push("## \u67B6\u6784\u8BF4\u660E");
4481
+ lines.push("");
4482
+ for (const note of spec.architectureNotes) {
4483
+ lines.push(`- ${note}`);
4484
+ }
4485
+ lines.push("");
4486
+ }
4487
+ if (spec.risks.length > 0) {
4488
+ lines.push("## \u26A0\uFE0F \u98CE\u9669\u8BC4\u4F30");
4489
+ lines.push("");
4490
+ for (const risk of spec.risks) {
4491
+ lines.push(`- ${risk}`);
4492
+ }
4493
+ lines.push("");
4494
+ }
4495
+ if (spec.suggestions.length > 0) {
4496
+ lines.push("## \u{1F4A1} \u5EFA\u8BAE");
4497
+ lines.push("");
4498
+ for (const suggestion of spec.suggestions) {
4499
+ lines.push(`- ${suggestion}`);
4500
+ }
4501
+ lines.push("");
4502
+ }
4503
+ lines.push("---");
4504
+ lines.push("");
4505
+ lines.push("## \u786E\u8BA4\u72B6\u6001");
4506
+ lines.push("");
4507
+ lines.push("- [ ] \u89C4\u683C\u5DF2\u5BA1\u9605");
4508
+ lines.push("- [ ] \u4EFB\u52A1\u62C6\u5206\u5DF2\u786E\u8BA4");
4509
+ lines.push("");
4510
+ lines.push("**\u786E\u8BA4\u540E\u6267\u884C**: `/opsx:confirm spec-review`");
4511
+ return lines.join("\n");
4094
4512
  }
4095
4513
  function parseArgs(args) {
4096
4514
  let forceComplexity;
@@ -4115,42 +4533,52 @@ async function readProjectContext(cwd) {
4115
4533
  type: "unknown",
4116
4534
  framework: null,
4117
4535
  techStack: [],
4118
- description: ""
4536
+ description: "",
4537
+ structure: {
4538
+ directories: [],
4539
+ keyFiles: [],
4540
+ srcStructure: ""
4541
+ },
4542
+ norms: {
4543
+ devStandards: "",
4544
+ patterns: "",
4545
+ weights: ""
4546
+ }
4547
+ };
4548
+ const [agentsContext, configContext, normsContext, structureContext] = await Promise.all([
4549
+ readAgentsMd(cwd),
4550
+ readConfigYaml(cwd),
4551
+ readNorms(cwd),
4552
+ analyzeStructure(cwd)
4553
+ ]);
4554
+ return {
4555
+ ...defaultContext,
4556
+ ...agentsContext,
4557
+ ...configContext,
4558
+ norms: normsContext,
4559
+ structure: structureContext
4119
4560
  };
4561
+ }
4562
+ async function readAgentsMd(cwd) {
4120
4563
  const agentsPath = path6__namespace.join(cwd, "AGENTS.md");
4121
4564
  try {
4122
4565
  const stats = await fs6__namespace.stat(agentsPath);
4123
4566
  if (stats.size > MAX_FILE_SIZE2) {
4124
4567
  console.warn(`\u8B66\u544A: AGENTS.md \u6587\u4EF6\u8FC7\u5927 (${stats.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
4125
- return defaultContext;
4568
+ return {};
4126
4569
  }
4127
4570
  const content = await fs6__namespace.readFile(agentsPath, "utf-8");
4128
- return parseAgentsMd(content, defaultContext);
4571
+ return parseAgentsMd(content);
4129
4572
  } catch (e) {
4130
4573
  const err = e;
4131
4574
  if (err.code !== "ENOENT") {
4132
4575
  console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 AGENTS.md - ${err.message}`);
4133
4576
  }
4577
+ return {};
4134
4578
  }
4135
- const configPath = path6__namespace.join(cwd, "openspec", "config.yaml");
4136
- try {
4137
- const stats = await fs6__namespace.stat(configPath);
4138
- if (stats.size > MAX_FILE_SIZE2) {
4139
- console.warn(`\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
4140
- return defaultContext;
4141
- }
4142
- const content = await fs6__namespace.readFile(configPath, "utf-8");
4143
- return parseConfigYaml(content, defaultContext);
4144
- } catch (e) {
4145
- const err = e;
4146
- if (err.code !== "ENOENT") {
4147
- console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
4148
- }
4149
- }
4150
- return defaultContext;
4151
4579
  }
4152
- function parseAgentsMd(content, defaults) {
4153
- const context = { ...defaults };
4580
+ function parseAgentsMd(content) {
4581
+ const context = {};
4154
4582
  const nameMatch = content.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
4155
4583
  if (nameMatch) {
4156
4584
  context.name = nameMatch[1];
@@ -4167,10 +4595,32 @@ function parseAgentsMd(content, defaults) {
4167
4595
  if (descMatch) {
4168
4596
  context.description = descMatch[1].trim();
4169
4597
  }
4598
+ const techStackMatch = content.match(/技术栈[::]\s*([^\n]+)/);
4599
+ if (techStackMatch) {
4600
+ context.techStack = techStackMatch[1].split(/[,,、]/).map((s) => s.trim()).filter(Boolean);
4601
+ }
4170
4602
  return context;
4171
4603
  }
4172
- function parseConfigYaml(content, defaults) {
4173
- const context = { ...defaults };
4604
+ async function readConfigYaml(cwd) {
4605
+ const configPath = path6__namespace.join(cwd, "openspec", "config.yaml");
4606
+ try {
4607
+ const stats = await fs6__namespace.stat(configPath);
4608
+ if (stats.size > MAX_FILE_SIZE2) {
4609
+ console.warn("\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6");
4610
+ return {};
4611
+ }
4612
+ const content = await fs6__namespace.readFile(configPath, "utf-8");
4613
+ return parseConfigYaml(content);
4614
+ } catch (e) {
4615
+ const err = e;
4616
+ if (err.code !== "ENOENT") {
4617
+ console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
4618
+ }
4619
+ return {};
4620
+ }
4621
+ }
4622
+ function parseConfigYaml(content) {
4623
+ const context = {};
4174
4624
  const nameMatch = content.match(/name:\s*(.+)/);
4175
4625
  if (nameMatch) {
4176
4626
  context.name = nameMatch[1].trim();
@@ -4185,6 +4635,67 @@ function parseConfigYaml(content, defaults) {
4185
4635
  }
4186
4636
  return context;
4187
4637
  }
4638
+ async function readNorms(cwd) {
4639
+ const normsDir = path6__namespace.join(cwd, ".sf-cli", "norms");
4640
+ const norms = {
4641
+ devStandards: "",
4642
+ patterns: "",
4643
+ weights: ""
4644
+ };
4645
+ try {
4646
+ const devStandardsPath = path6__namespace.join(normsDir, "devstanded.md");
4647
+ norms.devStandards = await fs6__namespace.readFile(devStandardsPath, "utf-8").catch(() => "");
4648
+ } catch {
4649
+ }
4650
+ try {
4651
+ const patternsPath = path6__namespace.join(normsDir, "patterns.json");
4652
+ norms.patterns = await fs6__namespace.readFile(patternsPath, "utf-8").catch(() => "");
4653
+ } catch {
4654
+ }
4655
+ try {
4656
+ const weightsPath = path6__namespace.join(normsDir, "weights.json");
4657
+ norms.weights = await fs6__namespace.readFile(weightsPath, "utf-8").catch(() => "");
4658
+ } catch {
4659
+ }
4660
+ return norms;
4661
+ }
4662
+ async function analyzeStructure(cwd) {
4663
+ const structure = {
4664
+ directories: [],
4665
+ keyFiles: [],
4666
+ srcStructure: ""
4667
+ };
4668
+ try {
4669
+ const entries = await fs6__namespace.readdir(cwd, { withFileTypes: true });
4670
+ for (const entry of entries) {
4671
+ if (entry.isDirectory() && !["node_modules", "dist", ".git", "build"].includes(entry.name)) {
4672
+ structure.directories.push(entry.name);
4673
+ }
4674
+ }
4675
+ const keyFiles = [
4676
+ "package.json",
4677
+ "tsconfig.json",
4678
+ "AGENTS.md",
4679
+ "README.md"
4680
+ ];
4681
+ for (const file of keyFiles) {
4682
+ const filePath = path6__namespace.join(cwd, file);
4683
+ try {
4684
+ await fs6__namespace.access(filePath);
4685
+ structure.keyFiles.push(file);
4686
+ } catch {
4687
+ }
4688
+ }
4689
+ const srcDir = path6__namespace.join(cwd, "src");
4690
+ try {
4691
+ const srcEntries = await fs6__namespace.readdir(srcDir, { withFileTypes: true });
4692
+ structure.srcStructure = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name).join("/");
4693
+ } catch {
4694
+ }
4695
+ } catch (e) {
4696
+ }
4697
+ return { structure };
4698
+ }
4188
4699
  function analyzeComplexity(requirement, context) {
4189
4700
  let score = 3;
4190
4701
  const factors = [];
@@ -4990,16 +5501,20 @@ async function handleOpsx(command, args, ctx) {
4990
5501
  case "rollback":
4991
5502
  return handleRollback(workflow, args);
4992
5503
  case "confirm":
4993
- return handleConfirm(workflow, args);
5504
+ return handleConfirm(workflow, args, ctx);
4994
5505
  case "next":
4995
5506
  return handleNext(workflow);
4996
5507
  case "auto":
4997
5508
  return handleAutoSchedule(args);
4998
5509
  case "test":
4999
5510
  return handleRegressionTest(ctx);
5511
+ case "list":
5512
+ return handleList(workflow);
5513
+ case "switch":
5514
+ return handleSwitch(workflow, args);
5000
5515
  default:
5001
5516
  return {
5002
- output: chalk9__default.default.red(`\u672A\u77E5\u7684OpenSpec\u547D\u4EE4: /${command}`)
5517
+ output: chalk9__default.default.red(`\u672A\u77E5\u7684OpenSpec\u547D\u4EE4: /${command}`) + chalk9__default.default.gray("\n\u53EF\u7528\u547D\u4EE4: /opsx:list, /opsx:status, /opsx:confirm, /opsx:next, /opsx:archive")
5003
5518
  };
5004
5519
  }
5005
5520
  }
@@ -5358,6 +5873,16 @@ async function handleConfirm(workflow, args, ctx) {
5358
5873
  const type = args[0];
5359
5874
  if (!type) {
5360
5875
  const pendingPoint = workflow.getCurrentConfirmationPoint();
5876
+ const specPath = await checkPendingSpec(ctx.options.workingDirectory, state.id);
5877
+ if (specPath) {
5878
+ return {
5879
+ output: chalk9__default.default.cyan("\u{1F4CB} \u89C4\u683C\u6587\u4EF6\u5F85\u786E\u8BA4") + chalk9__default.default.gray(`
5880
+
5881
+ \u89C4\u683C\u6587\u4EF6: ${specPath}`) + chalk9__default.default.gray(`
5882
+
5883
+ \u8BF7\u68C0\u67E5\u89C4\u683C\u6587\u4EF6\u540E\u786E\u8BA4:`) + chalk9__default.default.white("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C") + chalk9__default.default.white("\n /opsx:rollback explore - \u91CD\u65B0\u62C6\u5206")
5884
+ };
5885
+ }
5361
5886
  if (pendingPoint) {
5362
5887
  return {
5363
5888
  output: chalk9__default.default.cyan("\u5F85\u786E\u8BA4\u7684\u68C0\u67E5\u70B9:") + chalk9__default.default.white(`
@@ -5379,12 +5904,54 @@ ${generateConfirmationPrompt(pendingPoint)}`) + chalk9__default.default.gray(`
5379
5904
  }
5380
5905
  const comment = args.slice(1).join(" ");
5381
5906
  workflow.confirm(type, comment);
5382
- return {
5383
- output: chalk9__default.default.green("\u2713 \u5DF2\u786E\u8BA4") + chalk9__default.default.white(`
5907
+ const lines = [];
5908
+ lines.push(chalk9__default.default.green("\u2713 \u5DF2\u786E\u8BA4"));
5909
+ lines.push(chalk9__default.default.white(`
5910
+ ${point.name}`));
5911
+ if (comment) {
5912
+ lines.push(chalk9__default.default.gray(`
5913
+ \u5907\u6CE8: ${comment}`));
5914
+ }
5915
+ if (type === "spec-review") {
5916
+ const allowed = workflow.getAllowedTransitions();
5917
+ if (allowed.length > 0) {
5918
+ const nextStep = allowed[0];
5919
+ try {
5920
+ const transition = await workflow.transition(nextStep);
5921
+ lines.push("");
5922
+ lines.push(chalk9__default.default.cyan(`\u2713 \u5DF2\u81EA\u52A8\u8FDB\u5165 ${nextStep} \u9636\u6BB5`));
5923
+ lines.push(chalk9__default.default.gray(`\u8F6C\u6362: ${transition.from} \u2192 ${transition.to}`));
5924
+ if (nextStep === "new") {
5925
+ lines.push(chalk9__default.default.yellow("\n\u4E0B\u4E00\u6B65: \u8BBE\u8BA1\u65B9\u6848"));
5926
+ lines.push(chalk9__default.default.gray(" \u53EF\u8C03\u7528 $architect \u6216 $frontend-dev \u8FDB\u884C\u8BBE\u8BA1"));
5927
+ } else if (nextStep === "apply") {
5928
+ lines.push(chalk9__default.default.yellow("\n\u4E0B\u4E00\u6B65: \u6267\u884C\u53D8\u66F4"));
5929
+ lines.push(chalk9__default.default.gray(" \u53EF\u8C03\u7528 $frontend-dev \u6267\u884C\u4EE3\u7801\u4FEE\u6539"));
5930
+ }
5931
+ return { output: lines.join("\n") };
5932
+ } catch (e) {
5933
+ if (e instanceof ConfirmationRequiredError) {
5934
+ lines.push(chalk9__default.default.yellow("\n\u26A0 \u8FD8\u9700\u8981\u786E\u8BA4:") + chalk9__default.default.white(`
5935
+ ${generateConfirmationPrompt(e.point)}`) + chalk9__default.default.cyan(`
5384
5936
 
5385
- ${point.name}`) + (comment ? chalk9__default.default.gray(`
5386
- \u5907\u6CE8: ${comment}`) : "") + chalk9__default.default.yellow("\n\n\u4F7F\u7528 /opsx:next \u7EE7\u7EED\u4E0B\u4E00\u9636\u6BB5")
5387
- };
5937
+ \u4F7F\u7528 /opsx:confirm ${e.point.type}`));
5938
+ return { output: lines.join("\n") };
5939
+ }
5940
+ throw e;
5941
+ }
5942
+ }
5943
+ }
5944
+ lines.push(chalk9__default.default.yellow("\n\u4F7F\u7528 /opsx:next \u7EE7\u7EED\u4E0B\u4E00\u9636\u6BB5"));
5945
+ return { output: lines.join("\n") };
5946
+ }
5947
+ async function checkPendingSpec(workingDirectory, changeId) {
5948
+ const specPath = path6__namespace.join(workingDirectory, "openspec", "changes", `${changeId}-spec.md`);
5949
+ try {
5950
+ await fs9__namespace.promises.access(specPath);
5951
+ return specPath;
5952
+ } catch {
5953
+ return null;
5954
+ }
5388
5955
  }
5389
5956
  async function handleNext(workflow, args, ctx) {
5390
5957
  const state = workflow.getState();
@@ -5420,6 +5987,76 @@ ${generateConfirmationPrompt(e.point)}`) + chalk9__default.default.cyan(`
5420
5987
  throw e;
5421
5988
  }
5422
5989
  }
5990
+ async function handleList(workflow, ctx) {
5991
+ const lines = [];
5992
+ const activeWorkflows = await workflow.getAllActiveWorkflows();
5993
+ const currentState = workflow.getState();
5994
+ if (activeWorkflows.length === 0) {
5995
+ return {
5996
+ output: chalk9__default.default.gray("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9__default.default.yellow("\n\n\u4F7F\u7528 /new <\u9700\u6C42\u63CF\u8FF0> \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
5997
+ };
5998
+ }
5999
+ lines.push(chalk9__default.default.cyan.bold(`\u{1F4CB} \u6D3B\u8DC3\u5DE5\u4F5C\u6D41 (${activeWorkflows.length})`));
6000
+ lines.push("");
6001
+ for (const wf of activeWorkflows) {
6002
+ const isCurrent = currentState?.id === wf.id;
6003
+ const prefix = isCurrent ? chalk9__default.default.green("\u25B6 ") : " ";
6004
+ const title = isCurrent ? chalk9__default.default.white(wf.title) : chalk9__default.default.gray(wf.title);
6005
+ const stepIcon = wf.currentStep === "propose" || wf.currentStep === "explore" ? "\u23F3" : wf.currentStep === "apply" ? "\u{1F527}" : wf.currentStep === "archive" ? "\u{1F4E6}" : "\u{1F4DD}";
6006
+ lines.push(`${prefix}${title}`);
6007
+ lines.push(chalk9__default.default.gray(` ID: ${wf.id}`));
6008
+ lines.push(chalk9__default.default.gray(` \u9636\u6BB5: ${stepIcon} ${wf.currentStep} | \u590D\u6742\u5EA6: ${wf.complexity}/10`));
6009
+ if (isCurrent) {
6010
+ lines.push(chalk9__default.default.green(" (\u5F53\u524D)"));
6011
+ }
6012
+ lines.push("");
6013
+ }
6014
+ if (activeWorkflows.length > 1) {
6015
+ lines.push(chalk9__default.default.yellow("\u5207\u6362\u5DE5\u4F5C\u6D41:"));
6016
+ lines.push(chalk9__default.default.gray(" /opsx:switch <\u53D8\u66F4ID>"));
6017
+ }
6018
+ lines.push(chalk9__default.default.yellow("\n\u64CD\u4F5C\u547D\u4EE4:"));
6019
+ lines.push(chalk9__default.default.gray(" /opsx:status - \u67E5\u770B\u5F53\u524D\u5DE5\u4F5C\u6D41\u8BE6\u60C5"));
6020
+ lines.push(chalk9__default.default.gray(" /opsx:confirm - \u786E\u8BA4\u89C4\u683C"));
6021
+ lines.push(chalk9__default.default.gray(" /opsx:next - \u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5"));
6022
+ lines.push(chalk9__default.default.gray(" /opsx:cancel - \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41"));
6023
+ return { output: lines.join("\n") };
6024
+ }
6025
+ async function handleSwitch(workflow, args, ctx) {
6026
+ const targetId = args[0];
6027
+ if (!targetId) {
6028
+ const activeWorkflows = await workflow.getAllActiveWorkflows();
6029
+ if (activeWorkflows.length === 0) {
6030
+ return {
6031
+ output: chalk9__default.default.gray("\u6CA1\u6709\u53EF\u5207\u6362\u7684\u5DE5\u4F5C\u6D41")
6032
+ };
6033
+ }
6034
+ const lines = [
6035
+ chalk9__default.default.cyan("\u53EF\u7528\u5DE5\u4F5C\u6D41:"),
6036
+ ""
6037
+ ];
6038
+ for (const wf of activeWorkflows) {
6039
+ lines.push(chalk9__default.default.white(` ${wf.id}`) + chalk9__default.default.gray(` - ${wf.title.slice(0, 30)}...`));
6040
+ }
6041
+ lines.push("");
6042
+ lines.push(chalk9__default.default.gray("\u7528\u6CD5: /opsx:switch <\u53D8\u66F4ID>"));
6043
+ return { output: lines.join("\n") };
6044
+ }
6045
+ const success = await workflow.switchTo(targetId);
6046
+ if (success) {
6047
+ const state = workflow.getState();
6048
+ return {
6049
+ output: chalk9__default.default.green(`\u2713 \u5DF2\u5207\u6362\u5230\u5DE5\u4F5C\u6D41: ${targetId}`) + chalk9__default.default.gray(`
6050
+
6051
+ \u9700\u6C42: ${state?.title}`) + chalk9__default.default.cyan(`
6052
+ \u5F53\u524D\u9636\u6BB5: ${state?.currentStep}`) + chalk9__default.default.yellow("\n\n\u4F7F\u7528 /opsx:status \u67E5\u770B\u8BE6\u60C5")
6053
+ };
6054
+ } else {
6055
+ return {
6056
+ output: chalk9__default.default.red(`\u5207\u6362\u5931\u8D25: \u627E\u4E0D\u5230\u5DE5\u4F5C\u6D41 ${targetId}`) + chalk9__default.default.gray("\n\u4F7F\u7528 /opsx:list \u67E5\u770B\u6240\u6709\u6D3B\u8DC3\u5DE5\u4F5C\u6D41")
6057
+ };
6058
+ }
6059
+ }
5423
6060
 
5424
6061
  // src/commands/runner.ts
5425
6062
  function getVersion() {
@@ -5602,7 +6239,11 @@ var ALLOWED_COMMANDS_WITHOUT_WORKFLOW = [
5602
6239
  "update",
5603
6240
  "u",
5604
6241
  "version",
5605
- "v"
6242
+ "v",
6243
+ // OpenSpec 工作流管理命令(始终允许)
6244
+ "opsx:status",
6245
+ "opsx:cancel",
6246
+ "opsx:rollback"
5606
6247
  ];
5607
6248
  var STAGE_PERMISSIONS = {
5608
6249
  "explore": {
@@ -5675,6 +6316,12 @@ var CommandExecutor = class {
5675
6316
  checkWorkflowPermission(command, ctx) {
5676
6317
  const workflowEngine = ctx.workflowEngine;
5677
6318
  const workflowState = workflowEngine?.getState();
6319
+ if (command.type === "slash" /* SLASH */) {
6320
+ const cmd = command.command?.toLowerCase();
6321
+ if (cmd?.startsWith("opsx:")) {
6322
+ return { allowed: true };
6323
+ }
6324
+ }
5678
6325
  if (!workflowState) {
5679
6326
  if (command.type === "slash" /* SLASH */) {
5680
6327
  const cmd = command.command?.toLowerCase();