@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/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/workflow/checkpoint.ts
35
- function generateConfirmationPrompt(point) {
36
- let prompt2 = `\u{1F4CB} ${point.name}
37
-
38
- `;
39
- prompt2 += `${point.description}
40
-
41
- `;
42
- prompt2 += `\u786E\u8BA4\u9009\u9879:
43
- `;
44
- prompt2 += ` y - \u786E\u8BA4\u7EE7\u7EED
45
- `;
46
- prompt2 += ` n - \u62D2\u7EDD\uFF0C\u6682\u505C\u6D41\u7A0B
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
- return prompt2;
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 generateRollbackPrompt(fromStep) {
57
- const allowedTargets = ROLLBACK_RULES[fromStep] || [];
58
- if (allowedTargets.length === 0) {
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
- let prompt2 = `\u{1F504} \u56DE\u6EDA\u9009\u9879
62
-
63
- `;
64
- prompt2 += `\u5F53\u524D\u9636\u6BB5: ${fromStep}
65
- `;
66
- prompt2 += `\u53EF\u56DE\u6EDA\u5230:
67
- `;
68
- for (const target of allowedTargets) {
69
- prompt2 += ` ${target}
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
- prompt2 += `
73
- \u8BF7\u9009\u62E9\u56DE\u6EDA\u76EE\u6807\u9636\u6BB5`;
74
- return prompt2;
95
+ return { output: lines.join("\n") };
75
96
  }
