@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/index.mjs CHANGED
@@ -2,7 +2,7 @@ import * as path5 from 'path';
2
2
  import path5__default from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import * as fs4 from 'fs/promises';
5
- import * as fsSync from 'fs';
5
+ import * as fs10 from 'fs';
6
6
  import * as crypto from 'crypto';
7
7
  import * as os from 'os';
8
8
  import { encoding_for_model } from 'tiktoken';
@@ -31,8 +31,8 @@ var KEY_FILE = ".key";
31
31
  function getOrCreateEncryptionKey() {
32
32
  const keyPath = path5.join(os.homedir(), KEY_DIR, KEY_FILE);
33
33
  try {
34
- if (fsSync.existsSync(keyPath)) {
35
- const keyBase64 = fsSync.readFileSync(keyPath, "utf-8").trim();
34
+ if (fs10.existsSync(keyPath)) {
35
+ const keyBase64 = fs10.readFileSync(keyPath, "utf-8").trim();
36
36
  return Buffer.from(keyBase64, "base64");
37
37
  }
38
38
  } catch {
@@ -40,10 +40,10 @@ function getOrCreateEncryptionKey() {
40
40
  const key = crypto.randomBytes(32);
41
41
  try {
42
42
  const keyDir = path5.dirname(keyPath);
43
- if (!fsSync.existsSync(keyDir)) {
44
- fsSync.mkdirSync(keyDir, { recursive: true, mode: 448 });
43
+ if (!fs10.existsSync(keyDir)) {
44
+ fs10.mkdirSync(keyDir, { recursive: true, mode: 448 });
45
45
  }
46
- fsSync.writeFileSync(keyPath, key.toString("base64"), {
46
+ fs10.writeFileSync(keyPath, key.toString("base64"), {
47
47
  mode: 384,
48
48
  // 仅所有者可读写
49
49
  encoding: "utf-8"
@@ -2520,10 +2520,10 @@ var FRONTEND_DEV_AGENT = {
2520
2520
  var CODE_REVIEWER_AGENT = {
2521
2521
  id: "code-reviewer",
2522
2522
  name: "\u4EE3\u7801\u5BA1\u6838",
2523
- description: "\u5BA1\u6838\u4EE3\u7801\u8D28\u91CF\u3001\u5B89\u5168\u6027\u548C\u89C4\u8303\u6027\uFF0C\u63D0\u4F9B\u6539\u8FDB\u5EFA\u8BAE",
2523
+ 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",
2524
2524
  icon: "\u{1F50D}",
2525
- version: "1.0.0",
2526
- 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",
2525
+ version: "1.1.0",
2526
+ 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",
2527
2527
  capabilities: [
2528
2528
  {
2529
2529
  id: "quality-review",
@@ -2544,13 +2544,24 @@ var CODE_REVIEWER_AGENT = {
2544
2544
  id: "performance-review",
2545
2545
  name: "\u6027\u80FD\u5BA1\u67E5",
2546
2546
  description: "\u68C0\u67E5\u6027\u80FD\u95EE\u9898\u548C\u4F18\u5316\u5EFA\u8BAE"
2547
+ },
2548
+ {
2549
+ id: "regression-test",
2550
+ name: "\u56DE\u5F52\u6D4B\u8BD5",
2551
+ description: "\u6267\u884C\u6D4B\u8BD5\u5957\u4EF6\uFF0C\u786E\u4FDD\u4FEE\u6539\u4E0D\u7834\u574F\u5DF2\u6709\u529F\u80FD"
2552
+ },
2553
+ {
2554
+ id: "coverage-analysis",
2555
+ name: "\u8986\u76D6\u7387\u5206\u6790",
2556
+ description: "\u5206\u6790\u6D4B\u8BD5\u8986\u76D6\u7387\u5E76\u63D0\u4F9B\u6539\u8FDB\u5EFA\u8BAE"
2547
2557
  }
2548
2558
  ],
2549
2559
  tools: [
2550
2560
  { name: "read_file", description: "\u8BFB\u53D6\u6587\u4EF6\u5185\u5BB9", permission: "full" },
2551
2561
  { name: "glob", description: "\u641C\u7D22\u6587\u4EF6", permission: "full" },
2552
2562
  { name: "search_file_content", description: "\u641C\u7D22\u6587\u4EF6\u5185\u5BB9", permission: "full" },
2553
- { name: "list_directory", description: "\u5217\u51FA\u76EE\u5F55\u5185\u5BB9", permission: "full" }
2563
+ { name: "list_directory", description: "\u5217\u51FA\u76EE\u5F55\u5185\u5BB9", permission: "full" },
2564
+ { name: "run_shell_command", description: "\u6267\u884C\u6D4B\u8BD5\u547D\u4EE4", permission: "confirm" }
2554
2565
  ],
2555
2566
  triggers: [
2556
2567
  { type: "workflow", condition: { workflowStep: "apply" }, priority: 10 },
@@ -2561,6 +2572,7 @@ var CODE_REVIEWER_AGENT = {
2561
2572
  protectedPaths: ["node_modules", ".git"]
2562
2573
  },
2563
2574
  behavior: {
2575
+ requireConfirmation: ["run_shell_command"],
2564
2576
  autoCommit: false
2565
2577
  }
2566
2578
  },
@@ -2569,6 +2581,7 @@ var CODE_REVIEWER_AGENT = {
2569
2581
  ## \u4F60\u7684\u804C\u8D23
2570
2582
  - \u5BA1\u67E5\u4EE3\u7801\u8D28\u91CF\u548C\u53EF\u7EF4\u62A4\u6027
2571
2583
  - \u68C0\u67E5\u5B89\u5168\u6F0F\u6D1E\u548C\u98CE\u9669
2584
+ - \u6267\u884C\u56DE\u5F52\u6D4B\u8BD5\uFF0C\u786E\u4FDD\u4FEE\u6539\u4E0D\u7834\u574F\u5DF2\u6709\u529F\u80FD
2572
2585
  - \u63D0\u4F9B\u5177\u4F53\u7684\u6539\u8FDB\u5EFA\u8BAE
2573
2586
  - \u786E\u4FDD\u9075\u5FAA\u6700\u4F73\u5B9E\u8DF5
2574
2587
 
@@ -2577,8 +2590,20 @@ var CODE_REVIEWER_AGENT = {
2577
2590
  2. **\u5B89\u5168\u6027**: XSS\u3001\u6CE8\u5165\u3001\u654F\u611F\u6570\u636E\u5904\u7406
2578
2591
  3. **\u6027\u80FD**: \u7B97\u6CD5\u6548\u7387\u3001\u5185\u5B58\u4F7F\u7528\u3001\u6E32\u67D3\u4F18\u5316
2579
2592
  4. **\u89C4\u8303\u6027**: \u547D\u540D\u3001\u683C\u5F0F\u3001\u6CE8\u91CA
2593
+ 5. **\u6D4B\u8BD5\u8986\u76D6**: \u56DE\u5F52\u6D4B\u8BD5\u7ED3\u679C\u3001\u8986\u76D6\u7387\u5206\u6790
2594
+
2595
+ ## \u56DE\u5F52\u6D4B\u8BD5\u6D41\u7A0B
2596
+ 1. \u6267\u884C \`npm test -- --run\` \u8FD0\u884C\u6D4B\u8BD5\u5957\u4EF6
2597
+ 2. \u5206\u6790\u6D4B\u8BD5\u7ED3\u679C\uFF0C\u8BC6\u522B\u5931\u8D25\u7684\u6D4B\u8BD5\u7528\u4F8B
2598
+ 3. \u5982\u679C\u6D4B\u8BD5\u5931\u8D25\uFF0C\u963B\u6B62\u5F52\u6863\u5E76\u63D0\u793A\u4FEE\u590D
2599
+ 4. \u63D0\u4F9B\u8986\u76D6\u7387\u62A5\u544A\u548C\u6539\u8FDB\u5EFA\u8BAE
2580
2600
 
2581
2601
  ## \u8F93\u51FA\u683C\u5F0F
2602
+ ### \u56DE\u5F52\u6D4B\u8BD5\u7ED3\u679C
2603
+ - \u6D4B\u8BD5\u901A\u8FC7/\u5931\u8D25\u72B6\u6001
2604
+ - \u901A\u8FC7/\u5931\u8D25\u6570\u91CF
2605
+ - \u8986\u76D6\u7387\u767E\u5206\u6BD4
2606
+
2582
2607
  ### \u5BA1\u67E5\u7ED3\u679C
2583
2608
  - \u901A\u8FC7/\u9700\u4FEE\u6539/\u4E0D\u901A\u8FC7
2584
2609
 
@@ -2604,7 +2629,7 @@ var CODE_REVIEWER_AGENT = {
2604
2629
  ## \u4E0A\u4E0B\u6587
2605
2630
  {{context}}
2606
2631
 
2607
- \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`,
2632
+ \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`,
2608
2633
  outputFormat: {
2609
2634
  type: "markdown"
2610
2635
  }
@@ -3580,12 +3605,20 @@ function getScheduleRuleDescription(step) {
3580
3605
  var DEFAULT_CONFIRMATION_POINTS = [
3581
3606
  {
3582
3607
  type: "spec-review",
3583
- name: "\u89C4\u8303\u62C6\u89E3\u786E\u8BA4",
3584
- description: "\u89C4\u8303\u62C6\u89E3\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u662F\u5426\u7EE7\u7EED\u8FDB\u5165\u8BBE\u8BA1\u9636\u6BB5",
3608
+ name: "\u89C4\u683C\u786E\u8BA4",
3609
+ description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
3585
3610
  triggerStep: "explore",
3586
3611
  targetStep: "new",
3587
3612
  required: true
3588
3613
  },
3614
+ {
3615
+ type: "spec-review",
3616
+ name: "\u89C4\u683C\u786E\u8BA4",
3617
+ description: "\u89C4\u683C\u62C6\u5206\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u786E\u8BA4\u89C4\u683C\u6587\u4EF6\u540E\u7EE7\u7EED",
3618
+ triggerStep: "propose",
3619
+ targetStep: "apply",
3620
+ required: true
3621
+ },
3589
3622
  {
3590
3623
  type: "architecture",
3591
3624
  name: "\u67B6\u6784\u8C03\u6574\u786E\u8BA4",
@@ -3621,19 +3654,15 @@ var ConfirmationManager = class {
3621
3654
  confirmationPoints;
3622
3655
  confirmations = /* @__PURE__ */ new Map();
3623
3656
  constructor(customPoints) {
3624
- const points = customPoints || DEFAULT_CONFIRMATION_POINTS;
3625
- this.confirmationPoints = new Map(points.map((p) => [p.type, p]));
3657
+ this.confirmationPoints = customPoints || DEFAULT_CONFIRMATION_POINTS;
3626
3658
  }
3627
3659
  /**
3628
3660
  * 获取指定阶段的确认点
3629
3661
  */
3630
3662
  getConfirmationPointForTransition(from, to) {
3631
- for (const point of this.confirmationPoints.values()) {
3632
- if (point.triggerStep === from && point.targetStep === to) {
3633
- return point;
3634
- }
3635
- }
3636
- return void 0;
3663
+ return this.confirmationPoints.find(
3664
+ (point) => point.triggerStep === from && point.targetStep === to
3665
+ );
3637
3666
  }
3638
3667
  /**
3639
3668
  * 检查是否需要确认
@@ -3646,7 +3675,7 @@ var ConfirmationManager = class {
3646
3675
  * 获取确认点详情
3647
3676
  */
3648
3677
  getConfirmationPoint(type) {
3649
- return this.confirmationPoints.get(type);
3678
+ return this.confirmationPoints.find((point) => point.type === type);
3650
3679
  }
3651
3680
  /**
3652
3681
  * 记录确认
@@ -3690,14 +3719,14 @@ var ConfirmationManager = class {
3690
3719
  * 获取所有确认点
3691
3720
  */
3692
3721
  getAllConfirmationPoints() {
3693
- return Array.from(this.confirmationPoints.values());
3722
+ return [...this.confirmationPoints];
3694
3723
  }
3695
3724
  /**
3696
3725
  * 获取指定阶段需要清除的确认点
3697
3726
  */
3698
3727
  getConfirmationsToClear(targetStep) {
3699
3728
  const types = [];
3700
- for (const point of this.confirmationPoints.values()) {
3729
+ for (const point of this.confirmationPoints) {
3701
3730
  const stepOrder = ["explore", "new", "continue", "propose", "apply", "archive"];
3702
3731
  const targetIndex = stepOrder.indexOf(targetStep);
3703
3732
  const triggerIndex = stepOrder.indexOf(point.triggerStep);
@@ -3820,6 +3849,26 @@ var WorkflowEngine = class {
3820
3849
  devStandards: this.devStandards
3821
3850
  };
3822
3851
  }
3852
+ /**
3853
+ * 获取规格文件路径
3854
+ */
3855
+ getSpecFilePath() {
3856
+ if (!this.state) return null;
3857
+ return path5.join(this.openspecPath, "changes", `${this.state.id}-spec.md`);
3858
+ }
3859
+ /**
3860
+ * 检查规格文件是否存在
3861
+ */
3862
+ async hasSpecFile() {
3863
+ const specPath = this.getSpecFilePath();
3864
+ if (!specPath) return false;
3865
+ try {
3866
+ await fs4.access(specPath);
3867
+ return true;
3868
+ } catch {
3869
+ return false;
3870
+ }
3871
+ }
3823
3872
  /**
3824
3873
  * 启动新工作流
3825
3874
  */
@@ -5594,8 +5643,8 @@ var CommandParser = class {
5594
5643
  };
5595
5644
  }
5596
5645
  parseAtPath(input) {
5597
- const path14 = input.slice(1).trim();
5598
- if (!path14) {
5646
+ const path15 = input.slice(1).trim();
5647
+ if (!path15) {
5599
5648
  return { success: false, error: "\u7F3A\u5C11\u6587\u4EF6\u8DEF\u5F84" };
5600
5649
  }
5601
5650
  return {
@@ -5603,7 +5652,7 @@ var CommandParser = class {
5603
5652
  command: {
5604
5653
  type: "at" /* AT */,
5605
5654
  raw: input,
5606
- path: path14
5655
+ path: path15
5607
5656
  }
5608
5657
  };
5609
5658
  }
@@ -6447,10 +6496,26 @@ function createSpinner(message) {
6447
6496
  stop: () => process.stdout.write(" ".repeat(message.length + 10) + "\r")
6448
6497
  };
6449
6498
  }
6450
- var packageJsonPath = path5.resolve(__dirname$1, "../../package.json");
6451
- var packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, "utf-8"));
6452
- var CURRENT_VERSION = packageJson.version;
6453
- var PACKAGE_NAME = packageJson.name;
6499
+ function getPackageInfo() {
6500
+ const possiblePaths = [
6501
+ path5.resolve(__dirname$1, "..", "..", "package.json"),
6502
+ path5.resolve(__dirname$1, "..", "..", "..", "package.json")
6503
+ ];
6504
+ for (const pkgPath of possiblePaths) {
6505
+ try {
6506
+ if (fs10.existsSync(pkgPath)) {
6507
+ const content = fs10.readFileSync(pkgPath, "utf-8");
6508
+ return JSON.parse(content);
6509
+ }
6510
+ } catch {
6511
+ continue;
6512
+ }
6513
+ }
6514
+ return { version: "1.0.0", name: "@nick848/sf-cli" };
6515
+ }
6516
+ var packageInfo = getPackageInfo();
6517
+ var CURRENT_VERSION = packageInfo.version;
6518
+ var PACKAGE_NAME = packageInfo.name;
6454
6519
  async function handleUpdate(args, ctx) {
6455
6520
  const options = {
6456
6521
  check: args.includes("--check") || args.includes("-c"),
@@ -6667,16 +6732,23 @@ async function handleNew(args, ctx) {
6667
6732
  if (workflowEngine) {
6668
6733
  const existingState = workflowEngine.getState();
6669
6734
  if (existingState && existingState.status === "running") {
6735
+ if (existingState.currentStep === "explore" || existingState.currentStep === "propose") {
6736
+ const specPath = path5.join(workingDir, "openspec", "changes", `${existingState.id}-spec.md`);
6737
+ if (fs10.existsSync(specPath)) {
6738
+ return {
6739
+ output: chalk9.yellow("\u5F53\u524D\u5DE5\u4F5C\u6D41\u6B63\u5728\u7B49\u5F85\u89C4\u683C\u786E\u8BA4") + chalk9.gray(`
6740
+
6741
+ \u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9.gray(`
6742
+ \u53D8\u66F4ID: ${existingState.id}`) + chalk9.cyan("\n\n\u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210:") + chalk9.white(`
6743
+ ${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")
6744
+ };
6745
+ }
6746
+ }
6670
6747
  return {
6671
6748
  output: chalk9.yellow("\u5F53\u524D\u5DF2\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9.gray(`
6672
6749
 
6673
6750
  \u5DE5\u4F5C\u6D41: ${existingState.title}`) + chalk9.gray(`
6674
- \u5F53\u524D\u9636\u6BB5: ${existingState.currentStep}`) + chalk9.gray(`
6675
-
6676
- \u9009\u9879:`) + chalk9.gray(`
6677
- 1. \u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:${existingState.currentStep}`) + chalk9.gray(`
6678
- 2. \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:cancel`) + chalk9.gray(`
6679
- 3. \u67E5\u770B\u5DE5\u4F5C\u6D41\u72B6\u6001: /opsx:status`)
6751
+ \u5F53\u524D\u9636\u6BB5: ${existingState.currentStep}`) + chalk9.gray("\n\n\u9009\u9879:") + chalk9.gray("\n 1. \u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:status") + chalk9.gray("\n 2. \u53D6\u6D88\u5F53\u524D\u5DE5\u4F5C\u6D41: /opsx:cancel")
6680
6752
  };
6681
6753
  }
6682
6754
  }
@@ -6691,6 +6763,7 @@ async function handleNew(args, ctx) {
6691
6763
  async function newFeature(options, workingDir, workflowEngine) {
6692
6764
  const cwd = workingDir || process.cwd();
6693
6765
  const { requirement, forceComplexity } = options;
6766
+ const lines = [];
6694
6767
  try {
6695
6768
  const stats = await fs4.stat(cwd);
6696
6769
  if (!stats.isDirectory()) {
@@ -6698,51 +6771,283 @@ async function newFeature(options, workingDir, workflowEngine) {
6698
6771
  output: chalk9.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
6699
6772
  };
6700
6773
  }
6701
- } catch (e) {
6774
+ } catch {
6702
6775
  return {
6703
6776
  output: chalk9.red(`\u9519\u8BEF: \u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6743\u9650\u8BBF\u95EE ${cwd}`)
6704
6777
  };
6705
6778
  }
6706
- try {
6707
- const context = await readProjectContext(cwd);
6708
- const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
6709
- const workflow = workflowEngine || new WorkflowEngine();
6710
- if (!workflowEngine) {
6711
- await workflow.initialize(cwd);
6712
- }
6713
- const state = await workflow.start(requirement, analysis.score, {
6714
- title: extractTitle(requirement)
6779
+ lines.push(chalk9.cyan("\u{1F50D} \u5206\u6790\u9879\u76EE..."));
6780
+ const context = await readProjectContext(cwd);
6781
+ lines.push(chalk9.gray(` \u9879\u76EE: ${context.name}`));
6782
+ lines.push(chalk9.gray(` \u7C7B\u578B: ${context.type}`));
6783
+ lines.push(chalk9.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`));
6784
+ lines.push("");
6785
+ lines.push(chalk9.cyan("\u{1F4CA} \u5206\u6790\u9700\u6C42\u590D\u6742\u5EA6..."));
6786
+ const analysis = forceComplexity ? createForcedAnalysis(forceComplexity) : analyzeComplexity(requirement, context);
6787
+ lines.push(chalk9.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`));
6788
+ lines.push(chalk9.gray(` \u6D41\u7A0B\u7C7B\u578B: ${analysis.recommendation === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`));
6789
+ for (const factor of analysis.factors) {
6790
+ lines.push(chalk9.gray(` - ${factor}`));
6791
+ }
6792
+ lines.push("");
6793
+ lines.push(chalk9.cyan("\u{1F4CB} \u521D\u59CB\u5316\u5DE5\u4F5C\u6D41..."));
6794
+ const workflow = workflowEngine || new WorkflowEngine();
6795
+ if (!workflowEngine) {
6796
+ await workflow.initialize(cwd);
6797
+ }
6798
+ const state = await workflow.start(requirement, analysis.score, {
6799
+ title: extractTitle(requirement)
6800
+ });
6801
+ lines.push(chalk9.gray(` \u53D8\u66F4ID: ${state.id}`));
6802
+ lines.push(chalk9.gray(` \u5DE5\u4F5C\u6D41: ${state.type}`));
6803
+ lines.push("");
6804
+ lines.push(chalk9.cyan("\u{1F4DD} \u751F\u6210\u89C4\u683C\u62C6\u5206..."));
6805
+ const spec = await generateSpec(requirement, context, analysis, state.id);
6806
+ const specPath = await saveSpecFile(cwd, spec);
6807
+ lines.push(chalk9.green(" \u2713 \u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210"));
6808
+ lines.push(chalk9.gray(` \u8DEF\u5F84: ${specPath}`));
6809
+ lines.push("");
6810
+ lines.push(chalk9.cyan.bold("\u{1F4CB} \u89C4\u683C\u6982\u89C8:"));
6811
+ lines.push(chalk9.white(`
6812
+ ${spec.summary}`));
6813
+ if (spec.items.length > 0) {
6814
+ lines.push("");
6815
+ lines.push(chalk9.cyan(" \u4EFB\u52A1\u62C6\u5206:"));
6816
+ for (const item of spec.items) {
6817
+ const priorityIcon = item.priority === "high" ? "\u{1F534}" : item.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
6818
+ lines.push(chalk9.gray(` ${priorityIcon} [${item.id}] ${item.title}`));
6819
+ }
6820
+ }
6821
+ if (spec.risks.length > 0) {
6822
+ lines.push("");
6823
+ lines.push(chalk9.yellow(" \u26A0\uFE0F \u98CE\u9669\u63D0\u793A:"));
6824
+ for (const risk of spec.risks) {
6825
+ lines.push(chalk9.gray(` - ${risk}`));
6826
+ }
6827
+ }
6828
+ lines.push("");
6829
+ lines.push(chalk9.yellow.bold("\u23F3 \u7B49\u5F85\u89C4\u683C\u786E\u8BA4"));
6830
+ lines.push(chalk9.gray("\n\u8BF7\u68C0\u67E5\u751F\u6210\u7684\u89C4\u683C\u6587\u4EF6\uFF0C\u786E\u8BA4\u540E\u7EE7\u7EED:"));
6831
+ lines.push(chalk9.white("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C\uFF0C\u8FDB\u5165\u4E0B\u4E00\u9636\u6BB5"));
6832
+ lines.push(chalk9.white(" /opsx:rollback explore - \u89C4\u683C\u4E0D\u7B26\uFF0C\u91CD\u65B0\u62C6\u5206"));
6833
+ lines.push(chalk9.white(" /opsx:status - \u67E5\u770B\u5DE5\u4F5C\u6D41\u72B6\u6001"));
6834
+ return { output: lines.join("\n") };
6835
+ }
6836
+ async function generateSpec(requirement, context, analysis, changeId) {
6837
+ const spec = {
6838
+ changeId,
6839
+ requirement,
6840
+ summary: "",
6841
+ items: [],
6842
+ architectureNotes: [],
6843
+ risks: [],
6844
+ suggestions: []
6845
+ };
6846
+ spec.summary = generateSummary(requirement);
6847
+ if (analysis.recommendation === "complex") {
6848
+ spec.items = generateComplexTasks(requirement, context, analysis);
6849
+ spec.architectureNotes = generateArchitectureNotes(requirement, context);
6850
+ } else {
6851
+ spec.items = generateSimpleTasks(requirement);
6852
+ }
6853
+ spec.risks = generateRisks(requirement, context, analysis);
6854
+ spec.suggestions = generateSuggestions(requirement, context, analysis);
6855
+ return spec;
6856
+ }
6857
+ function generateSummary(requirement) {
6858
+ const firstSentence = requirement.split(/[。!?\n]/)[0];
6859
+ return firstSentence.length > 100 ? firstSentence.slice(0, 97) + "..." : firstSentence;
6860
+ }
6861
+ function generateComplexTasks(requirement, context, analysis) {
6862
+ const items = [];
6863
+ let itemId = 1;
6864
+ const featurePatterns = [
6865
+ { pattern: /用户|登录|注册|认证|权限/, title: "\u7528\u6237\u8BA4\u8BC1\u6A21\u5757", priority: "high" },
6866
+ { pattern: /数据|存储|缓存|数据库/, title: "\u6570\u636E\u5C42\u5B9E\u73B0", priority: "high" },
6867
+ { pattern: /接口|API|请求|响应/, title: "API \u63A5\u53E3\u5F00\u53D1", priority: "high" },
6868
+ { pattern: /界面|页面|组件|UI/, title: "\u754C\u9762\u5F00\u53D1", priority: "medium" },
6869
+ { pattern: /测试|单测|覆盖/, title: "\u6D4B\u8BD5\u7528\u4F8B\u7F16\u5199", priority: "medium" },
6870
+ { pattern: /文档|说明/, title: "\u6587\u6863\u7F16\u5199", priority: "low" },
6871
+ { pattern: /配置|设置/, title: "\u914D\u7F6E\u7BA1\u7406", priority: "low" },
6872
+ { pattern: /优化|性能/, title: "\u6027\u80FD\u4F18\u5316", priority: "medium" },
6873
+ { pattern: /安全|加密/, title: "\u5B89\u5168\u5B9E\u73B0", priority: "high" },
6874
+ { pattern: /日志|监控/, title: "\u65E5\u5FD7\u76D1\u63A7", priority: "low" }
6875
+ ];
6876
+ for (const { pattern, title, priority } of featurePatterns) {
6877
+ if (pattern.test(requirement)) {
6878
+ items.push({
6879
+ id: `T${itemId.toString().padStart(3, "0")}`,
6880
+ title,
6881
+ description: `${title}\u76F8\u5173\u7684\u529F\u80FD\u5B9E\u73B0`,
6882
+ priority,
6883
+ dependencies: itemId > 1 ? [`T${(itemId - 1).toString().padStart(3, "0")}`] : [],
6884
+ estimatedComplexity: priority === "high" ? 3 : priority === "medium" ? 2 : 1
6885
+ });
6886
+ itemId++;
6887
+ }
6888
+ }
6889
+ if (items.length === 0) {
6890
+ items.push({
6891
+ id: "T001",
6892
+ title: "\u9700\u6C42\u5206\u6790\u4E0E\u8BBE\u8BA1",
6893
+ description: "\u5206\u6790\u9700\u6C42\u7EC6\u8282\uFF0C\u8BBE\u8BA1\u5B9E\u73B0\u65B9\u6848",
6894
+ priority: "high",
6895
+ dependencies: [],
6896
+ estimatedComplexity: 2
6715
6897
  });
6716
- const lines = [
6717
- chalk9.green("\u2713 \u9700\u6C42\u5206\u6790\u5B8C\u6210\n"),
6718
- chalk9.cyan("\u9879\u76EE\u4FE1\u606F:"),
6719
- chalk9.gray(` \u540D\u79F0: ${context.name}`),
6720
- chalk9.gray(` \u7C7B\u578B: ${context.type}`),
6721
- chalk9.gray(` \u6846\u67B6: ${context.framework || "\u672A\u8BC6\u522B"}`),
6722
- "",
6723
- chalk9.cyan("\u9700\u6C42\u5206\u6790:"),
6724
- chalk9.gray(` \u590D\u6742\u5EA6: ${analysis.score}/10`),
6725
- chalk9.gray(` \u5DE5\u4F5C\u6D41: ${state.type === "complex" ? "\u590D\u6742\u6D41\u7A0B" : "\u7B80\u5355\u6D41\u7A0B"}`),
6726
- chalk9.gray(` \u53D8\u66F4ID: ${state.id}`),
6727
- "",
6728
- chalk9.cyan("\u590D\u6742\u5EA6\u56E0\u7D20:"),
6729
- ...analysis.factors.map((f) => chalk9.gray(` - ${f}`)),
6730
- "",
6731
- chalk9.yellow("\u4E0B\u4E00\u6B65:"),
6732
- state.type === "complex" ? chalk9.gray(" \u6267\u884C /opsx:explore \u5F00\u59CB\u63A2\u7D22\u9636\u6BB5") : chalk9.gray(" \u6267\u884C /opsx:propose \u63D0\u4EA4\u53D8\u66F4\u63D0\u6848")
6733
- ];
6734
- return { output: lines.join("\n") };
6735
- } catch (error) {
6736
- const err = error;
6737
- if (err.code === "EACCES") {
6738
- return {
6739
- output: chalk9.red(`\u9519\u8BEF: \u65E0\u6743\u9650\u8BBF\u95EE\u76EE\u5F55 ${cwd}`)
6740
- };
6898
+ items.push({
6899
+ id: "T002",
6900
+ title: "\u6838\u5FC3\u529F\u80FD\u5B9E\u73B0",
6901
+ description: requirement,
6902
+ priority: "high",
6903
+ dependencies: ["T001"],
6904
+ estimatedComplexity: analysis.score
6905
+ });
6906
+ items.push({
6907
+ id: "T003",
6908
+ title: "\u6D4B\u8BD5\u4E0E\u9A8C\u8BC1",
6909
+ description: "\u7F16\u5199\u6D4B\u8BD5\u7528\u4F8B\uFF0C\u9A8C\u8BC1\u529F\u80FD\u6B63\u786E\u6027",
6910
+ priority: "medium",
6911
+ dependencies: ["T002"],
6912
+ estimatedComplexity: 2
6913
+ });
6914
+ }
6915
+ return items;
6916
+ }
6917
+ function generateSimpleTasks(requirement, context) {
6918
+ return [
6919
+ {
6920
+ id: "T001",
6921
+ title: "\u5B9E\u73B0\u53D8\u66F4",
6922
+ description: requirement,
6923
+ priority: "high",
6924
+ dependencies: [],
6925
+ estimatedComplexity: 3
6926
+ },
6927
+ {
6928
+ id: "T002",
6929
+ title: "\u6D4B\u8BD5\u9A8C\u8BC1",
6930
+ description: "\u9A8C\u8BC1\u53D8\u66F4\u6B63\u786E\u6027",
6931
+ priority: "medium",
6932
+ dependencies: ["T001"],
6933
+ estimatedComplexity: 1
6741
6934
  }
6742
- return {
6743
- output: chalk9.red(`\u542F\u52A8\u9700\u6C42\u5931\u8D25: ${error.message}`)
6744
- };
6935
+ ];
6936
+ }
6937
+ function generateArchitectureNotes(requirement, context) {
6938
+ const notes = [];
6939
+ if (context.framework) {
6940
+ notes.push(`\u9879\u76EE\u4F7F\u7528 ${context.framework} \u6846\u67B6\uFF0C\u9700\u9075\u5FAA\u5176\u6700\u4F73\u5B9E\u8DF5`);
6941
+ }
6942
+ if (requirement.includes("\u6A21\u5757") || requirement.includes("\u7EC4\u4EF6")) {
6943
+ notes.push("\u5EFA\u8BAE\u91C7\u7528\u6A21\u5757\u5316\u8BBE\u8BA1\uFF0C\u4FDD\u6301\u7EC4\u4EF6\u804C\u8D23\u5355\u4E00");
6944
+ }
6945
+ if (requirement.includes("API") || requirement.includes("\u63A5\u53E3")) {
6946
+ notes.push("API \u8BBE\u8BA1\u9700\u8003\u8651\u7248\u672C\u63A7\u5236\u548C\u5411\u540E\u517C\u5BB9");
6947
+ }
6948
+ if (context.structure.srcStructure) {
6949
+ notes.push(`\u73B0\u6709\u6E90\u7801\u7ED3\u6784: ${context.structure.srcStructure}`);
6950
+ }
6951
+ return notes;
6952
+ }
6953
+ function generateRisks(requirement, context, analysis) {
6954
+ const risks = [];
6955
+ if (!context.framework) {
6956
+ risks.push("\u9879\u76EE\u6846\u67B6\u672A\u8BC6\u522B\uFF0C\u53EF\u80FD\u5F71\u54CD\u4EE3\u7801\u98CE\u683C\u4E00\u81F4\u6027");
6957
+ }
6958
+ if (analysis.score >= 7) {
6959
+ risks.push("\u9700\u6C42\u590D\u6742\u5EA6\u8F83\u9AD8\uFF0C\u5EFA\u8BAE\u5206\u9636\u6BB5\u5B9E\u73B0");
6745
6960
  }
6961
+ if (requirement.includes("\u8FC1\u79FB") || requirement.includes("\u91CD\u6784")) {
6962
+ risks.push("\u6D89\u53CA\u73B0\u6709\u4EE3\u7801\u4FEE\u6539\uFF0C\u9700\u6CE8\u610F\u56DE\u5F52\u6D4B\u8BD5");
6963
+ }
6964
+ if (requirement.includes("\u6743\u9650") || requirement.includes("\u5B89\u5168")) {
6965
+ risks.push("\u6D89\u53CA\u5B89\u5168\u654F\u611F\u529F\u80FD\uFF0C\u9700\u8981\u989D\u5916\u5BA1\u67E5");
6966
+ }
6967
+ return risks;
6968
+ }
6969
+ function generateSuggestions(requirement, context, analysis) {
6970
+ const suggestions = [];
6971
+ if (context.norms.devStandards) {
6972
+ suggestions.push("\u9879\u76EE\u5DF2\u6709\u5F00\u53D1\u89C4\u8303\uFF0C\u8BF7\u9075\u5FAA\u73B0\u6709\u89C4\u8303");
6973
+ }
6974
+ if (analysis.recommendation === "complex") {
6975
+ suggestions.push("\u590D\u6742\u9700\u6C42\u5EFA\u8BAE\u5148\u8FDB\u884C\u6280\u672F\u8BC4\u5BA1");
6976
+ suggestions.push("\u5EFA\u8BAE\u8C03\u7528 $architect \u83B7\u53D6\u67B6\u6784\u5EFA\u8BAE");
6977
+ }
6978
+ if (context.techStack.length > 0) {
6979
+ suggestions.push(`\u6280\u672F\u6808: ${context.techStack.join(", ")}`);
6980
+ }
6981
+ return suggestions;
6982
+ }
6983
+ async function saveSpecFile(cwd, spec) {
6984
+ const changesDir = path5.join(cwd, "openspec", "changes");
6985
+ await fs4.mkdir(changesDir, { recursive: true });
6986
+ const specPath = path5.join(changesDir, `${spec.changeId}-spec.md`);
6987
+ const content = formatSpecFile(spec);
6988
+ await fs4.writeFile(specPath, content, "utf-8");
6989
+ return specPath;
6990
+ }
6991
+ function formatSpecFile(spec) {
6992
+ const lines = [];
6993
+ lines.push(`# Spec: ${spec.summary}`);
6994
+ lines.push("");
6995
+ lines.push(`> \u53D8\u66F4ID: ${spec.changeId}`);
6996
+ lines.push(`> \u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}`);
6997
+ lines.push("");
6998
+ lines.push("---");
6999
+ lines.push("");
7000
+ lines.push("## \u9700\u6C42\u6982\u8FF0");
7001
+ lines.push("");
7002
+ lines.push(spec.requirement);
7003
+ lines.push("");
7004
+ lines.push("## \u4EFB\u52A1\u62C6\u5206");
7005
+ lines.push("");
7006
+ for (const item of spec.items) {
7007
+ const priorityLabel = item.priority === "high" ? "\u{1F534} \u9AD8" : item.priority === "medium" ? "\u{1F7E1} \u4E2D" : "\u{1F7E2} \u4F4E";
7008
+ lines.push(`### ${item.id}: ${item.title}`);
7009
+ lines.push("");
7010
+ lines.push(`- **\u4F18\u5148\u7EA7**: ${priorityLabel}`);
7011
+ lines.push(`- **\u63CF\u8FF0**: ${item.description}`);
7012
+ lines.push(`- **\u9884\u4F30\u590D\u6742\u5EA6**: ${item.estimatedComplexity}/5`);
7013
+ if (item.dependencies.length > 0) {
7014
+ lines.push(`- **\u4F9D\u8D56**: ${item.dependencies.join(", ")}`);
7015
+ }
7016
+ lines.push("");
7017
+ }
7018
+ if (spec.architectureNotes.length > 0) {
7019
+ lines.push("## \u67B6\u6784\u8BF4\u660E");
7020
+ lines.push("");
7021
+ for (const note of spec.architectureNotes) {
7022
+ lines.push(`- ${note}`);
7023
+ }
7024
+ lines.push("");
7025
+ }
7026
+ if (spec.risks.length > 0) {
7027
+ lines.push("## \u26A0\uFE0F \u98CE\u9669\u8BC4\u4F30");
7028
+ lines.push("");
7029
+ for (const risk of spec.risks) {
7030
+ lines.push(`- ${risk}`);
7031
+ }
7032
+ lines.push("");
7033
+ }
7034
+ if (spec.suggestions.length > 0) {
7035
+ lines.push("## \u{1F4A1} \u5EFA\u8BAE");
7036
+ lines.push("");
7037
+ for (const suggestion of spec.suggestions) {
7038
+ lines.push(`- ${suggestion}`);
7039
+ }
7040
+ lines.push("");
7041
+ }
7042
+ lines.push("---");
7043
+ lines.push("");
7044
+ lines.push("## \u786E\u8BA4\u72B6\u6001");
7045
+ lines.push("");
7046
+ lines.push("- [ ] \u89C4\u683C\u5DF2\u5BA1\u9605");
7047
+ lines.push("- [ ] \u4EFB\u52A1\u62C6\u5206\u5DF2\u786E\u8BA4");
7048
+ lines.push("");
7049
+ lines.push("**\u786E\u8BA4\u540E\u6267\u884C**: `/opsx:confirm spec-review`");
7050
+ return lines.join("\n");
6746
7051
  }
6747
7052
  function parseArgs(args) {
6748
7053
  let forceComplexity;
@@ -6767,42 +7072,52 @@ async function readProjectContext(cwd) {
6767
7072
  type: "unknown",
6768
7073
  framework: null,
6769
7074
  techStack: [],
6770
- description: ""
7075
+ description: "",
7076
+ structure: {
7077
+ directories: [],
7078
+ keyFiles: [],
7079
+ srcStructure: ""
7080
+ },
7081
+ norms: {
7082
+ devStandards: "",
7083
+ patterns: "",
7084
+ weights: ""
7085
+ }
7086
+ };
7087
+ const [agentsContext, configContext, normsContext, structureContext] = await Promise.all([
7088
+ readAgentsMd(cwd),
7089
+ readConfigYaml(cwd),
7090
+ readNorms(cwd),
7091
+ analyzeStructure(cwd)
7092
+ ]);
7093
+ return {
7094
+ ...defaultContext,
7095
+ ...agentsContext,
7096
+ ...configContext,
7097
+ norms: normsContext,
7098
+ structure: structureContext
6771
7099
  };
7100
+ }
7101
+ async function readAgentsMd(cwd) {
6772
7102
  const agentsPath = path5.join(cwd, "AGENTS.md");
6773
7103
  try {
6774
7104
  const stats = await fs4.stat(agentsPath);
6775
7105
  if (stats.size > MAX_FILE_SIZE2) {
6776
7106
  console.warn(`\u8B66\u544A: AGENTS.md \u6587\u4EF6\u8FC7\u5927 (${stats.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
6777
- return defaultContext;
7107
+ return {};
6778
7108
  }
6779
7109
  const content = await fs4.readFile(agentsPath, "utf-8");
6780
- return parseAgentsMd(content, defaultContext);
7110
+ return parseAgentsMd(content);
6781
7111
  } catch (e) {
6782
7112
  const err = e;
6783
7113
  if (err.code !== "ENOENT") {
6784
7114
  console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 AGENTS.md - ${err.message}`);
6785
7115
  }
7116
+ return {};
6786
7117
  }
6787
- const configPath = path5.join(cwd, "openspec", "config.yaml");
6788
- try {
6789
- const stats = await fs4.stat(configPath);
6790
- if (stats.size > MAX_FILE_SIZE2) {
6791
- console.warn(`\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6`);
6792
- return defaultContext;
6793
- }
6794
- const content = await fs4.readFile(configPath, "utf-8");
6795
- return parseConfigYaml(content, defaultContext);
6796
- } catch (e) {
6797
- const err = e;
6798
- if (err.code !== "ENOENT") {
6799
- console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
6800
- }
6801
- }
6802
- return defaultContext;
6803
7118
  }
6804
- function parseAgentsMd(content, defaults) {
6805
- const context = { ...defaults };
7119
+ function parseAgentsMd(content) {
7120
+ const context = {};
6806
7121
  const nameMatch = content.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
6807
7122
  if (nameMatch) {
6808
7123
  context.name = nameMatch[1];
@@ -6819,10 +7134,32 @@ function parseAgentsMd(content, defaults) {
6819
7134
  if (descMatch) {
6820
7135
  context.description = descMatch[1].trim();
6821
7136
  }
7137
+ const techStackMatch = content.match(/技术栈[::]\s*([^\n]+)/);
7138
+ if (techStackMatch) {
7139
+ context.techStack = techStackMatch[1].split(/[,,、]/).map((s) => s.trim()).filter(Boolean);
7140
+ }
6822
7141
  return context;
6823
7142
  }
6824
- function parseConfigYaml(content, defaults) {
6825
- const context = { ...defaults };
7143
+ async function readConfigYaml(cwd) {
7144
+ const configPath = path5.join(cwd, "openspec", "config.yaml");
7145
+ try {
7146
+ const stats = await fs4.stat(configPath);
7147
+ if (stats.size > MAX_FILE_SIZE2) {
7148
+ console.warn("\u8B66\u544A: config.yaml \u6587\u4EF6\u8FC7\u5927\uFF0C\u8DF3\u8FC7\u8BFB\u53D6");
7149
+ return {};
7150
+ }
7151
+ const content = await fs4.readFile(configPath, "utf-8");
7152
+ return parseConfigYaml(content);
7153
+ } catch (e) {
7154
+ const err = e;
7155
+ if (err.code !== "ENOENT") {
7156
+ console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 config.yaml - ${err.message}`);
7157
+ }
7158
+ return {};
7159
+ }
7160
+ }
7161
+ function parseConfigYaml(content) {
7162
+ const context = {};
6826
7163
  const nameMatch = content.match(/name:\s*(.+)/);
6827
7164
  if (nameMatch) {
6828
7165
  context.name = nameMatch[1].trim();
@@ -6837,6 +7174,67 @@ function parseConfigYaml(content, defaults) {
6837
7174
  }
6838
7175
  return context;
6839
7176
  }
7177
+ async function readNorms(cwd) {
7178
+ const normsDir = path5.join(cwd, ".sf-cli", "norms");
7179
+ const norms = {
7180
+ devStandards: "",
7181
+ patterns: "",
7182
+ weights: ""
7183
+ };
7184
+ try {
7185
+ const devStandardsPath = path5.join(normsDir, "devstanded.md");
7186
+ norms.devStandards = await fs4.readFile(devStandardsPath, "utf-8").catch(() => "");
7187
+ } catch {
7188
+ }
7189
+ try {
7190
+ const patternsPath = path5.join(normsDir, "patterns.json");
7191
+ norms.patterns = await fs4.readFile(patternsPath, "utf-8").catch(() => "");
7192
+ } catch {
7193
+ }
7194
+ try {
7195
+ const weightsPath = path5.join(normsDir, "weights.json");
7196
+ norms.weights = await fs4.readFile(weightsPath, "utf-8").catch(() => "");
7197
+ } catch {
7198
+ }
7199
+ return norms;
7200
+ }
7201
+ async function analyzeStructure(cwd) {
7202
+ const structure = {
7203
+ directories: [],
7204
+ keyFiles: [],
7205
+ srcStructure: ""
7206
+ };
7207
+ try {
7208
+ const entries = await fs4.readdir(cwd, { withFileTypes: true });
7209
+ for (const entry of entries) {
7210
+ if (entry.isDirectory() && !["node_modules", "dist", ".git", "build"].includes(entry.name)) {
7211
+ structure.directories.push(entry.name);
7212
+ }
7213
+ }
7214
+ const keyFiles = [
7215
+ "package.json",
7216
+ "tsconfig.json",
7217
+ "AGENTS.md",
7218
+ "README.md"
7219
+ ];
7220
+ for (const file of keyFiles) {
7221
+ const filePath = path5.join(cwd, file);
7222
+ try {
7223
+ await fs4.access(filePath);
7224
+ structure.keyFiles.push(file);
7225
+ } catch {
7226
+ }
7227
+ }
7228
+ const srcDir = path5.join(cwd, "src");
7229
+ try {
7230
+ const srcEntries = await fs4.readdir(srcDir, { withFileTypes: true });
7231
+ structure.srcStructure = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name).join("/");
7232
+ } catch {
7233
+ }
7234
+ } catch (e) {
7235
+ }
7236
+ return { structure };
7237
+ }
6840
7238
  function analyzeComplexity(requirement, context) {
6841
7239
  let score = 3;
6842
7240
  const factors = [];
@@ -6904,6 +7302,78 @@ function extractTitle(requirement) {
6904
7302
  return requirement.slice(0, 47) + "...";
6905
7303
  }
6906
7304
  var autoScheduleEnabled = true;
7305
+ var DEFAULT_REGRESSION_CONFIG = {
7306
+ enabled: true,
7307
+ command: "npm test -- --run",
7308
+ timeout: 12e4,
7309
+ // 2分钟
7310
+ coverageThreshold: 80
7311
+ };
7312
+ async function runRegressionTest(workingDirectory, config = DEFAULT_REGRESSION_CONFIG) {
7313
+ const startTime = Date.now();
7314
+ const result = {
7315
+ success: false,
7316
+ passed: 0,
7317
+ failed: 0,
7318
+ total: 0,
7319
+ duration: 0,
7320
+ output: "",
7321
+ errors: []
7322
+ };
7323
+ return new Promise((resolve4) => {
7324
+ const proc = spawn(config.command, [], {
7325
+ cwd: workingDirectory,
7326
+ shell: true,
7327
+ stdio: "pipe"
7328
+ });
7329
+ let stdout = "";
7330
+ let stderr = "";
7331
+ proc.stdout?.on("data", (data) => {
7332
+ stdout += data.toString();
7333
+ });
7334
+ proc.stderr?.on("data", (data) => {
7335
+ stderr += data.toString();
7336
+ });
7337
+ const timeout = setTimeout(() => {
7338
+ proc.kill();
7339
+ result.errors.push("\u6D4B\u8BD5\u8D85\u65F6");
7340
+ result.output = stdout + stderr;
7341
+ result.duration = Date.now() - startTime;
7342
+ resolve4(result);
7343
+ }, config.timeout);
7344
+ proc.on("close", (code) => {
7345
+ clearTimeout(timeout);
7346
+ result.output = stdout + stderr;
7347
+ result.duration = Date.now() - startTime;
7348
+ const passMatch = stdout.match(/(\d+)\s+(?:passed|tests?\s+passed)/i);
7349
+ const failMatch = stdout.match(/(\d+)\s+(?:failed|tests?\s+failed)/i);
7350
+ const totalMatch = stdout.match(/Tests?:\s*(\d+)/i);
7351
+ if (passMatch) {
7352
+ result.passed = parseInt(passMatch[1], 10);
7353
+ }
7354
+ if (failMatch) {
7355
+ result.failed = parseInt(failMatch[1], 10);
7356
+ }
7357
+ if (totalMatch) {
7358
+ result.total = parseInt(totalMatch[1], 10);
7359
+ } else {
7360
+ result.total = result.passed + result.failed;
7361
+ }
7362
+ const coverageMatch = stdout.match(/All files[^\d]*(\d+(?:\.\d+)?)/);
7363
+ if (coverageMatch) {
7364
+ result.coverage = parseFloat(coverageMatch[1]);
7365
+ }
7366
+ result.success = code === 0 && result.failed === 0;
7367
+ resolve4(result);
7368
+ });
7369
+ proc.on("error", (err) => {
7370
+ clearTimeout(timeout);
7371
+ result.errors.push(err.message);
7372
+ result.duration = Date.now() - startTime;
7373
+ resolve4(result);
7374
+ });
7375
+ });
7376
+ }
6907
7377
  async function handleOpsx(command, args, ctx) {
6908
7378
  const step = command.replace("opsx:", "");
6909
7379
  const workflow = new WorkflowEngine();
@@ -6918,7 +7388,7 @@ async function handleOpsx(command, args, ctx) {
6918
7388
  case "apply":
6919
7389
  return handleApply(workflow);
6920
7390
  case "archive":
6921
- return handleArchive(workflow, args);
7391
+ return handleArchive(workflow, args, ctx);
6922
7392
  case "propose":
6923
7393
  return handlePropose(workflow, args);
6924
7394
  case "status":
@@ -6928,17 +7398,55 @@ async function handleOpsx(command, args, ctx) {
6928
7398
  case "rollback":
6929
7399
  return handleRollback(workflow, args);
6930
7400
  case "confirm":
6931
- return handleConfirm(workflow, args);
7401
+ return handleConfirm(workflow, args, ctx);
6932
7402
  case "next":
6933
7403
  return handleNext(workflow);
6934
7404
  case "auto":
6935
7405
  return handleAutoSchedule(args);
7406
+ case "test":
7407
+ return handleRegressionTest(ctx);
6936
7408
  default:
6937
7409
  return {
6938
7410
  output: chalk9.red(`\u672A\u77E5\u7684OpenSpec\u547D\u4EE4: /${command}`)
6939
7411
  };
6940
7412
  }
6941
7413
  }
7414
+ async function handleRegressionTest(ctx) {
7415
+ const lines = [];
7416
+ lines.push(chalk9.cyan("\u{1F50D} \u6267\u884C\u56DE\u5F52\u6D4B\u8BD5..."));
7417
+ lines.push(chalk9.gray(` \u5DE5\u4F5C\u76EE\u5F55: ${ctx.options.workingDirectory}`));
7418
+ lines.push("");
7419
+ const result = await runRegressionTest(ctx.options.workingDirectory);
7420
+ lines.push(chalk9.gray("\u2500".repeat(50)));
7421
+ if (result.success) {
7422
+ lines.push(chalk9.green("\u2713 \u56DE\u5F52\u6D4B\u8BD5\u901A\u8FC7"));
7423
+ } else {
7424
+ lines.push(chalk9.red("\u2717 \u56DE\u5F52\u6D4B\u8BD5\u5931\u8D25"));
7425
+ }
7426
+ lines.push("");
7427
+ lines.push(chalk9.cyan("\u6D4B\u8BD5\u7ED3\u679C:"));
7428
+ lines.push(chalk9.gray(` \u901A\u8FC7: ${result.passed}`));
7429
+ lines.push(chalk9.gray(` \u5931\u8D25: ${result.failed}`));
7430
+ lines.push(chalk9.gray(` \u603B\u8BA1: ${result.total}`));
7431
+ lines.push(chalk9.gray(` \u8017\u65F6: ${(result.duration / 1e3).toFixed(2)}s`));
7432
+ if (result.coverage !== void 0) {
7433
+ const coverageColor = result.coverage >= 80 ? chalk9.green : result.coverage >= 60 ? chalk9.yellow : chalk9.red;
7434
+ lines.push(coverageColor(` \u8986\u76D6\u7387: ${result.coverage}%`));
7435
+ }
7436
+ if (result.errors.length > 0) {
7437
+ lines.push("");
7438
+ lines.push(chalk9.red("\u9519\u8BEF\u4FE1\u606F:"));
7439
+ for (const error of result.errors) {
7440
+ lines.push(chalk9.gray(` - ${error}`));
7441
+ }
7442
+ }
7443
+ if (result.failed > 0) {
7444
+ lines.push("");
7445
+ lines.push(chalk9.yellow("\u26A0 \u5B58\u5728\u5931\u8D25\u7684\u6D4B\u8BD5\u7528\u4F8B\uFF0C\u8BF7\u68C0\u67E5\u5E76\u4FEE\u590D"));
7446
+ lines.push(chalk9.gray("\u4F7F\u7528 /opsx:rollback \u53EF\u56DE\u6EDA\u5230\u4E4B\u524D\u7684\u9636\u6BB5"));
7447
+ }
7448
+ return { output: lines.join("\n") };
7449
+ }
6942
7450
  function handleAutoSchedule(args) {
6943
7451
  const action = args[0]?.toLowerCase();
6944
7452
  if (!action) {
@@ -7068,13 +7576,70 @@ async function handleArchive(workflow, args, ctx) {
7068
7576
  ${generateConfirmationPrompt(confirmation.point)}`) + chalk9.cyan("\n\n\u4F7F\u7528 /opsx:confirm code-review \u786E\u8BA4\u540E\u5F52\u6863")
7069
7577
  };
7070
7578
  }
7579
+ const lines = [];
7580
+ lines.push(chalk9.cyan("\u{1F50D} \u6267\u884C\u5F52\u6863\u524D\u56DE\u5F52\u6D4B\u8BD5..."));
7581
+ lines.push("");
7582
+ const testResult = await runRegressionTest(ctx.options.workingDirectory);
7583
+ if (!testResult.success) {
7584
+ lines.push(chalk9.red("\u2717 \u56DE\u5F52\u6D4B\u8BD5\u5931\u8D25"));
7585
+ lines.push(chalk9.gray(` \u901A\u8FC7: ${testResult.passed} | \u5931\u8D25: ${testResult.failed} | \u603B\u8BA1: ${testResult.total}`));
7586
+ lines.push("");
7587
+ lines.push(chalk9.yellow("\u26A0 \u5F52\u6863\u88AB\u963B\u6B62"));
7588
+ lines.push(chalk9.gray("\n\u8BF7\u4FEE\u590D\u5931\u8D25\u7684\u6D4B\u8BD5\u7528\u4F8B\u540E\u91CD\u8BD5"));
7589
+ lines.push(chalk9.gray("\u4F7F\u7528 /opsx:rollback \u53EF\u56DE\u6EDA\u5230\u4E4B\u524D\u7684\u9636\u6BB5"));
7590
+ lines.push(chalk9.gray("\u4F7F\u7528 /opsx:test \u53EF\u5355\u72EC\u8FD0\u884C\u56DE\u5F52\u6D4B\u8BD5"));
7591
+ return { output: lines.join("\n") };
7592
+ }
7593
+ lines.push(chalk9.green("\u2713 \u56DE\u5F52\u6D4B\u8BD5\u901A\u8FC7"));
7594
+ lines.push(chalk9.gray(` \u901A\u8FC7: ${testResult.passed} | \u8017\u65F6: ${(testResult.duration / 1e3).toFixed(2)}s`));
7595
+ lines.push("");
7071
7596
  const changeId = state.id;
7072
7597
  const summary = args.join(" ") || "\u5B8C\u6210\u53D8\u66F4";
7073
7598
  await workflow.archive(summary);
7074
- return {
7075
- output: chalk9.green("\u2713 \u5DE5\u4F5C\u6D41\u5DF2\u5F52\u6863") + chalk9.gray(`
7076
- \u53D8\u66F4ID: ${changeId}`) + chalk9.cyan("\n\n\u5F52\u6863\u6587\u6863\u5DF2\u751F\u6210\u5230 openspec/spec/ \u76EE\u5F55")
7077
- };
7599
+ await updateChangelog(ctx.options.workingDirectory, summary, changeId);
7600
+ lines.push(chalk9.green("\u2713 \u5DE5\u4F5C\u6D41\u5DF2\u5F52\u6863") + chalk9.gray(`
7601
+ \u53D8\u66F4ID: ${changeId}`) + chalk9.cyan("\n\n\u5F52\u6863\u6587\u6863\u5DF2\u751F\u6210\u5230 openspec/spec/ \u76EE\u5F55") + chalk9.gray("\nCHANGELOG.md \u5DF2\u66F4\u65B0"));
7602
+ return { output: lines.join("\n") };
7603
+ }
7604
+ async function updateChangelog(workingDirectory, summary, changeId) {
7605
+ try {
7606
+ const changelogPath = path5.join(workingDirectory, "CHANGELOG.md");
7607
+ const pkgPath = path5.join(workingDirectory, "package.json");
7608
+ let version = "1.0.0";
7609
+ try {
7610
+ const pkgContent = fs10.readFileSync(pkgPath, "utf-8");
7611
+ const pkg = JSON.parse(pkgContent);
7612
+ version = pkg.version || "1.0.0";
7613
+ } catch {
7614
+ }
7615
+ const today = /* @__PURE__ */ new Date();
7616
+ const dateStr = today.toISOString().split("T")[0];
7617
+ const entry = `
7618
+ ## v${version} (${dateStr})
7619
+
7620
+ **\u53D8\u66F4\u5185\u5BB9**
7621
+
7622
+ - ${summary} (${changeId})
7623
+ `;
7624
+ if (fs10.existsSync(changelogPath)) {
7625
+ const content = fs10.readFileSync(changelogPath, "utf-8");
7626
+ const versionPattern = /^## v\d+\.\d+\.\d+/m;
7627
+ const match = content.match(versionPattern);
7628
+ if (match && match.index !== void 0) {
7629
+ const newContent = content.slice(0, match.index) + entry + content.slice(match.index);
7630
+ fs10.writeFileSync(changelogPath, newContent, "utf-8");
7631
+ } else {
7632
+ fs10.appendFileSync(changelogPath, entry, "utf-8");
7633
+ }
7634
+ } else {
7635
+ const header = `# Changelog
7636
+
7637
+ All notable changes to this project will be documented in this file.
7638
+ `;
7639
+ fs10.writeFileSync(changelogPath, header + entry, "utf-8");
7640
+ }
7641
+ } catch (error) {
7642
+ }
7078
7643
  }
7079
7644
  async function handlePropose(workflow, args, ctx) {
7080
7645
  const state = workflow.getState();
@@ -7201,6 +7766,16 @@ async function handleConfirm(workflow, args, ctx) {
7201
7766
  const type = args[0];
7202
7767
  if (!type) {
7203
7768
  const pendingPoint = workflow.getCurrentConfirmationPoint();
7769
+ const specPath = await checkPendingSpec(ctx.options.workingDirectory, state.id);
7770
+ if (specPath) {
7771
+ return {
7772
+ output: chalk9.cyan("\u{1F4CB} \u89C4\u683C\u6587\u4EF6\u5F85\u786E\u8BA4") + chalk9.gray(`
7773
+
7774
+ \u89C4\u683C\u6587\u4EF6: ${specPath}`) + chalk9.gray(`
7775
+
7776
+ \u8BF7\u68C0\u67E5\u89C4\u683C\u6587\u4EF6\u540E\u786E\u8BA4:`) + chalk9.white("\n /opsx:confirm spec-review - \u786E\u8BA4\u89C4\u683C") + chalk9.white("\n /opsx:rollback explore - \u91CD\u65B0\u62C6\u5206")
7777
+ };
7778
+ }
7204
7779
  if (pendingPoint) {
7205
7780
  return {
7206
7781
  output: chalk9.cyan("\u5F85\u786E\u8BA4\u7684\u68C0\u67E5\u70B9:") + chalk9.white(`
@@ -7222,12 +7797,54 @@ ${generateConfirmationPrompt(pendingPoint)}`) + chalk9.gray(`
7222
7797
  }
7223
7798
  const comment = args.slice(1).join(" ");
7224
7799
  workflow.confirm(type, comment);
7225
- return {
7226
- output: chalk9.green("\u2713 \u5DF2\u786E\u8BA4") + chalk9.white(`
7800
+ const lines = [];
7801
+ lines.push(chalk9.green("\u2713 \u5DF2\u786E\u8BA4"));
7802
+ lines.push(chalk9.white(`
7803
+ ${point.name}`));
7804
+ if (comment) {
7805
+ lines.push(chalk9.gray(`
7806
+ \u5907\u6CE8: ${comment}`));
7807
+ }
7808
+ if (type === "spec-review") {
7809
+ const allowed = workflow.getAllowedTransitions();
7810
+ if (allowed.length > 0) {
7811
+ const nextStep = allowed[0];
7812
+ try {
7813
+ const transition = await workflow.transition(nextStep);
7814
+ lines.push("");
7815
+ lines.push(chalk9.cyan(`\u2713 \u5DF2\u81EA\u52A8\u8FDB\u5165 ${nextStep} \u9636\u6BB5`));
7816
+ lines.push(chalk9.gray(`\u8F6C\u6362: ${transition.from} \u2192 ${transition.to}`));
7817
+ if (nextStep === "new") {
7818
+ lines.push(chalk9.yellow("\n\u4E0B\u4E00\u6B65: \u8BBE\u8BA1\u65B9\u6848"));
7819
+ lines.push(chalk9.gray(" \u53EF\u8C03\u7528 $architect \u6216 $frontend-dev \u8FDB\u884C\u8BBE\u8BA1"));
7820
+ } else if (nextStep === "apply") {
7821
+ lines.push(chalk9.yellow("\n\u4E0B\u4E00\u6B65: \u6267\u884C\u53D8\u66F4"));
7822
+ lines.push(chalk9.gray(" \u53EF\u8C03\u7528 $frontend-dev \u6267\u884C\u4EE3\u7801\u4FEE\u6539"));
7823
+ }
7824
+ return { output: lines.join("\n") };
7825
+ } catch (e) {
7826
+ if (e instanceof ConfirmationRequiredError) {
7827
+ lines.push(chalk9.yellow("\n\u26A0 \u8FD8\u9700\u8981\u786E\u8BA4:") + chalk9.white(`
7828
+ ${generateConfirmationPrompt(e.point)}`) + chalk9.cyan(`
7227
7829
 
7228
- ${point.name}`) + (comment ? chalk9.gray(`
7229
- \u5907\u6CE8: ${comment}`) : "") + chalk9.yellow("\n\n\u4F7F\u7528 /opsx:next \u7EE7\u7EED\u4E0B\u4E00\u9636\u6BB5")
7230
- };
7830
+ \u4F7F\u7528 /opsx:confirm ${e.point.type}`));
7831
+ return { output: lines.join("\n") };
7832
+ }
7833
+ throw e;
7834
+ }
7835
+ }
7836
+ }
7837
+ lines.push(chalk9.yellow("\n\u4F7F\u7528 /opsx:next \u7EE7\u7EED\u4E0B\u4E00\u9636\u6BB5"));
7838
+ return { output: lines.join("\n") };
7839
+ }
7840
+ async function checkPendingSpec(workingDirectory, changeId) {
7841
+ const specPath = path5.join(workingDirectory, "openspec", "changes", `${changeId}-spec.md`);
7842
+ try {
7843
+ await fs10.promises.access(specPath);
7844
+ return specPath;
7845
+ } catch {
7846
+ return null;
7847
+ }
7231
7848
  }
7232
7849
  async function handleNext(workflow, args, ctx) {
7233
7850
  const state = workflow.getState();
@@ -7265,9 +7882,25 @@ ${generateConfirmationPrompt(e.point)}`) + chalk9.cyan(`
7265
7882
  }
7266
7883
 
7267
7884
  // src/commands/runner.ts
7268
- var packageJsonPath2 = path5.resolve(__dirname$1, "../../package.json");
7269
- var packageJson2 = JSON.parse(fsSync.readFileSync(packageJsonPath2, "utf-8"));
7270
- var VERSION2 = packageJson2.version;
7885
+ function getVersion() {
7886
+ const possiblePaths = [
7887
+ path5.resolve(__dirname$1, "..", "..", "package.json"),
7888
+ path5.resolve(__dirname$1, "..", "..", "..", "package.json")
7889
+ ];
7890
+ for (const pkgPath of possiblePaths) {
7891
+ try {
7892
+ if (fs10.existsSync(pkgPath)) {
7893
+ const content = fs10.readFileSync(pkgPath, "utf-8");
7894
+ const pkg = JSON.parse(content);
7895
+ return pkg.version;
7896
+ }
7897
+ } catch {
7898
+ continue;
7899
+ }
7900
+ }
7901
+ return "1.0.0";
7902
+ }
7903
+ var VERSION2 = getVersion();
7271
7904
  async function runSlashCommand(command, args, ctx) {
7272
7905
  const normalizedCommand = normalizeCommand(command);
7273
7906
  switch (normalizedCommand) {