@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.js
CHANGED
|
@@ -3629,12 +3629,20 @@ function getScheduleRuleDescription(step) {
|
|
|
3629
3629
|
var DEFAULT_CONFIRMATION_POINTS = [
|
|
3630
3630
|
{
|
|
3631
3631
|
type: "spec-review",
|
|
3632
|
-
name: "\u89C4\
|
|
3633
|
-
description: "\u89C4\
|
|
3632
|
+
name: "\u89C4\u683C\u786E\u8BA4",
|
|
3633
|
+
description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
|
|
3634
3634
|
triggerStep: "explore",
|
|
3635
3635
|
targetStep: "new",
|
|
3636
3636
|
required: true
|
|
3637
3637
|
},
|
|
3638
|
+
{
|
|
3639
|
+
type: "spec-review",
|
|
3640
|
+
name: "\u89C4\u683C\u786E\u8BA4",
|
|
3641
|
+
description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
|
|
3642
|
+
triggerStep: "propose",
|
|
3643
|
+
targetStep: "apply",
|
|
3644
|
+
required: true
|
|
3645
|
+
},
|
|
3638
3646
|
{
|
|
3639
3647
|
type: "architecture",
|
|
3640
3648
|
name: "\u67B6\u6784\u8C03\u6574\u786E\u8BA4",
|
|
@@ -3670,19 +3678,15 @@ var ConfirmationManager = class {
|
|
|
3670
3678
|
confirmationPoints;
|
|
3671
3679
|
confirmations = /* @__PURE__ */ new Map();
|
|
3672
3680
|
constructor(customPoints) {
|
|
3673
|
-
|
|
3674
|
-
this.confirmationPoints = new Map(points.map((p) => [p.type, p]));
|
|
3681
|
+
this.confirmationPoints = customPoints || DEFAULT_CONFIRMATION_POINTS;
|
|
3675
3682
|
}
|
|
3676
3683
|
/**
|
|
3677
3684
|
* 获取指定阶段的确认点
|
|
3678
3685
|
*/
|
|
3679
3686
|
getConfirmationPointForTransition(from, to) {
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
}
|
|
3684
|
-
}
|
|
3685
|
-
return void 0;
|
|
3687
|
+
return this.confirmationPoints.find(
|
|
3688
|
+
(point) => point.triggerStep === from && point.targetStep === to
|
|
3689
|
+
);
|
|
3686
3690
|
}
|
|
3687
3691
|
/**
|
|
3688
3692
|
* 检查是否需要确认
|
|
@@ -3695,7 +3699,7 @@ var ConfirmationManager = class {
|
|
|
3695
3699
|
* 获取确认点详情
|
|
3696
3700
|
*/
|
|
3697
3701
|
getConfirmationPoint(type) {
|
|
3698
|
-
return this.confirmationPoints.
|
|
3702
|
+
return this.confirmationPoints.find((point) => point.type === type);
|
|
3699
3703
|
}
|
|
3700
3704
|
/**
|
|
3701
3705
|
* 记录确认
|
|
@@ -3739,14 +3743,14 @@ var ConfirmationManager = class {
|
|
|
3739
3743
|
* 获取所有确认点
|
|
3740
3744
|
*/
|
|
3741
3745
|
getAllConfirmationPoints() {
|
|
3742
|
-
return
|
|
3746
|
+
return [...this.confirmationPoints];
|
|
3743
3747
|
}
|
|
3744
3748
|
/**
|
|
3745
3749
|
* 获取指定阶段需要清除的确认点
|
|
3746
3750
|
*/
|
|
3747
3751
|
getConfirmationsToClear(targetStep) {
|
|
3748
3752
|
const types = [];
|
|
3749
|
-
for (const point of this.confirmationPoints
|
|
3753
|
+
for (const point of this.confirmationPoints) {
|
|
3750
3754
|
const stepOrder = ["explore", "new", "continue", "propose", "apply", "archive"];
|
|
3751
3755
|
const targetIndex = stepOrder.indexOf(targetStep);
|
|
3752
3756
|
const triggerIndex = stepOrder.indexOf(point.triggerStep);
|
|
@@ -3869,6 +3873,26 @@ var WorkflowEngine = class {
|
|
|
3869
3873
|
devStandards: this.devStandards
|
|
3870
3874
|
};
|
|
3871
3875
|
}
|
|
3876
|
+
/**
|
|
3877
|
+
* 获取规格文件路径
|
|
3878
|
+
*/
|
|
3879
|
+
getSpecFilePath() {
|
|
3880
|
+
if (!this.state) return null;
|
|
3881
|
+
return path4__namespace.join(this.openspecPath, "changes", `${this.state.id}-spec.md`);
|
|
3882
|
+
}
|
|
3883
|
+
/**
|
|
3884
|
+
* 检查规格文件是否存在
|
|
3885
|
+
*/
|
|
3886
|
+
async hasSpecFile() {
|
|
3887
|
+
const specPath = this.getSpecFilePath();
|
|
3888
|
+
if (!specPath) return false;
|
|
3889
|
+
try {
|
|
3890
|
+
await fs4__namespace.access(specPath);
|
|
3891
|
+
return true;
|
|
3892
|
+
} catch {
|
|
3893
|
+
return false;
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3872
3896
|
/**
|
|
3873
3897
|
* 启动新工作流
|
|
3874
3898
|
*/
|
|
@@ -4090,6 +4114,92 @@ var WorkflowEngine = class {
|
|
|
4090
4114
|
getState() {
|
|
4091
4115
|
return this.state;
|
|
4092
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
|
+
}
|
|
4093
4203
|
/**
|
|
4094
4204
|
* 获取允许的下一步
|
|
4095
4205
|
*/
|
|
@@ -4170,32 +4280,64 @@ var WorkflowEngine = class {
|
|
|
4170
4280
|
const changesDir = path4__namespace.join(this.openspecPath, "changes");
|
|
4171
4281
|
const archiveDir = path4__namespace.join(changesDir, "archive");
|
|
4172
4282
|
const specDir = path4__namespace.join(this.openspecPath, "spec");
|
|
4283
|
+
const statesDir = path4__namespace.join(this.openspecPath, ".workflow-states");
|
|
4173
4284
|
await fs4__namespace.mkdir(archiveDir, { recursive: true });
|
|
4174
4285
|
await fs4__namespace.mkdir(specDir, { recursive: true });
|
|
4286
|
+
await fs4__namespace.mkdir(statesDir, { recursive: true });
|
|
4175
4287
|
}
|
|
4176
4288
|
async restoreState() {
|
|
4177
|
-
const
|
|
4289
|
+
const activePath = path4__namespace.join(this.openspecPath, ".workflow-active.json");
|
|
4290
|
+
let activeId = null;
|
|
4178
4291
|
try {
|
|
4179
|
-
const
|
|
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");
|
|
4180
4314
|
this.state = JSON.parse(content, (key, value) => {
|
|
4181
4315
|
if (key.endsWith("At") && typeof value === "string") {
|
|
4182
4316
|
return new Date(value);
|
|
4183
4317
|
}
|
|
4184
4318
|
return value;
|
|
4185
4319
|
});
|
|
4320
|
+
if (this.state) {
|
|
4321
|
+
await this.saveState();
|
|
4322
|
+
await fs4__namespace.unlink(oldStatePath).catch(() => {
|
|
4323
|
+
});
|
|
4324
|
+
}
|
|
4186
4325
|
} catch (e) {
|
|
4187
4326
|
const err = e;
|
|
4188
4327
|
if (err.code !== "ENOENT") {
|
|
4189
4328
|
console.warn("\u8B66\u544A: \u5DE5\u4F5C\u6D41\u72B6\u6001\u6587\u4EF6\u5DF2\u635F\u574F\uFF0C\u5C06\u91CD\u65B0\u5F00\u59CB");
|
|
4190
|
-
await fs4__namespace.unlink(statePath).catch(() => {
|
|
4191
|
-
});
|
|
4192
4329
|
}
|
|
4193
4330
|
this.state = null;
|
|
4194
4331
|
}
|
|
4195
4332
|
}
|
|
4196
4333
|
async saveState() {
|
|
4197
|
-
|
|
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`);
|
|
4198
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));
|
|
4199
4341
|
}
|
|
4200
4342
|
async restoreSnapshots() {
|
|
4201
4343
|
const snapshotsPath = path4__namespace.join(this.openspecPath, ".workflow-snapshots.json");
|
|
@@ -5566,18 +5708,38 @@ var CommandType = /* @__PURE__ */ ((CommandType3) => {
|
|
|
5566
5708
|
var CommandParser = class {
|
|
5567
5709
|
slashCommands = [
|
|
5568
5710
|
"help",
|
|
5711
|
+
"h",
|
|
5712
|
+
"?",
|
|
5569
5713
|
"init",
|
|
5714
|
+
"i",
|
|
5570
5715
|
"new",
|
|
5716
|
+
"n",
|
|
5571
5717
|
"model",
|
|
5718
|
+
"m",
|
|
5572
5719
|
"update",
|
|
5720
|
+
"u",
|
|
5573
5721
|
"clear",
|
|
5722
|
+
"c",
|
|
5574
5723
|
"exit",
|
|
5724
|
+
"e",
|
|
5725
|
+
"q",
|
|
5726
|
+
"quit",
|
|
5727
|
+
"version",
|
|
5728
|
+
"v",
|
|
5729
|
+
// OpenSpec 工作流命令
|
|
5575
5730
|
"opsx:explore",
|
|
5576
5731
|
"opsx:new",
|
|
5577
5732
|
"opsx:continue",
|
|
5578
5733
|
"opsx:apply",
|
|
5579
5734
|
"opsx:archive",
|
|
5580
|
-
"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"
|
|
5581
5743
|
];
|
|
5582
5744
|
builtInAgents = [
|
|
5583
5745
|
"frontend-dev",
|
|
@@ -5626,8 +5788,19 @@ var CommandParser = class {
|
|
|
5626
5788
|
if (!command) {
|
|
5627
5789
|
return { success: false, error: "\u65E0\u6548\u7684\u547D\u4EE4\u683C\u5F0F" };
|
|
5628
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
|
+
}
|
|
5629
5802
|
const isValidCommand = this.slashCommands.some(
|
|
5630
|
-
(cmd) => cmd === command
|
|
5803
|
+
(cmd) => cmd === command
|
|
5631
5804
|
);
|
|
5632
5805
|
if (!isValidCommand) {
|
|
5633
5806
|
return { success: false, error: `\u672A\u77E5\u547D\u4EE4: /${command}` };
|
|
@@ -6732,16 +6905,28 @@ async function handleNew(args, ctx) {
|
|
|
6732
6905
|
if (workflowEngine) {
|
|
6733
6906
|
const existingState = workflowEngine.getState();
|
|
6734
6907
|
if (existingState && existingState.status === "running") {
|
|
6735
|
-
|
|
6736
|
-
|
|
6908
|
+
if (existingState.currentStep === "explore" || existingState.currentStep === "propose") {
|
|
6909
|
+
const specPath = path4__namespace.join(workingDir, "openspec", "changes", `${existingState.id}-spec.md`);
|
|
6910
|
+
if (fs10__namespace.existsSync(specPath)) {
|
|
6911
|
+
return {
|
|
6912
|
+
output: chalk9__default.default.yellow("\u5F53\u524D\u5DE5\u4F5C\u6D41\u6B63\u5728\u7B49\u5F85\u89C4\u683C\u786E\u8BA4") + chalk9__default.default.gray(`
|
|
6737
6913
|
|
|
6738
6914
|
\u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9__default.default.gray(`
|
|
6739
|
-
\
|
|
6915
|
+
\u53D8\u66F4ID: ${existingState.id}`) + chalk9__default.default.cyan("\n\n\u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210:") + chalk9__default.default.white(`
|
|
6916
|
+
${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")
|
|
6917
|
+
};
|
|
6918
|
+
}
|
|
6919
|
+
}
|
|
6920
|
+
return {
|
|
6921
|
+
output: chalk9__default.default.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9__default.default.white(`
|
|
6740
6922
|
|
|
6741
|
-
\
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
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")
|
|
6745
6930
|
};
|
|
6746
6931
|
}
|
|
6747
6932
|
}
|
|
@@ -6756,6 +6941,7 @@ async function handleNew(args, ctx) {
|
|
|
6756
6941
|
async function newFeature(options, workingDir, workflowEngine) {
|
|
6757
6942
|
const cwd = workingDir || process.cwd();
|
|
6758
6943
|
const { requirement, forceComplexity } = options;
|
|
6944
|
+
const lines = [];
|
|
6759
6945
|
try {
|
|
6760
6946
|
const stats = await fs4__namespace.stat(cwd);
|
|
6761
6947
|
if (!stats.isDirectory()) {
|
|
@@ -6763,51 +6949,283 @@ async function newFeature(options, workingDir, workflowEngine) {
|
|
|
6763
6949
|
output: chalk9__default.default.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
|
|
6764
6950
|
};
|
|
6765
6951
|
}
|
|
6766
|
-
} catch
|
|
6952
|
+
} catch {
|
|
6767
6953
|
return {
|
|
6768
6954
|
output: chalk9__default.default.red(`\u9519\u8BEF: \u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6743\u9650\u8BBF\u95EE ${cwd}`)
|
|
6769
6955
|
};
|
|
6770
6956
|
}
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6957
|
+
lines.push(chalk9__default.default.cyan("\u{1F50D} \u5206\u6790\u9879\u76EE..."));
|
|
6958
|
+
const context = await readProjectContext(cwd);
|
|
6959
|
+
lines.push(chalk9__default.default.gray(` \u9879\u76EE: ${context.name}`));
|
|
6960
|
+
lines.push(chalk9__default.default.gray(` \u7C7B\u578B: ${context.type}`));
|
|
6961
|
+
lines.push(chalk9__default.default.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`));
|
|
6962
|
+
lines.push("");
|
|
6963
|
+
lines.push(chalk9__default.default.cyan("\u{1F4CA} \u5206\u6790\u9700\u6C42\u590D\u6742\u5EA6..."));
|
|
6964
|
+
const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
|
|
6965
|
+
lines.push(chalk9__default.default.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`));
|
|
6966
|
+
lines.push(chalk9__default.default.gray(` \u6D41\u7A0B\u7C7B\u578B: ${analysis.recommendation === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`));
|
|
6967
|
+
for (const factor of analysis.factors) {
|
|
6968
|
+
lines.push(chalk9__default.default.gray(` - ${factor}`));
|
|
6969
|
+
}
|
|
6970
|
+
lines.push("");
|
|
6971
|
+
lines.push(chalk9__default.default.cyan("\u{1F4CB} \u521D\u59CB\u5316\u5DE5\u4F5C\u6D41..."));
|
|
6972
|
+
const workflow = workflowEngine || new WorkflowEngine();
|
|
6973
|
+
if (!workflowEngine) {
|
|
6974
|
+
await workflow.initialize(cwd);
|
|
6975
|
+
}
|
|
6976
|
+
const state = await workflow.start(requirement, analysis.score, {
|
|
6977
|
+
title: extractTitle(requirement)
|
|
6978
|
+
});
|
|
6979
|
+
lines.push(chalk9__default.default.gray(` \u53D8\u66F4ID: ${state.id}`));
|
|
6980
|
+
lines.push(chalk9__default.default.gray(` \u5DE5\u4F5C\u6D41: ${state.type}`));
|
|
6981
|
+
lines.push("");
|
|
6982
|
+
lines.push(chalk9__default.default.cyan("\u{1F4DD} \u751F\u6210\u89C4\u683C\u62C6\u5206..."));
|
|
6983
|
+
const spec = await generateSpec(requirement, context, analysis, state.id);
|
|
6984
|
+
const specPath = await saveSpecFile(cwd, spec);
|
|
6985
|
+
lines.push(chalk9__default.default.green(" \u2713 \u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210"));
|
|
6986
|
+
lines.push(chalk9__default.default.gray(` \u8DEF\u5F84: ${specPath}`));
|
|
6987
|
+
lines.push("");
|
|
6988
|
+
lines.push(chalk9__default.default.cyan.bold("\u{1F4CB} \u89C4\u683C\u6982\u89C8:"));
|
|
6989
|
+
lines.push(chalk9__default.default.white(`
|
|
6990
|
+
${spec.summary}`));
|
|
6991
|
+
if (spec.items.length > 0) {
|
|
6992
|
+
lines.push("");
|
|
6993
|
+
lines.push(chalk9__default.default.cyan(" \u4EFB\u52A1\u62C6\u5206:"));
|
|
6994
|
+
for (const item of spec.items) {
|
|
6995
|
+
const priorityIcon = item.priority === "high" ? "\u{1F534}" : item.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
6996
|
+
lines.push(chalk9__default.default.gray(` ${priorityIcon} [${item.id}] ${item.title}`));
|
|
6997
|
+
}
|
|
6998
|
+
}
|
|
6999
|
+
if (spec.risks.length > 0) {
|
|
7000
|
+
lines.push("");
|
|
7001
|
+
lines.push(chalk9__default.default.yellow(" \u26A0\uFE0F \u98CE\u9669\u63D0\u793A:"));
|
|
7002
|
+
for (const risk of spec.risks) {
|
|
7003
|
+
lines.push(chalk9__default.default.gray(` - ${risk}`));
|
|
7004
|
+
}
|
|
7005
|
+
}
|
|
7006
|
+
lines.push("");
|
|
7007
|
+
lines.push(chalk9__default.default.yellow.bold("\u23F3 \u7B49\u5F85\u89C4\u683C\u786E\u8BA4"));
|
|
7008
|
+
lines.push(chalk9__default.default.gray("\n\u8BF7\u68C0\u67E5\u751F\u6210\u7684\u89C4\u683C\u6587\u4EF6\uFF0C\u786E\u8BA4\u540E\u7EE7\u7EED:"));
|
|
7009
|
+
lines.push(chalk9__default.default.white("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C\uFF0C\u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5"));
|
|
7010
|
+
lines.push(chalk9__default.default.white(" /opsx:rollback explore - \u89C4\u683C\u4E0D\u7B26\uFF0C\u91CD\u65B0\u62C6\u5206"));
|
|
7011
|
+
lines.push(chalk9__default.default.white(" /opsx:status - \u67E5\u770B\u5DE5\u4F5C\u6D41\u72B6\u6001"));
|
|
7012
|
+
return { output: lines.join("\n") };
|
|
7013
|
+
}
|
|
7014
|
+
async function generateSpec(requirement, context, analysis, changeId) {
|
|
7015
|
+
const spec = {
|
|
7016
|
+
changeId,
|
|
7017
|
+
requirement,
|
|
7018
|
+
summary: "",
|
|
7019
|
+
items: [],
|
|
7020
|
+
architectureNotes: [],
|
|
7021
|
+
risks: [],
|
|
7022
|
+
suggestions: []
|
|
7023
|
+
};
|
|
7024
|
+
spec.summary = generateSummary(requirement);
|
|
7025
|
+
if (analysis.recommendation === "complex") {
|
|
7026
|
+
spec.items = generateComplexTasks(requirement, context, analysis);
|
|
7027
|
+
spec.architectureNotes = generateArchitectureNotes(requirement, context);
|
|
7028
|
+
} else {
|
|
7029
|
+
spec.items = generateSimpleTasks(requirement);
|
|
7030
|
+
}
|
|
7031
|
+
spec.risks = generateRisks(requirement, context, analysis);
|
|
7032
|
+
spec.suggestions = generateSuggestions(requirement, context, analysis);
|
|
7033
|
+
return spec;
|
|
7034
|
+
}
|
|
7035
|
+
function generateSummary(requirement) {
|
|
7036
|
+
const firstSentence = requirement.split(/[。!?\n]/)[0];
|
|
7037
|
+
return firstSentence.length > 100 ? firstSentence.slice(0, 97) + "..." : firstSentence;
|
|
7038
|
+
}
|
|
7039
|
+
function generateComplexTasks(requirement, context, analysis) {
|
|
7040
|
+
const items = [];
|
|
7041
|
+
let itemId = 1;
|
|
7042
|
+
const featurePatterns = [
|
|
7043
|
+
{ pattern: /用户|登录|注册|认证|权限/, title: "\u7528\u6237\u8BA4\u8BC1\u6A21\u5757", priority: "high" },
|
|
7044
|
+
{ pattern: /数据|存储|缓存|数据库/, title: "\u6570\u636E\u5C42\u5B9E\u73B0", priority: "high" },
|
|
7045
|
+
{ pattern: /接口|API|请求|响应/, title: "API \u63A5\u53E3\u5F00\u53D1", priority: "high" },
|
|
7046
|
+
{ pattern: /界面|页面|组件|UI/, title: "\u754C\u9762\u5F00\u53D1", priority: "medium" },
|
|
7047
|
+
{ pattern: /测试|单测|覆盖/, title: "\u6D4B\u8BD5\u7528\u4F8B\u7F16\u5199", priority: "medium" },
|
|
7048
|
+
{ pattern: /文档|说明/, title: "\u6587\u6863\u7F16\u5199", priority: "low" },
|
|
7049
|
+
{ pattern: /配置|设置/, title: "\u914D\u7F6E\u7BA1\u7406", priority: "low" },
|
|
7050
|
+
{ pattern: /优化|性能/, title: "\u6027\u80FD\u4F18\u5316", priority: "medium" },
|
|
7051
|
+
{ pattern: /安全|加密/, title: "\u5B89\u5168\u5B9E\u73B0", priority: "high" },
|
|
7052
|
+
{ pattern: /日志|监控/, title: "\u65E5\u5FD7\u76D1\u63A7", priority: "low" }
|
|
7053
|
+
];
|
|
7054
|
+
for (const { pattern, title, priority } of featurePatterns) {
|
|
7055
|
+
if (pattern.test(requirement)) {
|
|
7056
|
+
items.push({
|
|
7057
|
+
id: `T${itemId.toString().padStart(3, "0")}`,
|
|
7058
|
+
title,
|
|
7059
|
+
description: `${title}\u76F8\u5173\u7684\u529F\u80FD\u5B9E\u73B0`,
|
|
7060
|
+
priority,
|
|
7061
|
+
dependencies: itemId > 1 ? [`T${(itemId - 1).toString().padStart(3, "0")}`] : [],
|
|
7062
|
+
estimatedComplexity: priority === "high" ? 3 : priority === "medium" ? 2 : 1
|
|
7063
|
+
});
|
|
7064
|
+
itemId++;
|
|
7065
|
+
}
|
|
7066
|
+
}
|
|
7067
|
+
if (items.length === 0) {
|
|
7068
|
+
items.push({
|
|
7069
|
+
id: "T001",
|
|
7070
|
+
title: "\u9700\u6C42\u5206\u6790\u4E0E\u8BBE\u8BA1",
|
|
7071
|
+
description: "\u5206\u6790\u9700\u6C42\u7EC6\u8282\uFF0C\u8BBE\u8BA1\u5B9E\u73B0\u65B9\u6848",
|
|
7072
|
+
priority: "high",
|
|
7073
|
+
dependencies: [],
|
|
7074
|
+
estimatedComplexity: 2
|
|
6780
7075
|
});
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
"",
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
7076
|
+
items.push({
|
|
7077
|
+
id: "T002",
|
|
7078
|
+
title: "\u6838\u5FC3\u529F\u80FD\u5B9E\u73B0",
|
|
7079
|
+
description: requirement,
|
|
7080
|
+
priority: "high",
|
|
7081
|
+
dependencies: ["T001"],
|
|
7082
|
+
estimatedComplexity: analysis.score
|
|
7083
|
+
});
|
|
7084
|
+
items.push({
|
|
7085
|
+
id: "T003",
|
|
7086
|
+
title: "\u6D4B\u8BD5\u4E0E\u9A8C\u8BC1",
|
|
7087
|
+
description: "\u7F16\u5199\u6D4B\u8BD5\u7528\u4F8B\uFF0C\u9A8C\u8BC1\u529F\u80FD\u6B63\u786E\u6027",
|
|
7088
|
+
priority: "medium",
|
|
7089
|
+
dependencies: ["T002"],
|
|
7090
|
+
estimatedComplexity: 2
|
|
7091
|
+
});
|
|
7092
|
+
}
|
|
7093
|
+
return items;
|
|
7094
|
+
}
|
|
7095
|
+
function generateSimpleTasks(requirement, context) {
|
|
7096
|
+
return [
|
|
7097
|
+
{
|
|
7098
|
+
id: "T001",
|
|
7099
|
+
title: "\u5B9E\u73B0\u53D8\u66F4",
|
|
7100
|
+
description: requirement,
|
|
7101
|
+
priority: "high",
|
|
7102
|
+
dependencies: [],
|
|
7103
|
+
estimatedComplexity: 3
|
|
7104
|
+
},
|
|
7105
|
+
{
|
|
7106
|
+
id: "T002",
|
|
7107
|
+
title: "\u6D4B\u8BD5\u9A8C\u8BC1",
|
|
7108
|
+
description: "\u9A8C\u8BC1\u53D8\u66F4\u6B63\u786E\u6027",
|
|
7109
|
+
priority: "medium",
|
|
7110
|
+
dependencies: ["T001"],
|
|
7111
|
+
estimatedComplexity: 1
|
|
6806
7112
|
}
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
7113
|
+
];
|
|
7114
|
+
}
|
|
7115
|
+
function generateArchitectureNotes(requirement, context) {
|
|
7116
|
+
const notes = [];
|
|
7117
|
+
if (context.framework) {
|
|
7118
|
+
notes.push(`\u9879\u76EE\u4F7F\u7528 ${context.framework} \u6846\u67B6\uFF0C\u9700\u9075\u5FAA\u5176\u6700\u4F73\u5B9E\u8DF5`);
|
|
7119
|
+
}
|
|
7120
|
+
if (requirement.includes("\u6A21\u5757") || requirement.includes("\u7EC4\u4EF6")) {
|
|
7121
|
+
notes.push("\u5EFA\u8BAE\u91C7\u7528\u6A21\u5757\u5316\u8BBE\u8BA1\uFF0C\u4FDD\u6301\u7EC4\u4EF6\u804C\u8D23\u5355\u4E00");
|
|
7122
|
+
}
|
|
7123
|
+
if (requirement.includes("API") || requirement.includes("\u63A5\u53E3")) {
|
|
7124
|
+
notes.push("API \u8BBE\u8BA1\u9700\u8003\u8651\u7248\u672C\u63A7\u5236\u548C\u5411\u540E\u517C\u5BB9");
|
|
7125
|
+
}
|
|
7126
|
+
if (context.structure.srcStructure) {
|
|
7127
|
+
notes.push(`\u73B0\u6709\u6E90\u7801\u7ED3\u6784: ${context.structure.srcStructure}`);
|
|
7128
|
+
}
|
|
7129
|
+
return notes;
|
|
7130
|
+
}
|
|
7131
|
+
function generateRisks(requirement, context, analysis) {
|
|
7132
|
+
const risks = [];
|
|
7133
|
+
if (!context.framework) {
|
|
7134
|
+
risks.push("\u9879\u76EE\u6846\u67B6\u672A\u8BC6\u522B\uFF0C\u53EF\u80FD\u5F71\u54CD\u4EE3\u7801\u98CE\u683C\u4E00\u81F4\u6027");
|
|
7135
|
+
}
|
|
7136
|
+
if (analysis.score >= 7) {
|
|
7137
|
+
risks.push("\u9700\u6C42\u590D\u6742\u5EA6\u8F83\u9AD8\uFF0C\u5EFA\u8BAE\u5206\u9636\u6BB5\u5B9E\u73B0");
|
|
7138
|
+
}
|
|
7139
|
+
if (requirement.includes("\u8FC1\u79FB") || requirement.includes("\u91CD\u6784")) {
|
|
7140
|
+
risks.push("\u6D89\u53CA\u73B0\u6709\u4EE3\u7801\u4FEE\u6539\uFF0C\u9700\u6CE8\u610F\u56DE\u5F52\u6D4B\u8BD5");
|
|
7141
|
+
}
|
|
7142
|
+
if (requirement.includes("\u6743\u9650") || requirement.includes("\u5B89\u5168")) {
|
|
7143
|
+
risks.push("\u6D89\u53CA\u5B89\u5168\u654F\u611F\u529F\u80FD\uFF0C\u9700\u8981\u989D\u5916\u5BA1\u67E5");
|
|
7144
|
+
}
|
|
7145
|
+
return risks;
|
|
7146
|
+
}
|
|
7147
|
+
function generateSuggestions(requirement, context, analysis) {
|
|
7148
|
+
const suggestions = [];
|
|
7149
|
+
if (context.norms.devStandards) {
|
|
7150
|
+
suggestions.push("\u9879\u76EE\u5DF2\u6709\u5F00\u53D1\u89C4\u8303\uFF0C\u8BF7\u9075\u5FAA\u73B0\u6709\u89C4\u8303");
|
|
7151
|
+
}
|
|
7152
|
+
if (analysis.recommendation === "complex") {
|
|
7153
|
+
suggestions.push("\u590D\u6742\u9700\u6C42\u5EFA\u8BAE\u5148\u8FDB\u884C\u6280\u672F\u8BC4\u5BA1");
|
|
7154
|
+
suggestions.push("\u5EFA\u8BAE\u8C03\u7528 $architect \u83B7\u53D6\u67B6\u6784\u5EFA\u8BAE");
|
|
7155
|
+
}
|
|
7156
|
+
if (context.techStack.length > 0) {
|
|
7157
|
+
suggestions.push(`\u6280\u672F\u6808: ${context.techStack.join(", ")}`);
|
|
6810
7158
|
}
|
|
7159
|
+
return suggestions;
|
|
7160
|
+
}
|
|
7161
|
+
async function saveSpecFile(cwd, spec) {
|
|
7162
|
+
const changesDir = path4__namespace.join(cwd, "openspec", "changes");
|
|
7163
|
+
await fs4__namespace.mkdir(changesDir, { recursive: true });
|
|
7164
|
+
const specPath = path4__namespace.join(changesDir, `${spec.changeId}-spec.md`);
|
|
7165
|
+
const content = formatSpecFile(spec);
|
|
7166
|
+
await fs4__namespace.writeFile(specPath, content, "utf-8");
|
|
7167
|
+
return specPath;
|
|
7168
|
+
}
|
|
7169
|
+
function formatSpecFile(spec) {
|
|
7170
|
+
const lines = [];
|
|
7171
|
+
lines.push(`# Spec: ${spec.summary}`);
|
|
7172
|
+
lines.push("");
|
|
7173
|
+
lines.push(`> \u53D8\u66F4ID: ${spec.changeId}`);
|
|
7174
|
+
lines.push(`> \u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
7175
|
+
lines.push("");
|
|
7176
|
+
lines.push("---");
|
|
7177
|
+
lines.push("");
|
|
7178
|
+
lines.push("## \u9700\u6C42\u6982\u8FF0");
|
|
7179
|
+
lines.push("");
|
|
7180
|
+
lines.push(spec.requirement);
|
|
7181
|
+
lines.push("");
|
|
7182
|
+
lines.push("## \u4EFB\u52A1\u62C6\u5206");
|
|
7183
|
+
lines.push("");
|
|
7184
|
+
for (const item of spec.items) {
|
|
7185
|
+
const priorityLabel = item.priority === "high" ? "\u{1F534} \u9AD8" : item.priority === "medium" ? "\u{1F7E1} \u4E2D" : "\u{1F7E2} \u4F4E";
|
|
7186
|
+
lines.push(`### ${item.id}: ${item.title}`);
|
|
7187
|
+
lines.push("");
|
|
7188
|
+
lines.push(`- **\u4F18\u5148\u7EA7**: ${priorityLabel}`);
|
|
7189
|
+
lines.push(`- **\u63CF\u8FF0**: ${item.description}`);
|
|
7190
|
+
lines.push(`- **\u9884\u4F30\u590D\u6742\u5EA6**: ${item.estimatedComplexity}/5`);
|
|
7191
|
+
if (item.dependencies.length > 0) {
|
|
7192
|
+
lines.push(`- **\u4F9D\u8D56**: ${item.dependencies.join(", ")}`);
|
|
7193
|
+
}
|
|
7194
|
+
lines.push("");
|
|
7195
|
+
}
|
|
7196
|
+
if (spec.architectureNotes.length > 0) {
|
|
7197
|
+
lines.push("## \u67B6\u6784\u8BF4\u660E");
|
|
7198
|
+
lines.push("");
|
|
7199
|
+
for (const note of spec.architectureNotes) {
|
|
7200
|
+
lines.push(`- ${note}`);
|
|
7201
|
+
}
|
|
7202
|
+
lines.push("");
|
|
7203
|
+
}
|
|
7204
|
+
if (spec.risks.length > 0) {
|
|
7205
|
+
lines.push("## \u26A0\uFE0F \u98CE\u9669\u8BC4\u4F30");
|
|
7206
|
+
lines.push("");
|
|
7207
|
+
for (const risk of spec.risks) {
|
|
7208
|
+
lines.push(`- ${risk}`);
|
|
7209
|
+
}
|
|
7210
|
+
lines.push("");
|
|
7211
|
+
}
|
|
7212
|
+
if (spec.suggestions.length > 0) {
|
|
7213
|
+
lines.push("## \u{1F4A1} \u5EFA\u8BAE");
|
|
7214
|
+
lines.push("");
|
|
7215
|
+
for (const suggestion of spec.suggestions) {
|
|
7216
|
+
lines.push(`- ${suggestion}`);
|
|
7217
|
+
}
|
|
7218
|
+
lines.push("");
|
|
7219
|
+
}
|
|
7220
|
+
lines.push("---");
|
|
7221
|
+
lines.push("");
|
|
7222
|
+
lines.push("## \u786E\u8BA4\u72B6\u6001");
|
|
7223
|
+
lines.push("");
|
|
7224
|
+
lines.push("- [ ] \u89C4\u683C\u5DF2\u5BA1\u9605");
|
|
7225
|
+
lines.push("- [ ] \u4EFB\u52A1\u62C6\u5206\u5DF2\u786E\u8BA4");
|
|
7226
|
+
lines.push("");
|
|
7227
|
+
lines.push("**\u786E\u8BA4\u540E\u6267\u884C**: `/opsx:confirm spec-review`");
|
|
7228
|
+
return lines.join("\n");
|
|
6811
7229
|
}
|
|
6812
7230
|
function parseArgs(args) {
|
|
6813
7231
|
let forceComplexity;
|
|
@@ -6832,42 +7250,52 @@ async function readProjectContext(cwd) {
|
|
|
6832
7250
|
type: "unknown",
|
|
6833
7251
|
framework: null,
|
|
6834
7252
|
techStack: [],
|
|
6835
|
-
description: ""
|
|
7253
|
+
description: "",
|
|
7254
|
+
structure: {
|
|
7255
|
+
directories: [],
|
|
7256
|
+
keyFiles: [],
|
|
7257
|
+
srcStructure: ""
|
|
7258
|
+
},
|
|
7259
|
+
norms: {
|
|
7260
|
+
devStandards: "",
|
|
7261
|
+
patterns: "",
|
|
7262
|
+
weights: ""
|
|
7263
|
+
}
|
|
7264
|
+
};
|
|
7265
|
+
const [agentsContext, configContext, normsContext, structureContext] = await Promise.all([
|
|
7266
|
+
readAgentsMd(cwd),
|
|
7267
|
+
readConfigYaml(cwd),
|
|
7268
|
+
readNorms(cwd),
|
|
7269
|
+
analyzeStructure(cwd)
|
|
7270
|
+
]);
|
|
7271
|
+
return {
|
|
7272
|
+
...defaultContext,
|
|
7273
|
+
...agentsContext,
|
|
7274
|
+
...configContext,
|
|
7275
|
+
norms: normsContext,
|
|
7276
|
+
structure: structureContext
|
|
6836
7277
|
};
|
|
7278
|
+
}
|
|
7279
|
+
async function readAgentsMd(cwd) {
|
|
6837
7280
|
const agentsPath = path4__namespace.join(cwd, "AGENTS.md");
|
|
6838
7281
|
try {
|
|
6839
7282
|
const stats = await fs4__namespace.stat(agentsPath);
|
|
6840
7283
|
if (stats.size > MAX_FILE_SIZE2) {
|
|
6841
7284
|
console.warn(`\u8B66\u544A: AGENTS.md \u6587\u4EF6\u8FC7\u5927 (${stats.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
|
|
6842
|
-
return
|
|
7285
|
+
return {};
|
|
6843
7286
|
}
|
|
6844
7287
|
const content = await fs4__namespace.readFile(agentsPath, "utf-8");
|
|
6845
|
-
return parseAgentsMd(content
|
|
7288
|
+
return parseAgentsMd(content);
|
|
6846
7289
|
} catch (e) {
|
|
6847
7290
|
const err = e;
|
|
6848
7291
|
if (err.code !== "ENOENT") {
|
|
6849
7292
|
console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 AGENTS.md - ${err.message}`);
|
|
6850
7293
|
}
|
|
7294
|
+
return {};
|
|
6851
7295
|
}
|
|
6852
|
-
const configPath = path4__namespace.join(cwd, "openspec", "config.yaml");
|
|
6853
|
-
try {
|
|
6854
|
-
const stats = await fs4__namespace.stat(configPath);
|
|
6855
|
-
if (stats.size > MAX_FILE_SIZE2) {
|
|
6856
|
-
console.warn(`\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
|
|
6857
|
-
return defaultContext;
|
|
6858
|
-
}
|
|
6859
|
-
const content = await fs4__namespace.readFile(configPath, "utf-8");
|
|
6860
|
-
return parseConfigYaml(content, defaultContext);
|
|
6861
|
-
} catch (e) {
|
|
6862
|
-
const err = e;
|
|
6863
|
-
if (err.code !== "ENOENT") {
|
|
6864
|
-
console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
|
|
6865
|
-
}
|
|
6866
|
-
}
|
|
6867
|
-
return defaultContext;
|
|
6868
7296
|
}
|
|
6869
|
-
function parseAgentsMd(content
|
|
6870
|
-
const context = {
|
|
7297
|
+
function parseAgentsMd(content) {
|
|
7298
|
+
const context = {};
|
|
6871
7299
|
const nameMatch = content.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
|
|
6872
7300
|
if (nameMatch) {
|
|
6873
7301
|
context.name = nameMatch[1];
|
|
@@ -6884,10 +7312,32 @@ function parseAgentsMd(content, defaults) {
|
|
|
6884
7312
|
if (descMatch) {
|
|
6885
7313
|
context.description = descMatch[1].trim();
|
|
6886
7314
|
}
|
|
7315
|
+
const techStackMatch = content.match(/技术栈[::]\s*([^\n]+)/);
|
|
7316
|
+
if (techStackMatch) {
|
|
7317
|
+
context.techStack = techStackMatch[1].split(/[,,、]/).map((s) => s.trim()).filter(Boolean);
|
|
7318
|
+
}
|
|
6887
7319
|
return context;
|
|
6888
7320
|
}
|
|
6889
|
-
function
|
|
6890
|
-
const
|
|
7321
|
+
async function readConfigYaml(cwd) {
|
|
7322
|
+
const configPath = path4__namespace.join(cwd, "openspec", "config.yaml");
|
|
7323
|
+
try {
|
|
7324
|
+
const stats = await fs4__namespace.stat(configPath);
|
|
7325
|
+
if (stats.size > MAX_FILE_SIZE2) {
|
|
7326
|
+
console.warn("\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6");
|
|
7327
|
+
return {};
|
|
7328
|
+
}
|
|
7329
|
+
const content = await fs4__namespace.readFile(configPath, "utf-8");
|
|
7330
|
+
return parseConfigYaml(content);
|
|
7331
|
+
} catch (e) {
|
|
7332
|
+
const err = e;
|
|
7333
|
+
if (err.code !== "ENOENT") {
|
|
7334
|
+
console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
|
|
7335
|
+
}
|
|
7336
|
+
return {};
|
|
7337
|
+
}
|
|
7338
|
+
}
|
|
7339
|
+
function parseConfigYaml(content) {
|
|
7340
|
+
const context = {};
|
|
6891
7341
|
const nameMatch = content.match(/name:\s*(.+)/);
|
|
6892
7342
|
if (nameMatch) {
|
|
6893
7343
|
context.name = nameMatch[1].trim();
|
|
@@ -6902,6 +7352,67 @@ function parseConfigYaml(content, defaults) {
|
|
|
6902
7352
|
}
|
|
6903
7353
|
return context;
|
|
6904
7354
|
}
|
|
7355
|
+
async function readNorms(cwd) {
|
|
7356
|
+
const normsDir = path4__namespace.join(cwd, ".sf-cli", "norms");
|
|
7357
|
+
const norms = {
|
|
7358
|
+
devStandards: "",
|
|
7359
|
+
patterns: "",
|
|
7360
|
+
weights: ""
|
|
7361
|
+
};
|
|
7362
|
+
try {
|
|
7363
|
+
const devStandardsPath = path4__namespace.join(normsDir, "devstanded.md");
|
|
7364
|
+
norms.devStandards = await fs4__namespace.readFile(devStandardsPath, "utf-8").catch(() => "");
|
|
7365
|
+
} catch {
|
|
7366
|
+
}
|
|
7367
|
+
try {
|
|
7368
|
+
const patternsPath = path4__namespace.join(normsDir, "patterns.json");
|
|
7369
|
+
norms.patterns = await fs4__namespace.readFile(patternsPath, "utf-8").catch(() => "");
|
|
7370
|
+
} catch {
|
|
7371
|
+
}
|
|
7372
|
+
try {
|
|
7373
|
+
const weightsPath = path4__namespace.join(normsDir, "weights.json");
|
|
7374
|
+
norms.weights = await fs4__namespace.readFile(weightsPath, "utf-8").catch(() => "");
|
|
7375
|
+
} catch {
|
|
7376
|
+
}
|
|
7377
|
+
return norms;
|
|
7378
|
+
}
|
|
7379
|
+
async function analyzeStructure(cwd) {
|
|
7380
|
+
const structure = {
|
|
7381
|
+
directories: [],
|
|
7382
|
+
keyFiles: [],
|
|
7383
|
+
srcStructure: ""
|
|
7384
|
+
};
|
|
7385
|
+
try {
|
|
7386
|
+
const entries = await fs4__namespace.readdir(cwd, { withFileTypes: true });
|
|
7387
|
+
for (const entry of entries) {
|
|
7388
|
+
if (entry.isDirectory() && !["node_modules", "dist", ".git", "build"].includes(entry.name)) {
|
|
7389
|
+
structure.directories.push(entry.name);
|
|
7390
|
+
}
|
|
7391
|
+
}
|
|
7392
|
+
const keyFiles = [
|
|
7393
|
+
"package.json",
|
|
7394
|
+
"tsconfig.json",
|
|
7395
|
+
"AGENTS.md",
|
|
7396
|
+
"README.md"
|
|
7397
|
+
];
|
|
7398
|
+
for (const file of keyFiles) {
|
|
7399
|
+
const filePath = path4__namespace.join(cwd, file);
|
|
7400
|
+
try {
|
|
7401
|
+
await fs4__namespace.access(filePath);
|
|
7402
|
+
structure.keyFiles.push(file);
|
|
7403
|
+
} catch {
|
|
7404
|
+
}
|
|
7405
|
+
}
|
|
7406
|
+
const srcDir = path4__namespace.join(cwd, "src");
|
|
7407
|
+
try {
|
|
7408
|
+
const srcEntries = await fs4__namespace.readdir(srcDir, { withFileTypes: true });
|
|
7409
|
+
structure.srcStructure = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name).join("/");
|
|
7410
|
+
} catch {
|
|
7411
|
+
}
|
|
7412
|
+
} catch (e) {
|
|
7413
|
+
}
|
|
7414
|
+
return { structure };
|
|
7415
|
+
}
|
|
6905
7416
|
function analyzeComplexity(requirement, context) {
|
|
6906
7417
|
let score = 3;
|
|
6907
7418
|
const factors = [];
|
|
@@ -7065,16 +7576,20 @@ async function handleOpsx(command, args, ctx) {
|
|
|
7065
7576
|
case "rollback":
|
|
7066
7577
|
return handleRollback(workflow, args);
|
|
7067
7578
|
case "confirm":
|
|
7068
|
-
return handleConfirm(workflow, args);
|
|
7579
|
+
return handleConfirm(workflow, args, ctx);
|
|
7069
7580
|
case "next":
|
|
7070
7581
|
return handleNext(workflow);
|
|
7071
7582
|
case "auto":
|
|
7072
7583
|
return handleAutoSchedule(args);
|
|
7073
7584
|
case "test":
|
|
7074
7585
|
return handleRegressionTest(ctx);
|
|
7586
|
+
case "list":
|
|
7587
|
+
return handleList(workflow);
|
|
7588
|
+
case "switch":
|
|
7589
|
+
return handleSwitch(workflow, args);
|
|
7075
7590
|
default:
|
|
7076
7591
|
return {
|
|
7077
|
-
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")
|
|
7078
7593
|
};
|
|
7079
7594
|
}
|
|
7080
7595
|
}
|
|
@@ -7433,6 +7948,16 @@ async function handleConfirm(workflow, args, ctx) {
|
|
|
7433
7948
|
const type = args[0];
|
|
7434
7949
|
if (!type) {
|
|
7435
7950
|
const pendingPoint = workflow.getCurrentConfirmationPoint();
|
|
7951
|
+
const specPath = await checkPendingSpec(ctx.options.workingDirectory, state.id);
|
|
7952
|
+
if (specPath) {
|
|
7953
|
+
return {
|
|
7954
|
+
output: chalk9__default.default.cyan("\u{1F4CB} \u89C4\u683C\u6587\u4EF6\u5F85\u786E\u8BA4") + chalk9__default.default.gray(`
|
|
7955
|
+
|
|
7956
|
+
\u89C4\u683C\u6587\u4EF6: ${specPath}`) + chalk9__default.default.gray(`
|
|
7957
|
+
|
|
7958
|
+
\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")
|
|
7959
|
+
};
|
|
7960
|
+
}
|
|
7436
7961
|
if (pendingPoint) {
|
|
7437
7962
|
return {
|
|
7438
7963
|
output: chalk9__default.default.cyan("\u5F85\u786E\u8BA4\u7684\u68C0\u67E5\u70B9:") + chalk9__default.default.white(`
|
|
@@ -7454,12 +7979,54 @@ ${generateConfirmationPrompt(pendingPoint)}`) + chalk9__default.default.gray(`
|
|
|
7454
7979
|
}
|
|
7455
7980
|
const comment = args.slice(1).join(" ");
|
|
7456
7981
|
workflow.confirm(type, comment);
|
|
7457
|
-
|
|
7458
|
-
|
|
7982
|
+
const lines = [];
|
|
7983
|
+
lines.push(chalk9__default.default.green("\u2713 \u5DF2\u786E\u8BA4"));
|
|
7984
|
+
lines.push(chalk9__default.default.white(`
|
|
7985
|
+
${point.name}`));
|
|
7986
|
+
if (comment) {
|
|
7987
|
+
lines.push(chalk9__default.default.gray(`
|
|
7988
|
+
\u5907\u6CE8: ${comment}`));
|
|
7989
|
+
}
|
|
7990
|
+
if (type === "spec-review") {
|
|
7991
|
+
const allowed = workflow.getAllowedTransitions();
|
|
7992
|
+
if (allowed.length > 0) {
|
|
7993
|
+
const nextStep = allowed[0];
|
|
7994
|
+
try {
|
|
7995
|
+
const transition = await workflow.transition(nextStep);
|
|
7996
|
+
lines.push("");
|
|
7997
|
+
lines.push(chalk9__default.default.cyan(`\u2713 \u5DF2\u81EA\u52A8\u8FDB\u5165 ${nextStep} \u9636\u6BB5`));
|
|
7998
|
+
lines.push(chalk9__default.default.gray(`\u8F6C\u6362: ${transition.from} \u2192 ${transition.to}`));
|
|
7999
|
+
if (nextStep === "new") {
|
|
8000
|
+
lines.push(chalk9__default.default.yellow("\n\u4E0B\u4E00\u6B65: \u8BBE\u8BA1\u65B9\u6848"));
|
|
8001
|
+
lines.push(chalk9__default.default.gray(" \u53EF\u8C03\u7528 $architect \u6216 $frontend-dev \u8FDB\u884C\u8BBE\u8BA1"));
|
|
8002
|
+
} else if (nextStep === "apply") {
|
|
8003
|
+
lines.push(chalk9__default.default.yellow("\n\u4E0B\u4E00\u6B65: \u6267\u884C\u53D8\u66F4"));
|
|
8004
|
+
lines.push(chalk9__default.default.gray(" \u53EF\u8C03\u7528 $frontend-dev \u6267\u884C\u4EE3\u7801\u4FEE\u6539"));
|
|
8005
|
+
}
|
|
8006
|
+
return { output: lines.join("\n") };
|
|
8007
|
+
} catch (e) {
|
|
8008
|
+
if (e instanceof ConfirmationRequiredError) {
|
|
8009
|
+
lines.push(chalk9__default.default.yellow("\n\u26A0 \u8FD8\u9700\u8981\u786E\u8BA4:") + chalk9__default.default.white(`
|
|
8010
|
+
${generateConfirmationPrompt(e.point)}`) + chalk9__default.default.cyan(`
|
|
7459
8011
|
|
|
7460
|
-
${point.
|
|
7461
|
-
|
|
7462
|
-
|
|
8012
|
+
\u4F7F\u7528 /opsx:confirm ${e.point.type}`));
|
|
8013
|
+
return { output: lines.join("\n") };
|
|
8014
|
+
}
|
|
8015
|
+
throw e;
|
|
8016
|
+
}
|
|
8017
|
+
}
|
|
8018
|
+
}
|
|
8019
|
+
lines.push(chalk9__default.default.yellow("\n\u4F7F\u7528 /opsx:next \u7EE7\u7EED\u4E0B\u4E00\u9636\u6BB5"));
|
|
8020
|
+
return { output: lines.join("\n") };
|
|
8021
|
+
}
|
|
8022
|
+
async function checkPendingSpec(workingDirectory, changeId) {
|
|
8023
|
+
const specPath = path4__namespace.join(workingDirectory, "openspec", "changes", `${changeId}-spec.md`);
|
|
8024
|
+
try {
|
|
8025
|
+
await fs10__namespace.promises.access(specPath);
|
|
8026
|
+
return specPath;
|
|
8027
|
+
} catch {
|
|
8028
|
+
return null;
|
|
8029
|
+
}
|
|
7463
8030
|
}
|
|
7464
8031
|
async function handleNext(workflow, args, ctx) {
|
|
7465
8032
|
const state = workflow.getState();
|
|
@@ -7495,6 +8062,76 @@ ${generateConfirmationPrompt(e.point)}`) + chalk9__default.default.cyan(`
|
|
|
7495
8062
|
throw e;
|
|
7496
8063
|
}
|
|
7497
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
|
+
}
|
|
7498
8135
|
|
|
7499
8136
|
// src/commands/runner.ts
|
|
7500
8137
|
function getVersion() {
|
|
@@ -7677,7 +8314,11 @@ var ALLOWED_COMMANDS_WITHOUT_WORKFLOW = [
|
|
|
7677
8314
|
"update",
|
|
7678
8315
|
"u",
|
|
7679
8316
|
"version",
|
|
7680
|
-
"v"
|
|
8317
|
+
"v",
|
|
8318
|
+
// OpenSpec 工作流管理命令(始终允许)
|
|
8319
|
+
"opsx:status",
|
|
8320
|
+
"opsx:cancel",
|
|
8321
|
+
"opsx:rollback"
|
|
7681
8322
|
];
|
|
7682
8323
|
var STAGE_PERMISSIONS = {
|
|
7683
8324
|
"explore": {
|
|
@@ -7750,6 +8391,12 @@ var CommandExecutor = class {
|
|
|
7750
8391
|
checkWorkflowPermission(command, ctx) {
|
|
7751
8392
|
const workflowEngine = ctx.workflowEngine;
|
|
7752
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
|
+
}
|
|
7753
8400
|
if (!workflowState) {
|
|
7754
8401
|
if (command.type === "slash" /* SLASH */) {
|
|
7755
8402
|
const cmd = command.command?.toLowerCase();
|