76
- var DEFAULT_CONFIRMATION_POINTS, ROLLBACK_RULES, ConfirmationManager;
77
- var init_checkpoint = __esm({
78
- "src/workflow/checkpoint.ts"() {
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
- var TRANSITIONS, COMPLEX_STEPS, SIMPLE_STEPS, WorkflowEngine, ConfirmationRequiredError;
218
- var init_workflow = __esm({
219
- "src/workflow/index.ts"() {
220
- init_esm_shims();
221
- init_checkpoint();
222
- TRANSITIONS = {
223
- "explore": ["new"],
224
- "new": ["continue", "apply"],
225
- "continue": ["apply", "continue"],
226
- "propose": ["apply"],
227
- "apply": ["archive"],
228
- "archive": []
229
- };
230
- COMPLEX_STEPS = ["explore", "new", "continue", "apply", "archive"];
231
- SIMPLE_STEPS = ["propose", "apply", "archive"];
232
- WorkflowEngine = class {
233
- state = null;
234
- projectPath = "";
235
- openspecPath = "";
236
- confirmationManager;
237
- snapshots = /* @__PURE__ */ new Map();
238
- projectContext = "";
239
- // AGENTS.md 内容
240
- projectConfig = "";
241
- // config.yaml 内容
242
- devStandards = "";
243
- // devstanded.md 内容
244
- constructor() {
245
- this.confirmationManager = new ConfirmationManager();
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
- const targetStepRecord = this.state.steps.find((s) => s.step === targetStep);
413
- if (targetStepRecord) {
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
- this.state.currentStep = targetStep;
418
- this.state.updatedAt = /* @__PURE__ */ new Date();
419
- await this.createSnapshot();
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
- async rollback(targetStep, reason, description) {
449
- if (!this.state) {
450
- return {
451
- success: false,
452
- from: "explore",
453
- to: null,
454
- message: "\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41"
455
- };
456
- }
457
- const currentStep = this.state.currentStep;
458
- const allowedTargets = ROLLBACK_RULES[currentStep];
459
- if (!allowedTargets || !allowedTargets.includes(targetStep)) {
460
- return {
461
- success: false,
462
- from: currentStep,
463
- to: targetStep,
464
- message: `\u65E0\u6CD5\u4ECE ${currentStep} \u56DE\u6EDA\u5230 ${targetStep}`
465
- };
466
- }
467
- const snapshot = this.snapshots.get(targetStep);
468
- if (!snapshot) {
469
- return {
470
- success: false,
471
- from: currentStep,
472
- to: targetStep,
473
- message: `\u627E\u4E0D\u5230\u9636\u6BB5 ${targetStep} \u7684\u5FEB\u7167`
474
- };
475
- }
476
- const previousState = this.state;
477
- this.state = {
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
- this.state.artifacts = snapshot.artifacts;
500
- const confirmationsToClear = this.confirmationManager.getConfirmationsToClear(targetStep);
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
- async getAllActiveWorkflows() {
536
- const workflows = [];
537
- const changesDir = path5.join(this.openspecPath, "changes");
538
- try {
539
- const files = await fs4.readdir(changesDir);
540
- for (const file of files) {
541
- if (!file.endsWith(".md") || file.includes("-spec.md")) continue;
542
- const filePath = path5.join(changesDir, file);
543
- const content = await fs4.readFile(filePath, "utf-8");
544
- const state = this.parseChangeRecord(content);
545
- if (state && state.status === "running") {
546
- workflows.push(state);
547
- }
548
- }
549
- } catch {
550
- }
551
- if (this.state && !workflows.find((w) => w.id === this.state?.id)) {
552
- workflows.unshift(this.state);
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
- return workflows;
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
- parseChangeRecord(content) {
560
- try {
561
- const idMatch = content.match(/^id:\s*(.+)$/m);
562
- const titleMatch = content.match(/^title:\s*(.+)$/m);
563
- const statusMatch = content.match(/^status:\s*(.+)$/m);
564
- const complexityMatch = content.match(/^complexity:\s*(\d+)/m);
565
- const workflowMatch = content.match(/^workflow:\s*(.+)$/m);
566
- const requirementMatch = content.match(/## 变更概述\s*\n+([\s\S]+?)(?=\n##|$)/);
567
- if (!idMatch || !titleMatch) return null;
568
- return {
569
- id: idMatch[1].trim(),
570
- title: titleMatch[1].trim(),
571
- status: statusMatch?.[1].trim() || "running",
572
- requirement: requirementMatch?.[1].trim() || "",
573
- complexity: parseInt(complexityMatch?.[1] || "5", 10),
574
- type: workflowMatch?.[1].trim() || "simple",
575
- currentStep: "propose",
576
- // 默认值,实际值需要从状态文件读取
577
- steps: [],
578
- artifacts: [],
579
- createdAt: /* @__PURE__ */ new Date()
580
- };
581
- } catch {
582
- return null;
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
- async switchTo(changeId) {
589
- if (this.state) {
590
- await this.saveState();
591
- }
592
- const statePath = path5.join(this.openspecPath, ".workflow-states", `${changeId}.json`);
593
- try {
594
- const content = await fs4.readFile(statePath, "utf-8");
595
- this.state = JSON.parse(content, (key, value) => {
596
- if (key.endsWith("At") && typeof value === "string") {
597
- return new Date(value);
598
- }
599
- return value;
600
- });
601
- await this.restoreSnapshots();
602
- return true;
603
- } catch {
604
- const changesDir = path5.join(this.openspecPath, "changes");
605
- const changeFile = path5.join(changesDir, `${changeId}.md`);
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 { requirement, forceComplexity } = parseArgs(args);
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(cwd);
940
- if (!stats.isDirectory()) {
941
- return {
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
- lines.push(chalk9.cyan("\u{1F50D} \u5206\u6790\u9879\u76EE..."));
951
- const context = await readProjectContext(cwd);
952
- lines.push(chalk9.gray(` \u9879\u76EE: ${context.name}`));
953
- lines.push(chalk9.gray(` \u7C7B\u578B: ${context.type}`));
954
- lines.push(chalk9.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`));
955
- lines.push("");
956
- lines.push(chalk9.cyan("\u{1F4CA} \u5206\u6790\u9700\u6C42\u590D\u6742\u5EA6..."));
957
- const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
958
- lines.push(chalk9.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`));
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
- lines.push("");
964
- lines.push(chalk9.cyan("\u{1F4CB} \u521D\u59CB\u5316\u5DE5\u4F5C\u6D41..."));
965
- const workflow = workflowEngine || new WorkflowEngine();
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 state = await workflow.start(requirement, analysis.score, {
970
- title: extractTitle(requirement)
971
- });
972
- lines.push(chalk9.gray(` \u53D8\u66F4ID: ${state.id}`));
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 (spec.risks.length > 0) {
993
- lines.push("");
994
- lines.push(chalk9.yellow(" \u26A0\uFE0F \u98CE\u9669\u63D0\u793A:"));
995
- for (const risk of spec.risks) {
996
- lines.push(chalk9.gray(` - ${risk}`));
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
- lines.push("");
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
- async function generateSpec(requirement, context, analysis, changeId) {
1008
- const spec = {
1009
- changeId,
1010
- requirement,
1011
- summary: "",
1012
- items: [],
1013
- architectureNotes: [],
1014
- risks: [],
1015
- suggestions: []
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: /用户|登录|注册|认证|权限/, title: "\u7528\u6237\u8BA4\u8BC1\u6A21\u5757", priority: "high" },
1037
- { pattern: /数据|存储|缓存|数据库/, title: "\u6570\u636E\u5C42\u5B9E\u73B0", priority: "high" },
1038
- { pattern: /接口|API|请求|响应/, title: "API \u63A5\u53E3\u5F00\u53D1", priority: "high" },
1039
- { pattern: /界面|页面|组件|UI/, title: "\u754C\u9762\u5F00\u53D1", priority: "medium" },
1040
- { pattern: /测试|单测|覆盖/, title: "\u6D4B\u8BD5\u7528\u4F8B\u7F16\u5199", priority: "medium" },
1041
- { pattern: /文档|说明/, title: "\u6587\u6863\u7F16\u5199", priority: "low" },
1042
- { pattern: /配置|设置/, title: "\u914D\u7F6E\u7BA1\u7406", priority: "low" },
1043
- { pattern: /优化|性能/, title: "\u6027\u80FD\u4F18\u5316", priority: "medium" },
1044
- { pattern: /安全|加密/, title: "\u5B89\u5168\u5B9E\u73B0", priority: "high" },
1045
- { pattern: /日志|监控/, title: "\u65E5\u5FD7\u76D1\u63A7", priority: "low" }
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, priority } of featurePatterns) {
597
+ for (const { pattern, title, hasInput } of featurePatterns) {
1048
598
  if (pattern.test(requirement)) {
1049
- items.push({
1050
- id: `T${itemId.toString().padStart(3, "0")}`,
599
+ features.push({
1051
600
  title,
1052
- description: `${title}\u76F8\u5173\u7684\u529F\u80FD\u5B9E\u73B0`,
1053
- priority,
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 (items.length === 0) {
1061
- items.push({
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
- priority: "high",
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 items;
613
+ return features;
1087
614
  }
1088
- function generateSimpleTasks(requirement, context) {
615
+ function generateSpecItems(requirement, context, bddScenarios, questions) {
1089
616
  const items = [];
1090
- let itemId = 1;
1091
- const urlMatch = requirement.match(/https?:\/\/[^\s]+/g);
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: "T001",
1152
- title: "\u9700\u6C42\u5206\u6790\u4E0E\u8BBE\u8BA1",
1153
- description: "\u5206\u6790\u9700\u6C42\u7EC6\u8282\uFF0C\u8BBE\u8BA1\u5B9E\u73B0\u65B9\u6848",
1154
- priority: "high",
1155
- dependencies: [],
1156
- estimatedComplexity: 2
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${itemId.toString().padStart(3, "0")}`,
1169
- title: "\u6D4B\u8BD5\u9A8C\u8BC1",
1170
- description: "\u9A8C\u8BC1\u529F\u80FD\u6B63\u786E\u6027\uFF0C\u786E\u4FDD\u7B26\u5408\u9884\u671F",
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
- dependencies: items.length > 0 ? [items[items.length - 1].id] : [],
1173
- estimatedComplexity: 1
634
+ files: [],
635
+ tests: []
1174
636
  });
1175
637
  return items;
1176
638
  }
1177
- function generateArchitectureNotes(requirement, context) {
1178
- const notes = [];
1179
- if (context.framework) {
1180
- notes.push(`\u9879\u76EE\u4F7F\u7528 ${context.framework} \u6846\u67B6\uFF0C\u9700\u9075\u5FAA\u5176\u6700\u4F73\u5B9E\u8DF5`);
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(spec) {
647
+ function formatSpecFile(session) {
1232
648
  const lines = [];
1233
- lines.push(`# Spec: ${spec.summary}`);
649
+ lines.push(`# \u9700\u6C42\u89C4\u683C: ${session.requirement.slice(0, 50)}`);
1234
650
  lines.push("");
1235
- lines.push(`> \u53D8\u66F4ID: ${spec.changeId}`);
1236
- lines.push(`> \u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}`);
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
- lines.push("## \u9700\u6C42\u6982\u8FF0");
1241
- lines.push("");
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(`- **\u4F18\u5148\u7EA7**: ${priorityLabel}`);
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
- for (const note of spec.architectureNotes) {
1262
- lines.push(`- ${note}`);
1263
- }
663
+ lines.push("---");
1264
664
  lines.push("");
1265
665
  }
1266
- if (spec.risks.length > 0) {
1267
- lines.push("## \u26A0\uFE0F \u98CE\u9669\u8BC4\u4F30");
666
+ if (session.clarificationQuestions.some((q) => q.answered)) {
667
+ lines.push("## \u9700\u6C42\u6F84\u6E05");
1268
668
  lines.push("");
1269
- for (const risk of spec.risks) {
1270
- lines.push(`- ${risk}`);
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
- if (spec.suggestions.length > 0) {
1275
- lines.push("## \u{1F4A1} \u5EFA\u8BAE");
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 suggestion of spec.suggestions) {
1278
- lines.push(`- ${suggestion}`);
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
- lines.push("## \u786E\u8BA4\u72B6\u6001");
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("- [ ] \u89C4\u683C\u5DF2\u5BA1\u9605");
1287
- lines.push("- [ ] \u4EFB\u52A1\u62C6\u5206\u5DF2\u786E\u8BA4");
699
+ lines.push("---");
1288
700
  lines.push("");
1289
- lines.push("**\u786E\u8BA4\u540E\u6267\u884C**: `/opsx:confirm spec-review`");
701
+ lines.push("**\u786E\u8BA4\u72B6\u6001**: \u23F3 \u7B49\u5F85\u786E\u8BA4");
1290
702
  return lines.join("\n");
1291
703
  }
1292
- function parseArgs(args) {
1293
- let forceComplexity;
1294
- const filteredArgs = [];
1295
- for (const arg of args) {
1296
- if (arg === "--simple") {
1297
- forceComplexity = "simple";
1298
- } else if (arg === "--complex") {
1299
- forceComplexity = "complex";
1300
- } else {
1301
- filteredArgs.push(arg);
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
- return {
1305
- requirement: filteredArgs.join(" ").trim(),
1306
- forceComplexity
1307
- };
731
+ lines.push(`});`);
732
+ return lines.join("\n");
1308
733
  }
1309
- async function readProjectContext(cwd) {
1310
- const defaultContext = {
1311
- name: path5.basename(cwd),
1312
- type: "unknown",
1313
- framework: null,
1314
- techStack: [],
1315
- description: "",
1316
- structure: {
1317
- directories: [],
1318
- keyFiles: [],
1319
- srcStructure: ""
1320
- },
1321
- norms: {
1322
- devStandards: "",
1323
- patterns: "",
1324
- weights: ""
1325
- }
1326
- };
1327
- const [agentsContext, configContext, normsContext, structureContext] = await Promise.all([
1328
- readAgentsMd(cwd),
1329
- readConfigYaml(cwd),
1330
- readNorms(cwd),
1331
- analyzeStructure(cwd)
1332
- ]);
1333
- return {
1334
- ...defaultContext,
1335
- ...agentsContext,
1336
- ...configContext,
1337
- norms: normsContext,
1338
- structure: structureContext
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
- async function readAgentsMd(cwd) {
1342
- const agentsPath = path5.join(cwd, "AGENTS.md");
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 createForcedAnalysis(type) {
1531
- return {
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
- function extractTitle(requirement) {
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
- new_default = newFeature;
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
- * 执行 Agent
4956
+ * 解析变更记录
5093
4957
  */
5094
- async executeAgent(config, state, additionalContext) {
5095
- const agent = getAgentDefinition(config.agent);
5096
- if (!agent) {
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
- scheduled: false,
5099
- agent: config.agent,
5100
- strategy: "auto",
5101
- message: `Agent ${config.agent} \u4E0D\u5B58\u5728`
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
- const context = {
5105
- taskId: state.id,
5106
- workflowId: state.id,
5107
- workflowStep: state.currentStep,
5108
- requirement: state.requirement,
5109
- files: state.artifacts,
5110
- context: additionalContext?.context,
5111
- norms: additionalContext?.norms,
5112
- previousOutput: additionalContext?.previousOutput
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 result = await this.executor.execute({
5116
- agentId: config.agent,
5117
- context
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
- const scheduleResult = {
5120
- scheduled: true,
5121
- agent: config.agent,
5122
- strategy: "auto",
5123
- result,
5124
- message: result.success ? `$${config.agent} \u6267\u884C\u5B8C\u6210` : `$${config.agent} \u6267\u884C\u5931\u8D25: ${result.error}`
5125
- };
5126
- this.lastScheduleResult = scheduleResult;
5127
- if (this.onSchedule) {
5128
- this.onSchedule(scheduleResult);
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 scheduleResult;
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
- getScheduleConfig(step) {
5145
- return SCHEDULE_RULES[step];
5020
+ getAllowedTransitions() {
5021
+ if (!this.state) return [];
5022
+ return TRANSITIONS[this.state.currentStep] || [];
5146
5023
  }
5147
5024
  /**
5148
- * 获取阶段的推荐 Agent
5025
+ * 获取快照历史
5149
5026
  */
5150
- getRecommendedAgent(step) {
5151
- const rule = SCHEDULE_RULES[step];
5152
- return rule?.agent;
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
- getAllScheduleRules() {
5158
- return { ...SCHEDULE_RULES };
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
- getLastScheduleResult() {
5164
- return this.lastScheduleResult;
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
- shouldAutoSchedule(step) {
5170
- const rule = SCHEDULE_RULES[step];
5171
- return rule?.strategy === "auto";
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
- * 获取阶段可用的 Agent 列表
5175
- */
5176
- getAvailableAgents(step) {
5177
- const agents = getAgentsForWorkflowStep(step);
5178
- return agents.map((a) => a.id);
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
- function createAgentScheduler(executor) {
5182
- return new AgentScheduler(executor);
5183
- }
5184
- function getScheduleRuleDescription(step) {
5185
- const rule = SCHEDULE_RULES[step];
5186
- if (!rule) {
5187
- return `\u9636\u6BB5 ${step} \u65E0\u81EA\u52A8\u8C03\u5EA6\u89C4\u5219`;
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
- const agent = getAgentDefinition(rule.agent);
5190
- const strategyText = {
5191
- "auto": "\u81EA\u52A8\u6267\u884C",
5192
- "recommend": "\u63A8\u8350\u6267\u884C",
5193
- "manual": "\u624B\u52A8\u6267\u884C"
5194
- };
5195
- return `${strategyText[rule.strategy]} $${rule.agent} (${agent?.name})`;
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
- // src/index.ts
5199
- init_workflow();
5200
- init_checkpoint();
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 path16 = input.slice(1).trim();
6563
- if (!path16) {
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: path16
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: generateSpecContent2 } = await Promise.resolve().then(() => (init_new(), new_exports));
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 generateSpecContent2(state.id, state.requirement, analysis, context);
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
- const trimmedInput = input.trim().toLowerCase();
8531
- const workflowEngine = ctx.workflowEngine;
8532
- const workflowState = workflowEngine?.getState();
8533
- if (workflowState && workflowState.status === "running") {
8534
- const currentStep = workflowState.currentStep;
8535
- if (currentStep === "propose" || currentStep === "explore") {
8536
- if (trimmedInput === "y" || trimmedInput === "yes" || trimmedInput === "\u786E\u8BA4") {
8537
- workflowEngine.confirm("spec-review");
8538
- try {
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
- var ALLOWED_COMMANDS_WITHOUT_WORKFLOW = [
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 workflowCheck = this.checkWorkflowPermission(command, ctx);
8658
- if (!workflowCheck.allowed) {
8659
- return { output: workflowCheck.message };
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) {