@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/CHANGELOG.md +29 -0
- package/dist/cli/index.js +250 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.mts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +250 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +250 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## v1.0.7 (2026-03-22)
|
|
9
|
+
|
|
10
|
+
**新增功能**
|
|
11
|
+
|
|
12
|
+
- ✨ 多工作流管理 - 支持同时管理多个活跃变更
|
|
13
|
+
- 📋 `/opsx:list` - 列出所有活跃工作流
|
|
14
|
+
- 🔄 `/opsx:switch <变更ID>` - 切换工作流
|
|
15
|
+
|
|
16
|
+
**改进**
|
|
17
|
+
|
|
18
|
+
- 🔧 状态文件独立存储 - `.workflow-states/{changeId}.json`
|
|
19
|
+
- 🔧 自动迁移旧格式状态文件
|
|
20
|
+
- 🔧 简化工作流切换逻辑
|
|
21
|
+
|
|
22
|
+
## v1.0.6 (2026-03-22)
|
|
23
|
+
|
|
24
|
+
**修复问题**
|
|
25
|
+
|
|
26
|
+
- 🐛 修复 `/opsx:*` 命令无法识别的问题
|
|
27
|
+
- 🔧 `opsx:*` 命令直接通过解析器验证,无需预定义列表
|
|
28
|
+
- 🔧 `opsx:*` 命令始终允许执行(工作流管理命令)
|
|
29
|
+
|
|
30
|
+
## v1.0.5 (2026-03-22)
|
|
31
|
+
|
|
32
|
+
**修复问题**
|
|
33
|
+
|
|
34
|
+
- 🐛 修复交互问题 - 移除编号选项,改为直接显示可用命令
|
|
35
|
+
- 🎨 改进工作流状态显示 - 显示进度条和详细信息
|
|
36
|
+
|
|
8
37
|
## v1.0.4 (2026-03-22)
|
|
9
38
|
|
|
10
39
|
**新增功能**
|
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
|
|
143
|
+
(cmd) => cmd === command
|
|
113
144
|
);
|
|
114
145
|
if (!isValidCommand) {
|
|
115
146
|
return { success: false, error: `\u672A\u77E5\u547D\u4EE4: /${command}` };
|
|
@@ -3799,6 +3830,92 @@ var WorkflowEngine = class {
|
|
|
3799
3830
|
getState() {
|
|
3800
3831
|
return this.state;
|
|
3801
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
|
+
}
|
|
3802
3919
|
/**
|
|
3803
3920
|
* 获取允许的下一步
|
|
3804
3921
|
*/
|
|
@@ -3879,32 +3996,64 @@ var WorkflowEngine = class {
|
|
|
3879
3996
|
const changesDir = path6__namespace.join(this.openspecPath, "changes");
|
|
3880
3997
|
const archiveDir = path6__namespace.join(changesDir, "archive");
|
|
3881
3998
|
const specDir = path6__namespace.join(this.openspecPath, "spec");
|
|
3999
|
+
const statesDir = path6__namespace.join(this.openspecPath, ".workflow-states");
|
|
3882
4000
|
await fs6__namespace.mkdir(archiveDir, { recursive: true });
|
|
3883
4001
|
await fs6__namespace.mkdir(specDir, { recursive: true });
|
|
4002
|
+
await fs6__namespace.mkdir(statesDir, { recursive: true });
|
|
3884
4003
|
}
|
|
3885
4004
|
async restoreState() {
|
|
3886
|
-
const
|
|
4005
|
+
const activePath = path6__namespace.join(this.openspecPath, ".workflow-active.json");
|
|
4006
|
+
let activeId = null;
|
|
3887
4007
|
try {
|
|
3888
|
-
const
|
|
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");
|
|
3889
4030
|
this.state = JSON.parse(content, (key, value) => {
|
|
3890
4031
|
if (key.endsWith("At") && typeof value === "string") {
|
|
3891
4032
|
return new Date(value);
|
|
3892
4033
|
}
|
|
3893
4034
|
return value;
|
|
3894
4035
|
});
|
|
4036
|
+
if (this.state) {
|
|
4037
|
+
await this.saveState();
|
|
4038
|
+
await fs6__namespace.unlink(oldStatePath).catch(() => {
|
|
4039
|
+
});
|
|
4040
|
+
}
|
|
3895
4041
|
} catch (e) {
|
|
3896
4042
|
const err = e;
|
|
3897
4043
|
if (err.code !== "ENOENT") {
|
|
3898
4044
|
console.warn("\u8B66\u544A: \u5DE5\u4F5C\u6D41\u72B6\u6001\u6587\u4EF6\u5DF2\u635F\u574F\uFF0C\u5C06\u91CD\u65B0\u5F00\u59CB");
|
|
3899
|
-
await fs6__namespace.unlink(statePath).catch(() => {
|
|
3900
|
-
});
|
|
3901
4045
|
}
|
|
3902
4046
|
this.state = null;
|
|
3903
4047
|
}
|
|
3904
4048
|
}
|
|
3905
4049
|
async saveState() {
|
|
3906
|
-
|
|
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`);
|
|
3907
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));
|
|
3908
4057
|
}
|
|
3909
4058
|
async restoreSnapshots() {
|
|
3910
4059
|
const snapshotsPath = path6__namespace.join(this.openspecPath, ".workflow-snapshots.json");
|
|
@@ -4052,10 +4201,15 @@ async function handleNew(args, ctx) {
|
|
|
4052
4201
|
}
|
|
4053
4202
|
}
|
|
4054
4203
|
return {
|
|
4055
|
-
output: chalk9__default.default.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9__default.default.
|
|
4204
|
+
output: chalk9__default.default.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9__default.default.white(`
|
|
4056
4205
|
|
|
4057
|
-
\
|
|
4058
|
-
\
|
|
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(`
|
|
4208
|
+
|
|
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")
|
|
4059
4213
|
};
|
|
4060
4214
|
}
|
|
4061
4215
|
}
|
|
@@ -5354,9 +5508,13 @@ async function handleOpsx(command, args, ctx) {
|
|
|
5354
5508
|
return handleAutoSchedule(args);
|
|
5355
5509
|
case "test":
|
|
5356
5510
|
return handleRegressionTest(ctx);
|
|
5511
|
+
case "list":
|
|
5512
|
+
return handleList(workflow);
|
|
5513
|
+
case "switch":
|
|
5514
|
+
return handleSwitch(workflow, args);
|
|
5357
5515
|
default:
|
|
5358
5516
|
return {
|
|
5359
|
-
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")
|
|
5360
5518
|
};
|
|
5361
5519
|
}
|
|
5362
5520
|
}
|
|
@@ -5829,6 +5987,76 @@ ${generateConfirmationPrompt(e.point)}`) + chalk9__default.default.cyan(`
|
|
|
5829
5987
|
throw e;
|
|
5830
5988
|
}
|
|
5831
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
|
+
}
|
|
5832
6060
|
|
|
5833
6061
|
// src/commands/runner.ts
|
|
5834
6062
|
function getVersion() {
|
|
@@ -6011,7 +6239,11 @@ var ALLOWED_COMMANDS_WITHOUT_WORKFLOW = [
|
|
|
6011
6239
|
"update",
|
|
6012
6240
|
"u",
|
|
6013
6241
|
"version",
|
|
6014
|
-
"v"
|
|
6242
|
+
"v",
|
|
6243
|
+
// OpenSpec 工作流管理命令(始终允许)
|
|
6244
|
+
"opsx:status",
|
|
6245
|
+
"opsx:cancel",
|
|
6246
|
+
"opsx:rollback"
|
|
6015
6247
|
];
|
|
6016
6248
|
var STAGE_PERMISSIONS = {
|
|
6017
6249
|
"explore": {
|
|
@@ -6084,6 +6316,12 @@ var CommandExecutor = class {
|
|
|
6084
6316
|
checkWorkflowPermission(command, ctx) {
|
|
6085
6317
|
const workflowEngine = ctx.workflowEngine;
|
|
6086
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
|
+
}
|
|
6087
6325
|
if (!workflowState) {
|
|
6088
6326
|
if (command.type === "slash" /* SLASH */) {
|
|
6089
6327
|
const cmd = command.command?.toLowerCase();
|