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