@nick848/sf-cli 1.0.2 → 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
@@ -3,7 +3,7 @@
3
3
 
4
4
  var commander = require('commander');
5
5
  var chalk9 = require('chalk');
6
- var fsSync = require('fs');
6
+ var fs9 = require('fs');
7
7
  var path6 = require('path');
8
8
  var readline = require('readline');
9
9
  var fs6 = require('fs/promises');
@@ -37,7 +37,7 @@ function _interopNamespace(e) {
37
37
  }
38
38
 
39
39
  var chalk9__default = /*#__PURE__*/_interopDefault(chalk9);
40
- var fsSync__namespace = /*#__PURE__*/_interopNamespace(fsSync);
40
+ var fs9__namespace = /*#__PURE__*/_interopNamespace(fs9);
41
41
  var path6__namespace = /*#__PURE__*/_interopNamespace(path6);
42
42
  var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
43
43
  var fs6__namespace = /*#__PURE__*/_interopNamespace(fs6);
@@ -125,8 +125,8 @@ var CommandParser = class {
125
125
  };
126
126
  }
127
127
  parseAtPath(input) {
128
- const path15 = input.slice(1).trim();
129
- if (!path15) {
128
+ const path16 = input.slice(1).trim();
129
+ if (!path16) {
130
130
  return { success: false, error: "\u7F3A\u5C11\u6587\u4EF6\u8DEF\u5F84" };
131
131
  }
132
132
  return {
@@ -134,7 +134,7 @@ var CommandParser = class {
134
134
  command: {
135
135
  type: "at" /* AT */,
136
136
  raw: input,
137
- path: path15
137
+ path: path16
138
138
  }
139
139
  };
140
140
  }
@@ -1705,8 +1705,8 @@ var KEY_FILE = ".key";
1705
1705
  function getOrCreateEncryptionKey() {
1706
1706
  const keyPath = path6__namespace.join(os__namespace.homedir(), KEY_DIR, KEY_FILE);
1707
1707
  try {
1708
- if (fsSync__namespace.existsSync(keyPath)) {
1709
- const keyBase64 = fsSync__namespace.readFileSync(keyPath, "utf-8").trim();
1708
+ if (fs9__namespace.existsSync(keyPath)) {
1709
+ const keyBase64 = fs9__namespace.readFileSync(keyPath, "utf-8").trim();
1710
1710
  return Buffer.from(keyBase64, "base64");
1711
1711
  }
1712
1712
  } catch {
@@ -1714,10 +1714,10 @@ function getOrCreateEncryptionKey() {
1714
1714
  const key = crypto__namespace.randomBytes(32);
1715
1715
  try {
1716
1716
  const keyDir = path6__namespace.dirname(keyPath);
1717
- if (!fsSync__namespace.existsSync(keyDir)) {
1718
- fsSync__namespace.mkdirSync(keyDir, { recursive: true, mode: 448 });
1717
+ if (!fs9__namespace.existsSync(keyDir)) {
1718
+ fs9__namespace.mkdirSync(keyDir, { recursive: true, mode: 448 });
1719
1719
  }
1720
- fsSync__namespace.writeFileSync(keyPath, key.toString("base64"), {
1720
+ fs9__namespace.writeFileSync(keyPath, key.toString("base64"), {
1721
1721
  mode: 384,
1722
1722
  // 仅所有者可读写
1723
1723
  encoding: "utf-8"
@@ -3081,10 +3081,26 @@ function createSpinner(message) {
3081
3081
  stop: () => process.stdout.write(" ".repeat(message.length + 10) + "\r")
3082
3082
  };
3083
3083
  }
3084
- var packageJsonPath = path6__namespace.resolve(__dirname, "../../package.json");
3085
- var packageJson = JSON.parse(fsSync__namespace.readFileSync(packageJsonPath, "utf-8"));
3086
- var CURRENT_VERSION = packageJson.version;
3087
- var PACKAGE_NAME = packageJson.name;
3084
+ function getPackageInfo() {
3085
+ const possiblePaths = [
3086
+ path6__namespace.resolve(__dirname, "..", "..", "package.json"),
3087
+ path6__namespace.resolve(__dirname, "..", "..", "..", "package.json")
3088
+ ];
3089
+ for (const pkgPath of possiblePaths) {
3090
+ try {
3091
+ if (fs9__namespace.existsSync(pkgPath)) {
3092
+ const content = fs9__namespace.readFileSync(pkgPath, "utf-8");
3093
+ return JSON.parse(content);
3094
+ }
3095
+ } catch {
3096
+ continue;
3097
+ }
3098
+ }
3099
+ return { version: "1.0.0", name: "@nick848/sf-cli" };
3100
+ }
3101
+ var packageInfo = getPackageInfo();
3102
+ var CURRENT_VERSION = packageInfo.version;
3103
+ var PACKAGE_NAME = packageInfo.name;
3088
3104
  async function handleUpdate(args, ctx) {
3089
3105
  const options = {
3090
3106
  check: args.includes("--check") || args.includes("-c"),
@@ -3298,12 +3314,20 @@ async function exitCLI(ctx, options = {}) {
3298
3314
  var DEFAULT_CONFIRMATION_POINTS = [
3299
3315
  {
3300
3316
  type: "spec-review",
3301
- name: "\u89C4\u8303\u62C6\u89E3\u786E\u8BA4",
3302
- 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",
3303
3319
  triggerStep: "explore",
3304
3320
  targetStep: "new",
3305
3321
  required: true
3306
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
+ },
3307
3331
  {
3308
3332
  type: "architecture",
3309
3333
  name: "\u67B6\u6784\u8C03\u6574\u786E\u8BA4",
@@ -3339,19 +3363,15 @@ var ConfirmationManager = class {
3339
3363
  confirmationPoints;
3340
3364
  confirmations = /* @__PURE__ */ new Map();
3341
3365
  constructor(customPoints) {
3342
- const points = customPoints || DEFAULT_CONFIRMATION_POINTS;
3343
- this.confirmationPoints = new Map(points.map((p) => [p.type, p]));
3366
+ this.confirmationPoints = customPoints || DEFAULT_CONFIRMATION_POINTS;
3344
3367
  }
3345
3368
  /**
3346
3369
  * 获取指定阶段的确认点
3347
3370
  */
3348
3371
  getConfirmationPointForTransition(from, to) {
3349
- for (const point of this.confirmationPoints.values()) {
3350
- if (point.triggerStep === from && point.targetStep === to) {
3351
- return point;
3352
- }
3353
- }
3354
- return void 0;
3372
+ return this.confirmationPoints.find(
3373
+ (point) => point.triggerStep === from && point.targetStep === to
3374
+ );
3355
3375
  }
3356
3376
  /**
3357
3377
  * 检查是否需要确认
@@ -3364,7 +3384,7 @@ var ConfirmationManager = class {
3364
3384
  * 获取确认点详情
3365
3385
  */
3366
3386
  getConfirmationPoint(type) {
3367
- return this.confirmationPoints.get(type);
3387
+ return this.confirmationPoints.find((point) => point.type === type);
3368
3388
  }
3369
3389
  /**
3370
3390
  * 记录确认
@@ -3408,14 +3428,14 @@ var ConfirmationManager = class {
3408
3428
  * 获取所有确认点
3409
3429
  */
3410
3430
  getAllConfirmationPoints() {
3411
- return Array.from(this.confirmationPoints.values());
3431
+ return [...this.confirmationPoints];
3412
3432
  }
3413
3433
  /**
3414
3434
  * 获取指定阶段需要清除的确认点
3415
3435
  */
3416
3436
  getConfirmationsToClear(targetStep) {
3417
3437
  const types = [];
3418
- for (const point of this.confirmationPoints.values()) {
3438
+ for (const point of this.confirmationPoints) {
3419
3439
  const stepOrder = ["explore", "new", "continue", "propose", "apply", "archive"];
3420
3440
  const targetIndex = stepOrder.indexOf(targetStep);
3421
3441
  const triggerIndex = stepOrder.indexOf(point.triggerStep);
@@ -3538,6 +3558,26 @@ var WorkflowEngine = class {
3538
3558
  devStandards: this.devStandards
3539
3559
  };
3540
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
+ }
3541
3581
  /**
3542
3582
  * 启动新工作流
3543
3583
  */
@@ -3999,16 +4039,23 @@ async function handleNew(args, ctx) {
3999
4039
  if (workflowEngine) {
4000
4040
  const existingState = workflowEngine.getState();
4001
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
+ }
4002
4054
  return {
4003
4055
  output: chalk9__default.default.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9__default.default.gray(`
4004
4056
 
4005
4057
  \u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9__default.default.gray(`
4006
- \u5F53\u524D\u9636\u6BB5: ${existingState.currentStep}`) + chalk9__default.default.gray(`
4007
-
4008
- \u9009\u9879:`) + chalk9__default.default.gray(`
4009
- 1. \u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:${existingState.currentStep}`) + chalk9__default.default.gray(`
4010
- 2. \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:cancel`) + chalk9__default.default.gray(`
4011
- 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")
4012
4059
  };
4013
4060
  }
4014
4061
  }
@@ -4023,6 +4070,7 @@ async function handleNew(args, ctx) {
4023
4070
  async function newFeature(options, workingDir, workflowEngine) {
4024
4071
  const cwd = workingDir || process.cwd();
4025
4072
  const { requirement, forceComplexity } = options;
4073
+ const lines = [];
4026
4074
  try {
4027
4075
  const stats = await fs6__namespace.stat(cwd);
4028
4076
  if (!stats.isDirectory()) {
@@ -4030,51 +4078,283 @@ async function newFeature(options, workingDir, workflowEngine) {
4030
4078
  output: chalk9__default.default.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
4031
4079
  };
4032
4080
  }
4033
- } catch (e) {
4081
+ } catch {
4034
4082
  return {
4035
4083
  output: chalk9__default.default.red(`\u9519\u8BEF: \u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6743\u9650\u8BBF\u95EE ${cwd}`)
4036
4084
  };
4037
4085
  }
4038
- try {
4039
- const context = await readProjectContext(cwd);
4040
- const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
4041
- const workflow = workflowEngine || new WorkflowEngine();
4042
- if (!workflowEngine) {
4043
- await workflow.initialize(cwd);
4044
- }
4045
- const state = await workflow.start(requirement, analysis.score, {
4046
- 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
4047
4204
  });
4048
- const lines = [
4049
- chalk9__default.default.green("\u2713 \u9700\u6C42\u5206\u6790\u5B8C\u6210\n"),
4050
- chalk9__default.default.cyan("\u9879\u76EE\u4FE1\u606F:"),
4051
- chalk9__default.default.gray(` \u540D\u79F0: ${context.name}`),
4052
- chalk9__default.default.gray(` \u7C7B\u578B: ${context.type}`),
4053
- chalk9__default.default.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`),
4054
- "",
4055
- chalk9__default.default.cyan("\u9700\u6C42\u5206\u6790:"),
4056
- chalk9__default.default.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`),
4057
- chalk9__default.default.gray(` \u5DE5\u4F5C\u6D41: ${state.type === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`),
4058
- chalk9__default.default.gray(` \u53D8\u66F4ID: ${state.id}`),
4059
- "",
4060
- chalk9__default.default.cyan("\u590D\u6742\u5EA6\u56E0\u7D20:"),
4061
- ...analysis.factors.map((f) => chalk9__default.default.gray(` - ${f}`)),
4062
- "",
4063
- chalk9__default.default.yellow("\u4E0B\u4E00\u6B65:"),
4064
- 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")
4065
- ];
4066
- return { output: lines.join("\n") };
4067
- } catch (error) {
4068
- const err = error;
4069
- if (err.code === "EACCES") {
4070
- return {
4071
- output: chalk9__default.default.red(`\u9519\u8BEF: \u65E0\u6743\u9650\u8BBF\u95EE\u76EE\u5F55 ${cwd}`)
4072
- };
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
4073
4241
  }
4074
- return {
4075
- output: chalk9__default.default.red(`\u542F\u52A8\u9700\u6C42\u5931\u8D25: ${error.message}`)
4076
- };
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");
4077
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("");
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");
4078
4358
  }
4079
4359
  function parseArgs(args) {
4080
4360
  let forceComplexity;
@@ -4099,42 +4379,52 @@ async function readProjectContext(cwd) {
4099
4379
  type: "unknown",
4100
4380
  framework: null,
4101
4381
  techStack: [],
4102
- description: ""
4382
+ description: "",
4383
+ structure: {
4384
+ directories: [],
4385
+ keyFiles: [],
4386
+ srcStructure: ""
4387
+ },
4388
+ norms: {
4389
+ devStandards: "",
4390
+ patterns: "",
4391
+ weights: ""
4392
+ }
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
4103
4406
  };
4407
+ }
4408
+ async function readAgentsMd(cwd) {
4104
4409
  const agentsPath = path6__namespace.join(cwd, "AGENTS.md");
4105
4410
  try {
4106
4411
  const stats = await fs6__namespace.stat(agentsPath);
4107
4412
  if (stats.size > MAX_FILE_SIZE2) {
4108
4413
  console.warn(`\u8B66\u544A: AGENTS.md \u6587\u4EF6\u8FC7\u5927 (${stats.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
4109
- return defaultContext;
4414
+ return {};
4110
4415
  }
4111
4416
  const content = await fs6__namespace.readFile(agentsPath, "utf-8");
4112
- return parseAgentsMd(content, defaultContext);
4417
+ return parseAgentsMd(content);
4113
4418
  } catch (e) {
4114
4419
  const err = e;
4115
4420
  if (err.code !== "ENOENT") {
4116
4421
  console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 AGENTS.md - ${err.message}`);
4117
4422
  }
4423
+ return {};
4118
4424
  }
4119
- const configPath = path6__namespace.join(cwd, "openspec", "config.yaml");
4120
- try {
4121
- const stats = await fs6__namespace.stat(configPath);
4122
- if (stats.size > MAX_FILE_SIZE2) {
4123
- console.warn(`\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
4124
- return defaultContext;
4125
- }
4126
- const content = await fs6__namespace.readFile(configPath, "utf-8");
4127
- return parseConfigYaml(content, defaultContext);
4128
- } catch (e) {
4129
- const err = e;
4130
- if (err.code !== "ENOENT") {
4131
- console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
4132
- }
4133
- }
4134
- return defaultContext;
4135
4425
  }
4136
- function parseAgentsMd(content, defaults) {
4137
- const context = { ...defaults };
4426
+ function parseAgentsMd(content) {
4427
+ const context = {};
4138
4428
  const nameMatch = content.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
4139
4429
  if (nameMatch) {
4140
4430
  context.name = nameMatch[1];
@@ -4151,10 +4441,32 @@ function parseAgentsMd(content, defaults) {
4151
4441
  if (descMatch) {
4152
4442
  context.description = descMatch[1].trim();
4153
4443
  }
4444
+ const techStackMatch = content.match(/技术栈[::]\s*([^\n]+)/);
4445
+ if (techStackMatch) {
4446
+ context.techStack = techStackMatch[1].split(/[,,、]/).map((s) => s.trim()).filter(Boolean);
4447
+ }
4154
4448
  return context;
4155
4449
  }
4156
- function parseConfigYaml(content, defaults) {
4157
- 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 = {};
4158
4470
  const nameMatch = content.match(/name:\s*(.+)/);
4159
4471
  if (nameMatch) {
4160
4472
  context.name = nameMatch[1].trim();
@@ -4169,6 +4481,67 @@ function parseConfigYaml(content, defaults) {
4169
4481
  }
4170
4482
  return context;
4171
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
+ }
4172
4545
  function analyzeComplexity(requirement, context) {
4173
4546
  let score = 3;
4174
4547
  const factors = [];
@@ -4343,10 +4716,10 @@ var FRONTEND_DEV_AGENT = {
4343
4716
  var CODE_REVIEWER_AGENT = {
4344
4717
  id: "code-reviewer",
4345
4718
  name: "\u4EE3\u7801\u5BA1\u6838",
4346
- description: "\u5BA1\u6838\u4EE3\u7801\u8D28\u91CF\u3001\u5B89\u5168\u6027\u548C\u89C4\u8303\u6027\uFF0C\u63D0\u4F9B\u6539\u8FDB\u5EFA\u8BAE",
4719
+ description: "\u5BA1\u6838\u4EE3\u7801\u8D28\u91CF\u3001\u5B89\u5168\u6027\u548C\u89C4\u8303\u6027\uFF0C\u6267\u884C\u56DE\u5F52\u6D4B\u8BD5\uFF0C\u63D0\u4F9B\u6539\u8FDB\u5EFA\u8BAE",
4347
4720
  icon: "\u{1F50D}",
4348
- version: "1.0.0",
4349
- role: "\u4F60\u662F\u4E00\u540D\u8D44\u6DF1\u4EE3\u7801\u5BA1\u6838\u4E13\u5BB6\uFF0C\u4E13\u6CE8\u4E8E\u4EE3\u7801\u8D28\u91CF\u3001\u5B89\u5168\u6027\u548C\u6700\u4F73\u5B9E\u8DF5\u3002\u4F60\u8D1F\u8D23\u5BA1\u67E5\u4EE3\u7801\u5E76\u63D0\u4F9B\u6539\u8FDB\u5EFA\u8BAE\u3002",
4721
+ version: "1.1.0",
4722
+ role: "\u4F60\u662F\u4E00\u540D\u8D44\u6DF1\u4EE3\u7801\u5BA1\u6838\u4E13\u5BB6\uFF0C\u4E13\u6CE8\u4E8E\u4EE3\u7801\u8D28\u91CF\u3001\u5B89\u5168\u6027\u548C\u6700\u4F73\u5B9E\u8DF5\u3002\u4F60\u8D1F\u8D23\u5BA1\u67E5\u4EE3\u7801\u3001\u6267\u884C\u56DE\u5F52\u6D4B\u8BD5\u5E76\u63D0\u4F9B\u6539\u8FDB\u5EFA\u8BAE\u3002",
4350
4723
  capabilities: [
4351
4724
  {
4352
4725
  id: "quality-review",
@@ -4367,13 +4740,24 @@ var CODE_REVIEWER_AGENT = {
4367
4740
  id: "performance-review",
4368
4741
  name: "\u6027\u80FD\u5BA1\u67E5",
4369
4742
  description: "\u68C0\u67E5\u6027\u80FD\u95EE\u9898\u548C\u4F18\u5316\u5EFA\u8BAE"
4743
+ },
4744
+ {
4745
+ id: "regression-test",
4746
+ name: "\u56DE\u5F52\u6D4B\u8BD5",
4747
+ description: "\u6267\u884C\u6D4B\u8BD5\u5957\u4EF6\uFF0C\u786E\u4FDD\u4FEE\u6539\u4E0D\u7834\u574F\u5DF2\u6709\u529F\u80FD"
4748
+ },
4749
+ {
4750
+ id: "coverage-analysis",
4751
+ name: "\u8986\u76D6\u7387\u5206\u6790",
4752
+ description: "\u5206\u6790\u6D4B\u8BD5\u8986\u76D6\u7387\u5E76\u63D0\u4F9B\u6539\u8FDB\u5EFA\u8BAE"
4370
4753
  }
4371
4754
  ],
4372
4755
  tools: [
4373
4756
  { name: "read_file", description: "\u8BFB\u53D6\u6587\u4EF6\u5185\u5BB9", permission: "full" },
4374
4757
  { name: "glob", description: "\u641C\u7D22\u6587\u4EF6", permission: "full" },
4375
4758
  { name: "search_file_content", description: "\u641C\u7D22\u6587\u4EF6\u5185\u5BB9", permission: "full" },
4376
- { name: "list_directory", description: "\u5217\u51FA\u76EE\u5F55\u5185\u5BB9", permission: "full" }
4759
+ { name: "list_directory", description: "\u5217\u51FA\u76EE\u5F55\u5185\u5BB9", permission: "full" },
4760
+ { name: "run_shell_command", description: "\u6267\u884C\u6D4B\u8BD5\u547D\u4EE4", permission: "confirm" }
4377
4761
  ],
4378
4762
  triggers: [
4379
4763
  { type: "workflow", condition: { workflowStep: "apply" }, priority: 10 },
@@ -4384,6 +4768,7 @@ var CODE_REVIEWER_AGENT = {
4384
4768
  protectedPaths: ["node_modules", ".git"]
4385
4769
  },
4386
4770
  behavior: {
4771
+ requireConfirmation: ["run_shell_command"],
4387
4772
  autoCommit: false
4388
4773
  }
4389
4774
  },
@@ -4392,6 +4777,7 @@ var CODE_REVIEWER_AGENT = {
4392
4777
  ## \u4F60\u7684\u804C\u8D23
4393
4778
  - \u5BA1\u67E5\u4EE3\u7801\u8D28\u91CF\u548C\u53EF\u7EF4\u62A4\u6027
4394
4779
  - \u68C0\u67E5\u5B89\u5168\u6F0F\u6D1E\u548C\u98CE\u9669
4780
+ - \u6267\u884C\u56DE\u5F52\u6D4B\u8BD5\uFF0C\u786E\u4FDD\u4FEE\u6539\u4E0D\u7834\u574F\u5DF2\u6709\u529F\u80FD
4395
4781
  - \u63D0\u4F9B\u5177\u4F53\u7684\u6539\u8FDB\u5EFA\u8BAE
4396
4782
  - \u786E\u4FDD\u9075\u5FAA\u6700\u4F73\u5B9E\u8DF5
4397
4783
 
@@ -4400,8 +4786,20 @@ var CODE_REVIEWER_AGENT = {
4400
4786
  2. **\u5B89\u5168\u6027**: XSS\u3001\u6CE8\u5165\u3001\u654F\u611F\u6570\u636E\u5904\u7406
4401
4787
  3. **\u6027\u80FD**: \u7B97\u6CD5\u6548\u7387\u3001\u5185\u5B58\u4F7F\u7528\u3001\u6E32\u67D3\u4F18\u5316
4402
4788
  4. **\u89C4\u8303\u6027**: \u547D\u540D\u3001\u683C\u5F0F\u3001\u6CE8\u91CA
4789
+ 5. **\u6D4B\u8BD5\u8986\u76D6**: \u56DE\u5F52\u6D4B\u8BD5\u7ED3\u679C\u3001\u8986\u76D6\u7387\u5206\u6790
4790
+
4791
+ ## \u56DE\u5F52\u6D4B\u8BD5\u6D41\u7A0B
4792
+ 1. \u6267\u884C \`npm test -- --run\` \u8FD0\u884C\u6D4B\u8BD5\u5957\u4EF6
4793
+ 2. \u5206\u6790\u6D4B\u8BD5\u7ED3\u679C\uFF0C\u8BC6\u522B\u5931\u8D25\u7684\u6D4B\u8BD5\u7528\u4F8B
4794
+ 3. \u5982\u679C\u6D4B\u8BD5\u5931\u8D25\uFF0C\u963B\u6B62\u5F52\u6863\u5E76\u63D0\u793A\u4FEE\u590D
4795
+ 4. \u63D0\u4F9B\u8986\u76D6\u7387\u62A5\u544A\u548C\u6539\u8FDB\u5EFA\u8BAE
4403
4796
 
4404
4797
  ## \u8F93\u51FA\u683C\u5F0F
4798
+ ### \u56DE\u5F52\u6D4B\u8BD5\u7ED3\u679C
4799
+ - \u6D4B\u8BD5\u901A\u8FC7/\u5931\u8D25\u72B6\u6001
4800
+ - \u901A\u8FC7/\u5931\u8D25\u6570\u91CF
4801
+ - \u8986\u76D6\u7387\u767E\u5206\u6BD4
4802
+
4405
4803
  ### \u5BA1\u67E5\u7ED3\u679C
4406
4804
  - \u901A\u8FC7/\u9700\u4FEE\u6539/\u4E0D\u901A\u8FC7
4407
4805
 
@@ -4427,7 +4825,7 @@ var CODE_REVIEWER_AGENT = {
4427
4825
  ## \u4E0A\u4E0B\u6587
4428
4826
  {{context}}
4429
4827
 
4430
- \u8BF7\u5BF9\u4EE5\u4E0A\u6587\u4EF6\u8FDB\u884C\u5168\u9762\u5BA1\u67E5\uFF0C\u63D0\u4F9B\u8BE6\u7EC6\u7684\u5BA1\u67E5\u62A5\u544A\u548C\u6539\u8FDB\u5EFA\u8BAE\u3002`,
4828
+ \u8BF7\u5BF9\u4EE5\u4E0A\u6587\u4EF6\u8FDB\u884C\u5168\u9762\u5BA1\u67E5\uFF0C\u6267\u884C\u56DE\u5F52\u6D4B\u8BD5\uFF0C\u5E76\u63D0\u4F9B\u8BE6\u7EC6\u7684\u5BA1\u67E5\u62A5\u544A\u548C\u6539\u8FDB\u5EFA\u8BAE\u3002`,
4431
4829
  outputFormat: {
4432
4830
  type: "markdown"
4433
4831
  }
@@ -4853,6 +5251,78 @@ ${content}`;
4853
5251
 
4854
5252
  // src/commands/opsx.ts
4855
5253
  var autoScheduleEnabled = true;
5254
+ var DEFAULT_REGRESSION_CONFIG = {
5255
+ enabled: true,
5256
+ command: "npm test -- --run",
5257
+ timeout: 12e4,
5258
+ // 2分钟
5259
+ coverageThreshold: 80
5260
+ };
5261
+ async function runRegressionTest(workingDirectory, config = DEFAULT_REGRESSION_CONFIG) {
5262
+ const startTime = Date.now();
5263
+ const result = {
5264
+ success: false,
5265
+ passed: 0,
5266
+ failed: 0,
5267
+ total: 0,
5268
+ duration: 0,
5269
+ output: "",
5270
+ errors: []
5271
+ };
5272
+ return new Promise((resolve5) => {
5273
+ const proc = child_process.spawn(config.command, [], {
5274
+ cwd: workingDirectory,
5275
+ shell: true,
5276
+ stdio: "pipe"
5277
+ });
5278
+ let stdout = "";
5279
+ let stderr = "";
5280
+ proc.stdout?.on("data", (data) => {
5281
+ stdout += data.toString();
5282
+ });
5283
+ proc.stderr?.on("data", (data) => {
5284
+ stderr += data.toString();
5285
+ });
5286
+ const timeout = setTimeout(() => {
5287
+ proc.kill();
5288
+ result.errors.push("\u6D4B\u8BD5\u8D85\u65F6");
5289
+ result.output = stdout + stderr;
5290
+ result.duration = Date.now() - startTime;
5291
+ resolve5(result);
5292
+ }, config.timeout);
5293
+ proc.on("close", (code) => {
5294
+ clearTimeout(timeout);
5295
+ result.output = stdout + stderr;
5296
+ result.duration = Date.now() - startTime;
5297
+ const passMatch = stdout.match(/(\d+)\s+(?:passed|tests?\s+passed)/i);
5298
+ const failMatch = stdout.match(/(\d+)\s+(?:failed|tests?\s+failed)/i);
5299
+ const totalMatch = stdout.match(/Tests?:\s*(\d+)/i);
5300
+ if (passMatch) {
5301
+ result.passed = parseInt(passMatch[1], 10);
5302
+ }
5303
+ if (failMatch) {
5304
+ result.failed = parseInt(failMatch[1], 10);
5305
+ }
5306
+ if (totalMatch) {
5307
+ result.total = parseInt(totalMatch[1], 10);
5308
+ } else {
5309
+ result.total = result.passed + result.failed;
5310
+ }
5311
+ const coverageMatch = stdout.match(/All files[^\d]*(\d+(?:\.\d+)?)/);
5312
+ if (coverageMatch) {
5313
+ result.coverage = parseFloat(coverageMatch[1]);
5314
+ }
5315
+ result.success = code === 0 && result.failed === 0;
5316
+ resolve5(result);
5317
+ });
5318
+ proc.on("error", (err) => {
5319
+ clearTimeout(timeout);
5320
+ result.errors.push(err.message);
5321
+ result.duration = Date.now() - startTime;
5322
+ resolve5(result);
5323
+ });
5324
+ });
5325
+ }
4856
5326
  async function handleOpsx(command, args, ctx) {
4857
5327
  const step = command.replace("opsx:", "");
4858
5328
  const workflow = new WorkflowEngine();
@@ -4867,7 +5337,7 @@ async function handleOpsx(command, args, ctx) {
4867
5337
  case "apply":
4868
5338
  return handleApply(workflow);
4869
5339
  case "archive":
4870
- return handleArchive(workflow, args);
5340
+ return handleArchive(workflow, args, ctx);
4871
5341
  case "propose":
4872
5342
  return handlePropose(workflow, args);
4873
5343
  case "status":
@@ -4877,17 +5347,55 @@ async function handleOpsx(command, args, ctx) {
4877
5347
  case "rollback":
4878
5348
  return handleRollback(workflow, args);
4879
5349
  case "confirm":
4880
- return handleConfirm(workflow, args);
5350
+ return handleConfirm(workflow, args, ctx);
4881
5351
  case "next":
4882
5352
  return handleNext(workflow);
4883
5353
  case "auto":
4884
5354
  return handleAutoSchedule(args);
5355
+ case "test":
5356
+ return handleRegressionTest(ctx);
4885
5357
  default:
4886
5358
  return {
4887
5359
  output: chalk9__default.default.red(`\u672A\u77E5\u7684OpenSpec\u547D\u4EE4: /${command}`)
4888
5360
  };
4889
5361
  }
4890
5362
  }
5363
+ async function handleRegressionTest(ctx) {
5364
+ const lines = [];
5365
+ lines.push(chalk9__default.default.cyan("\u{1F50D} \u6267\u884C\u56DE\u5F52\u6D4B\u8BD5..."));
5366
+ lines.push(chalk9__default.default.gray(` \u5DE5\u4F5C\u76EE\u5F55: ${ctx.options.workingDirectory}`));
5367
+ lines.push("");
5368
+ const result = await runRegressionTest(ctx.options.workingDirectory);
5369
+ lines.push(chalk9__default.default.gray("\u2500".repeat(50)));
5370
+ if (result.success) {
5371
+ lines.push(chalk9__default.default.green("\u2713 \u56DE\u5F52\u6D4B\u8BD5\u901A\u8FC7"));
5372
+ } else {
5373
+ lines.push(chalk9__default.default.red("\u2717 \u56DE\u5F52\u6D4B\u8BD5\u5931\u8D25"));
5374
+ }
5375
+ lines.push("");
5376
+ lines.push(chalk9__default.default.cyan("\u6D4B\u8BD5\u7ED3\u679C:"));
5377
+ lines.push(chalk9__default.default.gray(` \u901A\u8FC7: ${result.passed}`));
5378
+ lines.push(chalk9__default.default.gray(` \u5931\u8D25: ${result.failed}`));
5379
+ lines.push(chalk9__default.default.gray(` \u603B\u8BA1: ${result.total}`));
5380
+ lines.push(chalk9__default.default.gray(` \u8017\u65F6: ${(result.duration / 1e3).toFixed(2)}s`));
5381
+ if (result.coverage !== void 0) {
5382
+ const coverageColor = result.coverage >= 80 ? chalk9__default.default.green : result.coverage >= 60 ? chalk9__default.default.yellow : chalk9__default.default.red;
5383
+ lines.push(coverageColor(` \u8986\u76D6\u7387: ${result.coverage}%`));
5384
+ }
5385
+ if (result.errors.length > 0) {
5386
+ lines.push("");
5387
+ lines.push(chalk9__default.default.red("\u9519\u8BEF\u4FE1\u606F:"));
5388
+ for (const error of result.errors) {
5389
+ lines.push(chalk9__default.default.gray(` - ${error}`));
5390
+ }
5391
+ }
5392
+ if (result.failed > 0) {
5393
+ lines.push("");
5394
+ lines.push(chalk9__default.default.yellow("\u26A0 \u5B58\u5728\u5931\u8D25\u7684\u6D4B\u8BD5\u7528\u4F8B\uFF0C\u8BF7\u68C0\u67E5\u5E76\u4FEE\u590D"));
5395
+ lines.push(chalk9__default.default.gray("\u4F7F\u7528 /opsx:rollback \u53EF\u56DE\u6EDA\u5230\u4E4B\u524D\u7684\u9636\u6BB5"));
5396
+ }
5397
+ return { output: lines.join("\n") };
5398
+ }
4891
5399
  function handleAutoSchedule(args) {
4892
5400
  const action = args[0]?.toLowerCase();
4893
5401
  if (!action) {
@@ -5017,13 +5525,70 @@ async function handleArchive(workflow, args, ctx) {
5017
5525
  ${generateConfirmationPrompt(confirmation.point)}`) + chalk9__default.default.cyan("\n\n\u4F7F\u7528 /opsx:confirm code-review \u786E\u8BA4\u540E\u5F52\u6863")
5018
5526
  };
5019
5527
  }
5528
+ const lines = [];
5529
+ lines.push(chalk9__default.default.cyan("\u{1F50D} \u6267\u884C\u5F52\u6863\u524D\u56DE\u5F52\u6D4B\u8BD5..."));
5530
+ lines.push("");
5531
+ const testResult = await runRegressionTest(ctx.options.workingDirectory);
5532
+ if (!testResult.success) {
5533
+ lines.push(chalk9__default.default.red("\u2717 \u56DE\u5F52\u6D4B\u8BD5\u5931\u8D25"));
5534
+ lines.push(chalk9__default.default.gray(` \u901A\u8FC7: ${testResult.passed} | \u5931\u8D25: ${testResult.failed} | \u603B\u8BA1: ${testResult.total}`));
5535
+ lines.push("");
5536
+ lines.push(chalk9__default.default.yellow("\u26A0 \u5F52\u6863\u88AB\u963B\u6B62"));
5537
+ lines.push(chalk9__default.default.gray("\n\u8BF7\u4FEE\u590D\u5931\u8D25\u7684\u6D4B\u8BD5\u7528\u4F8B\u540E\u91CD\u8BD5"));
5538
+ lines.push(chalk9__default.default.gray("\u4F7F\u7528 /opsx:rollback \u53EF\u56DE\u6EDA\u5230\u4E4B\u524D\u7684\u9636\u6BB5"));
5539
+ lines.push(chalk9__default.default.gray("\u4F7F\u7528 /opsx:test \u53EF\u5355\u72EC\u8FD0\u884C\u56DE\u5F52\u6D4B\u8BD5"));
5540
+ return { output: lines.join("\n") };
5541
+ }
5542
+ lines.push(chalk9__default.default.green("\u2713 \u56DE\u5F52\u6D4B\u8BD5\u901A\u8FC7"));
5543
+ lines.push(chalk9__default.default.gray(` \u901A\u8FC7: ${testResult.passed} | \u8017\u65F6: ${(testResult.duration / 1e3).toFixed(2)}s`));
5544
+ lines.push("");
5020
5545
  const changeId = state.id;
5021
5546
  const summary = args.join(" ") || "\u5B8C\u6210\u53D8\u66F4";
5022
5547
  await workflow.archive(summary);
5023
- return {
5024
- output: chalk9__default.default.green("\u2713 \u5DE5\u4F5C\u6D41\u5DF2\u5F52\u6863") + chalk9__default.default.gray(`
5025
- \u53D8\u66F4ID: ${changeId}`) + chalk9__default.default.cyan("\n\n\u5F52\u6863\u6587\u6863\u5DF2\u751F\u6210\u5230 openspec/spec/ \u76EE\u5F55")
5026
- };
5548
+ await updateChangelog(ctx.options.workingDirectory, summary, changeId);
5549
+ lines.push(chalk9__default.default.green("\u2713 \u5DE5\u4F5C\u6D41\u5DF2\u5F52\u6863") + chalk9__default.default.gray(`
5550
+ \u53D8\u66F4ID: ${changeId}`) + chalk9__default.default.cyan("\n\n\u5F52\u6863\u6587\u6863\u5DF2\u751F\u6210\u5230 openspec/spec/ \u76EE\u5F55") + chalk9__default.default.gray("\nCHANGELOG.md \u5DF2\u66F4\u65B0"));
5551
+ return { output: lines.join("\n") };
5552
+ }
5553
+ async function updateChangelog(workingDirectory, summary, changeId) {
5554
+ try {
5555
+ const changelogPath = path6__namespace.join(workingDirectory, "CHANGELOG.md");
5556
+ const pkgPath = path6__namespace.join(workingDirectory, "package.json");
5557
+ let version = "1.0.0";
5558
+ try {
5559
+ const pkgContent = fs9__namespace.readFileSync(pkgPath, "utf-8");
5560
+ const pkg = JSON.parse(pkgContent);
5561
+ version = pkg.version || "1.0.0";
5562
+ } catch {
5563
+ }
5564
+ const today = /* @__PURE__ */ new Date();
5565
+ const dateStr = today.toISOString().split("T")[0];
5566
+ const entry = `
5567
+ ## v${version} (${dateStr})
5568
+
5569
+ **\u53D8\u66F4\u5185\u5BB9**
5570
+
5571
+ - ${summary} (${changeId})
5572
+ `;
5573
+ if (fs9__namespace.existsSync(changelogPath)) {
5574
+ const content = fs9__namespace.readFileSync(changelogPath, "utf-8");
5575
+ const versionPattern = /^## v\d+\.\d+\.\d+/m;
5576
+ const match = content.match(versionPattern);
5577
+ if (match && match.index !== void 0) {
5578
+ const newContent = content.slice(0, match.index) + entry + content.slice(match.index);
5579
+ fs9__namespace.writeFileSync(changelogPath, newContent, "utf-8");
5580
+ } else {
5581
+ fs9__namespace.appendFileSync(changelogPath, entry, "utf-8");
5582
+ }
5583
+ } else {
5584
+ const header = `# Changelog
5585
+
5586
+ All notable changes to this project will be documented in this file.
5587
+ `;
5588
+ fs9__namespace.writeFileSync(changelogPath, header + entry, "utf-8");
5589
+ }
5590
+ } catch (error) {
5591
+ }
5027
5592
  }
5028
5593
  async function handlePropose(workflow, args, ctx) {
5029
5594
  const state = workflow.getState();
@@ -5150,6 +5715,16 @@ async function handleConfirm(workflow, args, ctx) {
5150
5715
  const type = args[0];
5151
5716
  if (!type) {
5152
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
+ }
5153
5728
  if (pendingPoint) {
5154
5729
  return {
5155
5730
  output: chalk9__default.default.cyan("\u5F85\u786E\u8BA4\u7684\u68C0\u67E5\u70B9:") + chalk9__default.default.white(`
@@ -5171,12 +5746,54 @@ ${generateConfirmationPrompt(pendingPoint)}`) + chalk9__default.default.gray(`
5171
5746
  }
5172
5747
  const comment = args.slice(1).join(" ");
5173
5748
  workflow.confirm(type, comment);
5174
- return {
5175
- 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(`
5176
5778
 
5177
- ${point.name}`) + (comment ? chalk9__default.default.gray(`
5178
- \u5907\u6CE8: ${comment}`) : "") + chalk9__default.default.yellow("\n\n\u4F7F\u7528 /opsx:next \u7EE7\u7EED\u4E0B\u4E00\u9636\u6BB5")
5179
- };
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
+ }
5180
5797
  }
5181
5798
  async function handleNext(workflow, args, ctx) {
5182
5799
  const state = workflow.getState();
@@ -5214,9 +5831,25 @@ ${generateConfirmationPrompt(e.point)}`) + chalk9__default.default.cyan(`
5214
5831
  }
5215
5832
 
5216
5833
  // src/commands/runner.ts
5217
- var packageJsonPath2 = path6__namespace.resolve(__dirname, "../../package.json");
5218
- var packageJson2 = JSON.parse(fsSync__namespace.readFileSync(packageJsonPath2, "utf-8"));
5219
- var VERSION2 = packageJson2.version;
5834
+ function getVersion() {
5835
+ const possiblePaths = [
5836
+ path6__namespace.resolve(__dirname, "..", "..", "package.json"),
5837
+ path6__namespace.resolve(__dirname, "..", "..", "..", "package.json")
5838
+ ];
5839
+ for (const pkgPath of possiblePaths) {
5840
+ try {
5841
+ if (fs9__namespace.existsSync(pkgPath)) {
5842
+ const content = fs9__namespace.readFileSync(pkgPath, "utf-8");
5843
+ const pkg = JSON.parse(content);
5844
+ return pkg.version;
5845
+ }
5846
+ } catch {
5847
+ continue;
5848
+ }
5849
+ }
5850
+ return "1.0.0";
5851
+ }
5852
+ var VERSION2 = getVersion();
5220
5853
  async function runSlashCommand(command, args, ctx) {
5221
5854
  const normalizedCommand = normalizeCommand(command);
5222
5855
  switch (normalizedCommand) {
@@ -6222,9 +6855,27 @@ async function startInteractiveMode(options) {
6222
6855
  }
6223
6856
 
6224
6857
  // src/cli/index.ts
6225
- var packageJsonPath3 = path6__namespace.resolve(__dirname, "../package.json");
6226
- var packageJson3 = JSON.parse(fsSync__namespace.readFileSync(packageJsonPath3, "utf-8"));
6227
- var VERSION3 = packageJson3.version;
6858
+ function getPackageJson() {
6859
+ const possiblePaths = [
6860
+ // 开发环境: dist/cli/index.js -> ../../package.json (项目根目录)
6861
+ path6__namespace.resolve(__dirname, "..", "..", "package.json"),
6862
+ // 生产环境: node_modules/@nick848/sf-cli/dist/cli/index.js -> ../../package.json
6863
+ path6__namespace.resolve(__dirname, "..", "..", "package.json")
6864
+ ];
6865
+ for (const pkgPath of possiblePaths) {
6866
+ try {
6867
+ if (fs9__namespace.existsSync(pkgPath)) {
6868
+ const content = fs9__namespace.readFileSync(pkgPath, "utf-8");
6869
+ return JSON.parse(content);
6870
+ }
6871
+ } catch {
6872
+ continue;
6873
+ }
6874
+ }
6875
+ return { version: "1.0.0", name: "@nick848/sf-cli" };
6876
+ }
6877
+ var packageJson = getPackageJson();
6878
+ var VERSION3 = packageJson.version;
6228
6879
  var NAME = "sf-cli";
6229
6880
  commander.program.name(NAME).description("\u4E13\u4E3A\u524D\u7AEF\u5F00\u53D1\u8BBE\u8BA1\u7684AI\u9A71\u52A8CLI\u5DE5\u5177").version(VERSION3, "-v, --version", "\u663E\u793A\u7248\u672C\u53F7");
6230
6881
  commander.program.argument("[directory]", "\u5DE5\u4F5C\u76EE\u5F55", process.cwd()).option("-m, --model <model>", "\u6307\u5B9AAI\u6A21\u578B").option("-y, --yolo", "\u81EA\u52A8\u786E\u8BA4\u6240\u6709\u64CD\u4F5C").action(async (directory, options) => {