@nick848/sf-cli 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,6 +1,8 @@
1
+ import * as path5 from 'path';
2
+ import path5__default from 'path';
3
+ import { fileURLToPath } from 'url';
1
4
  import * as fs4 from 'fs/promises';
2
- import * as fsSync from 'fs';
3
- import * as path4 from 'path';
5
+ import * as fs10 from 'fs';
4
6
  import * as crypto from 'crypto';
5
7
  import * as os from 'os';
6
8
  import { encoding_for_model } from 'tiktoken';
@@ -10,9 +12,11 @@ import chalk9 from 'chalk';
10
12
  import { v4 } from 'uuid';
11
13
  import { prompt } from 'enquirer';
12
14
  import { spawn } from 'child_process';
13
- import { createRequire } from 'module';
14
15
 
15
- // src/services/config.ts
16
+ // node_modules/tsup/assets/esm_shims.js
17
+ var getFilename = () => fileURLToPath(import.meta.url);
18
+ var getDirname = () => path5__default.dirname(getFilename());
19
+ var __dirname$1 = /* @__PURE__ */ getDirname();
16
20
  var DEFAULT_CONFIG = {
17
21
  model: "GLM-5",
18
22
  apiKey: "",
@@ -25,21 +29,21 @@ var IV_LENGTH = 16;
25
29
  var KEY_DIR = ".sf-cli";
26
30
  var KEY_FILE = ".key";
27
31
  function getOrCreateEncryptionKey() {
28
- const keyPath = path4.join(os.homedir(), KEY_DIR, KEY_FILE);
32
+ const keyPath = path5.join(os.homedir(), KEY_DIR, KEY_FILE);
29
33
  try {
30
- if (fsSync.existsSync(keyPath)) {
31
- const keyBase64 = fsSync.readFileSync(keyPath, "utf-8").trim();
34
+ if (fs10.existsSync(keyPath)) {
35
+ const keyBase64 = fs10.readFileSync(keyPath, "utf-8").trim();
32
36
  return Buffer.from(keyBase64, "base64");
33
37
  }
34
38
  } catch {
35
39
  }
36
40
  const key = crypto.randomBytes(32);
37
41
  try {
38
- const keyDir = path4.dirname(keyPath);
39
- if (!fsSync.existsSync(keyDir)) {
40
- fsSync.mkdirSync(keyDir, { recursive: true, mode: 448 });
42
+ const keyDir = path5.dirname(keyPath);
43
+ if (!fs10.existsSync(keyDir)) {
44
+ fs10.mkdirSync(keyDir, { recursive: true, mode: 448 });
41
45
  }
42
- fsSync.writeFileSync(keyPath, key.toString("base64"), {
46
+ fs10.writeFileSync(keyPath, key.toString("base64"), {
43
47
  mode: 384,
44
48
  // 仅所有者可读写
45
49
  encoding: "utf-8"
@@ -66,7 +70,7 @@ var ConfigManager = class {
66
70
  }
67
71
  async load(projectPath) {
68
72
  this.projectPath = projectPath;
69
- this.configPath = path4.join(projectPath, ".sf-cli", "config.json");
73
+ this.configPath = path5.join(projectPath, ".sf-cli", "config.json");
70
74
  try {
71
75
  const content = await fs4.readFile(this.configPath, "utf-8");
72
76
  const loaded = JSON.parse(content);
@@ -87,7 +91,7 @@ var ConfigManager = class {
87
91
  configToSave.apiKey = encrypted;
88
92
  configToSave.apiKeyEncrypted = true;
89
93
  }
90
- await fs4.mkdir(path4.dirname(this.configPath), { recursive: true });
94
+ await fs4.mkdir(path5.dirname(this.configPath), { recursive: true });
91
95
  await fs4.writeFile(
92
96
  this.configPath,
93
97
  JSON.stringify(configToSave, null, 2),
@@ -346,7 +350,7 @@ var BaseAdapter = class {
346
350
  * 延迟工具函数
347
351
  */
348
352
  delay(ms) {
349
- return new Promise((resolve2) => setTimeout(resolve2, ms));
353
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
350
354
  }
351
355
  };
352
356
 
@@ -967,7 +971,7 @@ var ModelService = class {
967
971
  * 初始化服务
968
972
  */
969
973
  async initialize(statsDir) {
970
- this.statsPath = path4.join(statsDir, "tokens");
974
+ this.statsPath = path5.join(statsDir, "tokens");
971
975
  await this.loadStats();
972
976
  const model = this.configManager.get("model");
973
977
  const apiKey = this.configManager.get("apiKey");
@@ -1140,8 +1144,8 @@ var ModelService = class {
1140
1144
  async loadStats() {
1141
1145
  if (!this.statsPath) return;
1142
1146
  try {
1143
- const dailyPath = path4.join(this.statsPath, "daily.json");
1144
- const totalPath = path4.join(this.statsPath, "total.json");
1147
+ const dailyPath = path5.join(this.statsPath, "daily.json");
1148
+ const totalPath = path5.join(this.statsPath, "total.json");
1145
1149
  const [daily, total] = await Promise.all([
1146
1150
  fs4.readFile(dailyPath, "utf-8").catch(() => "{}"),
1147
1151
  fs4.readFile(totalPath, "utf-8").catch(() => '{"totalInput":0,"totalOutput":0}')
@@ -1158,8 +1162,8 @@ var ModelService = class {
1158
1162
  if (!this.statsPath) return;
1159
1163
  try {
1160
1164
  await fs4.mkdir(this.statsPath, { recursive: true });
1161
- const dailyPath = path4.join(this.statsPath, "daily.json");
1162
- const totalPath = path4.join(this.statsPath, "total.json");
1165
+ const dailyPath = path5.join(this.statsPath, "daily.json");
1166
+ const totalPath = path5.join(this.statsPath, "total.json");
1163
1167
  await Promise.all([
1164
1168
  fs4.writeFile(dailyPath, JSON.stringify(this.stats.byDate, null, 2)),
1165
1169
  fs4.writeFile(totalPath, JSON.stringify({
@@ -2516,10 +2520,10 @@ var FRONTEND_DEV_AGENT = {
2516
2520
  var CODE_REVIEWER_AGENT = {
2517
2521
  id: "code-reviewer",
2518
2522
  name: "\u4EE3\u7801\u5BA1\u6838",
2519
- 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",
2520
2524
  icon: "\u{1F50D}",
2521
- version: "1.0.0",
2522
- 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",
2523
2527
  capabilities: [
2524
2528
  {
2525
2529
  id: "quality-review",
@@ -2540,13 +2544,24 @@ var CODE_REVIEWER_AGENT = {
2540
2544
  id: "performance-review",
2541
2545
  name: "\u6027\u80FD\u5BA1\u67E5",
2542
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"
2543
2557
  }
2544
2558
  ],
2545
2559
  tools: [
2546
2560
  { name: "read_file", description: "\u8BFB\u53D6\u6587\u4EF6\u5185\u5BB9", permission: "full" },
2547
2561
  { name: "glob", description: "\u641C\u7D22\u6587\u4EF6", permission: "full" },
2548
2562
  { name: "search_file_content", description: "\u641C\u7D22\u6587\u4EF6\u5185\u5BB9", permission: "full" },
2549
- { 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" }
2550
2565
  ],
2551
2566
  triggers: [
2552
2567
  { type: "workflow", condition: { workflowStep: "apply" }, priority: 10 },
@@ -2557,6 +2572,7 @@ var CODE_REVIEWER_AGENT = {
2557
2572
  protectedPaths: ["node_modules", ".git"]
2558
2573
  },
2559
2574
  behavior: {
2575
+ requireConfirmation: ["run_shell_command"],
2560
2576
  autoCommit: false
2561
2577
  }
2562
2578
  },
@@ -2565,6 +2581,7 @@ var CODE_REVIEWER_AGENT = {
2565
2581
  ## \u4F60\u7684\u804C\u8D23
2566
2582
  - \u5BA1\u67E5\u4EE3\u7801\u8D28\u91CF\u548C\u53EF\u7EF4\u62A4\u6027
2567
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
2568
2585
  - \u63D0\u4F9B\u5177\u4F53\u7684\u6539\u8FDB\u5EFA\u8BAE
2569
2586
  - \u786E\u4FDD\u9075\u5FAA\u6700\u4F73\u5B9E\u8DF5
2570
2587
 
@@ -2573,8 +2590,20 @@ var CODE_REVIEWER_AGENT = {
2573
2590
  2. **\u5B89\u5168\u6027**: XSS\u3001\u6CE8\u5165\u3001\u654F\u611F\u6570\u636E\u5904\u7406
2574
2591
  3. **\u6027\u80FD**: \u7B97\u6CD5\u6548\u7387\u3001\u5185\u5B58\u4F7F\u7528\u3001\u6E32\u67D3\u4F18\u5316
2575
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
2576
2600
 
2577
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
+
2578
2607
  ### \u5BA1\u67E5\u7ED3\u679C
2579
2608
  - \u901A\u8FC7/\u9700\u4FEE\u6539/\u4E0D\u901A\u8FC7
2580
2609
 
@@ -2600,7 +2629,7 @@ var CODE_REVIEWER_AGENT = {
2600
2629
  ## \u4E0A\u4E0B\u6587
2601
2630
  {{context}}
2602
2631
 
2603
- \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`,
2604
2633
  outputFormat: {
2605
2634
  type: "markdown"
2606
2635
  }
@@ -2833,15 +2862,15 @@ async function loadProjectContext(workingDirectory) {
2833
2862
  devStandards: ""
2834
2863
  };
2835
2864
  try {
2836
- context.agentsMd = await fs4.readFile(path4.join(workingDirectory, "AGENTS.md"), "utf-8");
2865
+ context.agentsMd = await fs4.readFile(path5.join(workingDirectory, "AGENTS.md"), "utf-8");
2837
2866
  } catch {
2838
2867
  }
2839
2868
  try {
2840
- context.configYaml = await fs4.readFile(path4.join(workingDirectory, "openspec", "config.yaml"), "utf-8");
2869
+ context.configYaml = await fs4.readFile(path5.join(workingDirectory, "openspec", "config.yaml"), "utf-8");
2841
2870
  } catch {
2842
2871
  }
2843
2872
  try {
2844
- context.devStandards = await fs4.readFile(path4.join(workingDirectory, ".sf-cli", "norms", "devstanded.md"), "utf-8");
2873
+ context.devStandards = await fs4.readFile(path5.join(workingDirectory, ".sf-cli", "norms", "devstanded.md"), "utf-8");
2845
2874
  } catch {
2846
2875
  }
2847
2876
  return context;
@@ -3777,7 +3806,7 @@ var WorkflowEngine = class {
3777
3806
  */
3778
3807
  async initialize(projectPath) {
3779
3808
  this.projectPath = projectPath;
3780
- this.openspecPath = path4.join(projectPath, "openspec");
3809
+ this.openspecPath = path5.join(projectPath, "openspec");
3781
3810
  await this.ensureDirectories();
3782
3811
  await this.loadProjectContext();
3783
3812
  await this.restoreState();
@@ -3787,19 +3816,19 @@ var WorkflowEngine = class {
3787
3816
  * 加载项目上下文(AGENTS.md 和 config.yaml)
3788
3817
  */
3789
3818
  async loadProjectContext() {
3790
- const agentsMdPath = path4.join(this.projectPath, "AGENTS.md");
3819
+ const agentsMdPath = path5.join(this.projectPath, "AGENTS.md");
3791
3820
  try {
3792
3821
  this.projectContext = await fs4.readFile(agentsMdPath, "utf-8");
3793
3822
  } catch {
3794
3823
  this.projectContext = "";
3795
3824
  }
3796
- const configPath = path4.join(this.openspecPath, "config.yaml");
3825
+ const configPath = path5.join(this.openspecPath, "config.yaml");
3797
3826
  try {
3798
3827
  this.projectConfig = await fs4.readFile(configPath, "utf-8");
3799
3828
  } catch {
3800
3829
  this.projectConfig = "";
3801
3830
  }
3802
- const devstandedPath = path4.join(this.projectPath, ".sf-cli", "norms", "devstanded.md");
3831
+ const devstandedPath = path5.join(this.projectPath, ".sf-cli", "norms", "devstanded.md");
3803
3832
  try {
3804
3833
  this.devStandards = await fs4.readFile(devstandedPath, "utf-8");
3805
3834
  } catch {
@@ -4087,10 +4116,10 @@ var WorkflowEngine = class {
4087
4116
  await this.createSpecDocument(summary);
4088
4117
  await this.updateChangeRecord("archived");
4089
4118
  await this.saveState();
4090
- const changesDir = path4.join(this.openspecPath, "changes");
4091
- const archiveDir = path4.join(changesDir, "archive");
4092
- const changeFile = path4.join(changesDir, `${changeId}.md`);
4093
- const archiveFile = path4.join(archiveDir, `${changeId}.md`);
4119
+ const changesDir = path5.join(this.openspecPath, "changes");
4120
+ const archiveDir = path5.join(changesDir, "archive");
4121
+ const changeFile = path5.join(changesDir, `${changeId}.md`);
4122
+ const archiveFile = path5.join(archiveDir, `${changeId}.md`);
4094
4123
  await fs4.mkdir(archiveDir, { recursive: true });
4095
4124
  await fs4.rename(changeFile, archiveFile).catch(() => {
4096
4125
  });
@@ -4114,14 +4143,14 @@ var WorkflowEngine = class {
4114
4143
  }
4115
4144
  // ==================== 私有方法 ====================
4116
4145
  async ensureDirectories() {
4117
- const changesDir = path4.join(this.openspecPath, "changes");
4118
- const archiveDir = path4.join(changesDir, "archive");
4119
- const specDir = path4.join(this.openspecPath, "spec");
4146
+ const changesDir = path5.join(this.openspecPath, "changes");
4147
+ const archiveDir = path5.join(changesDir, "archive");
4148
+ const specDir = path5.join(this.openspecPath, "spec");
4120
4149
  await fs4.mkdir(archiveDir, { recursive: true });
4121
4150
  await fs4.mkdir(specDir, { recursive: true });
4122
4151
  }
4123
4152
  async restoreState() {
4124
- const statePath = path4.join(this.openspecPath, ".workflow-state.json");
4153
+ const statePath = path5.join(this.openspecPath, ".workflow-state.json");
4125
4154
  try {
4126
4155
  const content = await fs4.readFile(statePath, "utf-8");
4127
4156
  this.state = JSON.parse(content, (key, value) => {
@@ -4141,11 +4170,11 @@ var WorkflowEngine = class {
4141
4170
  }
4142
4171
  }
4143
4172
  async saveState() {
4144
- const statePath = path4.join(this.openspecPath, ".workflow-state.json");
4173
+ const statePath = path5.join(this.openspecPath, ".workflow-state.json");
4145
4174
  await fs4.writeFile(statePath, JSON.stringify(this.state, null, 2));
4146
4175
  }
4147
4176
  async restoreSnapshots() {
4148
- const snapshotsPath = path4.join(this.openspecPath, ".workflow-snapshots.json");
4177
+ const snapshotsPath = path5.join(this.openspecPath, ".workflow-snapshots.json");
4149
4178
  try {
4150
4179
  const content = await fs4.readFile(snapshotsPath, "utf-8");
4151
4180
  const data = JSON.parse(content, (key, value) => {
@@ -4162,7 +4191,7 @@ var WorkflowEngine = class {
4162
4191
  }
4163
4192
  }
4164
4193
  async saveSnapshots() {
4165
- const snapshotsPath = path4.join(this.openspecPath, ".workflow-snapshots.json");
4194
+ const snapshotsPath = path5.join(this.openspecPath, ".workflow-snapshots.json");
4166
4195
  const data = Array.from(this.snapshots.values());
4167
4196
  await fs4.writeFile(snapshotsPath, JSON.stringify(data, null, 2));
4168
4197
  }
@@ -4184,7 +4213,7 @@ var WorkflowEngine = class {
4184
4213
  }
4185
4214
  async createChangeRecord() {
4186
4215
  if (!this.state) return;
4187
- const changePath = path4.join(this.openspecPath, "changes", `${this.state.id}.md`);
4216
+ const changePath = path5.join(this.openspecPath, "changes", `${this.state.id}.md`);
4188
4217
  await fs4.writeFile(changePath, this.formatChangeRecord());
4189
4218
  }
4190
4219
  async updateChangeRecord(status) {
@@ -4192,7 +4221,7 @@ var WorkflowEngine = class {
4192
4221
  if (status) {
4193
4222
  this.state.status = status;
4194
4223
  }
4195
- const changePath = path4.join(this.openspecPath, "changes", `${this.state.id}.md`);
4224
+ const changePath = path5.join(this.openspecPath, "changes", `${this.state.id}.md`);
4196
4225
  await fs4.writeFile(changePath, this.formatChangeRecord());
4197
4226
  }
4198
4227
  formatChangeRecord() {
@@ -4232,7 +4261,7 @@ ${this.state.artifacts.map((a) => `- ${a}`).join("\n") || "\u6682\u65E0"}
4232
4261
  }
4233
4262
  async createSpecDocument(summary) {
4234
4263
  if (!this.state) return;
4235
- const specPath = path4.join(this.openspecPath, "spec", `${this.state.id}.md`);
4264
+ const specPath = path5.join(this.openspecPath, "spec", `${this.state.id}.md`);
4236
4265
  const content = `# Spec: ${this.state.title}
4237
4266
 
4238
4267
  > \u53D8\u66F4ID: ${this.state.id}
@@ -4330,7 +4359,7 @@ var NormsManager = class {
4330
4359
  * 从单个文件学习规范
4331
4360
  */
4332
4361
  async learnFromFile(filePath, content) {
4333
- const ext = path4.extname(filePath);
4362
+ const ext = path5.extname(filePath);
4334
4363
  const patterns = this.extractPatterns(filePath, content, ext);
4335
4364
  for (const pattern of patterns) {
4336
4365
  await this.recordOccurrence({
@@ -4937,12 +4966,12 @@ ${response}`;
4937
4966
  for (const entry of entries) {
4938
4967
  if (entry.isDirectory()) {
4939
4968
  if (!IGNORED_DIRS.includes(entry.name)) {
4940
- await scan(path4.join(dir, entry.name));
4969
+ await scan(path5.join(dir, entry.name));
4941
4970
  }
4942
4971
  } else if (entry.isFile()) {
4943
- const ext = path4.extname(entry.name);
4972
+ const ext = path5.extname(entry.name);
4944
4973
  if (SCAN_EXTENSIONS.includes(ext)) {
4945
- files.push(path4.join(dir, entry.name));
4974
+ files.push(path5.join(dir, entry.name));
4946
4975
  }
4947
4976
  }
4948
4977
  }
@@ -4977,13 +5006,13 @@ ${response}`;
4977
5006
  */
4978
5007
  async ensureNormsDir() {
4979
5008
  await fs4.mkdir(this.config.normsDir, { recursive: true });
4980
- await fs4.mkdir(path4.join(this.config.normsDir, "weekly"), { recursive: true });
5009
+ await fs4.mkdir(path5.join(this.config.normsDir, "weekly"), { recursive: true });
4981
5010
  }
4982
5011
  /**
4983
5012
  * 加载已有规范
4984
5013
  */
4985
5014
  async loadStandards() {
4986
- const standardsPath = path4.join(this.config.normsDir, "patterns.json");
5015
+ const standardsPath = path5.join(this.config.normsDir, "patterns.json");
4987
5016
  try {
4988
5017
  const content = await fs4.readFile(standardsPath, "utf-8");
4989
5018
  const data = JSON.parse(content);
@@ -5001,7 +5030,7 @@ ${response}`;
5001
5030
  * 加载权重数据
5002
5031
  */
5003
5032
  async loadWeights() {
5004
- const weightsPath = path4.join(this.config.normsDir, "weights.json");
5033
+ const weightsPath = path5.join(this.config.normsDir, "weights.json");
5005
5034
  try {
5006
5035
  const content = await fs4.readFile(weightsPath, "utf-8");
5007
5036
  const data = JSON.parse(content);
@@ -5018,7 +5047,7 @@ ${response}`;
5018
5047
  * 保存规范
5019
5048
  */
5020
5049
  async saveStandards() {
5021
- const standardsPath = path4.join(this.config.normsDir, "patterns.json");
5050
+ const standardsPath = path5.join(this.config.normsDir, "patterns.json");
5022
5051
  await fs4.writeFile(
5023
5052
  standardsPath,
5024
5053
  JSON.stringify(Array.from(this.standards.values()), null, 2),
@@ -5029,7 +5058,7 @@ ${response}`;
5029
5058
  * 保存权重
5030
5059
  */
5031
5060
  async saveWeights() {
5032
- const weightsPath = path4.join(this.config.normsDir, "weights.json");
5061
+ const weightsPath = path5.join(this.config.normsDir, "weights.json");
5033
5062
  await fs4.writeFile(
5034
5063
  weightsPath,
5035
5064
  JSON.stringify(Array.from(this.weights.values()), null, 2),
@@ -5057,7 +5086,7 @@ ${response}`;
5057
5086
  * 保存周报
5058
5087
  */
5059
5088
  async saveWeeklyReport(weekId, report) {
5060
- const reportPath = path4.join(this.config.normsDir, "weekly", `${weekId}.json`);
5089
+ const reportPath = path5.join(this.config.normsDir, "weekly", `${weekId}.json`);
5061
5090
  await fs4.writeFile(reportPath, JSON.stringify(report, null, 2), "utf-8");
5062
5091
  }
5063
5092
  /**
@@ -5066,7 +5095,7 @@ ${response}`;
5066
5095
  async updateDevStandedMd() {
5067
5096
  const standards = this.getEffectiveStandards();
5068
5097
  const content = this.generateDevStandedMd(standards);
5069
- const mdPath = path4.join(this.config.normsDir, "devstanded.md");
5098
+ const mdPath = path5.join(this.config.normsDir, "devstanded.md");
5070
5099
  await fs4.writeFile(mdPath, content, "utf-8");
5071
5100
  }
5072
5101
  /**
@@ -5214,7 +5243,7 @@ var ContextManager = class {
5214
5243
  }
5215
5244
  async initialize(projectPath) {
5216
5245
  this.projectPath = projectPath;
5217
- this.persistPath = path4.join(projectPath, ".sf-cli", "cache", "context", "context.json");
5246
+ this.persistPath = path5.join(projectPath, ".sf-cli", "cache", "context", "context.json");
5218
5247
  await this.loadPersistedContext();
5219
5248
  }
5220
5249
  /**
@@ -5455,7 +5484,7 @@ ${summary}`,
5455
5484
  */
5456
5485
  async persist() {
5457
5486
  if (!this.persistPath) return;
5458
- const dir = path4.dirname(this.persistPath);
5487
+ const dir = path5.dirname(this.persistPath);
5459
5488
  await fs4.mkdir(dir, { recursive: true });
5460
5489
  const data = {
5461
5490
  messages: this.state.messages,
@@ -5590,8 +5619,8 @@ var CommandParser = class {
5590
5619
  };
5591
5620
  }
5592
5621
  parseAtPath(input) {
5593
- const path11 = input.slice(1).trim();
5594
- if (!path11) {
5622
+ const path15 = input.slice(1).trim();
5623
+ if (!path15) {
5595
5624
  return { success: false, error: "\u7F3A\u5C11\u6587\u4EF6\u8DEF\u5F84" };
5596
5625
  }
5597
5626
  return {
@@ -5599,7 +5628,7 @@ var CommandParser = class {
5599
5628
  command: {
5600
5629
  type: "at" /* AT */,
5601
5630
  raw: input,
5602
- path: path11
5631
+ path: path15
5603
5632
  }
5604
5633
  };
5605
5634
  }
@@ -5655,9 +5684,9 @@ async function initProject(options = {}, workingDir) {
5655
5684
  output: chalk9.red(`\u9519\u8BEF: \u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6743\u9650\u8BBF\u95EE ${cwd}`)
5656
5685
  };
5657
5686
  }
5658
- const sfCliDir = path4.join(cwd, ".sf-cli");
5659
- const openspecDir = path4.join(cwd, "openspec");
5660
- const agentsMdPath = path4.join(cwd, "AGENTS.md");
5687
+ const sfCliDir = path5.join(cwd, ".sf-cli");
5688
+ const openspecDir = path5.join(cwd, "openspec");
5689
+ const agentsMdPath = path5.join(cwd, "AGENTS.md");
5661
5690
  try {
5662
5691
  const agentsExists = await fileExists(agentsMdPath);
5663
5692
  if (agentsExists && !options.force) {
@@ -5717,7 +5746,7 @@ async function createSfCliDirectory(basePath) {
5717
5746
  "logs"
5718
5747
  ];
5719
5748
  for (const dir of dirs) {
5720
- await fs4.mkdir(path4.join(basePath, dir), { recursive: true });
5749
+ await fs4.mkdir(path5.join(basePath, dir), { recursive: true });
5721
5750
  }
5722
5751
  const config = {
5723
5752
  version: "1.0.0",
@@ -5726,36 +5755,36 @@ async function createSfCliDirectory(basePath) {
5726
5755
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
5727
5756
  };
5728
5757
  await fs4.writeFile(
5729
- path4.join(basePath, "config.json"),
5758
+ path5.join(basePath, "config.json"),
5730
5759
  JSON.stringify(config, null, 2),
5731
5760
  "utf-8"
5732
5761
  );
5733
5762
  await fs4.writeFile(
5734
- path4.join(basePath, "agents", "registry.json"),
5763
+ path5.join(basePath, "agents", "registry.json"),
5735
5764
  JSON.stringify({ version: "1.0.0", agents: {} }, null, 2),
5736
5765
  "utf-8"
5737
5766
  );
5738
5767
  await fs4.writeFile(
5739
- path4.join(basePath, "skills", "registry.json"),
5768
+ path5.join(basePath, "skills", "registry.json"),
5740
5769
  JSON.stringify({ version: "1.0.0", skills: {} }, null, 2),
5741
5770
  "utf-8"
5742
5771
  );
5743
5772
  await fs4.writeFile(
5744
- path4.join(basePath, "health", "health.md"),
5773
+ path5.join(basePath, "health", "health.md"),
5745
5774
  generateHealthTemplate(),
5746
5775
  "utf-8"
5747
5776
  );
5748
5777
  }
5749
5778
  async function createOpenSpecDirectory(basePath) {
5750
- const changesDir = path4.join(basePath, "changes");
5751
- const archiveDir = path4.join(changesDir, "archive");
5752
- const specDir = path4.join(basePath, "spec");
5779
+ const changesDir = path5.join(basePath, "changes");
5780
+ const archiveDir = path5.join(changesDir, "archive");
5781
+ const specDir = path5.join(basePath, "spec");
5753
5782
  await fs4.mkdir(archiveDir, { recursive: true });
5754
5783
  await fs4.mkdir(specDir, { recursive: true });
5755
5784
  }
5756
5785
  async function analyzeProject(cwd) {
5757
5786
  const result = {
5758
- name: path4.basename(cwd),
5787
+ name: path5.basename(cwd),
5759
5788
  type: "unknown",
5760
5789
  framework: null,
5761
5790
  techStack: [],
@@ -5774,7 +5803,7 @@ async function analyzeProject(cwd) {
5774
5803
  hasEslint: false,
5775
5804
  hasPrettier: false
5776
5805
  };
5777
- const pkgPath = path4.join(cwd, "package.json");
5806
+ const pkgPath = path5.join(cwd, "package.json");
5778
5807
  try {
5779
5808
  const pkgContent = await fs4.readFile(pkgPath, "utf-8");
5780
5809
  const pkg = JSON.parse(pkgContent);
@@ -5895,7 +5924,7 @@ async function findEntryPoints(cwd) {
5895
5924
  "index.js"
5896
5925
  ];
5897
5926
  for (const entry of possibleEntries) {
5898
- const fullPath = path4.join(cwd, entry);
5927
+ const fullPath = path5.join(cwd, entry);
5899
5928
  if (await fileExists(fullPath)) {
5900
5929
  entryPoints.push(entry);
5901
5930
  }
@@ -5903,7 +5932,7 @@ async function findEntryPoints(cwd) {
5903
5932
  return entryPoints;
5904
5933
  }
5905
5934
  async function saveProjectAnalysis(sfCliDir, analysis) {
5906
- const analysisPath = path4.join(sfCliDir, "cache", "analysis", "project-analysis.json");
5935
+ const analysisPath = path5.join(sfCliDir, "cache", "analysis", "project-analysis.json");
5907
5936
  await fs4.writeFile(
5908
5937
  analysisPath,
5909
5938
  JSON.stringify(analysis, null, 2),
@@ -5911,7 +5940,7 @@ async function saveProjectAnalysis(sfCliDir, analysis) {
5911
5940
  );
5912
5941
  }
5913
5942
  async function generateOpenSpecConfig(openspecDir, analysis) {
5914
- const configPath = path4.join(openspecDir, "config.yaml");
5943
+ const configPath = path5.join(openspecDir, "config.yaml");
5915
5944
  const content = `# OpenSpec \u9879\u76EE\u914D\u7F6E
5916
5945
  # \u6B64\u6587\u4EF6\u5B9A\u4E49\u9879\u76EE\u7684\u57FA\u672C\u4FE1\u606F\uFF0C\u7528\u4E8E\u6307\u5BFC AI \u7406\u89E3\u9879\u76EE\u4E0A\u4E0B\u6587
5917
5946
 
@@ -6443,8 +6472,26 @@ function createSpinner(message) {
6443
6472
  stop: () => process.stdout.write(" ".repeat(message.length + 10) + "\r")
6444
6473
  };
6445
6474
  }
6446
- var CURRENT_VERSION = "1.0.0";
6447
- var PACKAGE_NAME = "sf-cli";
6475
+ function getPackageInfo() {
6476
+ const possiblePaths = [
6477
+ path5.resolve(__dirname$1, "..", "..", "package.json"),
6478
+ path5.resolve(__dirname$1, "..", "..", "..", "package.json")
6479
+ ];
6480
+ for (const pkgPath of possiblePaths) {
6481
+ try {
6482
+ if (fs10.existsSync(pkgPath)) {
6483
+ const content = fs10.readFileSync(pkgPath, "utf-8");
6484
+ return JSON.parse(content);
6485
+ }
6486
+ } catch {
6487
+ continue;
6488
+ }
6489
+ }
6490
+ return { version: "1.0.0", name: "@nick848/sf-cli" };
6491
+ }
6492
+ var packageInfo = getPackageInfo();
6493
+ var CURRENT_VERSION = packageInfo.version;
6494
+ var PACKAGE_NAME = packageInfo.name;
6448
6495
  async function handleUpdate(args, ctx) {
6449
6496
  const options = {
6450
6497
  check: args.includes("--check") || args.includes("-c"),
@@ -6471,6 +6518,9 @@ async function checkForUpdates() {
6471
6518
  const latestVersion = await getLatestVersion();
6472
6519
  if (!latestVersion) {
6473
6520
  lines.push(chalk9.yellow("\u65E0\u6CD5\u83B7\u53D6\u6700\u65B0\u7248\u672C\u4FE1\u606F"));
6521
+ lines.push(chalk9.gray("\u53EF\u80FD\u539F\u56E0:"));
6522
+ lines.push(chalk9.gray(" 1. \u5305\u5C1A\u672A\u53D1\u5E03\u5230 npm"));
6523
+ lines.push(chalk9.gray(" 2. \u7F51\u7EDC\u8FDE\u63A5\u95EE\u9898"));
6474
6524
  return { output: lines.join("\n") };
6475
6525
  }
6476
6526
  lines.push(chalk9.gray(` \u5F53\u524D\u7248\u672C: v${CURRENT_VERSION}`));
@@ -6489,43 +6539,70 @@ async function checkForUpdates() {
6489
6539
  async function performUpdate(targetVersion) {
6490
6540
  const lines = [];
6491
6541
  lines.push(chalk9.cyan("\u6B63\u5728\u66F4\u65B0 sf-cli..."));
6542
+ lines.push(chalk9.gray(` \u5305\u540D: ${PACKAGE_NAME}`));
6543
+ lines.push(chalk9.gray(` \u5F53\u524D\u7248\u672C: v${CURRENT_VERSION}`));
6492
6544
  try {
6493
6545
  const latestVersion = await getLatestVersion();
6546
+ if (!latestVersion && !targetVersion) {
6547
+ lines.push(chalk9.yellow("\n\u26A0 \u65E0\u6CD5\u83B7\u53D6\u6700\u65B0\u7248\u672C\u4FE1\u606F"));
6548
+ lines.push(chalk9.gray("\n\u53EF\u80FD\u539F\u56E0:"));
6549
+ lines.push(chalk9.gray(" 1. \u5305\u5C1A\u672A\u53D1\u5E03\u5230 npm"));
6550
+ lines.push(chalk9.gray(" 2. \u7F51\u7EDC\u8FDE\u63A5\u95EE\u9898"));
6551
+ lines.push(chalk9.gray("\n\u60A8\u53EF\u4EE5\u5C1D\u8BD5\u624B\u52A8\u66F4\u65B0:"));
6552
+ lines.push(chalk9.cyan(` npm install -g ${PACKAGE_NAME}@latest`));
6553
+ return { output: lines.join("\n") };
6554
+ }
6494
6555
  if (latestVersion === CURRENT_VERSION && !targetVersion) {
6495
- lines.push(chalk9.green("\u2713 \u5DF2\u662F\u6700\u65B0\u7248\u672C\uFF0C\u65E0\u9700\u66F4\u65B0"));
6556
+ lines.push(chalk9.green("\n\u2713 \u5DF2\u662F\u6700\u65B0\u7248\u672C\uFF0C\u65E0\u9700\u66F4\u65B0"));
6496
6557
  return { output: lines.join("\n") };
6497
6558
  }
6498
6559
  const packageSpec = targetVersion ? `${PACKAGE_NAME}@${targetVersion}` : `${PACKAGE_NAME}@latest`;
6499
6560
  lines.push(chalk9.gray(` \u5B89\u88C5: ${packageSpec}`));
6500
- await new Promise((resolve2, reject) => {
6561
+ const installResult = await new Promise((resolve4) => {
6501
6562
  const proc = spawn("npm", ["install", "-g", packageSpec], {
6502
6563
  stdio: "pipe",
6503
6564
  shell: true
6504
6565
  });
6566
+ let output = "";
6567
+ proc.stdout?.on("data", (data) => {
6568
+ output += data.toString();
6569
+ });
6570
+ proc.stderr?.on("data", (data) => {
6571
+ output += data.toString();
6572
+ });
6505
6573
  proc.on("close", (code) => {
6506
- if (code === 0) {
6507
- resolve2();
6508
- } else {
6509
- reject(new Error(`npm install exited with code ${code}`));
6510
- }
6574
+ resolve4({
6575
+ success: code === 0,
6576
+ output
6577
+ });
6511
6578
  });
6512
6579
  proc.on("error", (err) => {
6513
- reject(err);
6580
+ resolve4({
6581
+ success: false,
6582
+ output: err.message
6583
+ });
6514
6584
  });
6515
6585
  });
6516
- lines.push(chalk9.green("\u2713 \u66F4\u65B0\u5B8C\u6210!"));
6586
+ if (!installResult.success) {
6587
+ lines.push(chalk9.red("\n\u2717 \u66F4\u65B0\u5931\u8D25"));
6588
+ lines.push(chalk9.gray(installResult.output));
6589
+ lines.push(chalk9.gray("\n\u60A8\u53EF\u4EE5\u5C1D\u8BD5\u624B\u52A8\u66F4\u65B0:"));
6590
+ lines.push(chalk9.cyan(` npm install -g ${PACKAGE_NAME}@latest`));
6591
+ return { output: lines.join("\n") };
6592
+ }
6593
+ lines.push(chalk9.green("\n\u2713 \u66F4\u65B0\u5B8C\u6210!"));
6517
6594
  lines.push(chalk9.gray(` \u65B0\u7248\u672C: v${targetVersion || latestVersion}`));
6518
6595
  lines.push(chalk9.gray("\n\u8BF7\u91CD\u542F CLI \u4EE5\u4F7F\u7528\u65B0\u7248\u672C"));
6519
6596
  } catch (error) {
6520
6597
  lines.push(chalk9.red("\u66F4\u65B0\u5931\u8D25: " + error.message));
6521
6598
  lines.push(chalk9.gray("\n\u60A8\u53EF\u4EE5\u5C1D\u8BD5\u624B\u52A8\u66F4\u65B0:"));
6522
- lines.push(chalk9.gray(" npm install -g sf-cli@latest"));
6599
+ lines.push(chalk9.cyan(` npm install -g ${PACKAGE_NAME}@latest`));
6523
6600
  }
6524
6601
  return { output: lines.join("\n") };
6525
6602
  }
6526
6603
  async function getLatestVersion() {
6527
6604
  try {
6528
- const result = await new Promise((resolve2, reject) => {
6605
+ const result = await new Promise((resolve4, reject) => {
6529
6606
  const proc = spawn("npm", ["view", PACKAGE_NAME, "version"], {
6530
6607
  stdio: "pipe",
6531
6608
  shell: true
@@ -6535,13 +6612,17 @@ async function getLatestVersion() {
6535
6612
  output += data.toString();
6536
6613
  });
6537
6614
  proc.on("close", (code) => {
6538
- if (code === 0) {
6539
- resolve2(output.trim());
6615
+ if (code === 0 && output.trim()) {
6616
+ resolve4(output.trim());
6540
6617
  } else {
6541
6618
  reject(new Error("Failed to get version"));
6542
6619
  }
6543
6620
  });
6544
6621
  proc.on("error", reject);
6622
+ setTimeout(() => {
6623
+ proc.kill();
6624
+ reject(new Error("Timeout"));
6625
+ }, 1e4);
6545
6626
  });
6546
6627
  return result || null;
6547
6628
  } catch {
@@ -6723,13 +6804,13 @@ function parseArgs(args) {
6723
6804
  }
6724
6805
  async function readProjectContext(cwd) {
6725
6806
  const defaultContext = {
6726
- name: path4.basename(cwd),
6807
+ name: path5.basename(cwd),
6727
6808
  type: "unknown",
6728
6809
  framework: null,
6729
6810
  techStack: [],
6730
6811
  description: ""
6731
6812
  };
6732
- const agentsPath = path4.join(cwd, "AGENTS.md");
6813
+ const agentsPath = path5.join(cwd, "AGENTS.md");
6733
6814
  try {
6734
6815
  const stats = await fs4.stat(agentsPath);
6735
6816
  if (stats.size > MAX_FILE_SIZE2) {
@@ -6744,7 +6825,7 @@ async function readProjectContext(cwd) {
6744
6825
  console.warn(`\u8B66\u544A: \u65E0\u6CD5\u8BFB\u53D6 AGENTS.md - ${err.message}`);
6745
6826
  }
6746
6827
  }
6747
- const configPath = path4.join(cwd, "openspec", "config.yaml");
6828
+ const configPath = path5.join(cwd, "openspec", "config.yaml");
6748
6829
  try {
6749
6830
  const stats = await fs4.stat(configPath);
6750
6831
  if (stats.size > MAX_FILE_SIZE2) {
@@ -6864,6 +6945,78 @@ function extractTitle(requirement) {
6864
6945
  return requirement.slice(0, 47) + "...";
6865
6946
  }
6866
6947
  var autoScheduleEnabled = true;
6948
+ var DEFAULT_REGRESSION_CONFIG = {
6949
+ enabled: true,
6950
+ command: "npm test -- --run",
6951
+ timeout: 12e4,
6952
+ // 2分钟
6953
+ coverageThreshold: 80
6954
+ };
6955
+ async function runRegressionTest(workingDirectory, config = DEFAULT_REGRESSION_CONFIG) {
6956
+ const startTime = Date.now();
6957
+ const result = {
6958
+ success: false,
6959
+ passed: 0,
6960
+ failed: 0,
6961
+ total: 0,
6962
+ duration: 0,
6963
+ output: "",
6964
+ errors: []
6965
+ };
6966
+ return new Promise((resolve4) => {
6967
+ const proc = spawn(config.command, [], {
6968
+ cwd: workingDirectory,
6969
+ shell: true,
6970
+ stdio: "pipe"
6971
+ });
6972
+ let stdout = "";
6973
+ let stderr = "";
6974
+ proc.stdout?.on("data", (data) => {
6975
+ stdout += data.toString();
6976
+ });
6977
+ proc.stderr?.on("data", (data) => {
6978
+ stderr += data.toString();
6979
+ });
6980
+ const timeout = setTimeout(() => {
6981
+ proc.kill();
6982
+ result.errors.push("\u6D4B\u8BD5\u8D85\u65F6");
6983
+ result.output = stdout + stderr;
6984
+ result.duration = Date.now() - startTime;
6985
+ resolve4(result);
6986
+ }, config.timeout);
6987
+ proc.on("close", (code) => {
6988
+ clearTimeout(timeout);
6989
+ result.output = stdout + stderr;
6990
+ result.duration = Date.now() - startTime;
6991
+ const passMatch = stdout.match(/(\d+)\s+(?:passed|tests?\s+passed)/i);
6992
+ const failMatch = stdout.match(/(\d+)\s+(?:failed|tests?\s+failed)/i);
6993
+ const totalMatch = stdout.match(/Tests?:\s*(\d+)/i);
6994
+ if (passMatch) {
6995
+ result.passed = parseInt(passMatch[1], 10);
6996
+ }
6997
+ if (failMatch) {
6998
+ result.failed = parseInt(failMatch[1], 10);
6999
+ }
7000
+ if (totalMatch) {
7001
+ result.total = parseInt(totalMatch[1], 10);
7002
+ } else {
7003
+ result.total = result.passed + result.failed;
7004
+ }
7005
+ const coverageMatch = stdout.match(/All files[^\d]*(\d+(?:\.\d+)?)/);
7006
+ if (coverageMatch) {
7007
+ result.coverage = parseFloat(coverageMatch[1]);
7008
+ }
7009
+ result.success = code === 0 && result.failed === 0;
7010
+ resolve4(result);
7011
+ });
7012
+ proc.on("error", (err) => {
7013
+ clearTimeout(timeout);
7014
+ result.errors.push(err.message);
7015
+ result.duration = Date.now() - startTime;
7016
+ resolve4(result);
7017
+ });
7018
+ });
7019
+ }
6867
7020
  async function handleOpsx(command, args, ctx) {
6868
7021
  const step = command.replace("opsx:", "");
6869
7022
  const workflow = new WorkflowEngine();
@@ -6878,7 +7031,7 @@ async function handleOpsx(command, args, ctx) {
6878
7031
  case "apply":
6879
7032
  return handleApply(workflow);
6880
7033
  case "archive":
6881
- return handleArchive(workflow, args);
7034
+ return handleArchive(workflow, args, ctx);
6882
7035
  case "propose":
6883
7036
  return handlePropose(workflow, args);
6884
7037
  case "status":
@@ -6893,12 +7046,50 @@ async function handleOpsx(command, args, ctx) {
6893
7046
  return handleNext(workflow);
6894
7047
  case "auto":
6895
7048
  return handleAutoSchedule(args);
7049
+ case "test":
7050
+ return handleRegressionTest(ctx);
6896
7051
  default:
6897
7052
  return {
6898
7053
  output: chalk9.red(`\u672A\u77E5\u7684OpenSpec\u547D\u4EE4: /${command}`)
6899
7054
  };
6900
7055
  }
6901
7056
  }
7057
+ async function handleRegressionTest(ctx) {
7058
+ const lines = [];
7059
+ lines.push(chalk9.cyan("\u{1F50D} \u6267\u884C\u56DE\u5F52\u6D4B\u8BD5..."));
7060
+ lines.push(chalk9.gray(` \u5DE5\u4F5C\u76EE\u5F55: ${ctx.options.workingDirectory}`));
7061
+ lines.push("");
7062
+ const result = await runRegressionTest(ctx.options.workingDirectory);
7063
+ lines.push(chalk9.gray("\u2500".repeat(50)));
7064
+ if (result.success) {
7065
+ lines.push(chalk9.green("\u2713 \u56DE\u5F52\u6D4B\u8BD5\u901A\u8FC7"));
7066
+ } else {
7067
+ lines.push(chalk9.red("\u2717 \u56DE\u5F52\u6D4B\u8BD5\u5931\u8D25"));
7068
+ }
7069
+ lines.push("");
7070
+ lines.push(chalk9.cyan("\u6D4B\u8BD5\u7ED3\u679C:"));
7071
+ lines.push(chalk9.gray(` \u901A\u8FC7: ${result.passed}`));
7072
+ lines.push(chalk9.gray(` \u5931\u8D25: ${result.failed}`));
7073
+ lines.push(chalk9.gray(` \u603B\u8BA1: ${result.total}`));
7074
+ lines.push(chalk9.gray(` \u8017\u65F6: ${(result.duration / 1e3).toFixed(2)}s`));
7075
+ if (result.coverage !== void 0) {
7076
+ const coverageColor = result.coverage >= 80 ? chalk9.green : result.coverage >= 60 ? chalk9.yellow : chalk9.red;
7077
+ lines.push(coverageColor(` \u8986\u76D6\u7387: ${result.coverage}%`));
7078
+ }
7079
+ if (result.errors.length > 0) {
7080
+ lines.push("");
7081
+ lines.push(chalk9.red("\u9519\u8BEF\u4FE1\u606F:"));
7082
+ for (const error of result.errors) {
7083
+ lines.push(chalk9.gray(` - ${error}`));
7084
+ }
7085
+ }
7086
+ if (result.failed > 0) {
7087
+ lines.push("");
7088
+ lines.push(chalk9.yellow("\u26A0 \u5B58\u5728\u5931\u8D25\u7684\u6D4B\u8BD5\u7528\u4F8B\uFF0C\u8BF7\u68C0\u67E5\u5E76\u4FEE\u590D"));
7089
+ lines.push(chalk9.gray("\u4F7F\u7528 /opsx:rollback \u53EF\u56DE\u6EDA\u5230\u4E4B\u524D\u7684\u9636\u6BB5"));
7090
+ }
7091
+ return { output: lines.join("\n") };
7092
+ }
6902
7093
  function handleAutoSchedule(args) {
6903
7094
  const action = args[0]?.toLowerCase();
6904
7095
  if (!action) {
@@ -7028,13 +7219,70 @@ async function handleArchive(workflow, args, ctx) {
7028
7219
  ${generateConfirmationPrompt(confirmation.point)}`) + chalk9.cyan("\n\n\u4F7F\u7528 /opsx:confirm code-review \u786E\u8BA4\u540E\u5F52\u6863")
7029
7220
  };
7030
7221
  }
7222
+ const lines = [];
7223
+ lines.push(chalk9.cyan("\u{1F50D} \u6267\u884C\u5F52\u6863\u524D\u56DE\u5F52\u6D4B\u8BD5..."));
7224
+ lines.push("");
7225
+ const testResult = await runRegressionTest(ctx.options.workingDirectory);
7226
+ if (!testResult.success) {
7227
+ lines.push(chalk9.red("\u2717 \u56DE\u5F52\u6D4B\u8BD5\u5931\u8D25"));
7228
+ lines.push(chalk9.gray(` \u901A\u8FC7: ${testResult.passed} | \u5931\u8D25: ${testResult.failed} | \u603B\u8BA1: ${testResult.total}`));
7229
+ lines.push("");
7230
+ lines.push(chalk9.yellow("\u26A0 \u5F52\u6863\u88AB\u963B\u6B62"));
7231
+ lines.push(chalk9.gray("\n\u8BF7\u4FEE\u590D\u5931\u8D25\u7684\u6D4B\u8BD5\u7528\u4F8B\u540E\u91CD\u8BD5"));
7232
+ lines.push(chalk9.gray("\u4F7F\u7528 /opsx:rollback \u53EF\u56DE\u6EDA\u5230\u4E4B\u524D\u7684\u9636\u6BB5"));
7233
+ lines.push(chalk9.gray("\u4F7F\u7528 /opsx:test \u53EF\u5355\u72EC\u8FD0\u884C\u56DE\u5F52\u6D4B\u8BD5"));
7234
+ return { output: lines.join("\n") };
7235
+ }
7236
+ lines.push(chalk9.green("\u2713 \u56DE\u5F52\u6D4B\u8BD5\u901A\u8FC7"));
7237
+ lines.push(chalk9.gray(` \u901A\u8FC7: ${testResult.passed} | \u8017\u65F6: ${(testResult.duration / 1e3).toFixed(2)}s`));
7238
+ lines.push("");
7031
7239
  const changeId = state.id;
7032
7240
  const summary = args.join(" ") || "\u5B8C\u6210\u53D8\u66F4";
7033
7241
  await workflow.archive(summary);
7034
- return {
7035
- output: chalk9.green("\u2713 \u5DE5\u4F5C\u6D41\u5DF2\u5F52\u6863") + chalk9.gray(`
7036
- \u53D8\u66F4ID: ${changeId}`) + chalk9.cyan("\n\n\u5F52\u6863\u6587\u6863\u5DF2\u751F\u6210\u5230 openspec/spec/ \u76EE\u5F55")
7037
- };
7242
+ await updateChangelog(ctx.options.workingDirectory, summary, changeId);
7243
+ lines.push(chalk9.green("\u2713 \u5DE5\u4F5C\u6D41\u5DF2\u5F52\u6863") + chalk9.gray(`
7244
+ \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"));
7245
+ return { output: lines.join("\n") };
7246
+ }
7247
+ async function updateChangelog(workingDirectory, summary, changeId) {
7248
+ try {
7249
+ const changelogPath = path5.join(workingDirectory, "CHANGELOG.md");
7250
+ const pkgPath = path5.join(workingDirectory, "package.json");
7251
+ let version = "1.0.0";
7252
+ try {
7253
+ const pkgContent = fs10.readFileSync(pkgPath, "utf-8");
7254
+ const pkg = JSON.parse(pkgContent);
7255
+ version = pkg.version || "1.0.0";
7256
+ } catch {
7257
+ }
7258
+ const today = /* @__PURE__ */ new Date();
7259
+ const dateStr = today.toISOString().split("T")[0];
7260
+ const entry = `
7261
+ ## v${version} (${dateStr})
7262
+
7263
+ **\u53D8\u66F4\u5185\u5BB9**
7264
+
7265
+ - ${summary} (${changeId})
7266
+ `;
7267
+ if (fs10.existsSync(changelogPath)) {
7268
+ const content = fs10.readFileSync(changelogPath, "utf-8");
7269
+ const versionPattern = /^## v\d+\.\d+\.\d+/m;
7270
+ const match = content.match(versionPattern);
7271
+ if (match && match.index !== void 0) {
7272
+ const newContent = content.slice(0, match.index) + entry + content.slice(match.index);
7273
+ fs10.writeFileSync(changelogPath, newContent, "utf-8");
7274
+ } else {
7275
+ fs10.appendFileSync(changelogPath, entry, "utf-8");
7276
+ }
7277
+ } else {
7278
+ const header = `# Changelog
7279
+
7280
+ All notable changes to this project will be documented in this file.
7281
+ `;
7282
+ fs10.writeFileSync(changelogPath, header + entry, "utf-8");
7283
+ }
7284
+ } catch (error) {
7285
+ }
7038
7286
  }
7039
7287
  async function handlePropose(workflow, args, ctx) {
7040
7288
  const state = workflow.getState();
@@ -7223,9 +7471,27 @@ ${generateConfirmationPrompt(e.point)}`) + chalk9.cyan(`
7223
7471
  throw e;
7224
7472
  }
7225
7473
  }
7226
- var require2 = createRequire(import.meta.url);
7227
- var packageJson = require2("../../package.json");
7228
- var VERSION2 = packageJson.version;
7474
+
7475
+ // src/commands/runner.ts
7476
+ function getVersion() {
7477
+ const possiblePaths = [
7478
+ path5.resolve(__dirname$1, "..", "..", "package.json"),
7479
+ path5.resolve(__dirname$1, "..", "..", "..", "package.json")
7480
+ ];
7481
+ for (const pkgPath of possiblePaths) {
7482
+ try {
7483
+ if (fs10.existsSync(pkgPath)) {
7484
+ const content = fs10.readFileSync(pkgPath, "utf-8");
7485
+ const pkg = JSON.parse(content);
7486
+ return pkg.version;
7487
+ }
7488
+ } catch {
7489
+ continue;
7490
+ }
7491
+ }
7492
+ return "1.0.0";
7493
+ }
7494
+ var VERSION2 = getVersion();
7229
7495
  async function runSlashCommand(command, args, ctx) {
7230
7496
  const normalizedCommand = normalizeCommand(command);
7231
7497
  switch (normalizedCommand) {
@@ -7272,7 +7538,7 @@ function normalizeCommand(command) {
7272
7538
  }
7273
7539
  async function handleFileReference(filePath, ctx) {
7274
7540
  const cwd = ctx.options.workingDirectory;
7275
- const absolutePath = path4.isAbsolute(filePath) ? filePath : path4.join(cwd, filePath);
7541
+ const absolutePath = path5.isAbsolute(filePath) ? filePath : path5.join(cwd, filePath);
7276
7542
  try {
7277
7543
  const stats = await fs4.stat(absolutePath);
7278
7544
  if (stats.isDirectory()) {
@@ -7320,7 +7586,7 @@ async function executeShell(command, ctx) {
7320
7586
  \u547D\u4EE4 "${command}" \u53EF\u80FD\u4F1A\u5220\u9664\u91CD\u8981\u6587\u4EF6`) + chalk9.gray("\n\u4F7F\u7528 yolo \u6A21\u5F0F\u5F3A\u5236\u6267\u884C")
7321
7587
  };
7322
7588
  }
7323
- return new Promise((resolve2) => {
7589
+ return new Promise((resolve4) => {
7324
7590
  const shell = spawn(command, [], {
7325
7591
  shell: true,
7326
7592
  cwd: ctx.options.workingDirectory
@@ -7345,11 +7611,11 @@ async function executeShell(command, ctx) {
7345
7611
  output += chalk9.red(`
7346
7612
  \u9000\u51FA\u7801: ${code}`);
7347
7613
  }
7348
- resolve2({ output: output || chalk9.gray("(\u65E0\u8F93\u51FA)") });
7614
+ resolve4({ output: output || chalk9.gray("(\u65E0\u8F93\u51FA)") });
7349
7615
  });
7350
7616
  setTimeout(() => {
7351
7617
  shell.kill();
7352
- resolve2({
7618
+ resolve4({
7353
7619
  output: chalk9.yellow("\u547D\u4EE4\u6267\u884C\u8D85\u65F6\uFF0C\u5DF2\u7EC8\u6B62")
7354
7620
  });
7355
7621
  }, 6e4);
@@ -7656,7 +7922,7 @@ var Completer = class {
7656
7922
  prefix = filePath;
7657
7923
  } else {
7658
7924
  const dir = filePath.slice(0, lastSlash);
7659
- dirPath = path4.resolve(this.workingDirectory, dir);
7925
+ dirPath = path5.resolve(this.workingDirectory, dir);
7660
7926
  prefix = filePath.slice(lastSlash + 1);
7661
7927
  }
7662
7928
  try {
@@ -7666,7 +7932,7 @@ var Completer = class {
7666
7932
  if (entry.name.startsWith(prefix)) {
7667
7933
  const isDir = entry.isDirectory();
7668
7934
  matches.push({
7669
- text: "@" + path4.relative(this.workingDirectory, path4.join(dirPath, entry.name)).replace(/\\/g, "/") + (isDir ? "/" : ""),
7935
+ text: "@" + path5.relative(this.workingDirectory, path5.join(dirPath, entry.name)).replace(/\\/g, "/") + (isDir ? "/" : ""),
7670
7936
  displayText: entry.name + (isDir ? "/" : ""),
7671
7937
  description: isDir ? "\u76EE\u5F55" : "\u6587\u4EF6",
7672
7938
  type: isDir ? "directory" : "file"