@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/CHANGELOG.md +44 -0
- package/dist/cli/index.js +743 -96
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.mts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +743 -96
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +743 -96
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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\
|
|
3609
|
-
description: "\u89C4\
|
|
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
|
-
|
|
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
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
4265
|
+
const activePath = path5.join(this.openspecPath, ".workflow-active.json");
|
|
4266
|
+
let activeId = null;
|
|
4154
4267
|
try {
|
|
4155
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
6712
|
-
|
|
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
|
-
\
|
|
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
|
-
\
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
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
|
|
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
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
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
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
"",
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
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
|
-
|
|
6784
|
-
|
|
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
|
|
7261
|
+
return {};
|
|
6819
7262
|
}
|
|
6820
7263
|
const content = await fs4.readFile(agentsPath, "utf-8");
|
|
6821
|
-
return parseAgentsMd(content
|
|
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
|
|
6846
|
-
const context = {
|
|
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
|
|
6866
|
-
const
|
|
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
|
-
|
|
7434
|
-
|
|
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.
|
|
7437
|
-
|
|
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();
|