@nick848/sf-cli 1.0.3 → 1.0.4

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/cli/index.js CHANGED
@@ -3314,12 +3314,20 @@ async function exitCLI(ctx, options = {}) {
3314
3314
  var DEFAULT_CONFIRMATION_POINTS = [
3315
3315
  {
3316
3316
  type: "spec-review",
3317
- name: "\u89C4\u8303\u62C6\u89E3\u786E\u8BA4",
3318
- description: "\u89C4\u8303\u62C6\u89E3\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u662F\u5426\u7EE7\u7EED\u8FDB\u5165\u8BBE\u8BA1\u9636\u6BB5",
3317
+ name: "\u89C4\u683C\u786E\u8BA4",
3318
+ description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
3319
3319
  triggerStep: "explore",
3320
3320
  targetStep: "new",
3321
3321
  required: true
3322
3322
  },
3323
+ {
3324
+ type: "spec-review",
3325
+ name: "\u89C4\u683C\u786E\u8BA4",
3326
+ description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
3327
+ triggerStep: "propose",
3328
+ targetStep: "apply",
3329
+ required: true
3330
+ },
3323
3331
  {
3324
3332
  type: "architecture",
3325
3333
  name: "\u67B6\u6784\u8C03\u6574\u786E\u8BA4",
@@ -3355,19 +3363,15 @@ var ConfirmationManager = class {
3355
3363
  confirmationPoints;
3356
3364
  confirmations = /* @__PURE__ */ new Map();
3357
3365
  constructor(customPoints) {
3358
- const points = customPoints || DEFAULT_CONFIRMATION_POINTS;
3359
- this.confirmationPoints = new Map(points.map((p) => [p.type, p]));
3366
+ this.confirmationPoints = customPoints || DEFAULT_CONFIRMATION_POINTS;
3360
3367
  }
3361
3368
  /**
3362
3369
  * 获取指定阶段的确认点
3363
3370
  */
3364
3371
  getConfirmationPointForTransition(from, to) {
3365
- for (const point of this.confirmationPoints.values()) {
3366
- if (point.triggerStep === from && point.targetStep === to) {
3367
- return point;
3368
- }
3369
- }
3370
- return void 0;
3372
+ return this.confirmationPoints.find(
3373
+ (point) => point.triggerStep === from && point.targetStep === to
3374
+ );
3371
3375
  }
3372
3376
  /**
3373
3377
  * 检查是否需要确认
@@ -3380,7 +3384,7 @@ var ConfirmationManager = class {
3380
3384
  * 获取确认点详情
3381
3385
  */
3382
3386
  getConfirmationPoint(type) {
3383
- return this.confirmationPoints.get(type);
3387
+ return this.confirmationPoints.find((point) => point.type === type);
3384
3388
  }
3385
3389
  /**
3386
3390
  * 记录确认
@@ -3424,14 +3428,14 @@ var ConfirmationManager = class {
3424
3428
  * 获取所有确认点
3425
3429
  */
3426
3430
  getAllConfirmationPoints() {
3427
- return Array.from(this.confirmationPoints.values());
3431
+ return [...this.confirmationPoints];
3428
3432
  }
3429
3433
  /**
3430
3434
  * 获取指定阶段需要清除的确认点
3431
3435
  */
3432
3436
  getConfirmationsToClear(targetStep) {
3433
3437
  const types = [];
3434
- for (const point of this.confirmationPoints.values()) {
3438
+ for (const point of this.confirmationPoints) {
3435
3439
  const stepOrder = ["explore", "new", "continue", "propose", "apply", "archive"];
3436
3440
  const targetIndex = stepOrder.indexOf(targetStep);
3437
3441
  const triggerIndex = stepOrder.indexOf(point.triggerStep);
@@ -3554,6 +3558,26 @@ var WorkflowEngine = class {
3554
3558
  devStandards: this.devStandards
3555
3559
  };
3556
3560
  }
3561
+ /**
3562
+ * 获取规格文件路径
3563
+ */
3564
+ getSpecFilePath() {
3565
+ if (!this.state) return null;
3566
+ return path6__namespace.join(this.openspecPath, "changes", `${this.state.id}-spec.md`);
3567
+ }
3568
+ /**
3569
+ * 检查规格文件是否存在
3570
+ */
3571
+ async hasSpecFile() {
3572
+ const specPath = this.getSpecFilePath();
3573
+ if (!specPath) return false;
3574
+ try {
3575
+ await fs6__namespace.access(specPath);
3576
+ return true;
3577
+ } catch {
3578
+ return false;
3579
+ }
3580
+ }
3557
3581
  /**
3558
3582
  * 启动新工作流
3559
3583
  */
@@ -4015,16 +4039,23 @@ async function handleNew(args, ctx) {
4015
4039
  if (workflowEngine) {
4016
4040
  const existingState = workflowEngine.getState();
4017
4041
  if (existingState && existingState.status === "running") {
4042
+ if (existingState.currentStep === "explore" || existingState.currentStep === "propose") {
4043
+ const specPath = path6__namespace.join(workingDir, "openspec", "changes", `${existingState.id}-spec.md`);
4044
+ if (fs9__namespace.existsSync(specPath)) {
4045
+ return {
4046
+ output: chalk9__default.default.yellow("\u5F53\u524D\u5DE5\u4F5C\u6D41\u6B63\u5728\u7B49\u5F85\u89C4\u683C\u786E\u8BA4") + chalk9__default.default.gray(`
4047
+
4048
+ \u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9__default.default.gray(`
4049
+ \u53D8\u66F4ID: ${existingState.id}`) + chalk9__default.default.cyan("\n\n\u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210:") + chalk9__default.default.white(`
4050
+ ${specPath}`) + chalk9__default.default.yellow("\n\n\u8BF7\u786E\u8BA4\u89C4\u683C\u540E\u7EE7\u7EED:") + chalk9__default.default.gray("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C") + chalk9__default.default.gray("\n /opsx:status - \u67E5\u770B\u8BE6\u60C5")
4051
+ };
4052
+ }
4053
+ }
4018
4054
  return {
4019
4055
  output: chalk9__default.default.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9__default.default.gray(`
4020
4056
 
4021
4057
  \u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9__default.default.gray(`
4022
- \u5F53\u524D\u9636\u6BB5: ${existingState.currentStep}`) + chalk9__default.default.gray(`
4023
-
4024
- \u9009\u9879:`) + chalk9__default.default.gray(`
4025
- 1. \u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:${existingState.currentStep}`) + chalk9__default.default.gray(`
4026
- 2. \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:cancel`) + chalk9__default.default.gray(`
4027
- 3. \u67E5\u770B\u5DE5\u4F5C\u6D41\u72B6\u6001: /opsx:status`)
4058
+ \u5F53\u524D\u9636\u6BB5: ${existingState.currentStep}`) + chalk9__default.default.gray("\n\n\u9009\u9879:") + chalk9__default.default.gray("\n 1. \u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:status") + chalk9__default.default.gray("\n 2. \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:cancel")
4028
4059
  };
4029
4060
  }
4030
4061
  }
@@ -4039,6 +4070,7 @@ async function handleNew(args, ctx) {
4039
4070
  async function newFeature(options, workingDir, workflowEngine) {
4040
4071
  const cwd = workingDir || process.cwd();
4041
4072
  const { requirement, forceComplexity } = options;
4073
+ const lines = [];
4042
4074
  try {
4043
4075
  const stats = await fs6__namespace.stat(cwd);
4044
4076
  if (!stats.isDirectory()) {
@@ -4046,51 +4078,283 @@ async function newFeature(options, workingDir, workflowEngine) {
4046
4078
  output: chalk9__default.default.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
4047
4079
  };
4048
4080
  }
4049
- } catch (e) {
4081
+ } catch {
4050
4082
  return {
4051
4083
  output: chalk9__default.default.red(`\u9519\u8BEF: \u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6743\u9650\u8BBF\u95EE ${cwd}`)
4052
4084
  };
4053
4085
  }
4054
- try {
4055
- const context = await readProjectContext(cwd);
4056
- const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
4057
- const workflow = workflowEngine || new WorkflowEngine();
4058
- if (!workflowEngine) {
4059
- await workflow.initialize(cwd);
4060
- }
4061
- const state = await workflow.start(requirement, analysis.score, {
4062
- title: extractTitle(requirement)
4086
+ lines.push(chalk9__default.default.cyan("\u{1F50D} \u5206\u6790\u9879\u76EE..."));
4087
+ const context = await readProjectContext(cwd);
4088
+ lines.push(chalk9__default.default.gray(` \u9879\u76EE: ${context.name}`));
4089
+ lines.push(chalk9__default.default.gray(` \u7C7B\u578B: ${context.type}`));
4090
+ lines.push(chalk9__default.default.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`));
4091
+ lines.push("");
4092
+ lines.push(chalk9__default.default.cyan("\u{1F4CA} \u5206\u6790\u9700\u6C42\u590D\u6742\u5EA6..."));
4093
+ const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
4094
+ lines.push(chalk9__default.default.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`));
4095
+ lines.push(chalk9__default.default.gray(` \u6D41\u7A0B\u7C7B\u578B: ${analysis.recommendation === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`));
4096
+ for (const factor of analysis.factors) {
4097
+ lines.push(chalk9__default.default.gray(` - ${factor}`));
4098
+ }
4099
+ lines.push("");
4100
+ lines.push(chalk9__default.default.cyan("\u{1F4CB} \u521D\u59CB\u5316\u5DE5\u4F5C\u6D41..."));
4101
+ const workflow = workflowEngine || new WorkflowEngine();
4102
+ if (!workflowEngine) {
4103
+ await workflow.initialize(cwd);
4104
+ }
4105
+ const state = await workflow.start(requirement, analysis.score, {
4106
+ title: extractTitle(requirement)
4107
+ });
4108
+ lines.push(chalk9__default.default.gray(` \u53D8\u66F4ID: ${state.id}`));
4109
+ lines.push(chalk9__default.default.gray(` \u5DE5\u4F5C\u6D41: ${state.type}`));
4110
+ lines.push("");
4111
+ lines.push(chalk9__default.default.cyan("\u{1F4DD} \u751F\u6210\u89C4\u683C\u62C6\u5206..."));
4112
+ const spec = await generateSpec(requirement, context, analysis, state.id);
4113
+ const specPath = await saveSpecFile(cwd, spec);
4114
+ lines.push(chalk9__default.default.green(" \u2713 \u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210"));
4115
+ lines.push(chalk9__default.default.gray(` \u8DEF\u5F84: ${specPath}`));
4116
+ lines.push("");
4117
+ lines.push(chalk9__default.default.cyan.bold("\u{1F4CB} \u89C4\u683C\u6982\u89C8:"));
4118
+ lines.push(chalk9__default.default.white(`
4119
+ ${spec.summary}`));
4120
+ if (spec.items.length > 0) {
4121
+ lines.push("");
4122
+ lines.push(chalk9__default.default.cyan(" \u4EFB\u52A1\u62C6\u5206:"));
4123
+ for (const item of spec.items) {
4124
+ const priorityIcon = item.priority === "high" ? "\u{1F534}" : item.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
4125
+ lines.push(chalk9__default.default.gray(` ${priorityIcon} [${item.id}] ${item.title}`));
4126
+ }
4127
+ }
4128
+ if (spec.risks.length > 0) {
4129
+ lines.push("");
4130
+ lines.push(chalk9__default.default.yellow(" \u26A0\uFE0F \u98CE\u9669\u63D0\u793A:"));
4131
+ for (const risk of spec.risks) {
4132
+ lines.push(chalk9__default.default.gray(` - ${risk}`));
4133
+ }
4134
+ }
4135
+ lines.push("");
4136
+ lines.push(chalk9__default.default.yellow.bold("\u23F3 \u7B49\u5F85\u89C4\u683C\u786E\u8BA4"));
4137
+ lines.push(chalk9__default.default.gray("\n\u8BF7\u68C0\u67E5\u751F\u6210\u7684\u89C4\u683C\u6587\u4EF6\uFF0C\u786E\u8BA4\u540E\u7EE7\u7EED:"));
4138
+ lines.push(chalk9__default.default.white("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C\uFF0C\u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5"));
4139
+ lines.push(chalk9__default.default.white(" /opsx:rollback explore - \u89C4\u683C\u4E0D\u7B26\uFF0C\u91CD\u65B0\u62C6\u5206"));
4140
+ lines.push(chalk9__default.default.white(" /opsx:status - \u67E5\u770B\u5DE5\u4F5C\u6D41\u72B6\u6001"));
4141
+ return { output: lines.join("\n") };
4142
+ }
4143
+ async function generateSpec(requirement, context, analysis, changeId) {
4144
+ const spec = {
4145
+ changeId,
4146
+ requirement,
4147
+ summary: "",
4148
+ items: [],
4149
+ architectureNotes: [],
4150
+ risks: [],
4151
+ suggestions: []
4152
+ };
4153
+ spec.summary = generateSummary(requirement);
4154
+ if (analysis.recommendation === "complex") {
4155
+ spec.items = generateComplexTasks(requirement, context, analysis);
4156
+ spec.architectureNotes = generateArchitectureNotes(requirement, context);
4157
+ } else {
4158
+ spec.items = generateSimpleTasks(requirement);
4159
+ }
4160
+ spec.risks = generateRisks(requirement, context, analysis);
4161
+ spec.suggestions = generateSuggestions(requirement, context, analysis);
4162
+ return spec;
4163
+ }
4164
+ function generateSummary(requirement) {
4165
+ const firstSentence = requirement.split(/[。!?\n]/)[0];
4166
+ return firstSentence.length > 100 ? firstSentence.slice(0, 97) + "..." : firstSentence;
4167
+ }
4168
+ function generateComplexTasks(requirement, context, analysis) {
4169
+ const items = [];
4170
+ let itemId = 1;
4171
+ const featurePatterns = [
4172
+ { pattern: /用户|登录|注册|认证|权限/, title: "\u7528\u6237\u8BA4\u8BC1\u6A21\u5757", priority: "high" },
4173
+ { pattern: /数据|存储|缓存|数据库/, title: "\u6570\u636E\u5C42\u5B9E\u73B0", priority: "high" },
4174
+ { pattern: /接口|API|请求|响应/, title: "API \u63A5\u53E3\u5F00\u53D1", priority: "high" },
4175
+ { pattern: /界面|页面|组件|UI/, title: "\u754C\u9762\u5F00\u53D1", priority: "medium" },
4176
+ { pattern: /测试|单测|覆盖/, title: "\u6D4B\u8BD5\u7528\u4F8B\u7F16\u5199", priority: "medium" },
4177
+ { pattern: /文档|说明/, title: "\u6587\u6863\u7F16\u5199", priority: "low" },
4178
+ { pattern: /配置|设置/, title: "\u914D\u7F6E\u7BA1\u7406", priority: "low" },
4179
+ { pattern: /优化|性能/, title: "\u6027\u80FD\u4F18\u5316", priority: "medium" },
4180
+ { pattern: /安全|加密/, title: "\u5B89\u5168\u5B9E\u73B0", priority: "high" },
4181
+ { pattern: /日志|监控/, title: "\u65E5\u5FD7\u76D1\u63A7", priority: "low" }
4182
+ ];
4183
+ for (const { pattern, title, priority } of featurePatterns) {
4184
+ if (pattern.test(requirement)) {
4185
+ items.push({
4186
+ id: `T${itemId.toString().padStart(3, "0")}`,
4187
+ title,
4188
+ description: `${title}\u76F8\u5173\u7684\u529F\u80FD\u5B9E\u73B0`,
4189
+ priority,
4190
+ dependencies: itemId > 1 ? [`T${(itemId - 1).toString().padStart(3, "0")}`] : [],
4191
+ estimatedComplexity: priority === "high" ? 3 : priority === "medium" ? 2 : 1
4192
+ });
4193
+ itemId++;
4194
+ }
4195
+ }
4196
+ if (items.length === 0) {
4197
+ items.push({
4198
+ id: "T001",
4199
+ title: "\u9700\u6C42\u5206\u6790\u4E0E\u8BBE\u8BA1",
4200
+ description: "\u5206\u6790\u9700\u6C42\u7EC6\u8282\uFF0C\u8BBE\u8BA1\u5B9E\u73B0\u65B9\u6848",
4201
+ priority: "high",
4202
+ dependencies: [],
4203
+ estimatedComplexity: 2
4063
4204
  });
4064
- const lines = [
4065
- chalk9__default.default.green("\u2713 \u9700\u6C42\u5206\u6790\u5B8C\u6210\n"),
4066
- chalk9__default.default.cyan("\u9879\u76EE\u4FE1\u606F:"),
4067
- chalk9__default.default.gray(` \u540D\u79F0: ${context.name}`),
4068
- chalk9__default.default.gray(` \u7C7B\u578B: ${context.type}`),
4069
- chalk9__default.default.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`),
4070
- "",
4071
- chalk9__default.default.cyan("\u9700\u6C42\u5206\u6790:"),
4072
- chalk9__default.default.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`),
4073
- chalk9__default.default.gray(` \u5DE5\u4F5C\u6D41: ${state.type === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`),
4074
- chalk9__default.default.gray(` \u53D8\u66F4ID: ${state.id}`),
4075
- "",
4076
- chalk9__default.default.cyan("\u590D\u6742\u5EA6\u56E0\u7D20:"),
4077
- ...analysis.factors.map((f) => chalk9__default.default.gray(` - ${f}`)),
4078
- "",
4079
- chalk9__default.default.yellow("\u4E0B\u4E00\u6B65:"),
4080
- state.type === "complex" ? chalk9__default.default.gray(" \u6267\u884C /opsx:explore \u5F00\u59CB\u63A2\u7D22\u9636\u6BB5") : chalk9__default.default.gray(" \u6267\u884C /opsx:propose \u63D0\u4EA4\u53D8\u66F4\u63D0\u6848")
4081
- ];
4082
- return { output: lines.join("\n") };
4083
- } catch (error) {
4084
- const err = error;
4085
- if (err.code === "EACCES") {
4086
- return {
4087
- output: chalk9__default.default.red(`\u9519\u8BEF: \u65E0\u6743\u9650\u8BBF\u95EE\u76EE\u5F55 ${cwd}`)
4088
- };
4205
+ items.push({
4206
+ id: "T002",
4207
+ title: "\u6838\u5FC3\u529F\u80FD\u5B9E\u73B0",
4208
+ description: requirement,
4209
+ priority: "high",
4210
+ dependencies: ["T001"],
4211
+ estimatedComplexity: analysis.score
4212
+ });
4213
+ items.push({
4214
+ id: "T003",
4215
+ title: "\u6D4B\u8BD5\u4E0E\u9A8C\u8BC1",
4216
+ description: "\u7F16\u5199\u6D4B\u8BD5\u7528\u4F8B\uFF0C\u9A8C\u8BC1\u529F\u80FD\u6B63\u786E\u6027",
4217
+ priority: "medium",
4218
+ dependencies: ["T002"],
4219
+ estimatedComplexity: 2
4220
+ });
4221
+ }
4222
+ return items;
4223
+ }
4224
+ function generateSimpleTasks(requirement, context) {
4225
+ return [
4226
+ {
4227
+ id: "T001",
4228
+ title: "\u5B9E\u73B0\u53D8\u66F4",
4229
+ description: requirement,
4230
+ priority: "high",
4231
+ dependencies: [],
4232
+ estimatedComplexity: 3
4233
+ },
4234
+ {
4235
+ id: "T002",
4236
+ title: "\u6D4B\u8BD5\u9A8C\u8BC1",
4237
+ description: "\u9A8C\u8BC1\u53D8\u66F4\u6B63\u786E\u6027",
4238
+ priority: "medium",
4239
+ dependencies: ["T001"],
4240
+ estimatedComplexity: 1
4089
4241
  }
4090
- return {
4091
- output: chalk9__default.default.red(`\u542F\u52A8\u9700\u6C42\u5931\u8D25: ${error.message}`)
4092
- };
4242
+ ];
4243
+ }
4244
+ function generateArchitectureNotes(requirement, context) {
4245
+ const notes = [];
4246
+ if (context.framework) {
4247
+ notes.push(`\u9879\u76EE\u4F7F\u7528 ${context.framework} \u6846\u67B6\uFF0C\u9700\u9075\u5FAA\u5176\u6700\u4F73\u5B9E\u8DF5`);
4248
+ }
4249
+ if (requirement.includes("\u6A21\u5757") || requirement.includes("\u7EC4\u4EF6")) {
4250
+ notes.push("\u5EFA\u8BAE\u91C7\u7528\u6A21\u5757\u5316\u8BBE\u8BA1\uFF0C\u4FDD\u6301\u7EC4\u4EF6\u804C\u8D23\u5355\u4E00");
4251
+ }
4252
+ if (requirement.includes("API") || requirement.includes("\u63A5\u53E3")) {
4253
+ notes.push("API \u8BBE\u8BA1\u9700\u8003\u8651\u7248\u672C\u63A7\u5236\u548C\u5411\u540E\u517C\u5BB9");
4254
+ }
4255
+ if (context.structure.srcStructure) {
4256
+ notes.push(`\u73B0\u6709\u6E90\u7801\u7ED3\u6784: ${context.structure.srcStructure}`);
4257
+ }
4258
+ return notes;
4259
+ }
4260
+ function generateRisks(requirement, context, analysis) {
4261
+ const risks = [];
4262
+ if (!context.framework) {
4263
+ risks.push("\u9879\u76EE\u6846\u67B6\u672A\u8BC6\u522B\uFF0C\u53EF\u80FD\u5F71\u54CD\u4EE3\u7801\u98CE\u683C\u4E00\u81F4\u6027");
4264
+ }
4265
+ if (analysis.score >= 7) {
4266
+ risks.push("\u9700\u6C42\u590D\u6742\u5EA6\u8F83\u9AD8\uFF0C\u5EFA\u8BAE\u5206\u9636\u6BB5\u5B9E\u73B0");
4267
+ }
4268
+ if (requirement.includes("\u8FC1\u79FB") || requirement.includes("\u91CD\u6784")) {
4269
+ risks.push("\u6D89\u53CA\u73B0\u6709\u4EE3\u7801\u4FEE\u6539\uFF0C\u9700\u6CE8\u610F\u56DE\u5F52\u6D4B\u8BD5");
4270
+ }
4271
+ if (requirement.includes("\u6743\u9650") || requirement.includes("\u5B89\u5168")) {
4272
+ risks.push("\u6D89\u53CA\u5B89\u5168\u654F\u611F\u529F\u80FD\uFF0C\u9700\u8981\u989D\u5916\u5BA1\u67E5");
4273
+ }
4274
+ return risks;
4275
+ }
4276
+ function generateSuggestions(requirement, context, analysis) {
4277
+ const suggestions = [];
4278
+ if (context.norms.devStandards) {
4279
+ suggestions.push("\u9879\u76EE\u5DF2\u6709\u5F00\u53D1\u89C4\u8303\uFF0C\u8BF7\u9075\u5FAA\u73B0\u6709\u89C4\u8303");
4280
+ }
4281
+ if (analysis.recommendation === "complex") {
4282
+ suggestions.push("\u590D\u6742\u9700\u6C42\u5EFA\u8BAE\u5148\u8FDB\u884C\u6280\u672F\u8BC4\u5BA1");
4283
+ suggestions.push("\u5EFA\u8BAE\u8C03\u7528 $architect \u83B7\u53D6\u67B6\u6784\u5EFA\u8BAE");
4284
+ }
4285
+ if (context.techStack.length > 0) {
4286
+ suggestions.push(`\u6280\u672F\u6808: ${context.techStack.join(", ")}`);
4287
+ }
4288
+ return suggestions;
4289
+ }
4290
+ async function saveSpecFile(cwd, spec) {
4291
+ const changesDir = path6__namespace.join(cwd, "openspec", "changes");
4292
+ await fs6__namespace.mkdir(changesDir, { recursive: true });
4293
+ const specPath = path6__namespace.join(changesDir, `${spec.changeId}-spec.md`);
4294
+ const content = formatSpecFile(spec);
4295
+ await fs6__namespace.writeFile(specPath, content, "utf-8");
4296
+ return specPath;
4297
+ }
4298
+ function formatSpecFile(spec) {
4299
+ const lines = [];
4300
+ lines.push(`# Spec: ${spec.summary}`);
4301
+ lines.push("");
4302
+ lines.push(`> \u53D8\u66F4ID: ${spec.changeId}`);
4303
+ lines.push(`> \u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}`);
4304
+ lines.push("");
4305
+ lines.push("---");
4306
+ lines.push("");
4307
+ lines.push("## \u9700\u6C42\u6982\u8FF0");
4308
+ lines.push("");
4309
+ lines.push(spec.requirement);
4310
+ lines.push("");
4311
+ lines.push("## \u4EFB\u52A1\u62C6\u5206");
4312
+ lines.push("");
4313
+ for (const item of spec.items) {
4314
+ const priorityLabel = item.priority === "high" ? "\u{1F534} \u9AD8" : item.priority === "medium" ? "\u{1F7E1} \u4E2D" : "\u{1F7E2} \u4F4E";
4315
+ lines.push(`### ${item.id}: ${item.title}`);
4316
+ lines.push("");
4317
+ lines.push(`- **\u4F18\u5148\u7EA7**: ${priorityLabel}`);
4318
+ lines.push(`- **\u63CF\u8FF0**: ${item.description}`);
4319
+ lines.push(`- **\u9884\u4F30\u590D\u6742\u5EA6**: ${item.estimatedComplexity}/5`);
4320
+ if (item.dependencies.length > 0) {
4321
+ lines.push(`- **\u4F9D\u8D56**: ${item.dependencies.join(", ")}`);
4322
+ }
4323
+ lines.push("");
4324
+ }
4325
+ if (spec.architectureNotes.length > 0) {
4326
+ lines.push("## \u67B6\u6784\u8BF4\u660E");
4327
+ lines.push("");
4328
+ for (const note of spec.architectureNotes) {
4329
+ lines.push(`- ${note}`);
4330
+ }
4331
+ lines.push("");
4332
+ }
4333
+ if (spec.risks.length > 0) {
4334
+ lines.push("## \u26A0\uFE0F \u98CE\u9669\u8BC4\u4F30");
4335
+ lines.push("");
4336
+ for (const risk of spec.risks) {
4337
+ lines.push(`- ${risk}`);
4338
+ }
4339
+ lines.push("");
4340
+ }
4341
+ if (spec.suggestions.length > 0) {
4342
+ lines.push("## \u{1F4A1} \u5EFA\u8BAE");
4343
+ lines.push("");
4344
+ for (const suggestion of spec.suggestions) {
4345
+ lines.push(`- ${suggestion}`);
4346
+ }
4347
+ lines.push("");
4093
4348
  }
4349
+ lines.push("---");
4350
+ lines.push("");
4351
+ lines.push("## \u786E\u8BA4\u72B6\u6001");
4352
+ lines.push("");
4353
+ lines.push("- [ ] \u89C4\u683C\u5DF2\u5BA1\u9605");
4354
+ lines.push("- [ ] \u4EFB\u52A1\u62C6\u5206\u5DF2\u786E\u8BA4");
4355
+ lines.push("");
4356
+ lines.push("**\u786E\u8BA4\u540E\u6267\u884C**: `/opsx:confirm spec-review`");
4357
+ return lines.join("\n");
4094
4358
  }
4095
4359
  function parseArgs(args) {
4096
4360
  let forceComplexity;
@@ -4115,42 +4379,52 @@ async function readProjectContext(cwd) {
4115
4379
  type: "unknown",
4116
4380
  framework: null,
4117
4381
  techStack: [],
4118
- description: ""
4382
+ description: "",
4383
+ structure: {
4384
+ directories: [],
4385
+ keyFiles: [],
4386
+ srcStructure: ""
4387
+ },
4388
+ norms: {
4389
+ devStandards: "",
4390
+ patterns: "",
4391
+ weights: ""
4392
+ }
4119
4393
  };
4394
+ const [agentsContext, configContext, normsContext, structureContext] = await Promise.all([
4395
+ readAgentsMd(cwd),
4396
+ readConfigYaml(cwd),
4397
+ readNorms(cwd),
4398
+ analyzeStructure(cwd)
4399
+ ]);
4400
+ return {
4401
+ ...defaultContext,
4402
+ ...agentsContext,
4403
+ ...configContext,
4404
+ norms: normsContext,
4405
+ structure: structureContext
4406
+ };
4407
+ }
4408
+ async function readAgentsMd(cwd) {
4120
4409
  const agentsPath = path6__namespace.join(cwd, "AGENTS.md");
4121
4410
  try {
4122
4411
  const stats = await fs6__namespace.stat(agentsPath);
4123
4412
  if (stats.size > MAX_FILE_SIZE2) {
4124
4413
  console.warn(`\u8B66\u544A: AGENTS.md \u6587\u4EF6\u8FC7\u5927 (${stats.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
4125
- return defaultContext;
4414
+ return {};
4126
4415
  }
4127
4416
  const content = await fs6__namespace.readFile(agentsPath, "utf-8");
4128
- return parseAgentsMd(content, defaultContext);
4417
+ return parseAgentsMd(content);
4129
4418
  } catch (e) {
4130
4419
  const err = e;
4131
4420
  if (err.code !== "ENOENT") {
4132
4421
  console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 AGENTS.md - ${err.message}`);
4133
4422
  }
4423
+ return {};
4134
4424
  }
4135
- const configPath = path6__namespace.join(cwd, "openspec", "config.yaml");
4136
- try {
4137
- const stats = await fs6__namespace.stat(configPath);
4138
- if (stats.size > MAX_FILE_SIZE2) {
4139
- console.warn(`\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
4140
- return defaultContext;
4141
- }
4142
- const content = await fs6__namespace.readFile(configPath, "utf-8");
4143
- return parseConfigYaml(content, defaultContext);
4144
- } catch (e) {
4145
- const err = e;
4146
- if (err.code !== "ENOENT") {
4147
- console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
4148
- }
4149
- }
4150
- return defaultContext;
4151
4425
  }
4152
- function parseAgentsMd(content, defaults) {
4153
- const context = { ...defaults };
4426
+ function parseAgentsMd(content) {
4427
+ const context = {};
4154
4428
  const nameMatch = content.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
4155
4429
  if (nameMatch) {
4156
4430
  context.name = nameMatch[1];
@@ -4167,10 +4441,32 @@ function parseAgentsMd(content, defaults) {
4167
4441
  if (descMatch) {
4168
4442
  context.description = descMatch[1].trim();
4169
4443
  }
4444
+ const techStackMatch = content.match(/技术栈[::]\s*([^\n]+)/);
4445
+ if (techStackMatch) {
4446
+ context.techStack = techStackMatch[1].split(/[,,、]/).map((s) => s.trim()).filter(Boolean);
4447
+ }
4170
4448
  return context;
4171
4449
  }
4172
- function parseConfigYaml(content, defaults) {
4173
- const context = { ...defaults };
4450
+ async function readConfigYaml(cwd) {
4451
+ const configPath = path6__namespace.join(cwd, "openspec", "config.yaml");
4452
+ try {
4453
+ const stats = await fs6__namespace.stat(configPath);
4454
+ if (stats.size > MAX_FILE_SIZE2) {
4455
+ console.warn("\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6");
4456
+ return {};
4457
+ }
4458
+ const content = await fs6__namespace.readFile(configPath, "utf-8");
4459
+ return parseConfigYaml(content);
4460
+ } catch (e) {
4461
+ const err = e;
4462
+ if (err.code !== "ENOENT") {
4463
+ console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
4464
+ }
4465
+ return {};
4466
+ }
4467
+ }
4468
+ function parseConfigYaml(content) {
4469
+ const context = {};
4174
4470
  const nameMatch = content.match(/name:\s*(.+)/);
4175
4471
  if (nameMatch) {
4176
4472
  context.name = nameMatch[1].trim();
@@ -4185,6 +4481,67 @@ function parseConfigYaml(content, defaults) {
4185
4481
  }
4186
4482
  return context;
4187
4483
  }
4484
+ async function readNorms(cwd) {
4485
+ const normsDir = path6__namespace.join(cwd, ".sf-cli", "norms");
4486
+ const norms = {
4487
+ devStandards: "",
4488
+ patterns: "",
4489
+ weights: ""
4490
+ };
4491
+ try {
4492
+ const devStandardsPath = path6__namespace.join(normsDir, "devstanded.md");
4493
+ norms.devStandards = await fs6__namespace.readFile(devStandardsPath, "utf-8").catch(() => "");
4494
+ } catch {
4495
+ }
4496
+ try {
4497
+ const patternsPath = path6__namespace.join(normsDir, "patterns.json");
4498
+ norms.patterns = await fs6__namespace.readFile(patternsPath, "utf-8").catch(() => "");
4499
+ } catch {
4500
+ }
4501
+ try {
4502
+ const weightsPath = path6__namespace.join(normsDir, "weights.json");
4503
+ norms.weights = await fs6__namespace.readFile(weightsPath, "utf-8").catch(() => "");
4504
+ } catch {
4505
+ }
4506
+ return norms;
4507
+ }
4508
+ async function analyzeStructure(cwd) {
4509
+ const structure = {
4510
+ directories: [],
4511
+ keyFiles: [],
4512
+ srcStructure: ""
4513
+ };
4514
+ try {
4515
+ const entries = await fs6__namespace.readdir(cwd, { withFileTypes: true });
4516
+ for (const entry of entries) {
4517
+ if (entry.isDirectory() && !["node_modules", "dist", ".git", "build"].includes(entry.name)) {
4518
+ structure.directories.push(entry.name);
4519
+ }
4520
+ }
4521
+ const keyFiles = [
4522
+ "package.json",
4523
+ "tsconfig.json",
4524
+ "AGENTS.md",
4525
+ "README.md"
4526
+ ];
4527
+ for (const file of keyFiles) {
4528
+ const filePath = path6__namespace.join(cwd, file);
4529
+ try {
4530
+ await fs6__namespace.access(filePath);
4531
+ structure.keyFiles.push(file);
4532
+ } catch {
4533
+ }
4534
+ }
4535
+ const srcDir = path6__namespace.join(cwd, "src");
4536
+ try {
4537
+ const srcEntries = await fs6__namespace.readdir(srcDir, { withFileTypes: true });
4538
+ structure.srcStructure = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name).join("/");
4539
+ } catch {
4540
+ }
4541
+ } catch (e) {
4542
+ }
4543
+ return { structure };
4544
+ }
4188
4545
  function analyzeComplexity(requirement, context) {
4189
4546
  let score = 3;
4190
4547
  const factors = [];
@@ -4990,7 +5347,7 @@ async function handleOpsx(command, args, ctx) {
4990
5347
  case "rollback":
4991
5348
  return handleRollback(workflow, args);
4992
5349
  case "confirm":
4993
- return handleConfirm(workflow, args);
5350
+ return handleConfirm(workflow, args, ctx);
4994
5351
  case "next":
4995
5352
  return handleNext(workflow);
4996
5353
  case "auto":
@@ -5358,6 +5715,16 @@ async function handleConfirm(workflow, args, ctx) {
5358
5715
  const type = args[0];
5359
5716
  if (!type) {
5360
5717
  const pendingPoint = workflow.getCurrentConfirmationPoint();
5718
+ const specPath = await checkPendingSpec(ctx.options.workingDirectory, state.id);
5719
+ if (specPath) {
5720
+ return {
5721
+ output: chalk9__default.default.cyan("\u{1F4CB} \u89C4\u683C\u6587\u4EF6\u5F85\u786E\u8BA4") + chalk9__default.default.gray(`
5722
+
5723
+ \u89C4\u683C\u6587\u4EF6: ${specPath}`) + chalk9__default.default.gray(`
5724
+
5725
+ \u8BF7\u68C0\u67E5\u89C4\u683C\u6587\u4EF6\u540E\u786E\u8BA4:`) + chalk9__default.default.white("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C") + chalk9__default.default.white("\n /opsx:rollback explore - \u91CD\u65B0\u62C6\u5206")
5726
+ };
5727
+ }
5361
5728
  if (pendingPoint) {
5362
5729
  return {
5363
5730
  output: chalk9__default.default.cyan("\u5F85\u786E\u8BA4\u7684\u68C0\u67E5\u70B9:") + chalk9__default.default.white(`
@@ -5379,12 +5746,54 @@ ${generateConfirmationPrompt(pendingPoint)}`) + chalk9__default.default.gray(`
5379
5746
  }
5380
5747
  const comment = args.slice(1).join(" ");
5381
5748
  workflow.confirm(type, comment);
5382
- return {
5383
- output: chalk9__default.default.green("\u2713 \u5DF2\u786E\u8BA4") + chalk9__default.default.white(`
5749
+ const lines = [];
5750
+ lines.push(chalk9__default.default.green("\u2713 \u5DF2\u786E\u8BA4"));
5751
+ lines.push(chalk9__default.default.white(`
5752
+ ${point.name}`));
5753
+ if (comment) {
5754
+ lines.push(chalk9__default.default.gray(`
5755
+ \u5907\u6CE8: ${comment}`));
5756
+ }
5757
+ if (type === "spec-review") {
5758
+ const allowed = workflow.getAllowedTransitions();
5759
+ if (allowed.length > 0) {
5760
+ const nextStep = allowed[0];
5761
+ try {
5762
+ const transition = await workflow.transition(nextStep);
5763
+ lines.push("");
5764
+ lines.push(chalk9__default.default.cyan(`\u2713 \u5DF2\u81EA\u52A8\u8FDB\u5165 ${nextStep} \u9636\u6BB5`));
5765
+ lines.push(chalk9__default.default.gray(`\u8F6C\u6362: ${transition.from} \u2192 ${transition.to}`));
5766
+ if (nextStep === "new") {
5767
+ lines.push(chalk9__default.default.yellow("\n\u4E0B\u4E00\u6B65: \u8BBE\u8BA1\u65B9\u6848"));
5768
+ lines.push(chalk9__default.default.gray(" \u53EF\u8C03\u7528 $architect \u6216 $frontend-dev \u8FDB\u884C\u8BBE\u8BA1"));
5769
+ } else if (nextStep === "apply") {
5770
+ lines.push(chalk9__default.default.yellow("\n\u4E0B\u4E00\u6B65: \u6267\u884C\u53D8\u66F4"));
5771
+ lines.push(chalk9__default.default.gray(" \u53EF\u8C03\u7528 $frontend-dev \u6267\u884C\u4EE3\u7801\u4FEE\u6539"));
5772
+ }
5773
+ return { output: lines.join("\n") };
5774
+ } catch (e) {
5775
+ if (e instanceof ConfirmationRequiredError) {
5776
+ lines.push(chalk9__default.default.yellow("\n\u26A0 \u8FD8\u9700\u8981\u786E\u8BA4:") + chalk9__default.default.white(`
5777
+ ${generateConfirmationPrompt(e.point)}`) + chalk9__default.default.cyan(`
5384
5778
 
5385
- ${point.name}`) + (comment ? chalk9__default.default.gray(`
5386
- \u5907\u6CE8: ${comment}`) : "") + chalk9__default.default.yellow("\n\n\u4F7F\u7528 /opsx:next \u7EE7\u7EED\u4E0B\u4E00\u9636\u6BB5")
5387
- };
5779
+ \u4F7F\u7528 /opsx:confirm ${e.point.type}`));
5780
+ return { output: lines.join("\n") };
5781
+ }
5782
+ throw e;
5783
+ }
5784
+ }
5785
+ }
5786
+ lines.push(chalk9__default.default.yellow("\n\u4F7F\u7528 /opsx:next \u7EE7\u7EED\u4E0B\u4E00\u9636\u6BB5"));
5787
+ return { output: lines.join("\n") };
5788
+ }
5789
+ async function checkPendingSpec(workingDirectory, changeId) {
5790
+ const specPath = path6__namespace.join(workingDirectory, "openspec", "changes", `${changeId}-spec.md`);
5791
+ try {
5792
+ await fs9__namespace.promises.access(specPath);
5793
+ return specPath;
5794
+ } catch {
5795
+ return null;
5796
+ }
5388
5797
  }
5389
5798
  async function handleNext(workflow, args, ctx) {
5390
5799
  const state = workflow.getState();