@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/CHANGELOG.md +44 -0
- package/README.md +4 -23
- package/dist/cli/index.js +459 -182
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +313 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +375 -109
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
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
|
|
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
|
-
//
|
|
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 =
|
|
32
|
+
const keyPath = path5.join(os.homedir(), KEY_DIR, KEY_FILE);
|
|
29
33
|
try {
|
|
30
|
-
if (
|
|
31
|
-
const keyBase64 =
|
|
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 =
|
|
39
|
-
if (!
|
|
40
|
-
|
|
42
|
+
const keyDir = path5.dirname(keyPath);
|
|
43
|
+
if (!fs10.existsSync(keyDir)) {
|
|
44
|
+
fs10.mkdirSync(keyDir, { recursive: true, mode: 448 });
|
|
41
45
|
}
|
|
42
|
-
|
|
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 =
|
|
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(
|
|
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((
|
|
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 =
|
|
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 =
|
|
1144
|
-
const totalPath =
|
|
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 =
|
|
1162
|
-
const totalPath =
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4091
|
-
const archiveDir =
|
|
4092
|
-
const changeFile =
|
|
4093
|
-
const archiveFile =
|
|
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 =
|
|
4118
|
-
const archiveDir =
|
|
4119
|
-
const specDir =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
4969
|
+
await scan(path5.join(dir, entry.name));
|
|
4941
4970
|
}
|
|
4942
4971
|
} else if (entry.isFile()) {
|
|
4943
|
-
const ext =
|
|
4972
|
+
const ext = path5.extname(entry.name);
|
|
4944
4973
|
if (SCAN_EXTENSIONS.includes(ext)) {
|
|
4945
|
-
files.push(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
5594
|
-
if (!
|
|
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:
|
|
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 =
|
|
5659
|
-
const openspecDir =
|
|
5660
|
-
const agentsMdPath =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
5751
|
-
const archiveDir =
|
|
5752
|
-
const specDir =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
6447
|
-
|
|
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((
|
|
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
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
}
|
|
6574
|
+
resolve4({
|
|
6575
|
+
success: code === 0,
|
|
6576
|
+
output
|
|
6577
|
+
});
|
|
6511
6578
|
});
|
|
6512
6579
|
proc.on("error", (err) => {
|
|
6513
|
-
|
|
6580
|
+
resolve4({
|
|
6581
|
+
success: false,
|
|
6582
|
+
output: err.message
|
|
6583
|
+
});
|
|
6514
6584
|
});
|
|
6515
6585
|
});
|
|
6516
|
-
|
|
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.
|
|
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((
|
|
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
|
-
|
|
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:
|
|
6807
|
+
name: path5.basename(cwd),
|
|
6727
6808
|
type: "unknown",
|
|
6728
6809
|
framework: null,
|
|
6729
6810
|
techStack: [],
|
|
6730
6811
|
description: ""
|
|
6731
6812
|
};
|
|
6732
|
-
const agentsPath =
|
|
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 =
|
|
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
|
-
|
|
7035
|
-
|
|
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
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
7614
|
+
resolve4({ output: output || chalk9.gray("(\u65E0\u8F93\u51FA)") });
|
|
7349
7615
|
});
|
|
7350
7616
|
setTimeout(() => {
|
|
7351
7617
|
shell.kill();
|
|
7352
|
-
|
|
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 =
|
|
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: "@" +
|
|
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"
|