@nick848/sf-cli 1.0.4 → 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.d.mts CHANGED
@@ -1588,6 +1588,18 @@ declare class WorkflowEngine {
1588
1588
  * 获取当前状态
1589
1589
  */
1590
1590
  getState(): WorkflowState | null;
1591
+ /**
1592
+ * 获取所有活跃工作流
1593
+ */
1594
+ getAllActiveWorkflows(): Promise<WorkflowState[]>;
1595
+ /**
1596
+ * 解析变更记录
1597
+ */
1598
+ private parseChangeRecord;
1599
+ /**
1600
+ * 切换到指定工作流
1601
+ */
1602
+ switchTo(changeId: string): Promise<boolean>;
1591
1603
  /**
1592
1604
  * 获取允许的下一步
1593
1605
  */
package/dist/index.d.ts CHANGED
@@ -1588,6 +1588,18 @@ declare class WorkflowEngine {
1588
1588
  * 获取当前状态
1589
1589
  */
1590
1590
  getState(): WorkflowState | null;
1591
+ /**
1592
+ * 获取所有活跃工作流
1593
+ */
1594
+ getAllActiveWorkflows(): Promise<WorkflowState[]>;
1595
+ /**
1596
+ * 解析变更记录
1597
+ */
1598
+ private parseChangeRecord;
1599
+ /**
1600
+ * 切换到指定工作流
1601
+ */
1602
+ switchTo(changeId: string): Promise<boolean>;
1591
1603
  /**
1592
1604
  * 获取允许的下一步
1593
1605
  */
package/dist/index.js CHANGED
@@ -4114,6 +4114,92 @@ var WorkflowEngine = class {
4114
4114
  getState() {
4115
4115
  return this.state;
4116
4116
  }
4117
+ /**
4118
+ * 获取所有活跃工作流
4119
+ */
4120
+ async getAllActiveWorkflows() {
4121
+ const workflows = [];
4122
+ const changesDir = path4__namespace.join(this.openspecPath, "changes");
4123
+ try {
4124
+ const files = await fs4__namespace.readdir(changesDir);
4125
+ for (const file of files) {
4126
+ if (!file.endsWith(".md") || file.includes("-spec.md")) continue;
4127
+ const filePath = path4__namespace.join(changesDir, file);
4128
+ const content = await fs4__namespace.readFile(filePath, "utf-8");
4129
+ const state = this.parseChangeRecord(content);
4130
+ if (state && state.status === "running") {
4131
+ workflows.push(state);
4132
+ }
4133
+ }
4134
+ } catch {
4135
+ }
4136
+ if (this.state && !workflows.find((w) => w.id === this.state?.id)) {
4137
+ workflows.unshift(this.state);
4138
+ }
4139
+ return workflows;
4140
+ }
4141
+ /**
4142
+ * 解析变更记录
4143
+ */
4144
+ parseChangeRecord(content) {
4145
+ try {
4146
+ const idMatch = content.match(/^id:\s*(.+)$/m);
4147
+ const titleMatch = content.match(/^title:\s*(.+)$/m);
4148
+ const statusMatch = content.match(/^status:\s*(.+)$/m);
4149
+ const complexityMatch = content.match(/^complexity:\s*(\d+)/m);
4150
+ const workflowMatch = content.match(/^workflow:\s*(.+)$/m);
4151
+ const requirementMatch = content.match(/## 变更概述\s*\n+([\s\S]+?)(?=\n##|$)/);
4152
+ if (!idMatch || !titleMatch) return null;
4153
+ return {
4154
+ id: idMatch[1].trim(),
4155
+ title: titleMatch[1].trim(),
4156
+ status: statusMatch?.[1].trim() || "running",
4157
+ requirement: requirementMatch?.[1].trim() || "",
4158
+ complexity: parseInt(complexityMatch?.[1] || "5", 10),
4159
+ type: workflowMatch?.[1].trim() || "simple",
4160
+ currentStep: "propose",
4161
+ // 默认值,实际值需要从状态文件读取
4162
+ steps: [],
4163
+ artifacts: [],
4164
+ createdAt: /* @__PURE__ */ new Date()
4165
+ };
4166
+ } catch {
4167
+ return null;
4168
+ }
4169
+ }
4170
+ /**
4171
+ * 切换到指定工作流
4172
+ */
4173
+ async switchTo(changeId) {
4174
+ if (this.state) {
4175
+ await this.saveState();
4176
+ }
4177
+ const statePath = path4__namespace.join(this.openspecPath, ".workflow-states", `${changeId}.json`);
4178
+ try {
4179
+ const content = await fs4__namespace.readFile(statePath, "utf-8");
4180
+ this.state = JSON.parse(content, (key, value) => {
4181
+ if (key.endsWith("At") && typeof value === "string") {
4182
+ return new Date(value);
4183
+ }
4184
+ return value;
4185
+ });
4186
+ await this.restoreSnapshots();
4187
+ return true;
4188
+ } catch {
4189
+ const changesDir = path4__namespace.join(this.openspecPath, "changes");
4190
+ const changeFile = path4__namespace.join(changesDir, `${changeId}.md`);
4191
+ try {
4192
+ const content = await fs4__namespace.readFile(changeFile, "utf-8");
4193
+ const parsed = this.parseChangeRecord(content);
4194
+ if (parsed && parsed.status === "running") {
4195
+ this.state = parsed;
4196
+ return true;
4197
+ }
4198
+ } catch {
4199
+ }
4200
+ return false;
4201
+ }
4202
+ }
4117
4203
  /**
4118
4204
  * 获取允许的下一步
4119
4205
  */
@@ -4194,32 +4280,64 @@ var WorkflowEngine = class {
4194
4280
  const changesDir = path4__namespace.join(this.openspecPath, "changes");
4195
4281
  const archiveDir = path4__namespace.join(changesDir, "archive");
4196
4282
  const specDir = path4__namespace.join(this.openspecPath, "spec");
4283
+ const statesDir = path4__namespace.join(this.openspecPath, ".workflow-states");
4197
4284
  await fs4__namespace.mkdir(archiveDir, { recursive: true });
4198
4285
  await fs4__namespace.mkdir(specDir, { recursive: true });
4286
+ await fs4__namespace.mkdir(statesDir, { recursive: true });
4199
4287
  }
4200
4288
  async restoreState() {
4201
- const statePath = path4__namespace.join(this.openspecPath, ".workflow-state.json");
4289
+ const activePath = path4__namespace.join(this.openspecPath, ".workflow-active.json");
4290
+ let activeId = null;
4202
4291
  try {
4203
- const content = await fs4__namespace.readFile(statePath, "utf-8");
4292
+ const activeContent = await fs4__namespace.readFile(activePath, "utf-8");
4293
+ const activeData = JSON.parse(activeContent);
4294
+ activeId = activeData.activeId;
4295
+ } catch {
4296
+ }
4297
+ if (activeId) {
4298
+ const statePath = path4__namespace.join(this.openspecPath, ".workflow-states", `${activeId}.json`);
4299
+ try {
4300
+ const content = await fs4__namespace.readFile(statePath, "utf-8");
4301
+ this.state = JSON.parse(content, (key, value) => {
4302
+ if (key.endsWith("At") && typeof value === "string") {
4303
+ return new Date(value);
4304
+ }
4305
+ return value;
4306
+ });
4307
+ return;
4308
+ } catch {
4309
+ }
4310
+ }
4311
+ const oldStatePath = path4__namespace.join(this.openspecPath, ".workflow-state.json");
4312
+ try {
4313
+ const content = await fs4__namespace.readFile(oldStatePath, "utf-8");
4204
4314
  this.state = JSON.parse(content, (key, value) => {
4205
4315
  if (key.endsWith("At") && typeof value === "string") {
4206
4316
  return new Date(value);
4207
4317
  }
4208
4318
  return value;
4209
4319
  });
4320
+ if (this.state) {
4321
+ await this.saveState();
4322
+ await fs4__namespace.unlink(oldStatePath).catch(() => {
4323
+ });
4324
+ }
4210
4325
  } catch (e) {
4211
4326
  const err = e;
4212
4327
  if (err.code !== "ENOENT") {
4213
4328
  console.warn("\u8B66\u544A: \u5DE5\u4F5C\u6D41\u72B6\u6001\u6587\u4EF6\u5DF2\u635F\u574F\uFF0C\u5C06\u91CD\u65B0\u5F00\u59CB");
4214
- await fs4__namespace.unlink(statePath).catch(() => {
4215
- });
4216
4329
  }
4217
4330
  this.state = null;
4218
4331
  }
4219
4332
  }
4220
4333
  async saveState() {
4221
- const statePath = path4__namespace.join(this.openspecPath, ".workflow-state.json");
4334
+ if (!this.state) return;
4335
+ const statesDir = path4__namespace.join(this.openspecPath, ".workflow-states");
4336
+ await fs4__namespace.mkdir(statesDir, { recursive: true });
4337
+ const statePath = path4__namespace.join(statesDir, `${this.state.id}.json`);
4222
4338
  await fs4__namespace.writeFile(statePath, JSON.stringify(this.state, null, 2));
4339
+ const activePath = path4__namespace.join(this.openspecPath, ".workflow-active.json");
4340
+ await fs4__namespace.writeFile(activePath, JSON.stringify({ activeId: this.state.id }, null, 2));
4223
4341
  }
4224
4342
  async restoreSnapshots() {
4225
4343
  const snapshotsPath = path4__namespace.join(this.openspecPath, ".workflow-snapshots.json");
@@ -5590,18 +5708,38 @@ var CommandType = /* @__PURE__ */ ((CommandType3) => {
5590
5708
  var CommandParser = class {
5591
5709
  slashCommands = [
5592
5710
  "help",
5711
+ "h",
5712
+ "?",
5593
5713
  "init",
5714
+ "i",
5594
5715
  "new",
5716
+ "n",
5595
5717
  "model",
5718
+ "m",
5596
5719
  "update",
5720
+ "u",
5597
5721
  "clear",
5722
+ "c",
5598
5723
  "exit",
5724
+ "e",
5725
+ "q",
5726
+ "quit",
5727
+ "version",
5728
+ "v",
5729
+ // OpenSpec 工作流命令
5599
5730
  "opsx:explore",
5600
5731
  "opsx:new",
5601
5732
  "opsx:continue",
5602
5733
  "opsx:apply",
5603
5734
  "opsx:archive",
5604
- "opsx:propose"
5735
+ "opsx:propose",
5736
+ "opsx:status",
5737
+ "opsx:cancel",
5738
+ "opsx:rollback",
5739
+ "opsx:confirm",
5740
+ "opsx:next",
5741
+ "opsx:auto",
5742
+ "opsx:test"
5605
5743
  ];
5606
5744
  builtInAgents = [
5607
5745
  "frontend-dev",
@@ -5650,8 +5788,19 @@ var CommandParser = class {
5650
5788
  if (!command) {
5651
5789
  return { success: false, error: "\u65E0\u6548\u7684\u547D\u4EE4\u683C\u5F0F" };
5652
5790
  }
5791
+ if (command.startsWith("opsx:")) {
5792
+ return {
5793
+ success: true,
5794
+ command: {
5795
+ type: "slash" /* SLASH */,
5796
+ raw: input,
5797
+ command,
5798
+ args
5799
+ }
5800
+ };
5801
+ }
5653
5802
  const isValidCommand = this.slashCommands.some(
5654
- (cmd) => cmd === command || cmd.startsWith(command)
5803
+ (cmd) => cmd === command
5655
5804
  );
5656
5805
  if (!isValidCommand) {
5657
5806
  return { success: false, error: `\u672A\u77E5\u547D\u4EE4: /${command}` };
@@ -6769,10 +6918,15 @@ async function handleNew(args, ctx) {
6769
6918
  }
6770
6919
  }
6771
6920
  return {
6772
- output: chalk9__default.default.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9__default.default.gray(`
6921
+ output: chalk9__default.default.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9__default.default.white(`
6773
6922
 
6774
- \u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9__default.default.gray(`
6775
- \u5F53\u524D\u9636\u6BB5: ${existingState.currentStep}`) + chalk9__default.default.gray("\n\n\u9009\u9879:") + chalk9__default.default.gray("\n 1. \u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:status") + chalk9__default.default.gray("\n 2. \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:cancel")
6923
+ \u{1F4CB} ${existingState.title || existingState.id}`) + chalk9__default.default.gray(`
6924
+ \u7C7B\u578B: ${existingState.type} | \u590D\u6742\u5EA6: ${existingState.complexity}/10`) + chalk9__default.default.cyan(`
6925
+
6926
+ \u8FDB\u5EA6: ${existingState.steps.map((s) => {
6927
+ const icon = s.status === "completed" ? "\u2713" : s.status === "running" ? "\u25CF" : "\u25CB";
6928
+ return `${icon} ${s.step}`;
6929
+ }).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")
6776
6930
  };
6777
6931
  }
6778
6932
  }
@@ -7429,9 +7583,13 @@ async function handleOpsx(command, args, ctx) {
7429
7583
  return handleAutoSchedule(args);
7430
7584
  case "test":
7431
7585
  return handleRegressionTest(ctx);
7586
+ case "list":
7587
+ return handleList(workflow);
7588
+ case "switch":
7589
+ return handleSwitch(workflow, args);
7432
7590
  default:
7433
7591
  return {
7434
- output: chalk9__default.default.red(`\u672A\u77E5\u7684OpenSpec\u547D\u4EE4: /${command}`)
7592
+ 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")
7435
7593
  };
7436
7594
  }
7437
7595
  }
@@ -7904,6 +8062,76 @@ ${generateConfirmationPrompt(e.point)}`) + chalk9__default.default.cyan(`
7904
8062
  throw e;
7905
8063
  }
7906
8064
  }
8065
+ async function handleList(workflow, ctx) {
8066
+ const lines = [];
8067
+ const activeWorkflows = await workflow.getAllActiveWorkflows();
8068
+ const currentState = workflow.getState();
8069
+ if (activeWorkflows.length === 0) {
8070
+ return {
8071
+ 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")
8072
+ };
8073
+ }
8074
+ lines.push(chalk9__default.default.cyan.bold(`\u{1F4CB} \u6D3B\u8DC3\u5DE5\u4F5C\u6D41 (${activeWorkflows.length})`));
8075
+ lines.push("");
8076
+ for (const wf of activeWorkflows) {
8077
+ const isCurrent = currentState?.id === wf.id;
8078
+ const prefix = isCurrent ? chalk9__default.default.green("\u25B6 ") : " ";
8079
+ const title = isCurrent ? chalk9__default.default.white(wf.title) : chalk9__default.default.gray(wf.title);
8080
+ const stepIcon = wf.currentStep === "propose" || wf.currentStep === "explore" ? "\u23F3" : wf.currentStep === "apply" ? "\u{1F527}" : wf.currentStep === "archive" ? "\u{1F4E6}" : "\u{1F4DD}";
8081
+ lines.push(`${prefix}${title}`);
8082
+ lines.push(chalk9__default.default.gray(` ID: ${wf.id}`));
8083
+ lines.push(chalk9__default.default.gray(` \u9636\u6BB5: ${stepIcon} ${wf.currentStep} | \u590D\u6742\u5EA6: ${wf.complexity}/10`));
8084
+ if (isCurrent) {
8085
+ lines.push(chalk9__default.default.green(" (\u5F53\u524D)"));
8086
+ }
8087
+ lines.push("");
8088
+ }
8089
+ if (activeWorkflows.length > 1) {
8090
+ lines.push(chalk9__default.default.yellow("\u5207\u6362\u5DE5\u4F5C\u6D41:"));
8091
+ lines.push(chalk9__default.default.gray(" /opsx:switch <\u53D8\u66F4ID>"));
8092
+ }
8093
+ lines.push(chalk9__default.default.yellow("\n\u64CD\u4F5C\u547D\u4EE4:"));
8094
+ lines.push(chalk9__default.default.gray(" /opsx:status - \u67E5\u770B\u5F53\u524D\u5DE5\u4F5C\u6D41\u8BE6\u60C5"));
8095
+ lines.push(chalk9__default.default.gray(" /opsx:confirm - \u786E\u8BA4\u89C4\u683C"));
8096
+ lines.push(chalk9__default.default.gray(" /opsx:next - \u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5"));
8097
+ lines.push(chalk9__default.default.gray(" /opsx:cancel - \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41"));
8098
+ return { output: lines.join("\n") };
8099
+ }
8100
+ async function handleSwitch(workflow, args, ctx) {
8101
+ const targetId = args[0];
8102
+ if (!targetId) {
8103
+ const activeWorkflows = await workflow.getAllActiveWorkflows();
8104
+ if (activeWorkflows.length === 0) {
8105
+ return {
8106
+ output: chalk9__default.default.gray("\u6CA1\u6709\u53EF\u5207\u6362\u7684\u5DE5\u4F5C\u6D41")
8107
+ };
8108
+ }
8109
+ const lines = [
8110
+ chalk9__default.default.cyan("\u53EF\u7528\u5DE5\u4F5C\u6D41:"),
8111
+ ""
8112
+ ];
8113
+ for (const wf of activeWorkflows) {
8114
+ lines.push(chalk9__default.default.white(` ${wf.id}`) + chalk9__default.default.gray(` - ${wf.title.slice(0, 30)}...`));
8115
+ }
8116
+ lines.push("");
8117
+ lines.push(chalk9__default.default.gray("\u7528\u6CD5: /opsx:switch <\u53D8\u66F4ID>"));
8118
+ return { output: lines.join("\n") };
8119
+ }
8120
+ const success = await workflow.switchTo(targetId);
8121
+ if (success) {
8122
+ const state = workflow.getState();
8123
+ return {
8124
+ output: chalk9__default.default.green(`\u2713 \u5DF2\u5207\u6362\u5230\u5DE5\u4F5C\u6D41: ${targetId}`) + chalk9__default.default.gray(`
8125
+
8126
+ \u9700\u6C42: ${state?.title}`) + chalk9__default.default.cyan(`
8127
+ \u5F53\u524D\u9636\u6BB5: ${state?.currentStep}`) + chalk9__default.default.yellow("\n\n\u4F7F\u7528 /opsx:status \u67E5\u770B\u8BE6\u60C5")
8128
+ };
8129
+ } else {
8130
+ return {
8131
+ 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")
8132
+ };
8133
+ }
8134
+ }
7907
8135
 
7908
8136
  // src/commands/runner.ts
7909
8137
  function getVersion() {
@@ -8086,7 +8314,11 @@ var ALLOWED_COMMANDS_WITHOUT_WORKFLOW = [
8086
8314
  "update",
8087
8315
  "u",
8088
8316
  "version",
8089
- "v"
8317
+ "v",
8318
+ // OpenSpec 工作流管理命令(始终允许)
8319
+ "opsx:status",
8320
+ "opsx:cancel",
8321
+ "opsx:rollback"
8090
8322
  ];
8091
8323
  var STAGE_PERMISSIONS = {
8092
8324
  "explore": {
@@ -8159,6 +8391,12 @@ var CommandExecutor = class {
8159
8391
  checkWorkflowPermission(command, ctx) {
8160
8392
  const workflowEngine = ctx.workflowEngine;
8161
8393
  const workflowState = workflowEngine?.getState();
8394
+ if (command.type === "slash" /* SLASH */) {
8395
+ const cmd = command.command?.toLowerCase();
8396
+ if (cmd?.startsWith("opsx:")) {
8397
+ return { allowed: true };
8398
+ }
8399
+ }
8162
8400
  if (!workflowState) {
8163
8401
  if (command.type === "slash" /* SLASH */) {
8164
8402
  const cmd = command.command?.toLowerCase();