@nick848/sf-cli 1.0.8 → 1.0.11
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 +39 -0
- package/dist/cli/index.js +3536 -3451
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.mts +374 -382
- package/dist/index.d.ts +374 -382
- package/dist/index.js +1653 -1701
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1648 -1700
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as path5 from 'path';
|
|
2
2
|
import path5__default from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import * as fs4 from 'fs/promises';
|
|
5
4
|
import chalk9 from 'chalk';
|
|
5
|
+
import * as fs4 from 'fs/promises';
|
|
6
6
|
import * as fs10 from 'fs';
|
|
7
7
|
import * as crypto from 'crypto';
|
|
8
8
|
import * as os from 'os';
|
|
@@ -31,1536 +31,776 @@ var init_esm_shims = __esm({
|
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
// src/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
`;
|
|
48
|
-
prompt2 += ` r - \u8BF7\u6C42\u4FEE\u6539
|
|
49
|
-
`;
|
|
50
|
-
if (!point.required) {
|
|
51
|
-
prompt2 += `
|
|
52
|
-
\u63D0\u793A: \u6B64\u786E\u8BA4\u70B9\u4E3A\u975E\u5FC5\u987B\u9879`;
|
|
34
|
+
// src/commands/new.ts
|
|
35
|
+
var new_exports = {};
|
|
36
|
+
__export(new_exports, {
|
|
37
|
+
clearActiveSession: () => clearActiveSession,
|
|
38
|
+
default: () => new_default,
|
|
39
|
+
getActiveSession: () => getActiveSession,
|
|
40
|
+
handleNew: () => handleNew,
|
|
41
|
+
handleWorkflowInput: () => handleWorkflowInput
|
|
42
|
+
});
|
|
43
|
+
async function handleNew(args, ctx) {
|
|
44
|
+
ctx.options.workingDirectory;
|
|
45
|
+
if (activeSession) {
|
|
46
|
+
return handleActiveSession();
|
|
53
47
|
}
|
|
54
|
-
|
|
48
|
+
const requirement = args.join(" ").trim();
|
|
49
|
+
if (!requirement) {
|
|
50
|
+
return {
|
|
51
|
+
output: chalk9.red("\u8BF7\u8F93\u5165\u9700\u6C42\u63CF\u8FF0") + chalk9.gray("\n\u7528\u6CD5: /new <\u9700\u6C42\u63CF\u8FF0>") + chalk9.gray("\n\n\u793A\u4F8B:") + chalk9.gray("\n /new \u5B9E\u73B0\u7528\u6237\u767B\u5F55\u529F\u80FD") + chalk9.gray("\n /new \u6DFB\u52A0\u6570\u636E\u5BFC\u51FA\u4E3AExcel\u7684\u529F\u80FD")
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
activeSession = {
|
|
55
|
+
id: generateSessionId(),
|
|
56
|
+
requirement,
|
|
57
|
+
refinedRequirement: requirement,
|
|
58
|
+
phase: "context",
|
|
59
|
+
context: null,
|
|
60
|
+
clarityScore: 0,
|
|
61
|
+
clarificationQuestions: [],
|
|
62
|
+
complexity: 0,
|
|
63
|
+
bddScenarios: [],
|
|
64
|
+
specItems: [],
|
|
65
|
+
testFiles: [],
|
|
66
|
+
implFiles: [],
|
|
67
|
+
reviewPassed: false,
|
|
68
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
69
|
+
};
|
|
70
|
+
return executeWorkflow(ctx);
|
|
55
71
|
}
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return `\u5F53\u524D\u9636\u6BB5 ${fromStep} \u4E0D\u652F\u6301\u56DE\u6EDA`;
|
|
72
|
+
function handleActiveSession(ctx) {
|
|
73
|
+
if (!activeSession) {
|
|
74
|
+
return { output: chalk9.red("\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") };
|
|
60
75
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
const lines = [];
|
|
77
|
+
lines.push(chalk9.cyan("\u{1F4CB} \u5F53\u524D\u5DE5\u4F5C\u6D41\u72B6\u6001"));
|
|
78
|
+
lines.push("");
|
|
79
|
+
lines.push(chalk9.white(`\u9700\u6C42: ${activeSession.requirement.slice(0, 60)}${activeSession.requirement.length > 60 ? "..." : ""}`));
|
|
80
|
+
lines.push(chalk9.gray(`\u9636\u6BB5: ${getPhaseLabel(activeSession.phase)}`));
|
|
81
|
+
lines.push("");
|
|
82
|
+
if (activeSession.phase === "clarify") {
|
|
83
|
+
lines.push(chalk9.yellow("\u23F8\uFE0F \u7B49\u5F85\u9700\u6C42\u6F84\u6E05"));
|
|
84
|
+
lines.push(chalk9.gray('\u8BF7\u56DE\u7B54\u4E0A\u8FF0\u95EE\u9898\uFF0C\u6216\u8F93\u5165 "done" \u8868\u793A\u56DE\u7B54\u5B8C\u6210'));
|
|
85
|
+
} else if (activeSession.phase === "spec") {
|
|
86
|
+
lines.push(chalk9.yellow("\u23F8\uFE0F \u7B49\u5F85\u89C4\u683C\u786E\u8BA4"));
|
|
87
|
+
lines.push("");
|
|
88
|
+
lines.push(chalk9.green(" y - \u786E\u8BA4\u89C4\u683C\uFF0C\u7EE7\u7EED\u5DE5\u4F5C\u6D41"));
|
|
89
|
+
lines.push(chalk9.red(" n - \u4E0D\u6EE1\u610F\uFF0C\u91CD\u65B0\u751F\u6210\u89C4\u683C"));
|
|
90
|
+
lines.push(chalk9.gray(" c - \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41"));
|
|
91
|
+
} else {
|
|
92
|
+
lines.push(chalk9.yellow("\u5DE5\u4F5C\u6D41\u8FDB\u884C\u4E2D..."));
|
|
93
|
+
lines.push(chalk9.gray("\u8F93\u5165\u4EFB\u610F\u5185\u5BB9\u7EE7\u7EED"));
|
|
71
94
|
}
|
|
72
|
-
|
|
73
|
-
\u8BF7\u9009\u62E9\u56DE\u6EDA\u76EE\u6807\u9636\u6BB5`;
|
|
74
|
-
return prompt2;
|
|
95
|
+
return { output: lines.join("\n") };
|
|
75
96
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
init_esm_shims();
|
|
80
|
-
DEFAULT_CONFIRMATION_POINTS = [
|
|
81
|
-
{
|
|
82
|
-
type: "spec-review",
|
|
83
|
-
name: "\u89C4\u683C\u786E\u8BA4",
|
|
84
|
-
description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
|
|
85
|
-
triggerStep: "explore",
|
|
86
|
-
targetStep: "new",
|
|
87
|
-
required: true
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
type: "spec-review",
|
|
91
|
-
name: "\u89C4\u683C\u786E\u8BA4",
|
|
92
|
-
description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
|
|
93
|
-
triggerStep: "propose",
|
|
94
|
-
targetStep: "apply",
|
|
95
|
-
required: true
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
type: "architecture",
|
|
99
|
-
name: "\u67B6\u6784\u8C03\u6574\u786E\u8BA4",
|
|
100
|
-
description: "\u8BBE\u8BA1\u65B9\u6848\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u67B6\u6784\u8BBE\u8BA1\u662F\u5426\u5408\u7406",
|
|
101
|
-
triggerStep: "new",
|
|
102
|
-
targetStep: "continue",
|
|
103
|
-
required: true
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
type: "code-review",
|
|
107
|
-
name: "\u4EE3\u7801\u5BA1\u67E5\u786E\u8BA4",
|
|
108
|
-
description: "\u4EE3\u7801\u5BA1\u67E5\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u662F\u5426\u53EF\u4EE5\u5F52\u6863",
|
|
109
|
-
triggerStep: "apply",
|
|
110
|
-
targetStep: "archive",
|
|
111
|
-
required: true
|
|
112
|
-
}
|
|
113
|
-
];
|
|
114
|
-
ROLLBACK_RULES = {
|
|
115
|
-
"explore": [],
|
|
116
|
-
// 初始阶段不可回滚
|
|
117
|
-
"new": ["explore"],
|
|
118
|
-
// 可回滚到 explore
|
|
119
|
-
"continue": ["new", "explore"],
|
|
120
|
-
// 可回滚到 new 或 explore
|
|
121
|
-
"propose": ["propose"],
|
|
122
|
-
// 可重新生成规格(回滚到自身)
|
|
123
|
-
"apply": ["new", "explore", "propose"],
|
|
124
|
-
// 代码审查未通过,可回滚到 new/explore (复杂) 或 propose (简单)
|
|
125
|
-
"archive": []
|
|
126
|
-
// 已归档不可回滚
|
|
127
|
-
};
|
|
128
|
-
ConfirmationManager = class {
|
|
129
|
-
confirmationPoints;
|
|
130
|
-
confirmations = /* @__PURE__ */ new Map();
|
|
131
|
-
constructor(customPoints) {
|
|
132
|
-
this.confirmationPoints = customPoints || DEFAULT_CONFIRMATION_POINTS;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* 获取指定阶段的确认点
|
|
136
|
-
*/
|
|
137
|
-
getConfirmationPointForTransition(from, to) {
|
|
138
|
-
return this.confirmationPoints.find(
|
|
139
|
-
(point) => point.triggerStep === from && point.targetStep === to
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* 检查是否需要确认
|
|
144
|
-
*/
|
|
145
|
-
needsConfirmation(from, to) {
|
|
146
|
-
const point = this.getConfirmationPointForTransition(from, to);
|
|
147
|
-
return point !== void 0 && point.required;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* 获取确认点详情
|
|
151
|
-
*/
|
|
152
|
-
getConfirmationPoint(type) {
|
|
153
|
-
return this.confirmationPoints.find((point) => point.type === type);
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* 记录确认
|
|
157
|
-
*/
|
|
158
|
-
confirm(type, comment) {
|
|
159
|
-
const status = {
|
|
160
|
-
type,
|
|
161
|
-
confirmed: true,
|
|
162
|
-
confirmedAt: /* @__PURE__ */ new Date(),
|
|
163
|
-
comment
|
|
164
|
-
};
|
|
165
|
-
this.confirmations.set(type, status);
|
|
166
|
-
return status;
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* 获取确认状态
|
|
170
|
-
*/
|
|
171
|
-
getConfirmationStatus(type) {
|
|
172
|
-
return this.confirmations.get(type);
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* 检查确认点是否已确认
|
|
176
|
-
*/
|
|
177
|
-
isConfirmed(type) {
|
|
178
|
-
const status = this.confirmations.get(type);
|
|
179
|
-
return status?.confirmed ?? false;
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* 清除确认状态(用于回滚后)
|
|
183
|
-
*/
|
|
184
|
-
clearConfirmation(type) {
|
|
185
|
-
this.confirmations.delete(type);
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* 清除所有确认状态
|
|
189
|
-
*/
|
|
190
|
-
clearAllConfirmations() {
|
|
191
|
-
this.confirmations.clear();
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* 获取所有确认点
|
|
195
|
-
*/
|
|
196
|
-
getAllConfirmationPoints() {
|
|
197
|
-
return [...this.confirmationPoints];
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* 获取指定阶段需要清除的确认点
|
|
201
|
-
*/
|
|
202
|
-
getConfirmationsToClear(targetStep) {
|
|
203
|
-
const types = [];
|
|
204
|
-
for (const point of this.confirmationPoints) {
|
|
205
|
-
const stepOrder = ["explore", "new", "continue", "propose", "apply", "archive"];
|
|
206
|
-
const targetIndex = stepOrder.indexOf(targetStep);
|
|
207
|
-
const triggerIndex = stepOrder.indexOf(point.triggerStep);
|
|
208
|
-
if (triggerIndex >= targetIndex) {
|
|
209
|
-
types.push(point.type);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return types;
|
|
213
|
-
}
|
|
214
|
-
};
|
|
97
|
+
async function executeWorkflow(ctx) {
|
|
98
|
+
if (!activeSession) {
|
|
99
|
+
return { output: chalk9.red("\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") };
|
|
215
100
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
async initialize(projectPath) {
|
|
251
|
-
this.projectPath = projectPath;
|
|
252
|
-
this.openspecPath = path5.join(projectPath, "openspec");
|
|
253
|
-
await this.ensureDirectories();
|
|
254
|
-
await this.loadProjectContext();
|
|
255
|
-
await this.restoreState();
|
|
256
|
-
await this.restoreSnapshots();
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* 加载项目上下文(AGENTS.md 和 config.yaml)
|
|
260
|
-
*/
|
|
261
|
-
async loadProjectContext() {
|
|
262
|
-
const agentsMdPath = path5.join(this.projectPath, "AGENTS.md");
|
|
263
|
-
try {
|
|
264
|
-
this.projectContext = await fs4.readFile(agentsMdPath, "utf-8");
|
|
265
|
-
} catch {
|
|
266
|
-
this.projectContext = "";
|
|
267
|
-
}
|
|
268
|
-
const configPath = path5.join(this.openspecPath, "config.yaml");
|
|
269
|
-
try {
|
|
270
|
-
this.projectConfig = await fs4.readFile(configPath, "utf-8");
|
|
271
|
-
} catch {
|
|
272
|
-
this.projectConfig = "";
|
|
273
|
-
}
|
|
274
|
-
const devstandedPath = path5.join(this.projectPath, ".sf-cli", "norms", "devstanded.md");
|
|
275
|
-
try {
|
|
276
|
-
this.devStandards = await fs4.readFile(devstandedPath, "utf-8");
|
|
277
|
-
} catch {
|
|
278
|
-
this.devStandards = "";
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* 获取项目上下文
|
|
283
|
-
*/
|
|
284
|
-
getProjectContext() {
|
|
285
|
-
return {
|
|
286
|
-
agentsMd: this.projectContext,
|
|
287
|
-
configYaml: this.projectConfig,
|
|
288
|
-
devStandards: this.devStandards
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* 获取规格文件路径
|
|
293
|
-
*/
|
|
294
|
-
getSpecFilePath() {
|
|
295
|
-
if (!this.state) return null;
|
|
296
|
-
return path5.join(this.openspecPath, "changes", `${this.state.id}-spec.md`);
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* 检查规格文件是否存在
|
|
300
|
-
*/
|
|
301
|
-
async hasSpecFile() {
|
|
302
|
-
const specPath = this.getSpecFilePath();
|
|
303
|
-
if (!specPath) return false;
|
|
304
|
-
try {
|
|
305
|
-
await fs4.access(specPath);
|
|
306
|
-
return true;
|
|
307
|
-
} catch {
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* 启动新工作流
|
|
313
|
-
*/
|
|
314
|
-
async start(requirement, complexity, options) {
|
|
315
|
-
const type = complexity >= 6 ? "complex" : "simple";
|
|
316
|
-
const steps = type === "complex" ? COMPLEX_STEPS : SIMPLE_STEPS;
|
|
317
|
-
const initialStep = steps[0];
|
|
318
|
-
const changeId = await this.generateChangeId();
|
|
319
|
-
this.state = {
|
|
320
|
-
id: changeId,
|
|
321
|
-
type,
|
|
322
|
-
currentStep: initialStep,
|
|
323
|
-
steps: steps.map((step, index) => ({
|
|
324
|
-
step,
|
|
325
|
-
status: index === 0 ? "running" : "pending",
|
|
326
|
-
startedAt: index === 0 ? /* @__PURE__ */ new Date() : void 0
|
|
327
|
-
})),
|
|
328
|
-
requirement,
|
|
329
|
-
complexity,
|
|
330
|
-
title: options?.title || requirement.slice(0, 50),
|
|
331
|
-
artifacts: [],
|
|
332
|
-
status: "running",
|
|
333
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
334
|
-
};
|
|
335
|
-
this.snapshots.clear();
|
|
336
|
-
this.confirmationManager.clearAllConfirmations();
|
|
337
|
-
await this.createSnapshot();
|
|
338
|
-
await this.createChangeRecord();
|
|
339
|
-
await this.saveState();
|
|
340
|
-
return this.state;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* 检查转换是否需要确认
|
|
344
|
-
*/
|
|
345
|
-
checkConfirmationNeeded(from, to) {
|
|
346
|
-
const point = this.confirmationManager.getConfirmationPointForTransition(from, to);
|
|
347
|
-
if (!point) {
|
|
348
|
-
return { required: false, confirmed: true };
|
|
349
|
-
}
|
|
350
|
-
const confirmed = this.confirmationManager.isConfirmed(point.type);
|
|
351
|
-
return {
|
|
352
|
-
required: point.required,
|
|
353
|
-
confirmed,
|
|
354
|
-
point
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* 确认检查点
|
|
359
|
-
*/
|
|
360
|
-
confirm(type, comment) {
|
|
361
|
-
this.confirmationManager.confirm(type, comment);
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* 获取确认点信息
|
|
365
|
-
*/
|
|
366
|
-
getConfirmationPoint(type) {
|
|
367
|
-
return this.confirmationManager.getConfirmationPoint(type);
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* 获取当前转换需要的确认点
|
|
371
|
-
*/
|
|
372
|
-
getCurrentConfirmationPoint() {
|
|
373
|
-
if (!this.state) return void 0;
|
|
374
|
-
const allowed = this.getAllowedTransitions();
|
|
375
|
-
for (const target of allowed) {
|
|
376
|
-
const point = this.confirmationManager.getConfirmationPointForTransition(
|
|
377
|
-
this.state.currentStep,
|
|
378
|
-
target
|
|
379
|
-
);
|
|
380
|
-
if (point && !this.confirmationManager.isConfirmed(point.type)) {
|
|
381
|
-
return point;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return void 0;
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* 执行状态转换
|
|
388
|
-
*/
|
|
389
|
-
async transition(targetStep, reason) {
|
|
390
|
-
if (!this.state) {
|
|
391
|
-
throw new Error("\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41");
|
|
392
|
-
}
|
|
393
|
-
const currentStep = this.state.currentStep;
|
|
394
|
-
const allowedTargets = TRANSITIONS[currentStep];
|
|
395
|
-
if (!allowedTargets.includes(targetStep)) {
|
|
396
|
-
throw new Error(
|
|
397
|
-
`\u65E0\u6548\u7684\u72B6\u6001\u8F6C\u6362: ${currentStep} \u2192 ${targetStep}\u3002\u5141\u8BB8\u7684\u8F6C\u6362: ${allowedTargets.join(", ")}`
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
const confirmationResult = this.checkConfirmationNeeded(currentStep, targetStep);
|
|
401
|
-
if (confirmationResult.required && !confirmationResult.confirmed) {
|
|
402
|
-
throw new ConfirmationRequiredError(
|
|
403
|
-
`\u9700\u8981\u786E\u8BA4: ${confirmationResult.point?.name}`,
|
|
404
|
-
confirmationResult.point
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
const currentStepRecord = this.state.steps.find((s) => s.step === currentStep);
|
|
408
|
-
if (currentStepRecord) {
|
|
409
|
-
currentStepRecord.status = "completed";
|
|
410
|
-
currentStepRecord.completedAt = /* @__PURE__ */ new Date();
|
|
101
|
+
const lines = [];
|
|
102
|
+
try {
|
|
103
|
+
if (activeSession.phase === "context") {
|
|
104
|
+
lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 1/8: \u9879\u76EE\u4E0A\u4E0B\u6587\u83B7\u53D6 \u2501\u2501\u2501"));
|
|
105
|
+
lines.push("");
|
|
106
|
+
activeSession.context = await readProjectContext(ctx.options.workingDirectory);
|
|
107
|
+
lines.push(chalk9.gray(` \u9879\u76EE: ${activeSession.context.name}`));
|
|
108
|
+
lines.push(chalk9.gray(` \u7C7B\u578B: ${activeSession.context.type}`));
|
|
109
|
+
lines.push(chalk9.gray(` \u6846\u67B6: ${activeSession.context.framework || "\u672A\u8BC6\u522B"}`));
|
|
110
|
+
lines.push(chalk9.gray(` \u6280\u672F\u6808: ${activeSession.context.techStack.join(", ") || "\u672A\u8BC6\u522B"}`));
|
|
111
|
+
if (activeSession.context.devStandards) {
|
|
112
|
+
lines.push(chalk9.green(" \u2713 \u5DF2\u8BFB\u53D6\u5F00\u53D1\u89C4\u8303"));
|
|
113
|
+
}
|
|
114
|
+
activeSession.phase = "clarify";
|
|
115
|
+
}
|
|
116
|
+
if (activeSession.phase === "clarify") {
|
|
117
|
+
lines.push("");
|
|
118
|
+
lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 2/8: \u9700\u6C42\u6F84\u6E05 \u2501\u2501\u2501"));
|
|
119
|
+
lines.push("");
|
|
120
|
+
const clarityResult = analyzeRequirementClarity(
|
|
121
|
+
activeSession.requirement,
|
|
122
|
+
activeSession.context
|
|
123
|
+
);
|
|
124
|
+
activeSession.clarityScore = clarityResult.score;
|
|
125
|
+
activeSession.clarificationQuestions = clarityResult.questions;
|
|
126
|
+
lines.push(chalk9.gray(` \u9700\u6C42\u6E05\u6670\u5EA6: ${Math.round(clarityResult.score * 100)}%`));
|
|
127
|
+
lines.push("");
|
|
128
|
+
if (clarityResult.score < CLARITY_THRESHOLD && clarityResult.questions.length > 0) {
|
|
129
|
+
lines.push(chalk9.yellow(" \u9700\u6C42\u4E0D\u591F\u660E\u786E\uFF0C\u8BF7\u8865\u5145\u4EE5\u4E0B\u4FE1\u606F\uFF1A"));
|
|
130
|
+
lines.push("");
|
|
131
|
+
const unansweredQuestions = clarityResult.questions.filter((q) => !q.answered);
|
|
132
|
+
for (let i = 0; i < Math.min(unansweredQuestions.length, 3); i++) {
|
|
133
|
+
const q = unansweredQuestions[i];
|
|
134
|
+
lines.push(chalk9.white(` ${i + 1}. ${q.question}`));
|
|
411
135
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
targetStepRecord.status = "running";
|
|
415
|
-
targetStepRecord.startedAt = /* @__PURE__ */ new Date();
|
|
136
|
+
if (unansweredQuestions.length > 3) {
|
|
137
|
+
lines.push(chalk9.gray(` ... \u8FD8\u6709 ${unansweredQuestions.length - 3} \u4E2A\u95EE\u9898`));
|
|
416
138
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
await this.saveState();
|
|
421
|
-
await this.updateChangeRecord();
|
|
422
|
-
return {
|
|
423
|
-
from: currentStep,
|
|
424
|
-
to: targetStep,
|
|
425
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
426
|
-
reason
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* 获取可回滚的目标阶段
|
|
431
|
-
*/
|
|
432
|
-
getRollbackTargets() {
|
|
433
|
-
if (!this.state) return [];
|
|
434
|
-
const allTargets = ROLLBACK_RULES[this.state.currentStep] || [];
|
|
435
|
-
const workflowSteps = this.state.type === "complex" ? COMPLEX_STEPS : SIMPLE_STEPS;
|
|
436
|
-
return allTargets.filter((target) => workflowSteps.includes(target));
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* 检查是否可以回滚到指定阶段
|
|
440
|
-
*/
|
|
441
|
-
canRollbackTo(targetStep) {
|
|
442
|
-
const targets = this.getRollbackTargets();
|
|
443
|
-
return targets.includes(targetStep);
|
|
139
|
+
lines.push("");
|
|
140
|
+
lines.push(chalk9.gray(' \u8BF7\u8F93\u5165\u56DE\u7B54\uFF0C\u6216\u8F93\u5165 "done" \u8DF3\u8FC7\u6F84\u6E05'));
|
|
141
|
+
return { output: lines.join("\n") };
|
|
444
142
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
...snapshot.state,
|
|
479
|
-
createdAt: previousState.createdAt,
|
|
480
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
481
|
-
};
|
|
482
|
-
const stepOrder = this.state.type === "complex" ? COMPLEX_STEPS : SIMPLE_STEPS;
|
|
483
|
-
const targetIndex = stepOrder.indexOf(targetStep);
|
|
484
|
-
for (let i = 0; i < this.state.steps.length; i++) {
|
|
485
|
-
const step = this.state.steps[i];
|
|
486
|
-
const stepIndex = stepOrder.indexOf(step.step);
|
|
487
|
-
if (stepIndex < targetIndex) {
|
|
488
|
-
step.status = "completed";
|
|
489
|
-
} else if (stepIndex === targetIndex) {
|
|
490
|
-
step.status = "running";
|
|
491
|
-
step.startedAt = /* @__PURE__ */ new Date();
|
|
492
|
-
step.completedAt = void 0;
|
|
493
|
-
} else {
|
|
494
|
-
step.status = "pending";
|
|
495
|
-
step.startedAt = void 0;
|
|
496
|
-
step.completedAt = void 0;
|
|
497
|
-
}
|
|
143
|
+
lines.push(chalk9.green(" \u2713 \u9700\u6C42\u6E05\u6670\uFF0C\u7EE7\u7EED\u4E0B\u4E00\u6B65"));
|
|
144
|
+
activeSession.phase = "analysis";
|
|
145
|
+
}
|
|
146
|
+
if (activeSession.phase === "analysis") {
|
|
147
|
+
lines.push("");
|
|
148
|
+
lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 3/8: \u590D\u6742\u5EA6\u8BC4\u4F30 \u2501\u2501\u2501"));
|
|
149
|
+
lines.push("");
|
|
150
|
+
activeSession.complexity = analyzeComplexity(
|
|
151
|
+
activeSession.refinedRequirement,
|
|
152
|
+
activeSession.context
|
|
153
|
+
);
|
|
154
|
+
const complexityBar = generateComplexityBar(activeSession.complexity);
|
|
155
|
+
lines.push(chalk9.gray(` \u590D\u6742\u5EA6: ${complexityBar} ${activeSession.complexity}/10`));
|
|
156
|
+
if (activeSession.complexity >= COMPLEXITY_THRESHOLD) {
|
|
157
|
+
lines.push(chalk9.yellow(" \u5224\u5B9A: \u590D\u6742\u9700\u6C42\uFF0C\u5EFA\u8BAE\u67B6\u6784\u5E08\u4ECB\u5165"));
|
|
158
|
+
} else {
|
|
159
|
+
lines.push(chalk9.green(" \u5224\u5B9A: \u7B80\u5355\u9700\u6C42\uFF0C\u76F4\u63A5\u8FDB\u5165\u89C4\u683C\u62C6\u89E3"));
|
|
160
|
+
}
|
|
161
|
+
activeSession.phase = "bdd";
|
|
162
|
+
}
|
|
163
|
+
if (activeSession.phase === "bdd") {
|
|
164
|
+
lines.push("");
|
|
165
|
+
lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 4/8: BDD \u573A\u666F\u62C6\u89E3 \u2501\u2501\u2501"));
|
|
166
|
+
lines.push("");
|
|
167
|
+
activeSession.bddScenarios = generateBDDScenarios(
|
|
168
|
+
activeSession.refinedRequirement,
|
|
169
|
+
activeSession.context,
|
|
170
|
+
activeSession.clarificationQuestions
|
|
171
|
+
);
|
|
172
|
+
for (const scenario of activeSession.bddScenarios) {
|
|
173
|
+
lines.push(chalk9.white(` Feature: ${scenario.feature}`));
|
|
174
|
+
for (const s of scenario.scenarios.slice(0, 3)) {
|
|
175
|
+
lines.push(chalk9.gray(` - ${s.name}`));
|
|
498
176
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
for (const type of confirmationsToClear) {
|
|
502
|
-
this.confirmationManager.clearConfirmation(type);
|
|
177
|
+
if (scenario.scenarios.length > 3) {
|
|
178
|
+
lines.push(chalk9.gray(` ... \u5171 ${scenario.scenarios.length} \u4E2A\u573A\u666F`));
|
|
503
179
|
}
|
|
504
|
-
await this.saveState();
|
|
505
|
-
await this.updateChangeRecord("rolled-back");
|
|
506
|
-
return {
|
|
507
|
-
success: true,
|
|
508
|
-
from: currentStep,
|
|
509
|
-
to: targetStep,
|
|
510
|
-
message: `\u5DF2\u56DE\u6EDA: ${currentStep} \u2192 ${targetStep}\uFF0C\u539F\u56E0: ${description || reason}`,
|
|
511
|
-
snapshot
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
/**
|
|
515
|
-
* 获取回滚原因描述
|
|
516
|
-
*/
|
|
517
|
-
getRollbackReasonDescription(reason) {
|
|
518
|
-
const descriptions = {
|
|
519
|
-
"code-review-failed": "\u4EE3\u7801\u5BA1\u67E5\u672A\u901A\u8FC7",
|
|
520
|
-
"requirement-changed": "\u9700\u6C42\u53D8\u66F4",
|
|
521
|
-
"design-issue": "\u8BBE\u8BA1\u95EE\u9898",
|
|
522
|
-
"user-request": "\u7528\u6237\u8BF7\u6C42"
|
|
523
|
-
};
|
|
524
|
-
return descriptions[reason];
|
|
525
|
-
}
|
|
526
|
-
/**
|
|
527
|
-
* 获取当前状态
|
|
528
|
-
*/
|
|
529
|
-
getState() {
|
|
530
|
-
return this.state;
|
|
531
180
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
|
|
181
|
+
activeSession.phase = "spec";
|
|
182
|
+
}
|
|
183
|
+
if (activeSession.phase === "spec") {
|
|
184
|
+
lines.push("");
|
|
185
|
+
lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 5/8: OpenSpec \u89C4\u683C \u2501\u2501\u2501"));
|
|
186
|
+
lines.push("");
|
|
187
|
+
activeSession.specItems = generateSpecItems(
|
|
188
|
+
activeSession.refinedRequirement,
|
|
189
|
+
activeSession.context,
|
|
190
|
+
activeSession.bddScenarios,
|
|
191
|
+
activeSession.clarificationQuestions
|
|
192
|
+
);
|
|
193
|
+
const specPath = await saveSpecFile(ctx.options.workingDirectory, activeSession);
|
|
194
|
+
lines.push(chalk9.green(" \u2713 \u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210"));
|
|
195
|
+
lines.push(chalk9.gray(` \u8DEF\u5F84: ${specPath}`));
|
|
196
|
+
lines.push("");
|
|
197
|
+
lines.push(chalk9.cyan(" \u4EFB\u52A1\u6982\u89C8:"));
|
|
198
|
+
for (const item of activeSession.specItems.slice(0, 5)) {
|
|
199
|
+
const icon = item.priority === "high" ? "\u{1F534}" : item.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
200
|
+
lines.push(chalk9.gray(` ${icon} [${item.id}] ${item.title}`));
|
|
201
|
+
}
|
|
202
|
+
if (activeSession.specItems.length > 5) {
|
|
203
|
+
lines.push(chalk9.gray(` ... \u5171 ${activeSession.specItems.length} \u4E2A\u4EFB\u52A1`));
|
|
204
|
+
}
|
|
205
|
+
lines.push("");
|
|
206
|
+
lines.push(chalk9.yellow.bold("\u23F8\uFE0F \u7B49\u5F85\u89C4\u683C\u786E\u8BA4"));
|
|
207
|
+
lines.push("");
|
|
208
|
+
lines.push(chalk9.green(" y - \u786E\u8BA4\u89C4\u683C\uFF0C\u7EE7\u7EED\u5DE5\u4F5C\u6D41"));
|
|
209
|
+
lines.push(chalk9.red(" n - \u4E0D\u6EE1\u610F\uFF0C\u91CD\u65B0\u751F\u6210\u89C4\u683C"));
|
|
210
|
+
lines.push(chalk9.gray(" c - \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41"));
|
|
211
|
+
return { output: lines.join("\n") };
|
|
212
|
+
}
|
|
213
|
+
if (activeSession.phase === "tdd") {
|
|
214
|
+
lines.push("");
|
|
215
|
+
lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 6/8: TDD \u6D4B\u8BD5\u751F\u6210 \u2501\u2501\u2501"));
|
|
216
|
+
lines.push("");
|
|
217
|
+
activeSession.testFiles = await generateTests(ctx.options.workingDirectory, activeSession);
|
|
218
|
+
lines.push(chalk9.green(" \u2713 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u751F\u6210"));
|
|
219
|
+
for (const file of activeSession.testFiles) {
|
|
220
|
+
lines.push(chalk9.gray(` - ${file}`));
|
|
221
|
+
}
|
|
222
|
+
activeSession.phase = "develop";
|
|
223
|
+
}
|
|
224
|
+
if (activeSession.phase === "develop") {
|
|
225
|
+
lines.push("");
|
|
226
|
+
lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 7/8: \u5F00\u53D1\u5B9E\u73B0 \u2501\u2501\u2501"));
|
|
227
|
+
lines.push("");
|
|
228
|
+
lines.push(chalk9.yellow(" \u{1F4DD} \u5F00\u53D1\u9636\u6BB5"));
|
|
229
|
+
lines.push(chalk9.gray(" \u8BF7\u8C03\u7528 $frontend-dev \u6267\u884C\u5F00\u53D1\u4EFB\u52A1"));
|
|
230
|
+
lines.push(chalk9.gray(" \u6216\u624B\u52A8\u5B9E\u73B0\u4EE3\u7801\u540E\u8F93\u5165 continue \u7EE7\u7EED"));
|
|
231
|
+
return { output: lines.join("\n") };
|
|
232
|
+
}
|
|
233
|
+
if (activeSession.phase === "review") {
|
|
234
|
+
lines.push("");
|
|
235
|
+
lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 8/8: \u4EE3\u7801\u5BA1\u6838 \u2501\u2501\u2501"));
|
|
236
|
+
lines.push("");
|
|
237
|
+
lines.push(chalk9.yellow(" \u{1F50D} \u4EE3\u7801\u5BA1\u6838\u9636\u6BB5"));
|
|
238
|
+
lines.push(chalk9.gray(" \u8BF7\u8C03\u7528 $code-reviewer \u6267\u884C\u4EE3\u7801\u5BA1\u6838"));
|
|
239
|
+
lines.push(chalk9.gray(" \u6216\u8F93\u5165 review \u5B8C\u6210\u5BA1\u6838"));
|
|
240
|
+
}
|
|
241
|
+
return { output: lines.join("\n") };
|
|
242
|
+
} catch (error) {
|
|
243
|
+
lines.push(chalk9.red(`\u9519\u8BEF: ${error.message}`));
|
|
244
|
+
return { output: lines.join("\n") };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function handleWorkflowInput(input, ctx) {
|
|
248
|
+
if (!activeSession) return null;
|
|
249
|
+
const trimmed = input.trim().toLowerCase();
|
|
250
|
+
if (trimmed === "c" || trimmed === "cancel" || trimmed === "\u53D6\u6D88") {
|
|
251
|
+
activeSession = null;
|
|
252
|
+
return {
|
|
253
|
+
output: chalk9.yellow("\u2713 \u5DE5\u4F5C\u6D41\u5DF2\u53D6\u6D88")
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (activeSession.phase === "clarify") {
|
|
257
|
+
if (trimmed === "done" || trimmed === "\u5B8C\u6210" || trimmed === "\u8DF3\u8FC7" || trimmed === "skip") {
|
|
258
|
+
activeSession.refinedRequirement = buildRefinedRequirement(activeSession);
|
|
259
|
+
activeSession.phase = "analysis";
|
|
260
|
+
return executeWorkflow(ctx);
|
|
261
|
+
}
|
|
262
|
+
activeSession.clarificationQuestions.filter((q) => q.answered).length;
|
|
263
|
+
const unansweredQuestions = activeSession.clarificationQuestions.filter((q) => !q.answered);
|
|
264
|
+
if (unansweredQuestions.length > 0) {
|
|
265
|
+
const question = unansweredQuestions[0];
|
|
266
|
+
question.answer = input.trim();
|
|
267
|
+
question.answered = true;
|
|
268
|
+
activeSession.refinedRequirement = buildRefinedRequirement(activeSession);
|
|
269
|
+
const remaining = activeSession.clarificationQuestions.filter((q) => !q.answered);
|
|
270
|
+
if (remaining.length > 0) {
|
|
271
|
+
activeSession.clarityScore = calculateClarityScore(activeSession.clarificationQuestions);
|
|
272
|
+
const lines = [];
|
|
273
|
+
lines.push(chalk9.green(" \u2713 \u5DF2\u8BB0\u5F55"));
|
|
274
|
+
lines.push("");
|
|
275
|
+
lines.push(chalk9.yellow(" \u7EE7\u7EED\u56DE\u7B54\uFF1A"));
|
|
276
|
+
for (let i = 0; i < Math.min(remaining.length, 3); i++) {
|
|
277
|
+
const q = remaining[i];
|
|
278
|
+
lines.push(chalk9.white(` ${i + 1}. ${q.question}`));
|
|
553
279
|
}
|
|
554
|
-
|
|
280
|
+
lines.push("");
|
|
281
|
+
lines.push(chalk9.gray(' \u8F93\u5165\u56DE\u7B54\uFF0C\u6216 "done" \u8DF3\u8FC7'));
|
|
282
|
+
return { output: lines.join("\n") };
|
|
555
283
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
284
|
+
activeSession.clarityScore = 1;
|
|
285
|
+
activeSession.phase = "analysis";
|
|
286
|
+
return executeWorkflow(ctx);
|
|
287
|
+
}
|
|
288
|
+
activeSession.phase = "analysis";
|
|
289
|
+
return executeWorkflow(ctx);
|
|
290
|
+
}
|
|
291
|
+
if (activeSession.phase === "spec") {
|
|
292
|
+
if (trimmed === "y" || trimmed === "yes" || trimmed === "\u786E\u8BA4") {
|
|
293
|
+
activeSession.phase = "tdd";
|
|
294
|
+
return executeWorkflow(ctx);
|
|
295
|
+
}
|
|
296
|
+
if (trimmed === "n" || trimmed === "no" || trimmed === "\u91CD\u65B0") {
|
|
297
|
+
activeSession.bddScenarios = generateBDDScenarios(
|
|
298
|
+
activeSession.refinedRequirement,
|
|
299
|
+
activeSession.context,
|
|
300
|
+
activeSession.clarificationQuestions
|
|
301
|
+
);
|
|
302
|
+
activeSession.specItems = generateSpecItems(
|
|
303
|
+
activeSession.refinedRequirement,
|
|
304
|
+
activeSession.context,
|
|
305
|
+
activeSession.bddScenarios,
|
|
306
|
+
activeSession.clarificationQuestions
|
|
307
|
+
);
|
|
308
|
+
const specPath = await saveSpecFile(ctx.options.workingDirectory, activeSession);
|
|
309
|
+
return {
|
|
310
|
+
output: chalk9.cyan("\u{1F504} \u89C4\u683C\u5DF2\u91CD\u65B0\u751F\u6210") + chalk9.gray(`
|
|
311
|
+
\u8DEF\u5F84: ${specPath}`) + chalk9.yellow("\n\n\u8BF7\u786E\u8BA4:") + chalk9.green("\n y - \u786E\u8BA4\u89C4\u683C") + chalk9.red("\n n - \u518D\u6B21\u91CD\u65B0\u751F\u6210")
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (activeSession.phase === "develop") {
|
|
316
|
+
if (trimmed === "continue" || trimmed === "\u7EE7\u7EED" || trimmed === "done" || trimmed === "\u5B8C\u6210") {
|
|
317
|
+
activeSession.phase = "review";
|
|
318
|
+
return executeWorkflow(ctx);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (activeSession.phase === "review") {
|
|
322
|
+
if (trimmed === "review" || trimmed === "\u5BA1\u6838" || trimmed === "pass" || trimmed === "\u901A\u8FC7") {
|
|
323
|
+
await archiveWorkflow(ctx.options.workingDirectory);
|
|
324
|
+
const summary = activeSession.refinedRequirement;
|
|
325
|
+
activeSession = null;
|
|
326
|
+
return {
|
|
327
|
+
output: chalk9.green("\u2713 \u5DE5\u4F5C\u6D41\u5DF2\u5B8C\u6210") + chalk9.gray(`
|
|
328
|
+
\u9700\u6C42: ${summary.slice(0, 60)}...`) + chalk9.cyan("\n\n\u4F7F\u7528 /new <\u9700\u6C42> \u5F00\u59CB\u65B0\u7684\u5DE5\u4F5C\u6D41")
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
if (trimmed === "fail" || trimmed === "\u5931\u8D25" || trimmed === "reject" || trimmed === "\u62D2\u7EDD") {
|
|
332
|
+
activeSession.phase = "spec";
|
|
333
|
+
return {
|
|
334
|
+
output: chalk9.yellow("\u21A9\uFE0F \u5BA1\u6838\u672A\u901A\u8FC7\uFF0C\u56DE\u9000\u5230\u89C4\u683C\u9636\u6BB5") + chalk9.gray("\n\u8BF7\u91CD\u65B0\u786E\u8BA4\u6216\u4FEE\u6539\u89C4\u683C") + chalk9.yellow("\n\n\u4F7F\u7528 y \u786E\u8BA4\uFF0Cn \u91CD\u65B0\u751F\u6210")
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
function analyzeRequirementClarity(requirement, context) {
|
|
341
|
+
const questions = [];
|
|
342
|
+
let score = 0.5;
|
|
343
|
+
if (requirement.length < 10) {
|
|
344
|
+
score -= 0.2;
|
|
345
|
+
questions.push({
|
|
346
|
+
id: "q-length",
|
|
347
|
+
question: "\u9700\u6C42\u63CF\u8FF0\u8F83\u77ED\uFF0C\u80FD\u5426\u8BE6\u7EC6\u8BF4\u660E\u5177\u4F53\u8981\u5B9E\u73B0\u4EC0\u4E48\u529F\u80FD\uFF1F",
|
|
348
|
+
category: "scope",
|
|
349
|
+
answered: false
|
|
350
|
+
});
|
|
351
|
+
} else if (requirement.length >= 50) {
|
|
352
|
+
score += 0.1;
|
|
353
|
+
}
|
|
354
|
+
const actionKeywords = ["\u5B9E\u73B0", "\u6DFB\u52A0", "\u4FEE\u6539", "\u5220\u9664", "\u4F18\u5316", "\u4FEE\u590D", "\u91CD\u6784", "\u5F00\u53D1", "\u521B\u5EFA", "\u8BBE\u8BA1"];
|
|
355
|
+
const hasAction = actionKeywords.some((kw) => requirement.includes(kw));
|
|
356
|
+
if (!hasAction) {
|
|
357
|
+
score -= 0.15;
|
|
358
|
+
questions.push({
|
|
359
|
+
id: "q-action",
|
|
360
|
+
question: "\u8FD9\u662F\u4E00\u4E2A\u65B0\u529F\u80FD\u3001\u4FEE\u6539\u8FD8\u662F\u4FEE\u590D\uFF1F\u8BF7\u8BF4\u660E\u5177\u4F53\u8981\u505A\u4EC0\u4E48\u3002",
|
|
361
|
+
category: "scope",
|
|
362
|
+
answered: false
|
|
363
|
+
});
|
|
364
|
+
} else {
|
|
365
|
+
score += 0.1;
|
|
366
|
+
}
|
|
367
|
+
if (requirement.match(/界面|页面|组件|按钮|表单|弹窗|布局|样式|UI/)) {
|
|
368
|
+
score += 0.1;
|
|
369
|
+
if (!requirement.match(/在.*页面|在.*位置|显示在|位于/)) {
|
|
370
|
+
questions.push({
|
|
371
|
+
id: "q-ui-position",
|
|
372
|
+
question: "\u8FD9\u4E2A\u529F\u80FD\u5E94\u8BE5\u653E\u5728\u54EA\u4E2A\u9875\u9762\u6216\u4F4D\u7F6E\uFF1F",
|
|
373
|
+
category: "ui",
|
|
374
|
+
answered: false
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
questions.push({
|
|
379
|
+
id: "q-ui-need",
|
|
380
|
+
question: "\u8FD9\u4E2A\u529F\u80FD\u9700\u8981\u7528\u6237\u754C\u9762\u5417\uFF1F\u5982\u679C\u9700\u8981\uFF0C\u5927\u6982\u957F\u4EC0\u4E48\u6837\uFF1F",
|
|
381
|
+
category: "ui",
|
|
382
|
+
answered: false
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
if (requirement.match(/数据|存储|保存|读取|API|接口|数据库|缓存/)) {
|
|
386
|
+
score += 0.1;
|
|
387
|
+
if (!requirement.match(/从.*获取|调用.*接口|读取.*数据|来源/)) {
|
|
388
|
+
questions.push({
|
|
389
|
+
id: "q-data-source",
|
|
390
|
+
question: "\u6570\u636E\u4ECE\u54EA\u91CC\u6765\uFF1F\u662F\u8C03\u7528API\u3001\u672C\u5730\u5B58\u50A8\u8FD8\u662F\u5176\u4ED6\u6765\u6E90\uFF1F",
|
|
391
|
+
category: "data",
|
|
392
|
+
answered: false
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (requirement.match(/点击|输入|选择|拖动|滑动|交互|操作/)) {
|
|
397
|
+
score += 0.1;
|
|
398
|
+
} else {
|
|
399
|
+
if (hasAction && requirement.match(/功能|特性|模块/)) {
|
|
400
|
+
questions.push({
|
|
401
|
+
id: "q-interaction",
|
|
402
|
+
question: "\u7528\u6237\u5982\u4F55\u64CD\u4F5C\u8FD9\u4E2A\u529F\u80FD\uFF1F\u6709\u4EC0\u4E48\u4EA4\u4E92\u6D41\u7A0B\uFF1F",
|
|
403
|
+
category: "interaction",
|
|
404
|
+
answered: false
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (requirement.match(/异常|错误|失败|边界|特殊情况|空值|验证/)) {
|
|
409
|
+
score += 0.15;
|
|
410
|
+
} else {
|
|
411
|
+
if (requirement.match(/功能|输入|表单|数据/)) {
|
|
412
|
+
questions.push({
|
|
413
|
+
id: "q-edge",
|
|
414
|
+
question: "\u6709\u4EC0\u4E48\u7279\u6B8A\u60C5\u51B5\u6216\u8FB9\u754C\u6761\u4EF6\u9700\u8981\u5904\u7406\uFF1F\u6BD4\u5982\u8F93\u5165\u4E3A\u7A7A\u3001\u6570\u636E\u52A0\u8F7D\u5931\u8D25\u7B49\u3002",
|
|
415
|
+
category: "edge",
|
|
416
|
+
answered: false
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (requirement.match(/https?:\/\/|参考|参照|类似/)) {
|
|
421
|
+
score += 0.15;
|
|
422
|
+
}
|
|
423
|
+
if (context.framework) {
|
|
424
|
+
score += 0.05;
|
|
425
|
+
}
|
|
426
|
+
const limitedQuestions = questions.slice(0, 5);
|
|
427
|
+
score = Math.max(0, Math.min(1, score));
|
|
428
|
+
return { score, questions: limitedQuestions };
|
|
429
|
+
}
|
|
430
|
+
function calculateClarityScore(questions) {
|
|
431
|
+
const answered = questions.filter((q) => q.answered).length;
|
|
432
|
+
if (questions.length === 0) return 1;
|
|
433
|
+
return 0.5 + answered / questions.length * 0.5;
|
|
434
|
+
}
|
|
435
|
+
function buildRefinedRequirement(session) {
|
|
436
|
+
const parts = [session.requirement];
|
|
437
|
+
for (const q of session.clarificationQuestions) {
|
|
438
|
+
if (q.answered && q.answer) {
|
|
439
|
+
parts.push(`
|
|
440
|
+
\u3010${getCategoryLabel(q.category)}\u3011${q.answer}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return parts.join("");
|
|
444
|
+
}
|
|
445
|
+
function getCategoryLabel(category) {
|
|
446
|
+
const labels = {
|
|
447
|
+
scope: "\u8303\u56F4",
|
|
448
|
+
ui: "\u754C\u9762",
|
|
449
|
+
data: "\u6570\u636E",
|
|
450
|
+
interaction: "\u4EA4\u4E92",
|
|
451
|
+
edge: "\u8FB9\u754C",
|
|
452
|
+
tech: "\u6280\u672F"
|
|
453
|
+
};
|
|
454
|
+
return labels[category] || category;
|
|
455
|
+
}
|
|
456
|
+
async function readProjectContext(workingDir) {
|
|
457
|
+
const context = {
|
|
458
|
+
name: path5.basename(workingDir),
|
|
459
|
+
type: "unknown",
|
|
460
|
+
framework: null,
|
|
461
|
+
techStack: [],
|
|
462
|
+
description: "",
|
|
463
|
+
devStandards: "",
|
|
464
|
+
agentsMd: "",
|
|
465
|
+
configYaml: ""
|
|
466
|
+
};
|
|
467
|
+
const agentsPath = path5.join(workingDir, "AGENTS.md");
|
|
468
|
+
try {
|
|
469
|
+
const stats = await fs4.stat(agentsPath);
|
|
470
|
+
if (stats.size <= MAX_FILE_SIZE2) {
|
|
471
|
+
context.agentsMd = await fs4.readFile(agentsPath, "utf-8");
|
|
472
|
+
const nameMatch = context.agentsMd.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
|
|
473
|
+
if (nameMatch) context.name = nameMatch[1];
|
|
474
|
+
const typeMatch = context.agentsMd.match(/\|\s*项目类型\s*\|\s*([^\s|]+)/);
|
|
475
|
+
if (typeMatch) context.type = typeMatch[1];
|
|
476
|
+
const frameworkMatch = context.agentsMd.match(/\|\s*技术框架\s*\|\s*([^\s|]+)/);
|
|
477
|
+
if (frameworkMatch && frameworkMatch[1] !== "\u5F85\u8BC6\u522B") {
|
|
478
|
+
context.framework = frameworkMatch[1];
|
|
584
479
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
try {
|
|
607
|
-
const content = await fs4.readFile(changeFile, "utf-8");
|
|
608
|
-
const parsed = this.parseChangeRecord(content);
|
|
609
|
-
if (parsed && parsed.status === "running") {
|
|
610
|
-
this.state = parsed;
|
|
611
|
-
return true;
|
|
612
|
-
}
|
|
613
|
-
} catch {
|
|
614
|
-
}
|
|
615
|
-
return false;
|
|
480
|
+
}
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
const configPath = path5.join(workingDir, "openspec", "config.yaml");
|
|
484
|
+
try {
|
|
485
|
+
const stats = await fs4.stat(configPath);
|
|
486
|
+
if (stats.size <= MAX_FILE_SIZE2) {
|
|
487
|
+
context.configYaml = await fs4.readFile(configPath, "utf-8");
|
|
488
|
+
const nameMatch = context.configYaml.match(/name:\s*(.+)/);
|
|
489
|
+
if (nameMatch) context.name = nameMatch[1].trim();
|
|
490
|
+
const typeMatch = context.configYaml.match(/type:\s*(.+)/);
|
|
491
|
+
if (typeMatch) context.type = typeMatch[1].trim();
|
|
492
|
+
const frameworkMatch = context.configYaml.match(/framework:\s*(.+)/);
|
|
493
|
+
if (frameworkMatch && frameworkMatch[1].trim() !== "null") {
|
|
494
|
+
context.framework = frameworkMatch[1].trim();
|
|
495
|
+
}
|
|
496
|
+
const techStackMatch = context.configYaml.match(/techStack:\s*([\s\S]+?)(?=\n\w)/);
|
|
497
|
+
if (techStackMatch) {
|
|
498
|
+
const techLines = techStackMatch[1].match(/-\s*(.+)/g);
|
|
499
|
+
if (techLines) {
|
|
500
|
+
context.techStack = techLines.map((l) => l.replace(/-\s*/, "").trim());
|
|
616
501
|
}
|
|
617
502
|
}
|
|
618
|
-
/**
|
|
619
|
-
* 获取允许的下一步
|
|
620
|
-
*/
|
|
621
|
-
getAllowedTransitions() {
|
|
622
|
-
if (!this.state) return [];
|
|
623
|
-
return TRANSITIONS[this.state.currentStep] || [];
|
|
624
|
-
}
|
|
625
|
-
/**
|
|
626
|
-
* 获取快照历史
|
|
627
|
-
*/
|
|
628
|
-
getSnapshots() {
|
|
629
|
-
return Array.from(this.snapshots.values()).sort(
|
|
630
|
-
(a, b) => a.timestamp.getTime() - b.timestamp.getTime()
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
/**
|
|
634
|
-
* 完成当前步骤
|
|
635
|
-
*/
|
|
636
|
-
async completeCurrentStep(artifacts) {
|
|
637
|
-
if (!this.state) return;
|
|
638
|
-
const currentStepRecord = this.state.steps.find(
|
|
639
|
-
(s) => s.step === this.state.currentStep
|
|
640
|
-
);
|
|
641
|
-
if (currentStepRecord) {
|
|
642
|
-
currentStepRecord.status = "completed";
|
|
643
|
-
currentStepRecord.completedAt = /* @__PURE__ */ new Date();
|
|
644
|
-
}
|
|
645
|
-
if (artifacts) {
|
|
646
|
-
this.state.artifacts.push(...artifacts);
|
|
647
|
-
}
|
|
648
|
-
await this.createSnapshot();
|
|
649
|
-
await this.saveState();
|
|
650
|
-
}
|
|
651
|
-
/**
|
|
652
|
-
* 归档工作流
|
|
653
|
-
*/
|
|
654
|
-
async archive(summary) {
|
|
655
|
-
if (!this.state) return;
|
|
656
|
-
const changeId = this.state.id;
|
|
657
|
-
for (const step of this.state.steps) {
|
|
658
|
-
if (step.status !== "completed") {
|
|
659
|
-
step.status = "completed";
|
|
660
|
-
step.completedAt = /* @__PURE__ */ new Date();
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
this.state.status = "completed";
|
|
664
|
-
this.state.completedAt = /* @__PURE__ */ new Date();
|
|
665
|
-
await this.createSpecDocument(summary);
|
|
666
|
-
await this.updateChangeRecord("archived");
|
|
667
|
-
await this.saveState();
|
|
668
|
-
const changesDir = path5.join(this.openspecPath, "changes");
|
|
669
|
-
const archiveDir = path5.join(changesDir, "archive");
|
|
670
|
-
const changeFile = path5.join(changesDir, `${changeId}.md`);
|
|
671
|
-
const archiveFile = path5.join(archiveDir, `${changeId}.md`);
|
|
672
|
-
await fs4.mkdir(archiveDir, { recursive: true });
|
|
673
|
-
await fs4.rename(changeFile, archiveFile).catch(() => {
|
|
674
|
-
});
|
|
675
|
-
this.state = null;
|
|
676
|
-
this.snapshots.clear();
|
|
677
|
-
this.confirmationManager.clearAllConfirmations();
|
|
678
|
-
}
|
|
679
|
-
/**
|
|
680
|
-
* 取消工作流
|
|
681
|
-
*/
|
|
682
|
-
async cancel(reason) {
|
|
683
|
-
if (!this.state) return;
|
|
684
|
-
this.state.status = "cancelled";
|
|
685
|
-
this.state.cancelledAt = /* @__PURE__ */ new Date();
|
|
686
|
-
this.state.cancelReason = reason;
|
|
687
|
-
await this.updateChangeRecord("cancelled");
|
|
688
|
-
await this.saveState();
|
|
689
|
-
this.state = null;
|
|
690
|
-
this.snapshots.clear();
|
|
691
|
-
this.confirmationManager.clearAllConfirmations();
|
|
692
|
-
}
|
|
693
|
-
// ==================== 私有方法 ====================
|
|
694
|
-
async ensureDirectories() {
|
|
695
|
-
const changesDir = path5.join(this.openspecPath, "changes");
|
|
696
|
-
const archiveDir = path5.join(changesDir, "archive");
|
|
697
|
-
const specDir = path5.join(this.openspecPath, "spec");
|
|
698
|
-
const statesDir = path5.join(this.openspecPath, ".workflow-states");
|
|
699
|
-
await fs4.mkdir(archiveDir, { recursive: true });
|
|
700
|
-
await fs4.mkdir(specDir, { recursive: true });
|
|
701
|
-
await fs4.mkdir(statesDir, { recursive: true });
|
|
702
|
-
}
|
|
703
|
-
async restoreState() {
|
|
704
|
-
const activePath = path5.join(this.openspecPath, ".workflow-active.json");
|
|
705
|
-
let activeId = null;
|
|
706
|
-
try {
|
|
707
|
-
const activeContent = await fs4.readFile(activePath, "utf-8");
|
|
708
|
-
const activeData = JSON.parse(activeContent);
|
|
709
|
-
activeId = activeData.activeId;
|
|
710
|
-
} catch {
|
|
711
|
-
}
|
|
712
|
-
if (activeId) {
|
|
713
|
-
const statePath = path5.join(this.openspecPath, ".workflow-states", `${activeId}.json`);
|
|
714
|
-
try {
|
|
715
|
-
const content = await fs4.readFile(statePath, "utf-8");
|
|
716
|
-
this.state = JSON.parse(content, (key, value) => {
|
|
717
|
-
if (key.endsWith("At") && typeof value === "string") {
|
|
718
|
-
return new Date(value);
|
|
719
|
-
}
|
|
720
|
-
return value;
|
|
721
|
-
});
|
|
722
|
-
return;
|
|
723
|
-
} catch {
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
const oldStatePath = path5.join(this.openspecPath, ".workflow-state.json");
|
|
727
|
-
try {
|
|
728
|
-
const content = await fs4.readFile(oldStatePath, "utf-8");
|
|
729
|
-
this.state = JSON.parse(content, (key, value) => {
|
|
730
|
-
if (key.endsWith("At") && typeof value === "string") {
|
|
731
|
-
return new Date(value);
|
|
732
|
-
}
|
|
733
|
-
return value;
|
|
734
|
-
});
|
|
735
|
-
if (this.state) {
|
|
736
|
-
await this.saveState();
|
|
737
|
-
await fs4.unlink(oldStatePath).catch(() => {
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
} catch (e) {
|
|
741
|
-
const err = e;
|
|
742
|
-
if (err.code !== "ENOENT") {
|
|
743
|
-
console.warn("\u8B66\u544A: \u5DE5\u4F5C\u6D41\u72B6\u6001\u6587\u4EF6\u5DF2\u635F\u574F\uFF0C\u5C06\u91CD\u65B0\u5F00\u59CB");
|
|
744
|
-
}
|
|
745
|
-
this.state = null;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
async saveState() {
|
|
749
|
-
if (!this.state) return;
|
|
750
|
-
const statesDir = path5.join(this.openspecPath, ".workflow-states");
|
|
751
|
-
await fs4.mkdir(statesDir, { recursive: true });
|
|
752
|
-
const statePath = path5.join(statesDir, `${this.state.id}.json`);
|
|
753
|
-
await fs4.writeFile(statePath, JSON.stringify(this.state, null, 2));
|
|
754
|
-
const activePath = path5.join(this.openspecPath, ".workflow-active.json");
|
|
755
|
-
await fs4.writeFile(activePath, JSON.stringify({ activeId: this.state.id }, null, 2));
|
|
756
|
-
}
|
|
757
|
-
async restoreSnapshots() {
|
|
758
|
-
const snapshotsPath = path5.join(this.openspecPath, ".workflow-snapshots.json");
|
|
759
|
-
try {
|
|
760
|
-
const content = await fs4.readFile(snapshotsPath, "utf-8");
|
|
761
|
-
const data = JSON.parse(content, (key, value) => {
|
|
762
|
-
if (key === "timestamp" && typeof value === "string") {
|
|
763
|
-
return new Date(value);
|
|
764
|
-
}
|
|
765
|
-
return value;
|
|
766
|
-
});
|
|
767
|
-
if (Array.isArray(data)) {
|
|
768
|
-
this.snapshots = new Map(data.map((s) => [s.step, s]));
|
|
769
|
-
}
|
|
770
|
-
} catch {
|
|
771
|
-
this.snapshots = /* @__PURE__ */ new Map();
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
async saveSnapshots() {
|
|
775
|
-
const snapshotsPath = path5.join(this.openspecPath, ".workflow-snapshots.json");
|
|
776
|
-
const data = Array.from(this.snapshots.values());
|
|
777
|
-
await fs4.writeFile(snapshotsPath, JSON.stringify(data, null, 2));
|
|
778
|
-
}
|
|
779
|
-
async createSnapshot() {
|
|
780
|
-
if (!this.state) return;
|
|
781
|
-
const snapshot = {
|
|
782
|
-
step: this.state.currentStep,
|
|
783
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
784
|
-
state: JSON.parse(JSON.stringify(this.state)),
|
|
785
|
-
artifacts: [...this.state.artifacts]
|
|
786
|
-
};
|
|
787
|
-
this.snapshots.set(snapshot.step, snapshot);
|
|
788
|
-
await this.saveSnapshots();
|
|
789
|
-
}
|
|
790
|
-
async generateChangeId() {
|
|
791
|
-
const timestamp = Date.now().toString(36);
|
|
792
|
-
const random = Math.random().toString(36).slice(2, 6);
|
|
793
|
-
return `CHG-${timestamp}-${random}`.toUpperCase();
|
|
794
|
-
}
|
|
795
|
-
async createChangeRecord() {
|
|
796
|
-
if (!this.state) return;
|
|
797
|
-
const changePath = path5.join(this.openspecPath, "changes", `${this.state.id}.md`);
|
|
798
|
-
await fs4.writeFile(changePath, this.formatChangeRecord());
|
|
799
|
-
}
|
|
800
|
-
async updateChangeRecord(status) {
|
|
801
|
-
if (!this.state) return;
|
|
802
|
-
if (status) {
|
|
803
|
-
this.state.status = status;
|
|
804
|
-
}
|
|
805
|
-
const changePath = path5.join(this.openspecPath, "changes", `${this.state.id}.md`);
|
|
806
|
-
await fs4.writeFile(changePath, this.formatChangeRecord());
|
|
807
|
-
}
|
|
808
|
-
formatChangeRecord() {
|
|
809
|
-
if (!this.state) return "";
|
|
810
|
-
const formatStepTime = (date) => {
|
|
811
|
-
if (!date) return "-";
|
|
812
|
-
if (typeof date === "string") return date;
|
|
813
|
-
return date.toISOString();
|
|
814
|
-
};
|
|
815
|
-
return `# Change: ${this.state.title}
|
|
816
|
-
|
|
817
|
-
id: ${this.state.id}
|
|
818
|
-
title: ${this.state.title}
|
|
819
|
-
status: ${this.state.status}
|
|
820
|
-
created: ${this.state.createdAt.toISOString()}
|
|
821
|
-
${this.state.completedAt ? `completed: ${formatStepTime(this.state.completedAt)}` : ""}
|
|
822
|
-
complexity: ${this.state.complexity}/10
|
|
823
|
-
workflow: ${this.state.type}
|
|
824
|
-
spec: spec/${this.state.id}.md
|
|
825
|
-
|
|
826
|
-
---
|
|
827
|
-
|
|
828
|
-
## \u53D8\u66F4\u6982\u8FF0
|
|
829
|
-
|
|
830
|
-
${this.state.requirement}
|
|
831
|
-
|
|
832
|
-
## \u5DE5\u4F5C\u6D41\u6267\u884C\u8BB0\u5F55
|
|
833
|
-
|
|
834
|
-
| \u9636\u6BB5 | \u5F00\u59CB\u65F6\u95F4 | \u7ED3\u675F\u65F6\u95F4 | \u72B6\u6001 |
|
|
835
|
-
|------|----------|----------|------|
|
|
836
|
-
${this.state.steps.map((s) => `| ${s.step} | ${formatStepTime(s.startedAt)} | ${formatStepTime(s.completedAt)} | ${s.status} |`).join("\n")}
|
|
837
|
-
|
|
838
|
-
## \u4EA7\u7269\u6587\u4EF6
|
|
839
|
-
|
|
840
|
-
${this.state.artifacts.map((a) => `- ${a}`).join("\n") || "\u6682\u65E0"}
|
|
841
|
-
`;
|
|
842
|
-
}
|
|
843
|
-
async createSpecDocument(summary) {
|
|
844
|
-
if (!this.state) return;
|
|
845
|
-
const specPath = path5.join(this.openspecPath, "spec", `${this.state.id}.md`);
|
|
846
|
-
const content = `# Spec: ${this.state.title}
|
|
847
|
-
|
|
848
|
-
> \u53D8\u66F4ID: ${this.state.id}
|
|
849
|
-
> \u5F52\u6863\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
850
|
-
> \u5DE5\u4F5C\u6D41\u7C7B\u578B: ${this.state.type}
|
|
851
|
-
|
|
852
|
-
---
|
|
853
|
-
|
|
854
|
-
## \u6982\u8FF0
|
|
855
|
-
|
|
856
|
-
${this.state.requirement}
|
|
857
|
-
|
|
858
|
-
## \u5B9E\u73B0\u603B\u7ED3
|
|
859
|
-
|
|
860
|
-
${summary}
|
|
861
|
-
|
|
862
|
-
## \u9A8C\u6536\u7ED3\u679C
|
|
863
|
-
|
|
864
|
-
${this.state.steps.map((s) => `- [${s.status === "completed" ? "x" : " "}] ${s.step}`).join("\n")}
|
|
865
|
-
|
|
866
|
-
## \u76F8\u5173\u6587\u4EF6
|
|
867
|
-
|
|
868
|
-
${this.state.artifacts.map((a) => `- ${a}`).join("\n") || "\u6682\u65E0"}
|
|
869
|
-
`;
|
|
870
|
-
await fs4.writeFile(specPath, content);
|
|
871
|
-
}
|
|
872
|
-
};
|
|
873
|
-
ConfirmationRequiredError = class extends Error {
|
|
874
|
-
constructor(message, point) {
|
|
875
|
-
super(message);
|
|
876
|
-
this.point = point;
|
|
877
|
-
this.name = "ConfirmationRequiredError";
|
|
878
|
-
}
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
});
|
|
882
|
-
|
|
883
|
-
// src/commands/new.ts
|
|
884
|
-
var new_exports = {};
|
|
885
|
-
__export(new_exports, {
|
|
886
|
-
analyzeComplexity: () => analyzeComplexity,
|
|
887
|
-
default: () => new_default,
|
|
888
|
-
extractTitle: () => extractTitle,
|
|
889
|
-
generateSpecContent: () => generateSpecContent,
|
|
890
|
-
handleNew: () => handleNew,
|
|
891
|
-
newFeature: () => newFeature,
|
|
892
|
-
parseArgs: () => parseArgs,
|
|
893
|
-
readProjectContext: () => readProjectContext
|
|
894
|
-
});
|
|
895
|
-
async function handleNew(args, ctx) {
|
|
896
|
-
const workingDir = ctx.options.workingDirectory;
|
|
897
|
-
const workflowEngine = ctx.workflowEngine;
|
|
898
|
-
if (workflowEngine) {
|
|
899
|
-
const existingState = workflowEngine.getState();
|
|
900
|
-
if (existingState && existingState.status === "running") {
|
|
901
|
-
if (existingState.currentStep === "explore" || existingState.currentStep === "propose") {
|
|
902
|
-
const specPath = path5.join(workingDir, "openspec", "changes", `${existingState.id}-spec.md`);
|
|
903
|
-
if (fs10.existsSync(specPath)) {
|
|
904
|
-
return {
|
|
905
|
-
output: chalk9.yellow("\u5F53\u524D\u5DE5\u4F5C\u6D41\u6B63\u5728\u7B49\u5F85\u89C4\u683C\u786E\u8BA4") + chalk9.gray(`
|
|
906
|
-
|
|
907
|
-
\u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9.gray(`
|
|
908
|
-
\u53D8\u66F4ID: ${existingState.id}`) + chalk9.cyan("\n\n\u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210:") + chalk9.white(`
|
|
909
|
-
${specPath}`) + chalk9.yellow("\n\n\u8BF7\u786E\u8BA4\u89C4\u683C\u540E\u7EE7\u7EED:") + chalk9.gray("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C") + chalk9.gray("\n /opsx:status - \u67E5\u770B\u8BE6\u60C5")
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
return {
|
|
914
|
-
output: chalk9.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9.white(`
|
|
915
|
-
|
|
916
|
-
\u{1F4CB} ${existingState.title || existingState.id}`) + chalk9.gray(`
|
|
917
|
-
\u7C7B\u578B: ${existingState.type} | \u590D\u6742\u5EA6: ${existingState.complexity}/10`) + chalk9.cyan(`
|
|
918
|
-
|
|
919
|
-
\u8FDB\u5EA6: ${existingState.steps.map((s) => {
|
|
920
|
-
const icon = s.status === "completed" ? "\u2713" : s.status === "running" ? "\u25CF" : "\u25CB";
|
|
921
|
-
return `${icon} ${s.step}`;
|
|
922
|
-
}).join(" \u2192 ")}`) + chalk9.yellow("\n\n\u53EF\u7528\u547D\u4EE4:") + chalk9.white("\n /opsx:status - \u67E5\u770B\u5DE5\u4F5C\u6D41\u8BE6\u60C5") + chalk9.white("\n /opsx:cancel - \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41")
|
|
923
|
-
};
|
|
924
503
|
}
|
|
504
|
+
} catch {
|
|
925
505
|
}
|
|
926
|
-
const
|
|
927
|
-
if (!requirement) {
|
|
928
|
-
return {
|
|
929
|
-
output: chalk9.red("\u8BF7\u8F93\u5165\u9700\u6C42\u63CF\u8FF0") + chalk9.gray("\n\u7528\u6CD5: /new <\u9700\u6C42\u63CF\u8FF0>") + chalk9.gray("\n\u9009\u9879:") + chalk9.gray("\n --simple \u5F3A\u5236\u4F7F\u7528\u7B80\u5355\u6D41\u7A0B") + chalk9.gray("\n --complex \u5F3A\u5236\u4F7F\u7528\u590D\u6742\u6D41\u7A0B")
|
|
930
|
-
};
|
|
931
|
-
}
|
|
932
|
-
return newFeature({ requirement, forceComplexity }, workingDir, workflowEngine);
|
|
933
|
-
}
|
|
934
|
-
async function newFeature(options, workingDir, workflowEngine) {
|
|
935
|
-
const cwd = workingDir || process.cwd();
|
|
936
|
-
const { requirement, forceComplexity } = options;
|
|
937
|
-
const lines = [];
|
|
506
|
+
const devStandardsPath = path5.join(workingDir, ".sf-cli", "norms", "devstanded.md");
|
|
938
507
|
try {
|
|
939
|
-
const stats = await fs4.stat(
|
|
940
|
-
if (
|
|
941
|
-
|
|
942
|
-
output: chalk9.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
|
|
943
|
-
};
|
|
508
|
+
const stats = await fs4.stat(devStandardsPath);
|
|
509
|
+
if (stats.size <= MAX_FILE_SIZE2) {
|
|
510
|
+
context.devStandards = await fs4.readFile(devStandardsPath, "utf-8");
|
|
944
511
|
}
|
|
945
512
|
} catch {
|
|
946
|
-
return {
|
|
947
|
-
output: chalk9.red(`\u9519\u8BEF: \u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6743\u9650\u8BBF\u95EE ${cwd}`)
|
|
948
|
-
};
|
|
949
513
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
lines.push(chalk9.gray(` \u6D41\u7A0B\u7C7B\u578B: ${analysis.recommendation === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`));
|
|
960
|
-
for (const factor of analysis.factors) {
|
|
961
|
-
lines.push(chalk9.gray(` - ${factor}`));
|
|
514
|
+
return context;
|
|
515
|
+
}
|
|
516
|
+
function analyzeComplexity(requirement, context) {
|
|
517
|
+
let score = 3;
|
|
518
|
+
if (requirement.length > 100) score += 1;
|
|
519
|
+
if (requirement.length > 200) score += 1;
|
|
520
|
+
const complexKeywords = ["\u67B6\u6784", "\u91CD\u6784", "\u8FC1\u79FB", "\u96C6\u6210", "\u7CFB\u7EDF", "\u6A21\u5757", "\u5DE5\u4F5C\u6D41", "\u6D41\u7A0B", "\u6743\u9650", "\u5B89\u5168"];
|
|
521
|
+
for (const keyword of complexKeywords) {
|
|
522
|
+
if (requirement.includes(keyword)) score += 1;
|
|
962
523
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
if (!workflowEngine) {
|
|
967
|
-
await workflow.initialize(cwd);
|
|
524
|
+
const simpleKeywords = ["\u4FEE\u590D", "\u8C03\u6574", "\u4F18\u5316", "\u66F4\u65B0", "\u6DFB\u52A0", "\u6837\u5F0F", "\u6587\u672C"];
|
|
525
|
+
for (const keyword of simpleKeywords) {
|
|
526
|
+
if (requirement.includes(keyword)) score -= 0.5;
|
|
968
527
|
}
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
lines.push(chalk9.gray(` \u5DE5\u4F5C\u6D41: ${state.type}`));
|
|
974
|
-
lines.push("");
|
|
975
|
-
lines.push(chalk9.cyan("\u{1F4DD} \u751F\u6210\u89C4\u683C\u62C6\u5206..."));
|
|
976
|
-
const spec = await generateSpec(requirement, context, analysis, state.id);
|
|
977
|
-
const specPath = await saveSpecFile(cwd, spec);
|
|
978
|
-
lines.push(chalk9.green(" \u2713 \u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210"));
|
|
979
|
-
lines.push(chalk9.gray(` \u8DEF\u5F84: ${specPath}`));
|
|
980
|
-
lines.push("");
|
|
981
|
-
lines.push(chalk9.cyan.bold("\u{1F4CB} \u89C4\u683C\u6982\u89C8:"));
|
|
982
|
-
lines.push(chalk9.white(`
|
|
983
|
-
${spec.summary}`));
|
|
984
|
-
if (spec.items.length > 0) {
|
|
985
|
-
lines.push("");
|
|
986
|
-
lines.push(chalk9.cyan(" \u4EFB\u52A1\u62C6\u5206:"));
|
|
987
|
-
for (const item of spec.items) {
|
|
988
|
-
const priorityIcon = item.priority === "high" ? "\u{1F534}" : item.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
989
|
-
lines.push(chalk9.gray(` ${priorityIcon} [${item.id}] ${item.title}`));
|
|
990
|
-
}
|
|
528
|
+
const connectors = ["\u548C", "\u4EE5\u53CA", "\u540C\u65F6", "\u53E6\u5916", "\u6B64\u5916", "\u3001"];
|
|
529
|
+
for (const conn of connectors) {
|
|
530
|
+
const count = (requirement.match(new RegExp(conn, "g")) || []).length;
|
|
531
|
+
score += count * 0.3;
|
|
991
532
|
}
|
|
992
|
-
if (
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
533
|
+
if (requirement.match(/https?:\/\//)) score += 0.5;
|
|
534
|
+
if (!context.framework) score += 0.5;
|
|
535
|
+
return Math.max(1, Math.min(10, Math.round(score)));
|
|
536
|
+
}
|
|
537
|
+
function generateBDDScenarios(requirement, context, questions) {
|
|
538
|
+
const scenarios = [];
|
|
539
|
+
questions.find((q) => q.category === "ui" && q.answered)?.answer;
|
|
540
|
+
const interactionAnswer = questions.find((q) => q.category === "interaction" && q.answered)?.answer;
|
|
541
|
+
const edgeAnswer = questions.find((q) => q.category === "edge" && q.answered)?.answer;
|
|
542
|
+
const features = extractFeatures(requirement);
|
|
543
|
+
for (const feature of features) {
|
|
544
|
+
const scenario = {
|
|
545
|
+
feature: feature.title,
|
|
546
|
+
description: feature.description,
|
|
547
|
+
scenarios: []
|
|
548
|
+
};
|
|
549
|
+
scenario.scenarios.push({
|
|
550
|
+
name: `\u6B63\u5E38\u6D41\u7A0B: ${feature.title}`,
|
|
551
|
+
given: [`\u7528\u6237\u8FDB\u5165\u76F8\u5173\u9875\u9762`],
|
|
552
|
+
when: [`\u7528\u6237\u6267\u884C "${feature.title}" \u64CD\u4F5C`],
|
|
553
|
+
then: [`\u7CFB\u7EDF\u5E94\u6B63\u786E\u5904\u7406\u5E76\u8FD4\u56DE\u9884\u671F\u7ED3\u679C`]
|
|
554
|
+
});
|
|
555
|
+
if (interactionAnswer) {
|
|
556
|
+
scenario.scenarios.push({
|
|
557
|
+
name: `\u4EA4\u4E92\u6D41\u7A0B: \u7528\u6237\u64CD\u4F5C`,
|
|
558
|
+
given: [`\u7528\u6237\u8FDB\u5165\u529F\u80FD\u754C\u9762`],
|
|
559
|
+
when: [`\u7528\u6237\u6309\u7167\u4EE5\u4E0B\u6D41\u7A0B\u64CD\u4F5C: ${interactionAnswer}`],
|
|
560
|
+
then: [`\u7CFB\u7EDF\u54CD\u5E94\u5E76\u5B8C\u6210\u529F\u80FD`]
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
if (feature.hasInput || edgeAnswer) {
|
|
564
|
+
scenario.scenarios.push({
|
|
565
|
+
name: `\u8FB9\u754C\u60C5\u51B5: \u5F02\u5E38\u5904\u7406`,
|
|
566
|
+
given: [`\u7528\u6237\u8FDB\u5165\u529F\u80FD\u754C\u9762`],
|
|
567
|
+
when: [`\u53D1\u751F\u5F02\u5E38\u60C5\u51B5${edgeAnswer ? `: ${edgeAnswer}` : ""}`],
|
|
568
|
+
then: [`\u7CFB\u7EDF\u5E94\u6B63\u786E\u5904\u7406\u5F02\u5E38\u5E76\u7ED9\u51FA\u63D0\u793A`]
|
|
569
|
+
});
|
|
997
570
|
}
|
|
571
|
+
scenarios.push(scenario);
|
|
998
572
|
}
|
|
999
|
-
|
|
1000
|
-
lines.push(chalk9.yellow.bold("\u23F3 \u7B49\u5F85\u89C4\u683C\u786E\u8BA4"));
|
|
1001
|
-
lines.push("");
|
|
1002
|
-
lines.push(chalk9.white(" y - \u786E\u8BA4\u89C4\u683C\uFF0C\u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5"));
|
|
1003
|
-
lines.push(chalk9.white(" n - \u4E0D\u6EE1\u610F\uFF0C\u91CD\u65B0\u751F\u6210\u89C4\u683C"));
|
|
1004
|
-
lines.push(chalk9.gray(" \u6216\u8F93\u5165\u547D\u4EE4: /opsx:confirm spec-review"));
|
|
1005
|
-
return { output: lines.join("\n") };
|
|
573
|
+
return scenarios;
|
|
1006
574
|
}
|
|
1007
|
-
|
|
1008
|
-
const
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
};
|
|
1017
|
-
spec.summary = generateSummary(requirement);
|
|
1018
|
-
if (analysis.recommendation === "complex") {
|
|
1019
|
-
spec.items = generateComplexTasks(requirement, context, analysis);
|
|
1020
|
-
spec.architectureNotes = generateArchitectureNotes(requirement, context);
|
|
1021
|
-
} else {
|
|
1022
|
-
spec.items = generateSimpleTasks(requirement);
|
|
575
|
+
function extractFeatures(requirement) {
|
|
576
|
+
const features = [];
|
|
577
|
+
const urlMatch = requirement.match(/https?:\/\/[^\s]+/);
|
|
578
|
+
if (urlMatch) {
|
|
579
|
+
features.push({
|
|
580
|
+
title: "\u53C2\u8003\u754C\u9762\u5206\u6790",
|
|
581
|
+
description: `\u5206\u6790\u53C2\u8003\u754C\u9762 ${urlMatch[0]}`,
|
|
582
|
+
hasInput: false
|
|
583
|
+
});
|
|
1023
584
|
}
|
|
1024
|
-
spec.risks = generateRisks(requirement, context, analysis);
|
|
1025
|
-
spec.suggestions = generateSuggestions(requirement, context, analysis);
|
|
1026
|
-
return spec;
|
|
1027
|
-
}
|
|
1028
|
-
function generateSummary(requirement) {
|
|
1029
|
-
const firstSentence = requirement.split(/[。!?\n]/)[0];
|
|
1030
|
-
return firstSentence.length > 100 ? firstSentence.slice(0, 97) + "..." : firstSentence;
|
|
1031
|
-
}
|
|
1032
|
-
function generateComplexTasks(requirement, context, analysis) {
|
|
1033
|
-
const items = [];
|
|
1034
|
-
let itemId = 1;
|
|
1035
585
|
const featurePatterns = [
|
|
1036
|
-
{ pattern:
|
|
1037
|
-
{ pattern:
|
|
1038
|
-
{ pattern:
|
|
1039
|
-
{ pattern:
|
|
1040
|
-
{ pattern:
|
|
1041
|
-
{ pattern:
|
|
1042
|
-
{ pattern:
|
|
1043
|
-
{ pattern:
|
|
1044
|
-
{ pattern:
|
|
1045
|
-
{ pattern:
|
|
586
|
+
{ pattern: /排盘|计算|算法/, title: "\u6838\u5FC3\u7B97\u6CD5\u5B9E\u73B0", hasInput: true },
|
|
587
|
+
{ pattern: /界面|UI|页面|显示|展示/, title: "\u754C\u9762\u5F00\u53D1", hasInput: true },
|
|
588
|
+
{ pattern: /表格|列表/, title: "\u6570\u636E\u5C55\u793A", hasInput: false },
|
|
589
|
+
{ pattern: /图表|图形|可视化/, title: "\u56FE\u8868\u53EF\u89C6\u5316", hasInput: false },
|
|
590
|
+
{ pattern: /表单|输入/, title: "\u8868\u5355\u5904\u7406", hasInput: true },
|
|
591
|
+
{ pattern: /登录|注册|认证/, title: "\u7528\u6237\u8BA4\u8BC1", hasInput: true },
|
|
592
|
+
{ pattern: /接口|API/, title: "API \u63A5\u53E3", hasInput: false },
|
|
593
|
+
{ pattern: /存储|缓存/, title: "\u6570\u636E\u5B58\u50A8", hasInput: false },
|
|
594
|
+
{ pattern: /导出|下载/, title: "\u5BFC\u51FA\u529F\u80FD", hasInput: false },
|
|
595
|
+
{ pattern: /配置|设置/, title: "\u914D\u7F6E\u7BA1\u7406", hasInput: true }
|
|
1046
596
|
];
|
|
1047
|
-
for (const { pattern, title,
|
|
597
|
+
for (const { pattern, title, hasInput } of featurePatterns) {
|
|
1048
598
|
if (pattern.test(requirement)) {
|
|
1049
|
-
|
|
1050
|
-
id: `T${itemId.toString().padStart(3, "0")}`,
|
|
599
|
+
features.push({
|
|
1051
600
|
title,
|
|
1052
|
-
description: `${title}\u76F8\u5173\
|
|
1053
|
-
|
|
1054
|
-
dependencies: itemId > 1 ? [`T${(itemId - 1).toString().padStart(3, "0")}`] : [],
|
|
1055
|
-
estimatedComplexity: priority === "high" ? 3 : priority === "medium" ? 2 : 1
|
|
601
|
+
description: `${title}\u76F8\u5173\u529F\u80FD`,
|
|
602
|
+
hasInput
|
|
1056
603
|
});
|
|
1057
|
-
itemId++;
|
|
1058
604
|
}
|
|
1059
605
|
}
|
|
1060
|
-
if (
|
|
1061
|
-
|
|
1062
|
-
id: "T001",
|
|
1063
|
-
title: "\u9700\u6C42\u5206\u6790\u4E0E\u8BBE\u8BA1",
|
|
1064
|
-
description: "\u5206\u6790\u9700\u6C42\u7EC6\u8282\uFF0C\u8BBE\u8BA1\u5B9E\u73B0\u65B9\u6848",
|
|
1065
|
-
priority: "high",
|
|
1066
|
-
dependencies: [],
|
|
1067
|
-
estimatedComplexity: 2
|
|
1068
|
-
});
|
|
1069
|
-
items.push({
|
|
1070
|
-
id: "T002",
|
|
606
|
+
if (features.length === 0) {
|
|
607
|
+
features.push({
|
|
1071
608
|
title: "\u6838\u5FC3\u529F\u80FD\u5B9E\u73B0",
|
|
1072
609
|
description: requirement,
|
|
1073
|
-
|
|
1074
|
-
dependencies: ["T001"],
|
|
1075
|
-
estimatedComplexity: analysis.score
|
|
1076
|
-
});
|
|
1077
|
-
items.push({
|
|
1078
|
-
id: "T003",
|
|
1079
|
-
title: "\u6D4B\u8BD5\u4E0E\u9A8C\u8BC1",
|
|
1080
|
-
description: "\u7F16\u5199\u6D4B\u8BD5\u7528\u4F8B\uFF0C\u9A8C\u8BC1\u529F\u80FD\u6B63\u786E\u6027",
|
|
1081
|
-
priority: "medium",
|
|
1082
|
-
dependencies: ["T002"],
|
|
1083
|
-
estimatedComplexity: 2
|
|
610
|
+
hasInput: true
|
|
1084
611
|
});
|
|
1085
612
|
}
|
|
1086
|
-
return
|
|
613
|
+
return features;
|
|
1087
614
|
}
|
|
1088
|
-
function
|
|
615
|
+
function generateSpecItems(requirement, context, bddScenarios, questions) {
|
|
1089
616
|
const items = [];
|
|
1090
|
-
let
|
|
1091
|
-
const
|
|
1092
|
-
const hasReferenceUrl = urlMatch !== null;
|
|
1093
|
-
if (hasReferenceUrl) {
|
|
1094
|
-
items.push({
|
|
1095
|
-
id: `T${itemId.toString().padStart(3, "0")}`,
|
|
1096
|
-
title: "\u53C2\u8003\u754C\u9762\u5206\u6790",
|
|
1097
|
-
description: `\u5206\u6790\u53C2\u8003\u754C\u9762 ${urlMatch[0]} \u7684\u7ED3\u6784\u548C\u4EA4\u4E92\u903B\u8F91`,
|
|
1098
|
-
priority: "high",
|
|
1099
|
-
dependencies: [],
|
|
1100
|
-
estimatedComplexity: 2
|
|
1101
|
-
});
|
|
1102
|
-
itemId++;
|
|
1103
|
-
}
|
|
1104
|
-
const featurePatterns = [
|
|
1105
|
-
{ pattern: /排盘|计算|算法|公式/, title: "\u6838\u5FC3\u7B97\u6CD5\u5B9E\u73B0", desc: "\u5B9E\u73B0\u6838\u5FC3\u8BA1\u7B97\u903B\u8F91" },
|
|
1106
|
-
{ pattern: /界面|UI|页面|显示|展示/, title: "\u754C\u9762\u5F00\u53D1", desc: "\u5F00\u53D1\u7528\u6237\u754C\u9762\u7EC4\u4EF6" },
|
|
1107
|
-
{ pattern: /表格|列表|数据/, title: "\u6570\u636E\u5C55\u793A", desc: "\u5B9E\u73B0\u6570\u636E\u8868\u683C/\u5217\u8868\u7EC4\u4EF6" },
|
|
1108
|
-
{ pattern: /图表|图形|可视化/, title: "\u56FE\u8868\u53EF\u89C6\u5316", desc: "\u5B9E\u73B0\u56FE\u8868\u5C55\u793A\u529F\u80FD" },
|
|
1109
|
-
{ pattern: /表单|输入|提交/, title: "\u8868\u5355\u5904\u7406", desc: "\u5B9E\u73B0\u8868\u5355\u8F93\u5165\u548C\u9A8C\u8BC1" },
|
|
1110
|
-
{ pattern: /登录|注册|认证/, title: "\u7528\u6237\u8BA4\u8BC1", desc: "\u5B9E\u73B0\u7528\u6237\u8BA4\u8BC1\u529F\u80FD" },
|
|
1111
|
-
{ pattern: /接口|API|请求/, title: "API \u63A5\u53E3", desc: "\u5F00\u53D1\u540E\u7AEF\u63A5\u53E3\u5BF9\u63A5" },
|
|
1112
|
-
{ pattern: /存储|缓存|持久化/, title: "\u6570\u636E\u5B58\u50A8", desc: "\u5B9E\u73B0\u6570\u636E\u5B58\u50A8\u529F\u80FD" },
|
|
1113
|
-
{ pattern: /导出|下载|打印/, title: "\u5BFC\u51FA\u529F\u80FD", desc: "\u5B9E\u73B0\u6570\u636E\u5BFC\u51FA\u529F\u80FD" },
|
|
1114
|
-
{ pattern: /配置|设置|选项/, title: "\u914D\u7F6E\u7BA1\u7406", desc: "\u5B9E\u73B0\u914D\u7F6E\u9009\u9879\u529F\u80FD" }
|
|
1115
|
-
];
|
|
1116
|
-
const matchedFeatures = [];
|
|
1117
|
-
for (const { pattern, title, desc } of featurePatterns) {
|
|
1118
|
-
if (pattern.test(requirement)) {
|
|
1119
|
-
matchedFeatures.push({ title, desc });
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
for (const feature of matchedFeatures) {
|
|
1123
|
-
items.push({
|
|
1124
|
-
id: `T${itemId.toString().padStart(3, "0")}`,
|
|
1125
|
-
title: feature.title,
|
|
1126
|
-
description: feature.desc,
|
|
1127
|
-
priority: "high",
|
|
1128
|
-
dependencies: hasReferenceUrl && itemId === 2 ? ["T001"] : itemId > (hasReferenceUrl ? 2 : 1) ? [`T${(itemId - 1).toString().padStart(3, "0")}`] : [],
|
|
1129
|
-
estimatedComplexity: 3
|
|
1130
|
-
});
|
|
1131
|
-
itemId++;
|
|
1132
|
-
}
|
|
1133
|
-
if (items.length === 0 || hasReferenceUrl && items.length === 1) {
|
|
1134
|
-
const requirementParts = requirement.split(/[,,。.!!??;;]/).filter((p) => p.trim());
|
|
1135
|
-
for (const part of requirementParts) {
|
|
1136
|
-
if (part.trim() && !part.includes("http")) {
|
|
1137
|
-
items.push({
|
|
1138
|
-
id: `T${itemId.toString().padStart(3, "0")}`,
|
|
1139
|
-
title: `\u5B9E\u73B0: ${part.trim().slice(0, 20)}${part.trim().length > 20 ? "..." : ""}`,
|
|
1140
|
-
description: part.trim(),
|
|
1141
|
-
priority: "high",
|
|
1142
|
-
dependencies: itemId > (hasReferenceUrl ? 2 : 1) ? [`T${(itemId - 1).toString().padStart(3, "0")}`] : [],
|
|
1143
|
-
estimatedComplexity: 2
|
|
1144
|
-
});
|
|
1145
|
-
itemId++;
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
if (items.length === 0) {
|
|
617
|
+
let id = 1;
|
|
618
|
+
for (const scenario of bddScenarios) {
|
|
1150
619
|
items.push({
|
|
1151
|
-
id: "
|
|
1152
|
-
title:
|
|
1153
|
-
description:
|
|
1154
|
-
priority: "high",
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
});
|
|
1158
|
-
items.push({
|
|
1159
|
-
id: "T002",
|
|
1160
|
-
title: "\u6838\u5FC3\u529F\u80FD\u5B9E\u73B0",
|
|
1161
|
-
description: requirement,
|
|
1162
|
-
priority: "high",
|
|
1163
|
-
dependencies: ["T001"],
|
|
1164
|
-
estimatedComplexity: 3
|
|
620
|
+
id: `T${id.toString().padStart(3, "0")}`,
|
|
621
|
+
title: scenario.feature,
|
|
622
|
+
description: scenario.description,
|
|
623
|
+
priority: id <= 2 ? "high" : "medium",
|
|
624
|
+
files: [],
|
|
625
|
+
tests: []
|
|
1165
626
|
});
|
|
627
|
+
id++;
|
|
1166
628
|
}
|
|
1167
629
|
items.push({
|
|
1168
|
-
id: `T${
|
|
1169
|
-
title: "\u6D4B\u8BD5
|
|
1170
|
-
description: "\
|
|
630
|
+
id: `T${id.toString().padStart(3, "0")}`,
|
|
631
|
+
title: "\u5355\u5143\u6D4B\u8BD5",
|
|
632
|
+
description: "\u7F16\u5199\u6D4B\u8BD5\u7528\u4F8B\u786E\u4FDD\u529F\u80FD\u6B63\u786E\u6027",
|
|
1171
633
|
priority: "medium",
|
|
1172
|
-
|
|
1173
|
-
|
|
634
|
+
files: [],
|
|
635
|
+
tests: []
|
|
1174
636
|
});
|
|
1175
637
|
return items;
|
|
1176
638
|
}
|
|
1177
|
-
function
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
if (requirement.includes("\u6A21\u5757") || requirement.includes("\u7EC4\u4EF6")) {
|
|
1183
|
-
notes.push("\u5EFA\u8BAE\u91C7\u7528\u6A21\u5757\u5316\u8BBE\u8BA1\uFF0C\u4FDD\u6301\u7EC4\u4EF6\u804C\u8D23\u5355\u4E00");
|
|
1184
|
-
}
|
|
1185
|
-
if (requirement.includes("API") || requirement.includes("\u63A5\u53E3")) {
|
|
1186
|
-
notes.push("API \u8BBE\u8BA1\u9700\u8003\u8651\u7248\u672C\u63A7\u5236\u548C\u5411\u540E\u517C\u5BB9");
|
|
1187
|
-
}
|
|
1188
|
-
if (context.structure.srcStructure) {
|
|
1189
|
-
notes.push(`\u73B0\u6709\u6E90\u7801\u7ED3\u6784: ${context.structure.srcStructure}`);
|
|
1190
|
-
}
|
|
1191
|
-
return notes;
|
|
1192
|
-
}
|
|
1193
|
-
function generateRisks(requirement, context, analysis) {
|
|
1194
|
-
const risks = [];
|
|
1195
|
-
if (!context.framework) {
|
|
1196
|
-
risks.push("\u9879\u76EE\u6846\u67B6\u672A\u8BC6\u522B\uFF0C\u53EF\u80FD\u5F71\u54CD\u4EE3\u7801\u98CE\u683C\u4E00\u81F4\u6027");
|
|
1197
|
-
}
|
|
1198
|
-
if (analysis.score >= 7) {
|
|
1199
|
-
risks.push("\u9700\u6C42\u590D\u6742\u5EA6\u8F83\u9AD8\uFF0C\u5EFA\u8BAE\u5206\u9636\u6BB5\u5B9E\u73B0");
|
|
1200
|
-
}
|
|
1201
|
-
if (requirement.includes("\u8FC1\u79FB") || requirement.includes("\u91CD\u6784")) {
|
|
1202
|
-
risks.push("\u6D89\u53CA\u73B0\u6709\u4EE3\u7801\u4FEE\u6539\uFF0C\u9700\u6CE8\u610F\u56DE\u5F52\u6D4B\u8BD5");
|
|
1203
|
-
}
|
|
1204
|
-
if (requirement.includes("\u6743\u9650") || requirement.includes("\u5B89\u5168")) {
|
|
1205
|
-
risks.push("\u6D89\u53CA\u5B89\u5168\u654F\u611F\u529F\u80FD\uFF0C\u9700\u8981\u989D\u5916\u5BA1\u67E5");
|
|
1206
|
-
}
|
|
1207
|
-
return risks;
|
|
1208
|
-
}
|
|
1209
|
-
function generateSuggestions(requirement, context, analysis) {
|
|
1210
|
-
const suggestions = [];
|
|
1211
|
-
if (context.norms.devStandards) {
|
|
1212
|
-
suggestions.push("\u9879\u76EE\u5DF2\u6709\u5F00\u53D1\u89C4\u8303\uFF0C\u8BF7\u9075\u5FAA\u73B0\u6709\u89C4\u8303");
|
|
1213
|
-
}
|
|
1214
|
-
if (analysis.recommendation === "complex") {
|
|
1215
|
-
suggestions.push("\u590D\u6742\u9700\u6C42\u5EFA\u8BAE\u5148\u8FDB\u884C\u6280\u672F\u8BC4\u5BA1");
|
|
1216
|
-
suggestions.push("\u5EFA\u8BAE\u8C03\u7528 $architect \u83B7\u53D6\u67B6\u6784\u5EFA\u8BAE");
|
|
1217
|
-
}
|
|
1218
|
-
if (context.techStack.length > 0) {
|
|
1219
|
-
suggestions.push(`\u6280\u672F\u6808: ${context.techStack.join(", ")}`);
|
|
1220
|
-
}
|
|
1221
|
-
return suggestions;
|
|
1222
|
-
}
|
|
1223
|
-
async function saveSpecFile(cwd, spec) {
|
|
1224
|
-
const changesDir = path5.join(cwd, "openspec", "changes");
|
|
1225
|
-
await fs4.mkdir(changesDir, { recursive: true });
|
|
1226
|
-
const specPath = path5.join(changesDir, `${spec.changeId}-spec.md`);
|
|
1227
|
-
const content = formatSpecFile(spec);
|
|
639
|
+
async function saveSpecFile(workingDir, session) {
|
|
640
|
+
const specDir = path5.join(workingDir, "openspec", "changes");
|
|
641
|
+
await fs4.mkdir(specDir, { recursive: true });
|
|
642
|
+
const specPath = path5.join(specDir, `${session.id}-spec.md`);
|
|
643
|
+
const content = formatSpecFile(session);
|
|
1228
644
|
await fs4.writeFile(specPath, content, "utf-8");
|
|
1229
645
|
return specPath;
|
|
1230
646
|
}
|
|
1231
|
-
function formatSpecFile(
|
|
647
|
+
function formatSpecFile(session) {
|
|
1232
648
|
const lines = [];
|
|
1233
|
-
lines.push(`#
|
|
649
|
+
lines.push(`# \u9700\u6C42\u89C4\u683C: ${session.requirement.slice(0, 50)}`);
|
|
1234
650
|
lines.push("");
|
|
1235
|
-
lines.push(`> \u53D8\u66F4ID: ${
|
|
1236
|
-
lines.push(`> \
|
|
651
|
+
lines.push(`> \u53D8\u66F4ID: ${session.id}`);
|
|
652
|
+
lines.push(`> \u9700\u6C42\u6E05\u6670\u5EA6: ${Math.round(session.clarityScore * 100)}%`);
|
|
653
|
+
lines.push(`> \u590D\u6742\u5EA6: ${session.complexity}/10`);
|
|
654
|
+
lines.push(`> \u751F\u6210\u65F6\u95F4: ${session.createdAt.toISOString()}`);
|
|
1237
655
|
lines.push("");
|
|
1238
656
|
lines.push("---");
|
|
1239
657
|
lines.push("");
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
lines.push(spec.requirement);
|
|
1243
|
-
lines.push("");
|
|
1244
|
-
lines.push("## \u4EFB\u52A1\u62C6\u5206");
|
|
1245
|
-
lines.push("");
|
|
1246
|
-
for (const item of spec.items) {
|
|
1247
|
-
const priorityLabel = item.priority === "high" ? "\u{1F534} \u9AD8" : item.priority === "medium" ? "\u{1F7E1} \u4E2D" : "\u{1F7E2} \u4F4E";
|
|
1248
|
-
lines.push(`### ${item.id}: ${item.title}`);
|
|
658
|
+
if (session.refinedRequirement !== session.requirement) {
|
|
659
|
+
lines.push("## \u9700\u6C42\u8BE6\u60C5");
|
|
1249
660
|
lines.push("");
|
|
1250
|
-
lines.push(
|
|
1251
|
-
lines.push(`- **\u63CF\u8FF0**: ${item.description}`);
|
|
1252
|
-
lines.push(`- **\u9884\u4F30\u590D\u6742\u5EA6**: ${item.estimatedComplexity}/5`);
|
|
1253
|
-
if (item.dependencies.length > 0) {
|
|
1254
|
-
lines.push(`- **\u4F9D\u8D56**: ${item.dependencies.join(", ")}`);
|
|
1255
|
-
}
|
|
1256
|
-
lines.push("");
|
|
1257
|
-
}
|
|
1258
|
-
if (spec.architectureNotes.length > 0) {
|
|
1259
|
-
lines.push("## \u67B6\u6784\u8BF4\u660E");
|
|
661
|
+
lines.push(session.refinedRequirement);
|
|
1260
662
|
lines.push("");
|
|
1261
|
-
|
|
1262
|
-
lines.push(`- ${note}`);
|
|
1263
|
-
}
|
|
663
|
+
lines.push("---");
|
|
1264
664
|
lines.push("");
|
|
1265
665
|
}
|
|
1266
|
-
if (
|
|
1267
|
-
lines.push("## \
|
|
666
|
+
if (session.clarificationQuestions.some((q) => q.answered)) {
|
|
667
|
+
lines.push("## \u9700\u6C42\u6F84\u6E05");
|
|
1268
668
|
lines.push("");
|
|
1269
|
-
for (const
|
|
1270
|
-
|
|
669
|
+
for (const q of session.clarificationQuestions) {
|
|
670
|
+
if (q.answered) {
|
|
671
|
+
lines.push(`**Q: ${q.question}**`);
|
|
672
|
+
lines.push(`A: ${q.answer}`);
|
|
673
|
+
lines.push("");
|
|
674
|
+
}
|
|
1271
675
|
}
|
|
676
|
+
lines.push("---");
|
|
1272
677
|
lines.push("");
|
|
1273
678
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
679
|
+
lines.push("## BDD \u573A\u666F");
|
|
680
|
+
lines.push("");
|
|
681
|
+
for (const scenario of session.bddScenarios) {
|
|
682
|
+
lines.push(`### Feature: ${scenario.feature}`);
|
|
1276
683
|
lines.push("");
|
|
1277
|
-
for (const
|
|
1278
|
-
lines.push(
|
|
684
|
+
for (const s of scenario.scenarios) {
|
|
685
|
+
lines.push(`**Scenario: ${s.name}**`);
|
|
686
|
+
for (const g of s.given) lines.push(` Given ${g}`);
|
|
687
|
+
for (const w of s.when) lines.push(` When ${w}`);
|
|
688
|
+
for (const t of s.then) lines.push(` Then ${t}`);
|
|
689
|
+
lines.push("");
|
|
1279
690
|
}
|
|
1280
|
-
lines.push("");
|
|
1281
691
|
}
|
|
1282
|
-
lines.push("
|
|
692
|
+
lines.push("## \u4EFB\u52A1\u5217\u8868");
|
|
1283
693
|
lines.push("");
|
|
1284
|
-
|
|
694
|
+
for (const item of session.specItems) {
|
|
695
|
+
const priority = item.priority === "high" ? "\u{1F534}" : item.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
696
|
+
lines.push(`- [ ] ${priority} [${item.id}] ${item.title}`);
|
|
697
|
+
}
|
|
1285
698
|
lines.push("");
|
|
1286
|
-
lines.push("
|
|
1287
|
-
lines.push("- [ ] \u4EFB\u52A1\u62C6\u5206\u5DF2\u786E\u8BA4");
|
|
699
|
+
lines.push("---");
|
|
1288
700
|
lines.push("");
|
|
1289
|
-
lines.push("**\u786E\u8BA4\
|
|
701
|
+
lines.push("**\u786E\u8BA4\u72B6\u6001**: \u23F3 \u7B49\u5F85\u786E\u8BA4");
|
|
1290
702
|
return lines.join("\n");
|
|
1291
703
|
}
|
|
1292
|
-
function
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
704
|
+
async function generateTests(workingDir, session) {
|
|
705
|
+
const testDir = path5.join(workingDir, "tests");
|
|
706
|
+
await fs4.mkdir(testDir, { recursive: true });
|
|
707
|
+
const testFiles = [];
|
|
708
|
+
for (const scenario of session.bddScenarios) {
|
|
709
|
+
const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
|
|
710
|
+
const testPath = path5.join(testDir, `${testName}.test.ts`);
|
|
711
|
+
const content = generateTestFile(scenario);
|
|
712
|
+
await fs4.writeFile(testPath, content, "utf-8");
|
|
713
|
+
testFiles.push(`tests/${testName}.test.ts`);
|
|
714
|
+
}
|
|
715
|
+
return testFiles;
|
|
716
|
+
}
|
|
717
|
+
function generateTestFile(scenario) {
|
|
718
|
+
const lines = [];
|
|
719
|
+
lines.push(`import { describe, it, expect } from 'vitest';`);
|
|
720
|
+
lines.push("");
|
|
721
|
+
lines.push(`describe('${scenario.feature}', () => {`);
|
|
722
|
+
for (const s of scenario.scenarios) {
|
|
723
|
+
lines.push(` it('${s.name}', () => {`);
|
|
724
|
+
lines.push(` // Given: ${s.given.join(", ")}`);
|
|
725
|
+
lines.push(` // When: ${s.when.join(", ")}`);
|
|
726
|
+
lines.push(` // Then: ${s.then.join(", ")}`);
|
|
727
|
+
lines.push(` expect(true).toBe(true); // TODO: \u5B9E\u73B0\u6D4B\u8BD5`);
|
|
728
|
+
lines.push(` });`);
|
|
729
|
+
lines.push("");
|
|
1303
730
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
forceComplexity
|
|
1307
|
-
};
|
|
731
|
+
lines.push(`});`);
|
|
732
|
+
return lines.join("\n");
|
|
1308
733
|
}
|
|
1309
|
-
async function
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
734
|
+
async function archiveWorkflow(workingDir) {
|
|
735
|
+
if (!activeSession) return;
|
|
736
|
+
const archiveDir = path5.join(workingDir, "openspec", "spec");
|
|
737
|
+
await fs4.mkdir(archiveDir, { recursive: true });
|
|
738
|
+
const archivePath = path5.join(archiveDir, `${activeSession.id}.md`);
|
|
739
|
+
const content = `# \u5F52\u6863: ${activeSession.requirement.slice(0, 50)}
|
|
740
|
+
|
|
741
|
+
> \u5F52\u6863\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
742
|
+
> \u9700\u6C42\u6E05\u6670\u5EA6: ${Math.round(activeSession.clarityScore * 100)}%
|
|
743
|
+
> \u590D\u6742\u5EA6: ${activeSession.complexity}/10
|
|
744
|
+
|
|
745
|
+
## \u9700\u6C42\u8BE6\u60C5
|
|
746
|
+
|
|
747
|
+
${activeSession.refinedRequirement}
|
|
748
|
+
|
|
749
|
+
## \u5B8C\u6210\u60C5\u51B5
|
|
750
|
+
|
|
751
|
+
- [x] \u9879\u76EE\u4E0A\u4E0B\u6587\u83B7\u53D6
|
|
752
|
+
- [x] \u9700\u6C42\u6F84\u6E05
|
|
753
|
+
- [x] \u590D\u6742\u5EA6\u8BC4\u4F30
|
|
754
|
+
- [x] BDD \u573A\u666F\u62C6\u89E3
|
|
755
|
+
- [x] OpenSpec \u89C4\u683C
|
|
756
|
+
- [x] TDD \u6D4B\u8BD5\u751F\u6210
|
|
757
|
+
- [x] \u5F00\u53D1\u5B9E\u73B0
|
|
758
|
+
- [x] \u4EE3\u7801\u5BA1\u6838
|
|
759
|
+
|
|
760
|
+
## \u6D4B\u8BD5\u6587\u4EF6
|
|
761
|
+
|
|
762
|
+
${activeSession.testFiles.map((f) => `- ${f}`).join("\n") || "\u65E0"}
|
|
763
|
+
`;
|
|
764
|
+
await fs4.writeFile(archivePath, content, "utf-8");
|
|
765
|
+
}
|
|
766
|
+
function generateSessionId() {
|
|
767
|
+
const timestamp = Date.now().toString(36);
|
|
768
|
+
const random = Math.random().toString(36).slice(2, 6);
|
|
769
|
+
return `WF-${timestamp}-${random}`.toUpperCase();
|
|
770
|
+
}
|
|
771
|
+
function generateComplexityBar(score) {
|
|
772
|
+
const filled = Math.round(score / 2);
|
|
773
|
+
const empty = 5 - filled;
|
|
774
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
775
|
+
}
|
|
776
|
+
function getPhaseLabel(phase) {
|
|
777
|
+
const labels = {
|
|
778
|
+
context: "\u9879\u76EE\u4E0A\u4E0B\u6587\u83B7\u53D6",
|
|
779
|
+
clarify: "\u9700\u6C42\u6F84\u6E05",
|
|
780
|
+
analysis: "\u590D\u6742\u5EA6\u8BC4\u4F30",
|
|
781
|
+
bdd: "BDD \u573A\u666F\u62C6\u89E3",
|
|
782
|
+
spec: "OpenSpec \u89C4\u683C",
|
|
783
|
+
tdd: "TDD \u6D4B\u8BD5\u751F\u6210",
|
|
784
|
+
develop: "\u5F00\u53D1\u5B9E\u73B0",
|
|
785
|
+
review: "\u4EE3\u7801\u5BA1\u6838"
|
|
1339
786
|
};
|
|
787
|
+
return labels[phase];
|
|
1340
788
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
try {
|
|
1344
|
-
const stats = await fs4.stat(agentsPath);
|
|
1345
|
-
if (stats.size > MAX_FILE_SIZE2) {
|
|
1346
|
-
console.warn(`\u8B66\u544A: AGENTS.md \u6587\u4EF6\u8FC7\u5927 (${stats.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
|
|
1347
|
-
return {};
|
|
1348
|
-
}
|
|
1349
|
-
const content = await fs4.readFile(agentsPath, "utf-8");
|
|
1350
|
-
return parseAgentsMd(content);
|
|
1351
|
-
} catch (e) {
|
|
1352
|
-
const err = e;
|
|
1353
|
-
if (err.code !== "ENOENT") {
|
|
1354
|
-
console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 AGENTS.md - ${err.message}`);
|
|
1355
|
-
}
|
|
1356
|
-
return {};
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
function parseAgentsMd(content) {
|
|
1360
|
-
const context = {};
|
|
1361
|
-
const nameMatch = content.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
|
|
1362
|
-
if (nameMatch) {
|
|
1363
|
-
context.name = nameMatch[1];
|
|
1364
|
-
}
|
|
1365
|
-
const typeMatch = content.match(/\|\s*项目类型\s*\|\s*([^\s|]+)/);
|
|
1366
|
-
if (typeMatch) {
|
|
1367
|
-
context.type = typeMatch[1];
|
|
1368
|
-
}
|
|
1369
|
-
const frameworkMatch = content.match(/\|\s*技术框架\s*\|\s*([^\s|]+)/);
|
|
1370
|
-
if (frameworkMatch && frameworkMatch[1] !== "\u5F85\u8BC6\u522B") {
|
|
1371
|
-
context.framework = frameworkMatch[1];
|
|
1372
|
-
}
|
|
1373
|
-
const descMatch = content.match(/###\s*1\.2\s*项目描述\s*\n+([^\n#]+)/);
|
|
1374
|
-
if (descMatch) {
|
|
1375
|
-
context.description = descMatch[1].trim();
|
|
1376
|
-
}
|
|
1377
|
-
const techStackMatch = content.match(/技术栈[::]\s*([^\n]+)/);
|
|
1378
|
-
if (techStackMatch) {
|
|
1379
|
-
context.techStack = techStackMatch[1].split(/[,,、]/).map((s) => s.trim()).filter(Boolean);
|
|
1380
|
-
}
|
|
1381
|
-
return context;
|
|
1382
|
-
}
|
|
1383
|
-
async function readConfigYaml(cwd) {
|
|
1384
|
-
const configPath = path5.join(cwd, "openspec", "config.yaml");
|
|
1385
|
-
try {
|
|
1386
|
-
const stats = await fs4.stat(configPath);
|
|
1387
|
-
if (stats.size > MAX_FILE_SIZE2) {
|
|
1388
|
-
console.warn("\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6");
|
|
1389
|
-
return {};
|
|
1390
|
-
}
|
|
1391
|
-
const content = await fs4.readFile(configPath, "utf-8");
|
|
1392
|
-
return parseConfigYaml(content);
|
|
1393
|
-
} catch (e) {
|
|
1394
|
-
const err = e;
|
|
1395
|
-
if (err.code !== "ENOENT") {
|
|
1396
|
-
console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
|
|
1397
|
-
}
|
|
1398
|
-
return {};
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
function parseConfigYaml(content) {
|
|
1402
|
-
const context = {};
|
|
1403
|
-
const nameMatch = content.match(/name:\s*(.+)/);
|
|
1404
|
-
if (nameMatch) {
|
|
1405
|
-
context.name = nameMatch[1].trim();
|
|
1406
|
-
}
|
|
1407
|
-
const typeMatch = content.match(/type:\s*(.+)/);
|
|
1408
|
-
if (typeMatch) {
|
|
1409
|
-
context.type = typeMatch[1].trim();
|
|
1410
|
-
}
|
|
1411
|
-
const frameworkMatch = content.match(/framework:\s*(.+)/);
|
|
1412
|
-
if (frameworkMatch && frameworkMatch[1].trim() !== "null") {
|
|
1413
|
-
context.framework = frameworkMatch[1].trim();
|
|
1414
|
-
}
|
|
1415
|
-
return context;
|
|
1416
|
-
}
|
|
1417
|
-
async function readNorms(cwd) {
|
|
1418
|
-
const normsDir = path5.join(cwd, ".sf-cli", "norms");
|
|
1419
|
-
const norms = {
|
|
1420
|
-
devStandards: "",
|
|
1421
|
-
patterns: "",
|
|
1422
|
-
weights: ""
|
|
1423
|
-
};
|
|
1424
|
-
try {
|
|
1425
|
-
const devStandardsPath = path5.join(normsDir, "devstanded.md");
|
|
1426
|
-
norms.devStandards = await fs4.readFile(devStandardsPath, "utf-8").catch(() => "");
|
|
1427
|
-
} catch {
|
|
1428
|
-
}
|
|
1429
|
-
try {
|
|
1430
|
-
const patternsPath = path5.join(normsDir, "patterns.json");
|
|
1431
|
-
norms.patterns = await fs4.readFile(patternsPath, "utf-8").catch(() => "");
|
|
1432
|
-
} catch {
|
|
1433
|
-
}
|
|
1434
|
-
try {
|
|
1435
|
-
const weightsPath = path5.join(normsDir, "weights.json");
|
|
1436
|
-
norms.weights = await fs4.readFile(weightsPath, "utf-8").catch(() => "");
|
|
1437
|
-
} catch {
|
|
1438
|
-
}
|
|
1439
|
-
return norms;
|
|
1440
|
-
}
|
|
1441
|
-
async function analyzeStructure(cwd) {
|
|
1442
|
-
const structure = {
|
|
1443
|
-
directories: [],
|
|
1444
|
-
keyFiles: [],
|
|
1445
|
-
srcStructure: ""
|
|
1446
|
-
};
|
|
1447
|
-
try {
|
|
1448
|
-
const entries = await fs4.readdir(cwd, { withFileTypes: true });
|
|
1449
|
-
for (const entry of entries) {
|
|
1450
|
-
if (entry.isDirectory() && !["node_modules", "dist", ".git", "build"].includes(entry.name)) {
|
|
1451
|
-
structure.directories.push(entry.name);
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
const keyFiles = [
|
|
1455
|
-
"package.json",
|
|
1456
|
-
"tsconfig.json",
|
|
1457
|
-
"AGENTS.md",
|
|
1458
|
-
"README.md"
|
|
1459
|
-
];
|
|
1460
|
-
for (const file of keyFiles) {
|
|
1461
|
-
const filePath = path5.join(cwd, file);
|
|
1462
|
-
try {
|
|
1463
|
-
await fs4.access(filePath);
|
|
1464
|
-
structure.keyFiles.push(file);
|
|
1465
|
-
} catch {
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
const srcDir = path5.join(cwd, "src");
|
|
1469
|
-
try {
|
|
1470
|
-
const srcEntries = await fs4.readdir(srcDir, { withFileTypes: true });
|
|
1471
|
-
structure.srcStructure = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name).join("/");
|
|
1472
|
-
} catch {
|
|
1473
|
-
}
|
|
1474
|
-
} catch (e) {
|
|
1475
|
-
}
|
|
1476
|
-
return { structure };
|
|
1477
|
-
}
|
|
1478
|
-
function analyzeComplexity(requirement, context) {
|
|
1479
|
-
let score = 3;
|
|
1480
|
-
const factors = [];
|
|
1481
|
-
if (requirement.length > 200) {
|
|
1482
|
-
score += 1;
|
|
1483
|
-
factors.push("\u9700\u6C42\u63CF\u8FF0\u8F83\u957F");
|
|
1484
|
-
}
|
|
1485
|
-
const complexKeywords = ["\u67B6\u6784", "\u91CD\u6784", "\u8FC1\u79FB", "\u96C6\u6210", "\u7CFB\u7EDF", "\u6A21\u5757", "\u5DE5\u4F5C\u6D41", "\u6D41\u7A0B"];
|
|
1486
|
-
const negationWords = ["\u4E0D\u9700\u8981", "\u4E0D\u7528", "\u65E0\u9700", "\u907F\u514D"];
|
|
1487
|
-
for (const keyword of complexKeywords) {
|
|
1488
|
-
if (requirement.includes(keyword)) {
|
|
1489
|
-
const keywordIndex = requirement.indexOf(keyword);
|
|
1490
|
-
const contextStart = Math.max(0, keywordIndex - 10);
|
|
1491
|
-
const contextText = requirement.slice(contextStart, keywordIndex);
|
|
1492
|
-
const hasNegation = negationWords.some((neg) => contextText.includes(neg));
|
|
1493
|
-
if (!hasNegation) {
|
|
1494
|
-
score += 1;
|
|
1495
|
-
factors.push(`\u6D89\u53CA"${keyword}"`);
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
const simpleKeywords = ["\u4FEE\u590D", "\u8C03\u6574", "\u4F18\u5316", "\u66F4\u65B0", "\u4FEE\u6539", "\u6DFB\u52A0", "\u6837\u5F0F"];
|
|
1500
|
-
for (const keyword of simpleKeywords) {
|
|
1501
|
-
if (requirement.includes(keyword)) {
|
|
1502
|
-
score -= 0.5;
|
|
1503
|
-
factors.push(`\u7B80\u5355\u4FEE\u6539"${keyword}"`);
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
const featureCount = (requirement.match(/和|以及|同时|另外|此外/g) || []).length;
|
|
1507
|
-
if (featureCount > 0) {
|
|
1508
|
-
score += featureCount * 0.5;
|
|
1509
|
-
factors.push(`\u6D89\u53CA\u591A\u4E2A\u529F\u80FD\u70B9 (${featureCount + 1}\u4E2A)`);
|
|
1510
|
-
}
|
|
1511
|
-
if (!context.framework) {
|
|
1512
|
-
score += 0.5;
|
|
1513
|
-
factors.push("\u9879\u76EE\u6846\u67B6\u672A\u8BC6\u522B");
|
|
1514
|
-
}
|
|
1515
|
-
if (requirement.includes("\u6570\u636E") || requirement.includes("\u63A5\u53E3") || requirement.includes("API")) {
|
|
1516
|
-
score += 1;
|
|
1517
|
-
factors.push("\u6D89\u53CA\u6570\u636E\u4EA4\u4E92");
|
|
1518
|
-
}
|
|
1519
|
-
if (requirement.includes("\u6743\u9650") || requirement.includes("\u5B89\u5168") || requirement.includes("\u8BA4\u8BC1")) {
|
|
1520
|
-
score += 1.5;
|
|
1521
|
-
factors.push("\u6D89\u53CA\u6743\u9650\u6216\u5B89\u5168");
|
|
1522
|
-
}
|
|
1523
|
-
score = Math.max(1, Math.min(10, Math.round(score)));
|
|
1524
|
-
return {
|
|
1525
|
-
score,
|
|
1526
|
-
factors: factors.length > 0 ? factors : ["\u9700\u6C42\u76F8\u5BF9\u7B80\u5355"],
|
|
1527
|
-
recommendation: score >= COMPLEXITY_THRESHOLD ? "complex" : "simple"
|
|
1528
|
-
};
|
|
789
|
+
function getActiveSession() {
|
|
790
|
+
return activeSession;
|
|
1529
791
|
}
|
|
1530
|
-
function
|
|
1531
|
-
|
|
1532
|
-
score: type === "complex" ? 8 : 3,
|
|
1533
|
-
factors: [`\u7528\u6237\u6307\u5B9A${type === "complex" ? "\u590D\u6742" : "\u7B80\u5355"}\u6D41\u7A0B`],
|
|
1534
|
-
recommendation: type
|
|
1535
|
-
};
|
|
792
|
+
function clearActiveSession() {
|
|
793
|
+
activeSession = null;
|
|
1536
794
|
}
|
|
1537
|
-
|
|
1538
|
-
const firstSentence = requirement.split(/[。!?\n]/)[0];
|
|
1539
|
-
if (firstSentence.length <= 50) {
|
|
1540
|
-
return firstSentence;
|
|
1541
|
-
}
|
|
1542
|
-
return requirement.slice(0, 47) + "...";
|
|
1543
|
-
}
|
|
1544
|
-
async function generateSpecContent(changeId, requirement, analysis, context) {
|
|
1545
|
-
const spec = {
|
|
1546
|
-
changeId,
|
|
1547
|
-
requirement,
|
|
1548
|
-
summary: extractTitle(requirement),
|
|
1549
|
-
items: analysis.recommendation === "complex" ? generateComplexTasks(requirement, context, analysis) : generateSimpleTasks(requirement),
|
|
1550
|
-
architectureNotes: generateArchitectureNotes(requirement, context),
|
|
1551
|
-
risks: generateRisks(requirement, context, analysis),
|
|
1552
|
-
suggestions: generateSuggestions(requirement, context, analysis)
|
|
1553
|
-
};
|
|
1554
|
-
return formatSpecFile(spec);
|
|
1555
|
-
}
|
|
1556
|
-
var MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, new_default;
|
|
795
|
+
var MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
|
|
1557
796
|
var init_new = __esm({
|
|
1558
797
|
"src/commands/new.ts"() {
|
|
1559
798
|
init_esm_shims();
|
|
1560
|
-
init_workflow();
|
|
1561
799
|
MAX_FILE_SIZE2 = 1024 * 1024;
|
|
1562
800
|
COMPLEXITY_THRESHOLD = 6;
|
|
1563
|
-
|
|
801
|
+
CLARITY_THRESHOLD = 0.6;
|
|
802
|
+
activeSession = null;
|
|
803
|
+
new_default = handleNew;
|
|
1564
804
|
}
|
|
1565
805
|
});
|
|
1566
806
|
|
|
@@ -5086,118 +4326,956 @@ var AgentScheduler = class {
|
|
|
5086
4326
|
message: `\u53EF\u624B\u52A8\u8C03\u7528 $${rule.agent} \u6267\u884C\u4EFB\u52A1`
|
|
5087
4327
|
};
|
|
5088
4328
|
}
|
|
5089
|
-
return this.executeAgent(rule, state, options?.context);
|
|
4329
|
+
return this.executeAgent(rule, state, options?.context);
|
|
4330
|
+
}
|
|
4331
|
+
/**
|
|
4332
|
+
* 执行 Agent
|
|
4333
|
+
*/
|
|
4334
|
+
async executeAgent(config, state, additionalContext) {
|
|
4335
|
+
const agent = getAgentDefinition(config.agent);
|
|
4336
|
+
if (!agent) {
|
|
4337
|
+
return {
|
|
4338
|
+
scheduled: false,
|
|
4339
|
+
agent: config.agent,
|
|
4340
|
+
strategy: "auto",
|
|
4341
|
+
message: `Agent ${config.agent} \u4E0D\u5B58\u5728`
|
|
4342
|
+
};
|
|
4343
|
+
}
|
|
4344
|
+
const context = {
|
|
4345
|
+
taskId: state.id,
|
|
4346
|
+
workflowId: state.id,
|
|
4347
|
+
workflowStep: state.currentStep,
|
|
4348
|
+
requirement: state.requirement,
|
|
4349
|
+
files: state.artifacts,
|
|
4350
|
+
context: additionalContext?.context,
|
|
4351
|
+
norms: additionalContext?.norms,
|
|
4352
|
+
previousOutput: additionalContext?.previousOutput
|
|
4353
|
+
};
|
|
4354
|
+
try {
|
|
4355
|
+
const result = await this.executor.execute({
|
|
4356
|
+
agentId: config.agent,
|
|
4357
|
+
context
|
|
4358
|
+
});
|
|
4359
|
+
const scheduleResult = {
|
|
4360
|
+
scheduled: true,
|
|
4361
|
+
agent: config.agent,
|
|
4362
|
+
strategy: "auto",
|
|
4363
|
+
result,
|
|
4364
|
+
message: result.success ? `$${config.agent} \u6267\u884C\u5B8C\u6210` : `$${config.agent} \u6267\u884C\u5931\u8D25: ${result.error}`
|
|
4365
|
+
};
|
|
4366
|
+
this.lastScheduleResult = scheduleResult;
|
|
4367
|
+
if (this.onSchedule) {
|
|
4368
|
+
this.onSchedule(scheduleResult);
|
|
4369
|
+
}
|
|
4370
|
+
return scheduleResult;
|
|
4371
|
+
} catch (error) {
|
|
4372
|
+
const err = error;
|
|
4373
|
+
return {
|
|
4374
|
+
scheduled: false,
|
|
4375
|
+
agent: config.agent,
|
|
4376
|
+
strategy: "auto",
|
|
4377
|
+
message: `$${config.agent} \u6267\u884C\u5F02\u5E38: ${err.message}`
|
|
4378
|
+
};
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
/**
|
|
4382
|
+
* 获取阶段的调度配置
|
|
4383
|
+
*/
|
|
4384
|
+
getScheduleConfig(step) {
|
|
4385
|
+
return SCHEDULE_RULES[step];
|
|
4386
|
+
}
|
|
4387
|
+
/**
|
|
4388
|
+
* 获取阶段的推荐 Agent
|
|
4389
|
+
*/
|
|
4390
|
+
getRecommendedAgent(step) {
|
|
4391
|
+
const rule = SCHEDULE_RULES[step];
|
|
4392
|
+
return rule?.agent;
|
|
4393
|
+
}
|
|
4394
|
+
/**
|
|
4395
|
+
* 获取所有调度规则
|
|
4396
|
+
*/
|
|
4397
|
+
getAllScheduleRules() {
|
|
4398
|
+
return { ...SCHEDULE_RULES };
|
|
4399
|
+
}
|
|
4400
|
+
/**
|
|
4401
|
+
* 获取上次调度结果
|
|
4402
|
+
*/
|
|
4403
|
+
getLastScheduleResult() {
|
|
4404
|
+
return this.lastScheduleResult;
|
|
4405
|
+
}
|
|
4406
|
+
/**
|
|
4407
|
+
* 判断阶段是否需要自动调度
|
|
4408
|
+
*/
|
|
4409
|
+
shouldAutoSchedule(step) {
|
|
4410
|
+
const rule = SCHEDULE_RULES[step];
|
|
4411
|
+
return rule?.strategy === "auto";
|
|
4412
|
+
}
|
|
4413
|
+
/**
|
|
4414
|
+
* 获取阶段可用的 Agent 列表
|
|
4415
|
+
*/
|
|
4416
|
+
getAvailableAgents(step) {
|
|
4417
|
+
const agents = getAgentsForWorkflowStep(step);
|
|
4418
|
+
return agents.map((a) => a.id);
|
|
4419
|
+
}
|
|
4420
|
+
};
|
|
4421
|
+
function createAgentScheduler(executor) {
|
|
4422
|
+
return new AgentScheduler(executor);
|
|
4423
|
+
}
|
|
4424
|
+
function getScheduleRuleDescription(step) {
|
|
4425
|
+
const rule = SCHEDULE_RULES[step];
|
|
4426
|
+
if (!rule) {
|
|
4427
|
+
return `\u9636\u6BB5 ${step} \u65E0\u81EA\u52A8\u8C03\u5EA6\u89C4\u5219`;
|
|
4428
|
+
}
|
|
4429
|
+
const agent = getAgentDefinition(rule.agent);
|
|
4430
|
+
const strategyText = {
|
|
4431
|
+
"auto": "\u81EA\u52A8\u6267\u884C",
|
|
4432
|
+
"recommend": "\u63A8\u8350\u6267\u884C",
|
|
4433
|
+
"manual": "\u624B\u52A8\u6267\u884C"
|
|
4434
|
+
};
|
|
4435
|
+
return `${strategyText[rule.strategy]} $${rule.agent} (${agent?.name})`;
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4438
|
+
// src/workflow/index.ts
|
|
4439
|
+
init_esm_shims();
|
|
4440
|
+
|
|
4441
|
+
// src/workflow/checkpoint.ts
|
|
4442
|
+
init_esm_shims();
|
|
4443
|
+
var DEFAULT_CONFIRMATION_POINTS = [
|
|
4444
|
+
{
|
|
4445
|
+
type: "spec-review",
|
|
4446
|
+
name: "\u89C4\u683C\u786E\u8BA4",
|
|
4447
|
+
description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
|
|
4448
|
+
triggerStep: "explore",
|
|
4449
|
+
targetStep: "new",
|
|
4450
|
+
required: true
|
|
4451
|
+
},
|
|
4452
|
+
{
|
|
4453
|
+
type: "spec-review",
|
|
4454
|
+
name: "\u89C4\u683C\u786E\u8BA4",
|
|
4455
|
+
description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
|
|
4456
|
+
triggerStep: "propose",
|
|
4457
|
+
targetStep: "apply",
|
|
4458
|
+
required: true
|
|
4459
|
+
},
|
|
4460
|
+
{
|
|
4461
|
+
type: "architecture",
|
|
4462
|
+
name: "\u67B6\u6784\u8C03\u6574\u786E\u8BA4",
|
|
4463
|
+
description: "\u8BBE\u8BA1\u65B9\u6848\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u67B6\u6784\u8BBE\u8BA1\u662F\u5426\u5408\u7406",
|
|
4464
|
+
triggerStep: "new",
|
|
4465
|
+
targetStep: "continue",
|
|
4466
|
+
required: true
|
|
4467
|
+
},
|
|
4468
|
+
{
|
|
4469
|
+
type: "code-review",
|
|
4470
|
+
name: "\u4EE3\u7801\u5BA1\u67E5\u786E\u8BA4",
|
|
4471
|
+
description: "\u4EE3\u7801\u5BA1\u67E5\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u662F\u5426\u53EF\u4EE5\u5F52\u6863",
|
|
4472
|
+
triggerStep: "apply",
|
|
4473
|
+
targetStep: "archive",
|
|
4474
|
+
required: true
|
|
4475
|
+
}
|
|
4476
|
+
];
|
|
4477
|
+
var ROLLBACK_RULES = {
|
|
4478
|
+
"explore": [],
|
|
4479
|
+
// 初始阶段不可回滚
|
|
4480
|
+
"new": ["explore"],
|
|
4481
|
+
// 可回滚到 explore
|
|
4482
|
+
"continue": ["new", "explore"],
|
|
4483
|
+
// 可回滚到 new 或 explore
|
|
4484
|
+
"propose": ["propose"],
|
|
4485
|
+
// 可重新生成规格(回滚到自身)
|
|
4486
|
+
"apply": ["new", "explore", "propose"],
|
|
4487
|
+
// 代码审查未通过,可回滚到 new/explore (复杂) 或 propose (简单)
|
|
4488
|
+
"archive": []
|
|
4489
|
+
// 已归档不可回滚
|
|
4490
|
+
};
|
|
4491
|
+
var ConfirmationManager = class {
|
|
4492
|
+
confirmationPoints;
|
|
4493
|
+
confirmations = /* @__PURE__ */ new Map();
|
|
4494
|
+
constructor(customPoints) {
|
|
4495
|
+
this.confirmationPoints = customPoints || DEFAULT_CONFIRMATION_POINTS;
|
|
4496
|
+
}
|
|
4497
|
+
/**
|
|
4498
|
+
* 获取指定阶段的确认点
|
|
4499
|
+
*/
|
|
4500
|
+
getConfirmationPointForTransition(from, to) {
|
|
4501
|
+
return this.confirmationPoints.find(
|
|
4502
|
+
(point) => point.triggerStep === from && point.targetStep === to
|
|
4503
|
+
);
|
|
4504
|
+
}
|
|
4505
|
+
/**
|
|
4506
|
+
* 检查是否需要确认
|
|
4507
|
+
*/
|
|
4508
|
+
needsConfirmation(from, to) {
|
|
4509
|
+
const point = this.getConfirmationPointForTransition(from, to);
|
|
4510
|
+
return point !== void 0 && point.required;
|
|
4511
|
+
}
|
|
4512
|
+
/**
|
|
4513
|
+
* 获取确认点详情
|
|
4514
|
+
*/
|
|
4515
|
+
getConfirmationPoint(type) {
|
|
4516
|
+
return this.confirmationPoints.find((point) => point.type === type);
|
|
4517
|
+
}
|
|
4518
|
+
/**
|
|
4519
|
+
* 记录确认
|
|
4520
|
+
*/
|
|
4521
|
+
confirm(type, comment) {
|
|
4522
|
+
const status = {
|
|
4523
|
+
type,
|
|
4524
|
+
confirmed: true,
|
|
4525
|
+
confirmedAt: /* @__PURE__ */ new Date(),
|
|
4526
|
+
comment
|
|
4527
|
+
};
|
|
4528
|
+
this.confirmations.set(type, status);
|
|
4529
|
+
return status;
|
|
4530
|
+
}
|
|
4531
|
+
/**
|
|
4532
|
+
* 获取确认状态
|
|
4533
|
+
*/
|
|
4534
|
+
getConfirmationStatus(type) {
|
|
4535
|
+
return this.confirmations.get(type);
|
|
4536
|
+
}
|
|
4537
|
+
/**
|
|
4538
|
+
* 检查确认点是否已确认
|
|
4539
|
+
*/
|
|
4540
|
+
isConfirmed(type) {
|
|
4541
|
+
const status = this.confirmations.get(type);
|
|
4542
|
+
return status?.confirmed ?? false;
|
|
4543
|
+
}
|
|
4544
|
+
/**
|
|
4545
|
+
* 清除确认状态(用于回滚后)
|
|
4546
|
+
*/
|
|
4547
|
+
clearConfirmation(type) {
|
|
4548
|
+
this.confirmations.delete(type);
|
|
4549
|
+
}
|
|
4550
|
+
/**
|
|
4551
|
+
* 清除所有确认状态
|
|
4552
|
+
*/
|
|
4553
|
+
clearAllConfirmations() {
|
|
4554
|
+
this.confirmations.clear();
|
|
4555
|
+
}
|
|
4556
|
+
/**
|
|
4557
|
+
* 获取所有确认点
|
|
4558
|
+
*/
|
|
4559
|
+
getAllConfirmationPoints() {
|
|
4560
|
+
return [...this.confirmationPoints];
|
|
4561
|
+
}
|
|
4562
|
+
/**
|
|
4563
|
+
* 获取指定阶段需要清除的确认点
|
|
4564
|
+
*/
|
|
4565
|
+
getConfirmationsToClear(targetStep) {
|
|
4566
|
+
const types = [];
|
|
4567
|
+
for (const point of this.confirmationPoints) {
|
|
4568
|
+
const stepOrder = ["explore", "new", "continue", "propose", "apply", "archive"];
|
|
4569
|
+
const targetIndex = stepOrder.indexOf(targetStep);
|
|
4570
|
+
const triggerIndex = stepOrder.indexOf(point.triggerStep);
|
|
4571
|
+
if (triggerIndex >= targetIndex) {
|
|
4572
|
+
types.push(point.type);
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
return types;
|
|
4576
|
+
}
|
|
4577
|
+
};
|
|
4578
|
+
function generateConfirmationPrompt(point) {
|
|
4579
|
+
let prompt2 = `\u{1F4CB} ${point.name}
|
|
4580
|
+
|
|
4581
|
+
`;
|
|
4582
|
+
prompt2 += `${point.description}
|
|
4583
|
+
|
|
4584
|
+
`;
|
|
4585
|
+
prompt2 += `\u786E\u8BA4\u9009\u9879:
|
|
4586
|
+
`;
|
|
4587
|
+
prompt2 += ` y - \u786E\u8BA4\u7EE7\u7EED
|
|
4588
|
+
`;
|
|
4589
|
+
prompt2 += ` n - \u62D2\u7EDD\uFF0C\u6682\u505C\u6D41\u7A0B
|
|
4590
|
+
`;
|
|
4591
|
+
prompt2 += ` r - \u8BF7\u6C42\u4FEE\u6539
|
|
4592
|
+
`;
|
|
4593
|
+
if (!point.required) {
|
|
4594
|
+
prompt2 += `
|
|
4595
|
+
\u63D0\u793A: \u6B64\u786E\u8BA4\u70B9\u4E3A\u975E\u5FC5\u987B\u9879`;
|
|
4596
|
+
}
|
|
4597
|
+
return prompt2;
|
|
4598
|
+
}
|
|
4599
|
+
function generateRollbackPrompt(fromStep) {
|
|
4600
|
+
const allowedTargets = ROLLBACK_RULES[fromStep] || [];
|
|
4601
|
+
if (allowedTargets.length === 0) {
|
|
4602
|
+
return `\u5F53\u524D\u9636\u6BB5 ${fromStep} \u4E0D\u652F\u6301\u56DE\u6EDA`;
|
|
4603
|
+
}
|
|
4604
|
+
let prompt2 = `\u{1F504} \u56DE\u6EDA\u9009\u9879
|
|
4605
|
+
|
|
4606
|
+
`;
|
|
4607
|
+
prompt2 += `\u5F53\u524D\u9636\u6BB5: ${fromStep}
|
|
4608
|
+
`;
|
|
4609
|
+
prompt2 += `\u53EF\u56DE\u6EDA\u5230:
|
|
4610
|
+
`;
|
|
4611
|
+
for (const target of allowedTargets) {
|
|
4612
|
+
prompt2 += ` ${target}
|
|
4613
|
+
`;
|
|
4614
|
+
}
|
|
4615
|
+
prompt2 += `
|
|
4616
|
+
\u8BF7\u9009\u62E9\u56DE\u6EDA\u76EE\u6807\u9636\u6BB5`;
|
|
4617
|
+
return prompt2;
|
|
4618
|
+
}
|
|
4619
|
+
|
|
4620
|
+
// src/workflow/index.ts
|
|
4621
|
+
var TRANSITIONS = {
|
|
4622
|
+
"explore": ["new"],
|
|
4623
|
+
"new": ["continue", "apply"],
|
|
4624
|
+
"continue": ["apply", "continue"],
|
|
4625
|
+
"propose": ["apply"],
|
|
4626
|
+
"apply": ["archive"],
|
|
4627
|
+
"archive": []
|
|
4628
|
+
};
|
|
4629
|
+
var COMPLEX_STEPS = ["explore", "new", "continue", "apply", "archive"];
|
|
4630
|
+
var SIMPLE_STEPS = ["propose", "apply", "archive"];
|
|
4631
|
+
var WorkflowEngine = class {
|
|
4632
|
+
state = null;
|
|
4633
|
+
projectPath = "";
|
|
4634
|
+
openspecPath = "";
|
|
4635
|
+
confirmationManager;
|
|
4636
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
4637
|
+
projectContext = "";
|
|
4638
|
+
// AGENTS.md 内容
|
|
4639
|
+
projectConfig = "";
|
|
4640
|
+
// config.yaml 内容
|
|
4641
|
+
devStandards = "";
|
|
4642
|
+
// devstanded.md 内容
|
|
4643
|
+
constructor() {
|
|
4644
|
+
this.confirmationManager = new ConfirmationManager();
|
|
4645
|
+
}
|
|
4646
|
+
/**
|
|
4647
|
+
* 初始化工作流引擎
|
|
4648
|
+
*/
|
|
4649
|
+
async initialize(projectPath) {
|
|
4650
|
+
this.projectPath = projectPath;
|
|
4651
|
+
this.openspecPath = path5.join(projectPath, "openspec");
|
|
4652
|
+
await this.ensureDirectories();
|
|
4653
|
+
await this.loadProjectContext();
|
|
4654
|
+
await this.restoreState();
|
|
4655
|
+
await this.restoreSnapshots();
|
|
4656
|
+
}
|
|
4657
|
+
/**
|
|
4658
|
+
* 加载项目上下文(AGENTS.md 和 config.yaml)
|
|
4659
|
+
*/
|
|
4660
|
+
async loadProjectContext() {
|
|
4661
|
+
const agentsMdPath = path5.join(this.projectPath, "AGENTS.md");
|
|
4662
|
+
try {
|
|
4663
|
+
this.projectContext = await fs4.readFile(agentsMdPath, "utf-8");
|
|
4664
|
+
} catch {
|
|
4665
|
+
this.projectContext = "";
|
|
4666
|
+
}
|
|
4667
|
+
const configPath = path5.join(this.openspecPath, "config.yaml");
|
|
4668
|
+
try {
|
|
4669
|
+
this.projectConfig = await fs4.readFile(configPath, "utf-8");
|
|
4670
|
+
} catch {
|
|
4671
|
+
this.projectConfig = "";
|
|
4672
|
+
}
|
|
4673
|
+
const devstandedPath = path5.join(this.projectPath, ".sf-cli", "norms", "devstanded.md");
|
|
4674
|
+
try {
|
|
4675
|
+
this.devStandards = await fs4.readFile(devstandedPath, "utf-8");
|
|
4676
|
+
} catch {
|
|
4677
|
+
this.devStandards = "";
|
|
4678
|
+
}
|
|
4679
|
+
}
|
|
4680
|
+
/**
|
|
4681
|
+
* 获取项目上下文
|
|
4682
|
+
*/
|
|
4683
|
+
getProjectContext() {
|
|
4684
|
+
return {
|
|
4685
|
+
agentsMd: this.projectContext,
|
|
4686
|
+
configYaml: this.projectConfig,
|
|
4687
|
+
devStandards: this.devStandards
|
|
4688
|
+
};
|
|
4689
|
+
}
|
|
4690
|
+
/**
|
|
4691
|
+
* 获取规格文件路径
|
|
4692
|
+
*/
|
|
4693
|
+
getSpecFilePath() {
|
|
4694
|
+
if (!this.state) return null;
|
|
4695
|
+
return path5.join(this.openspecPath, "changes", `${this.state.id}-spec.md`);
|
|
4696
|
+
}
|
|
4697
|
+
/**
|
|
4698
|
+
* 检查规格文件是否存在
|
|
4699
|
+
*/
|
|
4700
|
+
async hasSpecFile() {
|
|
4701
|
+
const specPath = this.getSpecFilePath();
|
|
4702
|
+
if (!specPath) return false;
|
|
4703
|
+
try {
|
|
4704
|
+
await fs4.access(specPath);
|
|
4705
|
+
return true;
|
|
4706
|
+
} catch {
|
|
4707
|
+
return false;
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
/**
|
|
4711
|
+
* 启动新工作流
|
|
4712
|
+
*/
|
|
4713
|
+
async start(requirement, complexity, options) {
|
|
4714
|
+
const type = complexity >= 6 ? "complex" : "simple";
|
|
4715
|
+
const steps = type === "complex" ? COMPLEX_STEPS : SIMPLE_STEPS;
|
|
4716
|
+
const initialStep = steps[0];
|
|
4717
|
+
const changeId = await this.generateChangeId();
|
|
4718
|
+
this.state = {
|
|
4719
|
+
id: changeId,
|
|
4720
|
+
type,
|
|
4721
|
+
currentStep: initialStep,
|
|
4722
|
+
steps: steps.map((step, index) => ({
|
|
4723
|
+
step,
|
|
4724
|
+
status: index === 0 ? "running" : "pending",
|
|
4725
|
+
startedAt: index === 0 ? /* @__PURE__ */ new Date() : void 0
|
|
4726
|
+
})),
|
|
4727
|
+
requirement,
|
|
4728
|
+
complexity,
|
|
4729
|
+
title: options?.title || requirement.slice(0, 50),
|
|
4730
|
+
artifacts: [],
|
|
4731
|
+
status: "running",
|
|
4732
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
4733
|
+
};
|
|
4734
|
+
this.snapshots.clear();
|
|
4735
|
+
this.confirmationManager.clearAllConfirmations();
|
|
4736
|
+
await this.createSnapshot();
|
|
4737
|
+
await this.createChangeRecord();
|
|
4738
|
+
await this.saveState();
|
|
4739
|
+
return this.state;
|
|
4740
|
+
}
|
|
4741
|
+
/**
|
|
4742
|
+
* 检查转换是否需要确认
|
|
4743
|
+
*/
|
|
4744
|
+
checkConfirmationNeeded(from, to) {
|
|
4745
|
+
const point = this.confirmationManager.getConfirmationPointForTransition(from, to);
|
|
4746
|
+
if (!point) {
|
|
4747
|
+
return { required: false, confirmed: true };
|
|
4748
|
+
}
|
|
4749
|
+
const confirmed = this.confirmationManager.isConfirmed(point.type);
|
|
4750
|
+
return {
|
|
4751
|
+
required: point.required,
|
|
4752
|
+
confirmed,
|
|
4753
|
+
point
|
|
4754
|
+
};
|
|
4755
|
+
}
|
|
4756
|
+
/**
|
|
4757
|
+
* 确认检查点
|
|
4758
|
+
*/
|
|
4759
|
+
confirm(type, comment) {
|
|
4760
|
+
this.confirmationManager.confirm(type, comment);
|
|
4761
|
+
}
|
|
4762
|
+
/**
|
|
4763
|
+
* 获取确认点信息
|
|
4764
|
+
*/
|
|
4765
|
+
getConfirmationPoint(type) {
|
|
4766
|
+
return this.confirmationManager.getConfirmationPoint(type);
|
|
4767
|
+
}
|
|
4768
|
+
/**
|
|
4769
|
+
* 获取当前转换需要的确认点
|
|
4770
|
+
*/
|
|
4771
|
+
getCurrentConfirmationPoint() {
|
|
4772
|
+
if (!this.state) return void 0;
|
|
4773
|
+
const allowed = this.getAllowedTransitions();
|
|
4774
|
+
for (const target of allowed) {
|
|
4775
|
+
const point = this.confirmationManager.getConfirmationPointForTransition(
|
|
4776
|
+
this.state.currentStep,
|
|
4777
|
+
target
|
|
4778
|
+
);
|
|
4779
|
+
if (point && !this.confirmationManager.isConfirmed(point.type)) {
|
|
4780
|
+
return point;
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4783
|
+
return void 0;
|
|
4784
|
+
}
|
|
4785
|
+
/**
|
|
4786
|
+
* 执行状态转换
|
|
4787
|
+
*/
|
|
4788
|
+
async transition(targetStep, reason) {
|
|
4789
|
+
if (!this.state) {
|
|
4790
|
+
throw new Error("\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41");
|
|
4791
|
+
}
|
|
4792
|
+
const currentStep = this.state.currentStep;
|
|
4793
|
+
const allowedTargets = TRANSITIONS[currentStep];
|
|
4794
|
+
if (!allowedTargets.includes(targetStep)) {
|
|
4795
|
+
throw new Error(
|
|
4796
|
+
`\u65E0\u6548\u7684\u72B6\u6001\u8F6C\u6362: ${currentStep} \u2192 ${targetStep}\u3002\u5141\u8BB8\u7684\u8F6C\u6362: ${allowedTargets.join(", ")}`
|
|
4797
|
+
);
|
|
4798
|
+
}
|
|
4799
|
+
const confirmationResult = this.checkConfirmationNeeded(currentStep, targetStep);
|
|
4800
|
+
if (confirmationResult.required && !confirmationResult.confirmed) {
|
|
4801
|
+
throw new ConfirmationRequiredError(
|
|
4802
|
+
`\u9700\u8981\u786E\u8BA4: ${confirmationResult.point?.name}`,
|
|
4803
|
+
confirmationResult.point
|
|
4804
|
+
);
|
|
4805
|
+
}
|
|
4806
|
+
const currentStepRecord = this.state.steps.find((s) => s.step === currentStep);
|
|
4807
|
+
if (currentStepRecord) {
|
|
4808
|
+
currentStepRecord.status = "completed";
|
|
4809
|
+
currentStepRecord.completedAt = /* @__PURE__ */ new Date();
|
|
4810
|
+
}
|
|
4811
|
+
const targetStepRecord = this.state.steps.find((s) => s.step === targetStep);
|
|
4812
|
+
if (targetStepRecord) {
|
|
4813
|
+
targetStepRecord.status = "running";
|
|
4814
|
+
targetStepRecord.startedAt = /* @__PURE__ */ new Date();
|
|
4815
|
+
}
|
|
4816
|
+
this.state.currentStep = targetStep;
|
|
4817
|
+
this.state.updatedAt = /* @__PURE__ */ new Date();
|
|
4818
|
+
await this.createSnapshot();
|
|
4819
|
+
await this.saveState();
|
|
4820
|
+
await this.updateChangeRecord();
|
|
4821
|
+
return {
|
|
4822
|
+
from: currentStep,
|
|
4823
|
+
to: targetStep,
|
|
4824
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
4825
|
+
reason
|
|
4826
|
+
};
|
|
4827
|
+
}
|
|
4828
|
+
/**
|
|
4829
|
+
* 获取可回滚的目标阶段
|
|
4830
|
+
*/
|
|
4831
|
+
getRollbackTargets() {
|
|
4832
|
+
if (!this.state) return [];
|
|
4833
|
+
const allTargets = ROLLBACK_RULES[this.state.currentStep] || [];
|
|
4834
|
+
const workflowSteps = this.state.type === "complex" ? COMPLEX_STEPS : SIMPLE_STEPS;
|
|
4835
|
+
return allTargets.filter((target) => workflowSteps.includes(target));
|
|
4836
|
+
}
|
|
4837
|
+
/**
|
|
4838
|
+
* 检查是否可以回滚到指定阶段
|
|
4839
|
+
*/
|
|
4840
|
+
canRollbackTo(targetStep) {
|
|
4841
|
+
const targets = this.getRollbackTargets();
|
|
4842
|
+
return targets.includes(targetStep);
|
|
4843
|
+
}
|
|
4844
|
+
/**
|
|
4845
|
+
* 执行回滚
|
|
4846
|
+
*/
|
|
4847
|
+
async rollback(targetStep, reason, description) {
|
|
4848
|
+
if (!this.state) {
|
|
4849
|
+
return {
|
|
4850
|
+
success: false,
|
|
4851
|
+
from: "explore",
|
|
4852
|
+
to: null,
|
|
4853
|
+
message: "\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41"
|
|
4854
|
+
};
|
|
4855
|
+
}
|
|
4856
|
+
const currentStep = this.state.currentStep;
|
|
4857
|
+
const allowedTargets = ROLLBACK_RULES[currentStep];
|
|
4858
|
+
if (!allowedTargets || !allowedTargets.includes(targetStep)) {
|
|
4859
|
+
return {
|
|
4860
|
+
success: false,
|
|
4861
|
+
from: currentStep,
|
|
4862
|
+
to: targetStep,
|
|
4863
|
+
message: `\u65E0\u6CD5\u4ECE ${currentStep} \u56DE\u6EDA\u5230 ${targetStep}`
|
|
4864
|
+
};
|
|
4865
|
+
}
|
|
4866
|
+
const snapshot = this.snapshots.get(targetStep);
|
|
4867
|
+
if (!snapshot) {
|
|
4868
|
+
return {
|
|
4869
|
+
success: false,
|
|
4870
|
+
from: currentStep,
|
|
4871
|
+
to: targetStep,
|
|
4872
|
+
message: `\u627E\u4E0D\u5230\u9636\u6BB5 ${targetStep} \u7684\u5FEB\u7167`
|
|
4873
|
+
};
|
|
4874
|
+
}
|
|
4875
|
+
const previousState = this.state;
|
|
4876
|
+
this.state = {
|
|
4877
|
+
...snapshot.state,
|
|
4878
|
+
createdAt: previousState.createdAt,
|
|
4879
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
4880
|
+
};
|
|
4881
|
+
const stepOrder = this.state.type === "complex" ? COMPLEX_STEPS : SIMPLE_STEPS;
|
|
4882
|
+
const targetIndex = stepOrder.indexOf(targetStep);
|
|
4883
|
+
for (let i = 0; i < this.state.steps.length; i++) {
|
|
4884
|
+
const step = this.state.steps[i];
|
|
4885
|
+
const stepIndex = stepOrder.indexOf(step.step);
|
|
4886
|
+
if (stepIndex < targetIndex) {
|
|
4887
|
+
step.status = "completed";
|
|
4888
|
+
} else if (stepIndex === targetIndex) {
|
|
4889
|
+
step.status = "running";
|
|
4890
|
+
step.startedAt = /* @__PURE__ */ new Date();
|
|
4891
|
+
step.completedAt = void 0;
|
|
4892
|
+
} else {
|
|
4893
|
+
step.status = "pending";
|
|
4894
|
+
step.startedAt = void 0;
|
|
4895
|
+
step.completedAt = void 0;
|
|
4896
|
+
}
|
|
4897
|
+
}
|
|
4898
|
+
this.state.artifacts = snapshot.artifacts;
|
|
4899
|
+
const confirmationsToClear = this.confirmationManager.getConfirmationsToClear(targetStep);
|
|
4900
|
+
for (const type of confirmationsToClear) {
|
|
4901
|
+
this.confirmationManager.clearConfirmation(type);
|
|
4902
|
+
}
|
|
4903
|
+
await this.saveState();
|
|
4904
|
+
await this.updateChangeRecord("rolled-back");
|
|
4905
|
+
return {
|
|
4906
|
+
success: true,
|
|
4907
|
+
from: currentStep,
|
|
4908
|
+
to: targetStep,
|
|
4909
|
+
message: `\u5DF2\u56DE\u6EDA: ${currentStep} \u2192 ${targetStep}\uFF0C\u539F\u56E0: ${description || reason}`,
|
|
4910
|
+
snapshot
|
|
4911
|
+
};
|
|
4912
|
+
}
|
|
4913
|
+
/**
|
|
4914
|
+
* 获取回滚原因描述
|
|
4915
|
+
*/
|
|
4916
|
+
getRollbackReasonDescription(reason) {
|
|
4917
|
+
const descriptions = {
|
|
4918
|
+
"code-review-failed": "\u4EE3\u7801\u5BA1\u67E5\u672A\u901A\u8FC7",
|
|
4919
|
+
"requirement-changed": "\u9700\u6C42\u53D8\u66F4",
|
|
4920
|
+
"design-issue": "\u8BBE\u8BA1\u95EE\u9898",
|
|
4921
|
+
"user-request": "\u7528\u6237\u8BF7\u6C42"
|
|
4922
|
+
};
|
|
4923
|
+
return descriptions[reason];
|
|
4924
|
+
}
|
|
4925
|
+
/**
|
|
4926
|
+
* 获取当前状态
|
|
4927
|
+
*/
|
|
4928
|
+
getState() {
|
|
4929
|
+
return this.state;
|
|
4930
|
+
}
|
|
4931
|
+
/**
|
|
4932
|
+
* 获取所有活跃工作流
|
|
4933
|
+
*/
|
|
4934
|
+
async getAllActiveWorkflows() {
|
|
4935
|
+
const workflows = [];
|
|
4936
|
+
const changesDir = path5.join(this.openspecPath, "changes");
|
|
4937
|
+
try {
|
|
4938
|
+
const files = await fs4.readdir(changesDir);
|
|
4939
|
+
for (const file of files) {
|
|
4940
|
+
if (!file.endsWith(".md") || file.includes("-spec.md")) continue;
|
|
4941
|
+
const filePath = path5.join(changesDir, file);
|
|
4942
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
4943
|
+
const state = this.parseChangeRecord(content);
|
|
4944
|
+
if (state && state.status === "running") {
|
|
4945
|
+
workflows.push(state);
|
|
4946
|
+
}
|
|
4947
|
+
}
|
|
4948
|
+
} catch {
|
|
4949
|
+
}
|
|
4950
|
+
if (this.state && !workflows.find((w) => w.id === this.state?.id)) {
|
|
4951
|
+
workflows.unshift(this.state);
|
|
4952
|
+
}
|
|
4953
|
+
return workflows;
|
|
5090
4954
|
}
|
|
5091
4955
|
/**
|
|
5092
|
-
*
|
|
4956
|
+
* 解析变更记录
|
|
5093
4957
|
*/
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
4958
|
+
parseChangeRecord(content) {
|
|
4959
|
+
try {
|
|
4960
|
+
const idMatch = content.match(/^id:\s*(.+)$/m);
|
|
4961
|
+
const titleMatch = content.match(/^title:\s*(.+)$/m);
|
|
4962
|
+
const statusMatch = content.match(/^status:\s*(.+)$/m);
|
|
4963
|
+
const complexityMatch = content.match(/^complexity:\s*(\d+)/m);
|
|
4964
|
+
const workflowMatch = content.match(/^workflow:\s*(.+)$/m);
|
|
4965
|
+
const requirementMatch = content.match(/## 变更概述\s*\n+([\s\S]+?)(?=\n##|$)/);
|
|
4966
|
+
if (!idMatch || !titleMatch) return null;
|
|
5097
4967
|
return {
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
4968
|
+
id: idMatch[1].trim(),
|
|
4969
|
+
title: titleMatch[1].trim(),
|
|
4970
|
+
status: statusMatch?.[1].trim() || "running",
|
|
4971
|
+
requirement: requirementMatch?.[1].trim() || "",
|
|
4972
|
+
complexity: parseInt(complexityMatch?.[1] || "5", 10),
|
|
4973
|
+
type: workflowMatch?.[1].trim() || "simple",
|
|
4974
|
+
currentStep: "propose",
|
|
4975
|
+
// 默认值,实际值需要从状态文件读取
|
|
4976
|
+
steps: [],
|
|
4977
|
+
artifacts: [],
|
|
4978
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
5102
4979
|
};
|
|
4980
|
+
} catch {
|
|
4981
|
+
return null;
|
|
5103
4982
|
}
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
};
|
|
4983
|
+
}
|
|
4984
|
+
/**
|
|
4985
|
+
* 切换到指定工作流
|
|
4986
|
+
*/
|
|
4987
|
+
async switchTo(changeId) {
|
|
4988
|
+
if (this.state) {
|
|
4989
|
+
await this.saveState();
|
|
4990
|
+
}
|
|
4991
|
+
const statePath = path5.join(this.openspecPath, ".workflow-states", `${changeId}.json`);
|
|
5114
4992
|
try {
|
|
5115
|
-
const
|
|
5116
|
-
|
|
5117
|
-
|
|
4993
|
+
const content = await fs4.readFile(statePath, "utf-8");
|
|
4994
|
+
this.state = JSON.parse(content, (key, value) => {
|
|
4995
|
+
if (key.endsWith("At") && typeof value === "string") {
|
|
4996
|
+
return new Date(value);
|
|
4997
|
+
}
|
|
4998
|
+
return value;
|
|
5118
4999
|
});
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5000
|
+
await this.restoreSnapshots();
|
|
5001
|
+
return true;
|
|
5002
|
+
} catch {
|
|
5003
|
+
const changesDir = path5.join(this.openspecPath, "changes");
|
|
5004
|
+
const changeFile = path5.join(changesDir, `${changeId}.md`);
|
|
5005
|
+
try {
|
|
5006
|
+
const content = await fs4.readFile(changeFile, "utf-8");
|
|
5007
|
+
const parsed = this.parseChangeRecord(content);
|
|
5008
|
+
if (parsed && parsed.status === "running") {
|
|
5009
|
+
this.state = parsed;
|
|
5010
|
+
return true;
|
|
5011
|
+
}
|
|
5012
|
+
} catch {
|
|
5129
5013
|
}
|
|
5130
|
-
return
|
|
5131
|
-
} catch (error) {
|
|
5132
|
-
const err = error;
|
|
5133
|
-
return {
|
|
5134
|
-
scheduled: false,
|
|
5135
|
-
agent: config.agent,
|
|
5136
|
-
strategy: "auto",
|
|
5137
|
-
message: `$${config.agent} \u6267\u884C\u5F02\u5E38: ${err.message}`
|
|
5138
|
-
};
|
|
5014
|
+
return false;
|
|
5139
5015
|
}
|
|
5140
5016
|
}
|
|
5141
5017
|
/**
|
|
5142
|
-
*
|
|
5018
|
+
* 获取允许的下一步
|
|
5143
5019
|
*/
|
|
5144
|
-
|
|
5145
|
-
return
|
|
5020
|
+
getAllowedTransitions() {
|
|
5021
|
+
if (!this.state) return [];
|
|
5022
|
+
return TRANSITIONS[this.state.currentStep] || [];
|
|
5146
5023
|
}
|
|
5147
5024
|
/**
|
|
5148
|
-
*
|
|
5025
|
+
* 获取快照历史
|
|
5149
5026
|
*/
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5027
|
+
getSnapshots() {
|
|
5028
|
+
return Array.from(this.snapshots.values()).sort(
|
|
5029
|
+
(a, b) => a.timestamp.getTime() - b.timestamp.getTime()
|
|
5030
|
+
);
|
|
5153
5031
|
}
|
|
5154
5032
|
/**
|
|
5155
|
-
*
|
|
5033
|
+
* 完成当前步骤
|
|
5156
5034
|
*/
|
|
5157
|
-
|
|
5158
|
-
|
|
5035
|
+
async completeCurrentStep(artifacts) {
|
|
5036
|
+
if (!this.state) return;
|
|
5037
|
+
const currentStepRecord = this.state.steps.find(
|
|
5038
|
+
(s) => s.step === this.state.currentStep
|
|
5039
|
+
);
|
|
5040
|
+
if (currentStepRecord) {
|
|
5041
|
+
currentStepRecord.status = "completed";
|
|
5042
|
+
currentStepRecord.completedAt = /* @__PURE__ */ new Date();
|
|
5043
|
+
}
|
|
5044
|
+
if (artifacts) {
|
|
5045
|
+
this.state.artifacts.push(...artifacts);
|
|
5046
|
+
}
|
|
5047
|
+
await this.createSnapshot();
|
|
5048
|
+
await this.saveState();
|
|
5049
|
+
}
|
|
5050
|
+
/**
|
|
5051
|
+
* 归档工作流
|
|
5052
|
+
*/
|
|
5053
|
+
async archive(summary) {
|
|
5054
|
+
if (!this.state) return;
|
|
5055
|
+
const changeId = this.state.id;
|
|
5056
|
+
for (const step of this.state.steps) {
|
|
5057
|
+
if (step.status !== "completed") {
|
|
5058
|
+
step.status = "completed";
|
|
5059
|
+
step.completedAt = /* @__PURE__ */ new Date();
|
|
5060
|
+
}
|
|
5061
|
+
}
|
|
5062
|
+
this.state.status = "completed";
|
|
5063
|
+
this.state.completedAt = /* @__PURE__ */ new Date();
|
|
5064
|
+
await this.createSpecDocument(summary);
|
|
5065
|
+
await this.updateChangeRecord("archived");
|
|
5066
|
+
await this.saveState();
|
|
5067
|
+
const changesDir = path5.join(this.openspecPath, "changes");
|
|
5068
|
+
const archiveDir = path5.join(changesDir, "archive");
|
|
5069
|
+
const changeFile = path5.join(changesDir, `${changeId}.md`);
|
|
5070
|
+
const archiveFile = path5.join(archiveDir, `${changeId}.md`);
|
|
5071
|
+
await fs4.mkdir(archiveDir, { recursive: true });
|
|
5072
|
+
await fs4.rename(changeFile, archiveFile).catch(() => {
|
|
5073
|
+
});
|
|
5074
|
+
this.state = null;
|
|
5075
|
+
this.snapshots.clear();
|
|
5076
|
+
this.confirmationManager.clearAllConfirmations();
|
|
5159
5077
|
}
|
|
5160
5078
|
/**
|
|
5161
|
-
*
|
|
5079
|
+
* 取消工作流
|
|
5162
5080
|
*/
|
|
5163
|
-
|
|
5164
|
-
|
|
5081
|
+
async cancel(reason) {
|
|
5082
|
+
if (!this.state) return;
|
|
5083
|
+
this.state.status = "cancelled";
|
|
5084
|
+
this.state.cancelledAt = /* @__PURE__ */ new Date();
|
|
5085
|
+
this.state.cancelReason = reason;
|
|
5086
|
+
await this.updateChangeRecord("cancelled");
|
|
5087
|
+
await this.saveState();
|
|
5088
|
+
this.state = null;
|
|
5089
|
+
this.snapshots.clear();
|
|
5090
|
+
this.confirmationManager.clearAllConfirmations();
|
|
5165
5091
|
}
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
const
|
|
5171
|
-
|
|
5092
|
+
// ==================== 私有方法 ====================
|
|
5093
|
+
async ensureDirectories() {
|
|
5094
|
+
const changesDir = path5.join(this.openspecPath, "changes");
|
|
5095
|
+
const archiveDir = path5.join(changesDir, "archive");
|
|
5096
|
+
const specDir = path5.join(this.openspecPath, "spec");
|
|
5097
|
+
const statesDir = path5.join(this.openspecPath, ".workflow-states");
|
|
5098
|
+
await fs4.mkdir(archiveDir, { recursive: true });
|
|
5099
|
+
await fs4.mkdir(specDir, { recursive: true });
|
|
5100
|
+
await fs4.mkdir(statesDir, { recursive: true });
|
|
5101
|
+
}
|
|
5102
|
+
async restoreState() {
|
|
5103
|
+
const activePath = path5.join(this.openspecPath, ".workflow-active.json");
|
|
5104
|
+
let activeId = null;
|
|
5105
|
+
try {
|
|
5106
|
+
const activeContent = await fs4.readFile(activePath, "utf-8");
|
|
5107
|
+
const activeData = JSON.parse(activeContent);
|
|
5108
|
+
activeId = activeData.activeId;
|
|
5109
|
+
} catch {
|
|
5110
|
+
}
|
|
5111
|
+
if (activeId) {
|
|
5112
|
+
const statePath = path5.join(this.openspecPath, ".workflow-states", `${activeId}.json`);
|
|
5113
|
+
try {
|
|
5114
|
+
const content = await fs4.readFile(statePath, "utf-8");
|
|
5115
|
+
this.state = JSON.parse(content, (key, value) => {
|
|
5116
|
+
if (key.endsWith("At") && typeof value === "string") {
|
|
5117
|
+
return new Date(value);
|
|
5118
|
+
}
|
|
5119
|
+
return value;
|
|
5120
|
+
});
|
|
5121
|
+
return;
|
|
5122
|
+
} catch {
|
|
5123
|
+
}
|
|
5124
|
+
}
|
|
5125
|
+
const oldStatePath = path5.join(this.openspecPath, ".workflow-state.json");
|
|
5126
|
+
try {
|
|
5127
|
+
const content = await fs4.readFile(oldStatePath, "utf-8");
|
|
5128
|
+
this.state = JSON.parse(content, (key, value) => {
|
|
5129
|
+
if (key.endsWith("At") && typeof value === "string") {
|
|
5130
|
+
return new Date(value);
|
|
5131
|
+
}
|
|
5132
|
+
return value;
|
|
5133
|
+
});
|
|
5134
|
+
if (this.state) {
|
|
5135
|
+
await this.saveState();
|
|
5136
|
+
await fs4.unlink(oldStatePath).catch(() => {
|
|
5137
|
+
});
|
|
5138
|
+
}
|
|
5139
|
+
} catch (e) {
|
|
5140
|
+
const err = e;
|
|
5141
|
+
if (err.code !== "ENOENT") {
|
|
5142
|
+
console.warn("\u8B66\u544A: \u5DE5\u4F5C\u6D41\u72B6\u6001\u6587\u4EF6\u5DF2\u635F\u574F\uFF0C\u5C06\u91CD\u65B0\u5F00\u59CB");
|
|
5143
|
+
}
|
|
5144
|
+
this.state = null;
|
|
5145
|
+
}
|
|
5172
5146
|
}
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
const
|
|
5178
|
-
|
|
5147
|
+
async saveState() {
|
|
5148
|
+
if (!this.state) return;
|
|
5149
|
+
const statesDir = path5.join(this.openspecPath, ".workflow-states");
|
|
5150
|
+
await fs4.mkdir(statesDir, { recursive: true });
|
|
5151
|
+
const statePath = path5.join(statesDir, `${this.state.id}.json`);
|
|
5152
|
+
await fs4.writeFile(statePath, JSON.stringify(this.state, null, 2));
|
|
5153
|
+
const activePath = path5.join(this.openspecPath, ".workflow-active.json");
|
|
5154
|
+
await fs4.writeFile(activePath, JSON.stringify({ activeId: this.state.id }, null, 2));
|
|
5179
5155
|
}
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5156
|
+
async restoreSnapshots() {
|
|
5157
|
+
const snapshotsPath = path5.join(this.openspecPath, ".workflow-snapshots.json");
|
|
5158
|
+
try {
|
|
5159
|
+
const content = await fs4.readFile(snapshotsPath, "utf-8");
|
|
5160
|
+
const data = JSON.parse(content, (key, value) => {
|
|
5161
|
+
if (key === "timestamp" && typeof value === "string") {
|
|
5162
|
+
return new Date(value);
|
|
5163
|
+
}
|
|
5164
|
+
return value;
|
|
5165
|
+
});
|
|
5166
|
+
if (Array.isArray(data)) {
|
|
5167
|
+
this.snapshots = new Map(data.map((s) => [s.step, s]));
|
|
5168
|
+
}
|
|
5169
|
+
} catch {
|
|
5170
|
+
this.snapshots = /* @__PURE__ */ new Map();
|
|
5171
|
+
}
|
|
5188
5172
|
}
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5173
|
+
async saveSnapshots() {
|
|
5174
|
+
const snapshotsPath = path5.join(this.openspecPath, ".workflow-snapshots.json");
|
|
5175
|
+
const data = Array.from(this.snapshots.values());
|
|
5176
|
+
await fs4.writeFile(snapshotsPath, JSON.stringify(data, null, 2));
|
|
5177
|
+
}
|
|
5178
|
+
async createSnapshot() {
|
|
5179
|
+
if (!this.state) return;
|
|
5180
|
+
const snapshot = {
|
|
5181
|
+
step: this.state.currentStep,
|
|
5182
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
5183
|
+
state: JSON.parse(JSON.stringify(this.state)),
|
|
5184
|
+
artifacts: [...this.state.artifacts]
|
|
5185
|
+
};
|
|
5186
|
+
this.snapshots.set(snapshot.step, snapshot);
|
|
5187
|
+
await this.saveSnapshots();
|
|
5188
|
+
}
|
|
5189
|
+
async generateChangeId() {
|
|
5190
|
+
const timestamp = Date.now().toString(36);
|
|
5191
|
+
const random = Math.random().toString(36).slice(2, 6);
|
|
5192
|
+
return `CHG-${timestamp}-${random}`.toUpperCase();
|
|
5193
|
+
}
|
|
5194
|
+
async createChangeRecord() {
|
|
5195
|
+
if (!this.state) return;
|
|
5196
|
+
const changePath = path5.join(this.openspecPath, "changes", `${this.state.id}.md`);
|
|
5197
|
+
await fs4.writeFile(changePath, this.formatChangeRecord());
|
|
5198
|
+
}
|
|
5199
|
+
async updateChangeRecord(status) {
|
|
5200
|
+
if (!this.state) return;
|
|
5201
|
+
if (status) {
|
|
5202
|
+
this.state.status = status;
|
|
5203
|
+
}
|
|
5204
|
+
const changePath = path5.join(this.openspecPath, "changes", `${this.state.id}.md`);
|
|
5205
|
+
await fs4.writeFile(changePath, this.formatChangeRecord());
|
|
5206
|
+
}
|
|
5207
|
+
formatChangeRecord() {
|
|
5208
|
+
if (!this.state) return "";
|
|
5209
|
+
const formatStepTime = (date) => {
|
|
5210
|
+
if (!date) return "-";
|
|
5211
|
+
if (typeof date === "string") return date;
|
|
5212
|
+
return date.toISOString();
|
|
5213
|
+
};
|
|
5214
|
+
return `# Change: ${this.state.title}
|
|
5197
5215
|
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5216
|
+
id: ${this.state.id}
|
|
5217
|
+
title: ${this.state.title}
|
|
5218
|
+
status: ${this.state.status}
|
|
5219
|
+
created: ${this.state.createdAt.toISOString()}
|
|
5220
|
+
${this.state.completedAt ? `completed: ${formatStepTime(this.state.completedAt)}` : ""}
|
|
5221
|
+
complexity: ${this.state.complexity}/10
|
|
5222
|
+
workflow: ${this.state.type}
|
|
5223
|
+
spec: spec/${this.state.id}.md
|
|
5224
|
+
|
|
5225
|
+
---
|
|
5226
|
+
|
|
5227
|
+
## \u53D8\u66F4\u6982\u8FF0
|
|
5228
|
+
|
|
5229
|
+
${this.state.requirement}
|
|
5230
|
+
|
|
5231
|
+
## \u5DE5\u4F5C\u6D41\u6267\u884C\u8BB0\u5F55
|
|
5232
|
+
|
|
5233
|
+
| \u9636\u6BB5 | \u5F00\u59CB\u65F6\u95F4 | \u7ED3\u675F\u65F6\u95F4 | \u72B6\u6001 |
|
|
5234
|
+
|------|----------|----------|------|
|
|
5235
|
+
${this.state.steps.map((s) => `| ${s.step} | ${formatStepTime(s.startedAt)} | ${formatStepTime(s.completedAt)} | ${s.status} |`).join("\n")}
|
|
5236
|
+
|
|
5237
|
+
## \u4EA7\u7269\u6587\u4EF6
|
|
5238
|
+
|
|
5239
|
+
${this.state.artifacts.map((a) => `- ${a}`).join("\n") || "\u6682\u65E0"}
|
|
5240
|
+
`;
|
|
5241
|
+
}
|
|
5242
|
+
async createSpecDocument(summary) {
|
|
5243
|
+
if (!this.state) return;
|
|
5244
|
+
const specPath = path5.join(this.openspecPath, "spec", `${this.state.id}.md`);
|
|
5245
|
+
const content = `# Spec: ${this.state.title}
|
|
5246
|
+
|
|
5247
|
+
> \u53D8\u66F4ID: ${this.state.id}
|
|
5248
|
+
> \u5F52\u6863\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
5249
|
+
> \u5DE5\u4F5C\u6D41\u7C7B\u578B: ${this.state.type}
|
|
5250
|
+
|
|
5251
|
+
---
|
|
5252
|
+
|
|
5253
|
+
## \u6982\u8FF0
|
|
5254
|
+
|
|
5255
|
+
${this.state.requirement}
|
|
5256
|
+
|
|
5257
|
+
## \u5B9E\u73B0\u603B\u7ED3
|
|
5258
|
+
|
|
5259
|
+
${summary}
|
|
5260
|
+
|
|
5261
|
+
## \u9A8C\u6536\u7ED3\u679C
|
|
5262
|
+
|
|
5263
|
+
${this.state.steps.map((s) => `- [${s.status === "completed" ? "x" : " "}] ${s.step}`).join("\n")}
|
|
5264
|
+
|
|
5265
|
+
## \u76F8\u5173\u6587\u4EF6
|
|
5266
|
+
|
|
5267
|
+
${this.state.artifacts.map((a) => `- ${a}`).join("\n") || "\u6682\u65E0"}
|
|
5268
|
+
`;
|
|
5269
|
+
await fs4.writeFile(specPath, content);
|
|
5270
|
+
}
|
|
5271
|
+
};
|
|
5272
|
+
var ConfirmationRequiredError = class extends Error {
|
|
5273
|
+
constructor(message, point) {
|
|
5274
|
+
super(message);
|
|
5275
|
+
this.point = point;
|
|
5276
|
+
this.name = "ConfirmationRequiredError";
|
|
5277
|
+
}
|
|
5278
|
+
};
|
|
5201
5279
|
|
|
5202
5280
|
// src/norms/index.ts
|
|
5203
5281
|
init_esm_shims();
|
|
@@ -6559,8 +6637,8 @@ var CommandParser = class {
|
|
|
6559
6637
|
};
|
|
6560
6638
|
}
|
|
6561
6639
|
parseAtPath(input) {
|
|
6562
|
-
const
|
|
6563
|
-
if (!
|
|
6640
|
+
const path15 = input.slice(1).trim();
|
|
6641
|
+
if (!path15) {
|
|
6564
6642
|
return { success: false, error: "\u7F3A\u5C11\u6587\u4EF6\u8DEF\u5F84" };
|
|
6565
6643
|
}
|
|
6566
6644
|
return {
|
|
@@ -6568,7 +6646,7 @@ var CommandParser = class {
|
|
|
6568
6646
|
command: {
|
|
6569
6647
|
type: "at" /* AT */,
|
|
6570
6648
|
raw: input,
|
|
6571
|
-
path:
|
|
6649
|
+
path: path15
|
|
6572
6650
|
}
|
|
6573
6651
|
};
|
|
6574
6652
|
}
|
|
@@ -7678,8 +7756,6 @@ init_new();
|
|
|
7678
7756
|
|
|
7679
7757
|
// src/commands/opsx.ts
|
|
7680
7758
|
init_esm_shims();
|
|
7681
|
-
init_workflow();
|
|
7682
|
-
init_checkpoint();
|
|
7683
7759
|
var autoScheduleEnabled = true;
|
|
7684
7760
|
var DEFAULT_REGRESSION_CONFIG = {
|
|
7685
7761
|
enabled: true,
|
|
@@ -8154,10 +8230,10 @@ async function handleRegenerateSpec(workflow, ctx) {
|
|
|
8154
8230
|
const lines = [];
|
|
8155
8231
|
lines.push(chalk9.cyan("\u{1F504} \u91CD\u65B0\u751F\u6210\u89C4\u683C..."));
|
|
8156
8232
|
lines.push("");
|
|
8157
|
-
const { readProjectContext: readProjectContext2, analyzeComplexity: analyzeComplexity2, generateSpecContent
|
|
8233
|
+
const { readProjectContext: readProjectContext2, analyzeComplexity: analyzeComplexity2, generateSpecContent } = await Promise.resolve().then(() => (init_new(), new_exports));
|
|
8158
8234
|
const context = await readProjectContext2(workingDir);
|
|
8159
8235
|
const analysis = analyzeComplexity2(state.requirement, context);
|
|
8160
|
-
const specContent = await
|
|
8236
|
+
const specContent = await generateSpecContent(state.id, state.requirement, analysis, context);
|
|
8161
8237
|
await fs10.promises.writeFile(specPath, specContent, "utf-8");
|
|
8162
8238
|
lines.push(chalk9.green("\u2713 \u89C4\u683C\u5DF2\u91CD\u65B0\u751F\u6210"));
|
|
8163
8239
|
lines.push(chalk9.gray(`\u8DEF\u5F84: ${specPath}`));
|
|
@@ -8526,51 +8602,17 @@ async function executeShell(command, ctx) {
|
|
|
8526
8602
|
|
|
8527
8603
|
// src/commands/natural.ts
|
|
8528
8604
|
init_esm_shims();
|
|
8605
|
+
init_new();
|
|
8529
8606
|
async function handleNaturalLanguage(input, ctx) {
|
|
8530
|
-
|
|
8531
|
-
const
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
const transition = await workflowEngine.transition("apply");
|
|
8540
|
-
return {
|
|
8541
|
-
output: chalk9.green("\u2713 \u5DF2\u786E\u8BA4\u5E76\u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5") + chalk9.gray(`
|
|
8542
|
-
\u8F6C\u6362: ${transition.from} \u2192 ${transition.to}`) + chalk9.cyan("\n\n\u4E0B\u4E00\u6B65: \u6267\u884C\u53D8\u66F4") + chalk9.gray("\n \u53EF\u8C03\u7528 $frontend-dev \u6267\u884C\u4EE3\u7801\u4FEE\u6539"),
|
|
8543
|
-
contextUsed: 0
|
|
8544
|
-
};
|
|
8545
|
-
} catch (e) {
|
|
8546
|
-
return {
|
|
8547
|
-
output: chalk9.green("\u2713 \u5DF2\u786E\u8BA4") + chalk9.yellow("\n\n\u4F7F\u7528 /opsx:next \u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5"),
|
|
8548
|
-
contextUsed: 0
|
|
8549
|
-
};
|
|
8550
|
-
}
|
|
8551
|
-
}
|
|
8552
|
-
if (trimmedInput === "n" || trimmedInput === "no" || trimmedInput === "\u91CD\u65B0" || trimmedInput === "\u4E0D\u6EE1\u610F") {
|
|
8553
|
-
const state = workflowEngine.getState();
|
|
8554
|
-
if (!state) {
|
|
8555
|
-
return {
|
|
8556
|
-
output: chalk9.red("\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41"),
|
|
8557
|
-
contextUsed: 0
|
|
8558
|
-
};
|
|
8559
|
-
}
|
|
8560
|
-
const workingDir = ctx.options.workingDirectory;
|
|
8561
|
-
const specPath = path5.join(workingDir, "openspec", "changes", `${state.id}-spec.md`);
|
|
8562
|
-
workflowEngine.confirm("spec-review", "regenerate");
|
|
8563
|
-
const { readProjectContext: readProjectContext2, analyzeComplexity: analyzeComplexity2, generateSpecContent: generateSpecContent2 } = await Promise.resolve().then(() => (init_new(), new_exports));
|
|
8564
|
-
const context = await readProjectContext2(workingDir);
|
|
8565
|
-
const analysis = analyzeComplexity2(state.requirement, context);
|
|
8566
|
-
const specContent = await generateSpecContent2(state.id, state.requirement, analysis, context);
|
|
8567
|
-
await fs4.writeFile(specPath, specContent, "utf-8");
|
|
8568
|
-
return {
|
|
8569
|
-
output: chalk9.cyan("\u{1F504} \u89C4\u683C\u5DF2\u91CD\u65B0\u751F\u6210") + chalk9.gray(`
|
|
8570
|
-
\u8DEF\u5F84: ${specPath}`) + chalk9.yellow("\n\n\u8BF7\u68C0\u67E5\u65B0\u7684\u89C4\u683C\u6587\u4EF6:") + chalk9.green("\n y - \u786E\u8BA4\u89C4\u683C") + chalk9.red("\n n - \u518D\u6B21\u91CD\u65B0\u751F\u6210"),
|
|
8571
|
-
contextUsed: 0
|
|
8572
|
-
};
|
|
8573
|
-
}
|
|
8607
|
+
input.trim().toLowerCase();
|
|
8608
|
+
const session = getActiveSession();
|
|
8609
|
+
if (session) {
|
|
8610
|
+
const result = await handleWorkflowInput(input, ctx);
|
|
8611
|
+
if (result) {
|
|
8612
|
+
return {
|
|
8613
|
+
output: result.output,
|
|
8614
|
+
contextUsed: 0
|
|
8615
|
+
};
|
|
8574
8616
|
}
|
|
8575
8617
|
}
|
|
8576
8618
|
ctx.contextManager.addMessage({
|
|
@@ -8585,7 +8627,8 @@ async function handleNaturalLanguage(input, ctx) {
|
|
|
8585
8627
|
}
|
|
8586
8628
|
|
|
8587
8629
|
// src/cli/executor.ts
|
|
8588
|
-
|
|
8630
|
+
init_new();
|
|
8631
|
+
var BASIC_COMMANDS = [
|
|
8589
8632
|
"help",
|
|
8590
8633
|
"h",
|
|
8591
8634
|
"?",
|
|
@@ -8604,59 +8647,32 @@ var ALLOWED_COMMANDS_WITHOUT_WORKFLOW = [
|
|
|
8604
8647
|
"update",
|
|
8605
8648
|
"u",
|
|
8606
8649
|
"version",
|
|
8607
|
-
"v"
|
|
8608
|
-
// OpenSpec 工作流管理命令(始终允许)
|
|
8609
|
-
"opsx:status",
|
|
8610
|
-
"opsx:cancel",
|
|
8611
|
-
"opsx:rollback"
|
|
8650
|
+
"v"
|
|
8612
8651
|
];
|
|
8613
|
-
var STAGE_PERMISSIONS = {
|
|
8614
|
-
"explore": {
|
|
8615
|
-
canRead: true,
|
|
8616
|
-
canWrite: false,
|
|
8617
|
-
canRunShell: false,
|
|
8618
|
-
allowedAgents: ["architect"]
|
|
8619
|
-
},
|
|
8620
|
-
"new": {
|
|
8621
|
-
canRead: true,
|
|
8622
|
-
canWrite: true,
|
|
8623
|
-
canRunShell: false,
|
|
8624
|
-
allowedAgents: ["frontend-dev", "architect"]
|
|
8625
|
-
},
|
|
8626
|
-
"continue": {
|
|
8627
|
-
canRead: true,
|
|
8628
|
-
canWrite: true,
|
|
8629
|
-
canRunShell: true,
|
|
8630
|
-
allowedAgents: ["frontend-dev", "tester"]
|
|
8631
|
-
},
|
|
8632
|
-
"propose": {
|
|
8633
|
-
canRead: true,
|
|
8634
|
-
canWrite: true,
|
|
8635
|
-
canRunShell: true,
|
|
8636
|
-
allowedAgents: ["frontend-dev"]
|
|
8637
|
-
},
|
|
8638
|
-
"apply": {
|
|
8639
|
-
canRead: true,
|
|
8640
|
-
canWrite: true,
|
|
8641
|
-
canRunShell: true,
|
|
8642
|
-
allowedAgents: ["code-reviewer"]
|
|
8643
|
-
},
|
|
8644
|
-
"archive": {
|
|
8645
|
-
canRead: true,
|
|
8646
|
-
canWrite: false,
|
|
8647
|
-
canRunShell: false,
|
|
8648
|
-
allowedAgents: []
|
|
8649
|
-
}
|
|
8650
|
-
};
|
|
8651
8652
|
var CommandExecutor = class {
|
|
8652
8653
|
async execute(parseResult, ctx) {
|
|
8653
8654
|
if (!parseResult.success || !parseResult.command) {
|
|
8654
8655
|
return { output: chalk9.red(`\u9519\u8BEF: ${parseResult.error}`) };
|
|
8655
8656
|
}
|
|
8656
8657
|
const { command } = parseResult;
|
|
8657
|
-
const
|
|
8658
|
-
if (!
|
|
8659
|
-
|
|
8658
|
+
const hasActiveWorkflow = getActiveSession() !== null;
|
|
8659
|
+
if (!hasActiveWorkflow) {
|
|
8660
|
+
if (command.type === "slash" /* SLASH */) {
|
|
8661
|
+
const cmd = command.command?.toLowerCase() || "";
|
|
8662
|
+
if (!BASIC_COMMANDS.includes(cmd)) {
|
|
8663
|
+
return {
|
|
8664
|
+
output: chalk9.yellow("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9.gray("\n\u8BF7\u5148\u4F7F\u7528 ") + chalk9.cyan("/new <\u9700\u6C42\u63CF\u8FF0>") + chalk9.gray(" \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
|
|
8665
|
+
};
|
|
8666
|
+
}
|
|
8667
|
+
} else if (command.type === "dollar" /* DOLLAR */) {
|
|
8668
|
+
return {
|
|
8669
|
+
output: chalk9.yellow("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41\uFF0C\u65E0\u6CD5\u8C03\u7528 Agent") + chalk9.gray("\n\u8BF7\u5148\u4F7F\u7528 ") + chalk9.cyan("/new <\u9700\u6C42\u63CF\u8FF0>") + chalk9.gray(" \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
|
|
8670
|
+
};
|
|
8671
|
+
} else if (command.type === "shell" /* SHELL */) {
|
|
8672
|
+
return {
|
|
8673
|
+
output: chalk9.yellow("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41\uFF0C\u65E0\u6CD5\u6267\u884C Shell \u547D\u4EE4") + chalk9.gray("\n\u8BF7\u5148\u4F7F\u7528 ") + chalk9.cyan("/new <\u9700\u6C42\u63CF\u8FF0>") + chalk9.gray(" \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
|
|
8674
|
+
};
|
|
8675
|
+
}
|
|
8660
8676
|
}
|
|
8661
8677
|
switch (command.type) {
|
|
8662
8678
|
case "slash" /* SLASH */:
|
|
@@ -8675,98 +8691,30 @@ var CommandExecutor = class {
|
|
|
8675
8691
|
return { output: chalk9.red("\u672A\u77E5\u7684\u547D\u4EE4\u7C7B\u578B") };
|
|
8676
8692
|
}
|
|
8677
8693
|
}
|
|
8678
|
-
/**
|
|
8679
|
-
* 检查工作流权限
|
|
8680
|
-
*/
|
|
8681
|
-
checkWorkflowPermission(command, ctx) {
|
|
8682
|
-
const workflowEngine = ctx.workflowEngine;
|
|
8683
|
-
const workflowState = workflowEngine?.getState();
|
|
8684
|
-
if (command.type === "slash" /* SLASH */) {
|
|
8685
|
-
const cmd = command.command?.toLowerCase();
|
|
8686
|
-
if (cmd?.startsWith("opsx:")) {
|
|
8687
|
-
return { allowed: true };
|
|
8688
|
-
}
|
|
8689
|
-
}
|
|
8690
|
-
if (!workflowState) {
|
|
8691
|
-
if (command.type === "slash" /* SLASH */) {
|
|
8692
|
-
const cmd = command.command?.toLowerCase();
|
|
8693
|
-
if (!ALLOWED_COMMANDS_WITHOUT_WORKFLOW.includes(cmd)) {
|
|
8694
|
-
return {
|
|
8695
|
-
allowed: false,
|
|
8696
|
-
message: chalk9.yellow("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9.gray("\n\u8BF7\u5148\u4F7F\u7528 ") + chalk9.cyan("/new <\u9700\u6C42\u63CF\u8FF0>") + chalk9.gray(" \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
|
|
8697
|
-
};
|
|
8698
|
-
}
|
|
8699
|
-
}
|
|
8700
|
-
if (command.type === "natural" /* NATURAL */) {
|
|
8701
|
-
return {
|
|
8702
|
-
allowed: false,
|
|
8703
|
-
message: chalk9.yellow("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9.gray("\n\u8BF7\u5148\u4F7F\u7528 ") + chalk9.cyan("/new <\u9700\u6C42\u63CF\u8FF0>") + chalk9.gray(" \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
|
|
8704
|
-
};
|
|
8705
|
-
}
|
|
8706
|
-
if (command.type === "dollar" /* DOLLAR */) {
|
|
8707
|
-
return {
|
|
8708
|
-
allowed: false,
|
|
8709
|
-
message: chalk9.yellow("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41\uFF0C\u65E0\u6CD5\u8C03\u7528 Agent") + chalk9.gray("\n\u8BF7\u5148\u4F7F\u7528 ") + chalk9.cyan("/new <\u9700\u6C42\u63CF\u8FF0>") + chalk9.gray(" \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
|
|
8710
|
-
};
|
|
8711
|
-
}
|
|
8712
|
-
if (command.type === "shell" /* SHELL */) {
|
|
8713
|
-
return {
|
|
8714
|
-
allowed: false,
|
|
8715
|
-
message: chalk9.yellow("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41\uFF0C\u65E0\u6CD5\u6267\u884C Shell \u547D\u4EE4") + chalk9.gray("\n\u8BF7\u5148\u4F7F\u7528 ") + chalk9.cyan("/new <\u9700\u6C42\u63CF\u8FF0>") + chalk9.gray(" \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
|
|
8716
|
-
};
|
|
8717
|
-
}
|
|
8718
|
-
return { allowed: true };
|
|
8719
|
-
}
|
|
8720
|
-
const currentStep = workflowState.currentStep;
|
|
8721
|
-
const permissions = STAGE_PERMISSIONS[currentStep];
|
|
8722
|
-
if (command.type === "at" /* AT */ && !permissions.canRead) {
|
|
8723
|
-
return {
|
|
8724
|
-
allowed: false,
|
|
8725
|
-
message: chalk9.yellow(`\u5F53\u524D\u9636\u6BB5 [${currentStep}] \u4E0D\u5141\u8BB8\u8BFB\u53D6\u6587\u4EF6`)
|
|
8726
|
-
};
|
|
8727
|
-
}
|
|
8728
|
-
if (command.type === "dollar" /* DOLLAR */) {
|
|
8729
|
-
const agentId = command.agent?.toLowerCase().replace("$", "");
|
|
8730
|
-
if (!permissions.allowedAgents.includes(agentId)) {
|
|
8731
|
-
return {
|
|
8732
|
-
allowed: false,
|
|
8733
|
-
message: chalk9.yellow(`\u5F53\u524D\u9636\u6BB5 [${currentStep}] \u4E0D\u5141\u8BB8\u8C03\u7528 $${agentId} Agent`) + chalk9.gray(`
|
|
8734
|
-
\u5F53\u524D\u9636\u6BB5\u5141\u8BB8\u7684 Agent: ${permissions.allowedAgents.map((a) => `$${a}`).join(", ") || "\u65E0"}`)
|
|
8735
|
-
};
|
|
8736
|
-
}
|
|
8737
|
-
}
|
|
8738
|
-
if (command.type === "shell" /* SHELL */ && !permissions.canRunShell) {
|
|
8739
|
-
return {
|
|
8740
|
-
allowed: false,
|
|
8741
|
-
message: chalk9.yellow(`\u5F53\u524D\u9636\u6BB5 [${currentStep}] \u4E0D\u5141\u8BB8\u6267\u884C Shell \u547D\u4EE4`)
|
|
8742
|
-
};
|
|
8743
|
-
}
|
|
8744
|
-
return { allowed: true };
|
|
8745
|
-
}
|
|
8746
8694
|
async executeSlashCommand(command, ctx) {
|
|
8747
8695
|
const result = await runSlashCommand(
|
|
8748
|
-
command.command,
|
|
8696
|
+
command.command || "",
|
|
8749
8697
|
command.args || [],
|
|
8750
8698
|
ctx
|
|
8751
8699
|
);
|
|
8752
8700
|
return { output: result.output, exit: result.exit };
|
|
8753
8701
|
}
|
|
8754
8702
|
async executeFileReference(command, ctx) {
|
|
8755
|
-
const result = await handleFileReference(command.path, ctx);
|
|
8703
|
+
const result = await handleFileReference(command.path || "", ctx);
|
|
8756
8704
|
return {
|
|
8757
8705
|
output: result.output,
|
|
8758
8706
|
contextUsed: result.contextUsed
|
|
8759
8707
|
};
|
|
8760
8708
|
}
|
|
8761
8709
|
async executeAgent(command, ctx) {
|
|
8762
|
-
const result = await runAgent(command.agent, command.args || [], ctx);
|
|
8710
|
+
const result = await runAgent(command.agent || "", command.args || [], ctx);
|
|
8763
8711
|
return {
|
|
8764
8712
|
output: result.output,
|
|
8765
8713
|
contextUsed: result.contextUsed
|
|
8766
8714
|
};
|
|
8767
8715
|
}
|
|
8768
8716
|
async executeShell(command, ctx) {
|
|
8769
|
-
const result = await executeShell(command.shellCommand, ctx);
|
|
8717
|
+
const result = await executeShell(command.shellCommand || "", ctx);
|
|
8770
8718
|
return { output: result.output };
|
|
8771
8719
|
}
|
|
8772
8720
|
async executeNaturalLanguage(command, ctx) {
|