@nick848/sf-cli 1.0.15 → 1.0.17
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 +24 -0
- package/dist/cli/index.js +843 -656
- 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 +250 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +250 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var fs7 = require('fs/promises');
|
|
5
|
-
var fs9 = require('fs');
|
|
6
|
-
var path7 = require('path');
|
|
7
|
-
var crypto = require('crypto');
|
|
8
|
-
var os = require('os');
|
|
9
4
|
var tiktoken = require('tiktoken');
|
|
10
|
-
require('@modelcontextprotocol/sdk/client/index.js');
|
|
11
|
-
require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
12
5
|
var chalk9 = require('chalk');
|
|
13
6
|
var enquirer = require('enquirer');
|
|
14
7
|
var child_process = require('child_process');
|
|
8
|
+
var fs7 = require('fs');
|
|
9
|
+
var path5 = require('path');
|
|
10
|
+
var fs5 = require('fs/promises');
|
|
15
11
|
var commander = require('commander');
|
|
16
12
|
var readline = require('readline');
|
|
17
13
|
require('uuid');
|
|
14
|
+
var crypto = require('crypto');
|
|
15
|
+
var os = require('os');
|
|
16
|
+
require('@modelcontextprotocol/sdk/client/index.js');
|
|
17
|
+
require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
18
18
|
|
|
19
19
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
20
20
|
|
|
@@ -36,13 +36,13 @@ function _interopNamespace(e) {
|
|
|
36
36
|
return Object.freeze(n);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
var chalk9__default = /*#__PURE__*/_interopDefault(chalk9);
|
|
39
40
|
var fs7__namespace = /*#__PURE__*/_interopNamespace(fs7);
|
|
40
|
-
var
|
|
41
|
-
var
|
|
41
|
+
var path5__namespace = /*#__PURE__*/_interopNamespace(path5);
|
|
42
|
+
var fs5__namespace = /*#__PURE__*/_interopNamespace(fs5);
|
|
43
|
+
var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
|
|
42
44
|
var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
|
|
43
45
|
var os__namespace = /*#__PURE__*/_interopNamespace(os);
|
|
44
|
-
var chalk9__default = /*#__PURE__*/_interopDefault(chalk9);
|
|
45
|
-
var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
|
|
46
46
|
|
|
47
47
|
var __defProp = Object.defineProperty;
|
|
48
48
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -59,138 +59,6 @@ var init_cjs_shims = __esm({
|
|
|
59
59
|
"node_modules/tsup/assets/cjs_shims.js"() {
|
|
60
60
|
}
|
|
61
61
|
});
|
|
62
|
-
function getOrCreateEncryptionKey() {
|
|
63
|
-
const keyPath = path7__namespace.join(os__namespace.homedir(), KEY_DIR, KEY_FILE);
|
|
64
|
-
try {
|
|
65
|
-
if (fs9__namespace.existsSync(keyPath)) {
|
|
66
|
-
const keyBase64 = fs9__namespace.readFileSync(keyPath, "utf-8").trim();
|
|
67
|
-
return Buffer.from(keyBase64, "base64");
|
|
68
|
-
}
|
|
69
|
-
} catch {
|
|
70
|
-
}
|
|
71
|
-
const key = crypto__namespace.randomBytes(32);
|
|
72
|
-
try {
|
|
73
|
-
const keyDir = path7__namespace.dirname(keyPath);
|
|
74
|
-
if (!fs9__namespace.existsSync(keyDir)) {
|
|
75
|
-
fs9__namespace.mkdirSync(keyDir, { recursive: true, mode: 448 });
|
|
76
|
-
}
|
|
77
|
-
fs9__namespace.writeFileSync(keyPath, key.toString("base64"), {
|
|
78
|
-
mode: 384,
|
|
79
|
-
// 仅所有者可读写
|
|
80
|
-
encoding: "utf-8"
|
|
81
|
-
});
|
|
82
|
-
return key;
|
|
83
|
-
} catch {
|
|
84
|
-
const machineId = [
|
|
85
|
-
os__namespace.hostname(),
|
|
86
|
-
os__namespace.platform(),
|
|
87
|
-
os__namespace.arch(),
|
|
88
|
-
process.env.USERNAME || process.env.USER || "default"
|
|
89
|
-
].join("-");
|
|
90
|
-
return crypto__namespace.createHash("sha256").update(machineId).digest();
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
var DEFAULT_CONFIG, ALGORITHM, IV_LENGTH, KEY_DIR, KEY_FILE, ConfigManager;
|
|
94
|
-
var init_config = __esm({
|
|
95
|
-
"src/services/config.ts"() {
|
|
96
|
-
init_cjs_shims();
|
|
97
|
-
DEFAULT_CONFIG = {
|
|
98
|
-
model: "GLM-5",
|
|
99
|
-
apiKey: "",
|
|
100
|
-
yolo: false,
|
|
101
|
-
locale: "zh-CN",
|
|
102
|
-
theme: "auto"
|
|
103
|
-
};
|
|
104
|
-
ALGORITHM = "aes-256-gcm";
|
|
105
|
-
IV_LENGTH = 16;
|
|
106
|
-
KEY_DIR = ".sf-cli";
|
|
107
|
-
KEY_FILE = ".key";
|
|
108
|
-
ConfigManager = class {
|
|
109
|
-
config;
|
|
110
|
-
configPath = "";
|
|
111
|
-
projectPath = "";
|
|
112
|
-
encryptionKey;
|
|
113
|
-
constructor() {
|
|
114
|
-
this.config = { ...DEFAULT_CONFIG };
|
|
115
|
-
this.encryptionKey = getOrCreateEncryptionKey();
|
|
116
|
-
}
|
|
117
|
-
async load(projectPath) {
|
|
118
|
-
this.projectPath = projectPath;
|
|
119
|
-
this.configPath = path7__namespace.join(projectPath, ".sf-cli", "config.json");
|
|
120
|
-
try {
|
|
121
|
-
const content = await fs7__namespace.readFile(this.configPath, "utf-8");
|
|
122
|
-
const loaded = JSON.parse(content);
|
|
123
|
-
if (loaded.apiKey && loaded.apiKeyEncrypted) {
|
|
124
|
-
loaded.apiKey = this.decrypt(loaded.apiKey);
|
|
125
|
-
delete loaded.apiKeyEncrypted;
|
|
126
|
-
}
|
|
127
|
-
this.config = { ...DEFAULT_CONFIG, ...loaded };
|
|
128
|
-
} catch {
|
|
129
|
-
this.config = { ...DEFAULT_CONFIG };
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
async save() {
|
|
133
|
-
if (!this.configPath) return;
|
|
134
|
-
const configToSave = { ...this.config };
|
|
135
|
-
if (configToSave.apiKey) {
|
|
136
|
-
const encrypted = this.encrypt(configToSave.apiKey);
|
|
137
|
-
configToSave.apiKey = encrypted;
|
|
138
|
-
configToSave.apiKeyEncrypted = true;
|
|
139
|
-
}
|
|
140
|
-
await fs7__namespace.mkdir(path7__namespace.dirname(this.configPath), { recursive: true });
|
|
141
|
-
await fs7__namespace.writeFile(
|
|
142
|
-
this.configPath,
|
|
143
|
-
JSON.stringify(configToSave, null, 2),
|
|
144
|
-
"utf-8"
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
get(key) {
|
|
148
|
-
return this.config[key];
|
|
149
|
-
}
|
|
150
|
-
set(key, value) {
|
|
151
|
-
this.config[key] = value;
|
|
152
|
-
}
|
|
153
|
-
getAll() {
|
|
154
|
-
return { ...this.config };
|
|
155
|
-
}
|
|
156
|
-
update(updates) {
|
|
157
|
-
this.config = { ...this.config, ...updates };
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* 加密敏感数据
|
|
161
|
-
*/
|
|
162
|
-
encrypt(text) {
|
|
163
|
-
const iv = crypto__namespace.randomBytes(IV_LENGTH);
|
|
164
|
-
const cipher = crypto__namespace.createCipheriv(ALGORITHM, this.encryptionKey, iv);
|
|
165
|
-
let encrypted = cipher.update(text, "utf8", "base64");
|
|
166
|
-
encrypted += cipher.final("base64");
|
|
167
|
-
const authTag = cipher.getAuthTag();
|
|
168
|
-
return `${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted}`;
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* 解密敏感数据
|
|
172
|
-
*/
|
|
173
|
-
decrypt(encryptedData) {
|
|
174
|
-
try {
|
|
175
|
-
const parts = encryptedData.split(":");
|
|
176
|
-
if (parts.length !== 3) {
|
|
177
|
-
return encryptedData;
|
|
178
|
-
}
|
|
179
|
-
const [ivBase64, authTagBase64, encrypted] = parts;
|
|
180
|
-
const iv = Buffer.from(ivBase64, "base64");
|
|
181
|
-
const authTag = Buffer.from(authTagBase64, "base64");
|
|
182
|
-
const decipher = crypto__namespace.createDecipheriv(ALGORITHM, this.encryptionKey, iv);
|
|
183
|
-
decipher.setAuthTag(authTag);
|
|
184
|
-
let decrypted = decipher.update(encrypted, "base64", "utf8");
|
|
185
|
-
decrypted += decipher.final("utf8");
|
|
186
|
-
return decrypted;
|
|
187
|
-
} catch {
|
|
188
|
-
return "";
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
62
|
|
|
195
63
|
// src/types/model.ts
|
|
196
64
|
function getModelInfo(modelId) {
|
|
@@ -1078,312 +946,6 @@ var init_adapters = __esm({
|
|
|
1078
946
|
DEEPSEEK_API_ENDPOINT = "https://api.deepseek.com/v1";
|
|
1079
947
|
}
|
|
1080
948
|
});
|
|
1081
|
-
var ModelService;
|
|
1082
|
-
var init_model2 = __esm({
|
|
1083
|
-
"src/services/model.ts"() {
|
|
1084
|
-
init_cjs_shims();
|
|
1085
|
-
init_model();
|
|
1086
|
-
init_adapters();
|
|
1087
|
-
ModelService = class {
|
|
1088
|
-
adapters = /* @__PURE__ */ new Map();
|
|
1089
|
-
currentConfig = null;
|
|
1090
|
-
stats;
|
|
1091
|
-
configManager;
|
|
1092
|
-
statsPath = "";
|
|
1093
|
-
constructor(configManager) {
|
|
1094
|
-
this.configManager = configManager;
|
|
1095
|
-
this.stats = {
|
|
1096
|
-
totalInput: 0,
|
|
1097
|
-
totalOutput: 0,
|
|
1098
|
-
byModel: {},
|
|
1099
|
-
byAgent: {},
|
|
1100
|
-
byDate: {}
|
|
1101
|
-
};
|
|
1102
|
-
}
|
|
1103
|
-
/**
|
|
1104
|
-
* 初始化服务
|
|
1105
|
-
*/
|
|
1106
|
-
async initialize(statsDir) {
|
|
1107
|
-
this.statsPath = path7__namespace.join(statsDir, "tokens");
|
|
1108
|
-
await this.loadStats();
|
|
1109
|
-
const model = this.configManager.get("model");
|
|
1110
|
-
const apiKey = this.configManager.get("apiKey");
|
|
1111
|
-
if (model && apiKey) {
|
|
1112
|
-
await this.configureModel({
|
|
1113
|
-
provider: this.getProviderFromModel(model),
|
|
1114
|
-
model,
|
|
1115
|
-
apiKey
|
|
1116
|
-
});
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
/**
|
|
1120
|
-
* 配置模型
|
|
1121
|
-
*/
|
|
1122
|
-
async configureModel(config) {
|
|
1123
|
-
let adapter = this.adapters.get(config.provider);
|
|
1124
|
-
if (!adapter) {
|
|
1125
|
-
adapter = createAdapter(config.provider);
|
|
1126
|
-
this.adapters.set(config.provider, adapter);
|
|
1127
|
-
}
|
|
1128
|
-
await adapter.initialize(config);
|
|
1129
|
-
this.currentConfig = config;
|
|
1130
|
-
this.configManager.set("model", config.model);
|
|
1131
|
-
this.configManager.set("apiKey", config.apiKey);
|
|
1132
|
-
await this.configManager.save();
|
|
1133
|
-
}
|
|
1134
|
-
/**
|
|
1135
|
-
* 发送消息
|
|
1136
|
-
*/
|
|
1137
|
-
async sendMessage(messages, options) {
|
|
1138
|
-
if (!this.currentConfig) {
|
|
1139
|
-
throw new Error("\u6A21\u578B\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u6267\u884C /model \u547D\u4EE4");
|
|
1140
|
-
}
|
|
1141
|
-
const adapter = this.adapters.get(this.currentConfig.provider);
|
|
1142
|
-
if (!adapter || !adapter.isInitialized()) {
|
|
1143
|
-
throw new Error("\u6A21\u578B\u9002\u914D\u5668\u672A\u521D\u59CB\u5316");
|
|
1144
|
-
}
|
|
1145
|
-
try {
|
|
1146
|
-
const response = await adapter.sendMessage(messages, options);
|
|
1147
|
-
this.updateStats(response.usage, {
|
|
1148
|
-
model: this.currentConfig.model,
|
|
1149
|
-
agent: options?.agent
|
|
1150
|
-
});
|
|
1151
|
-
this.saveStats().catch(() => {
|
|
1152
|
-
});
|
|
1153
|
-
return response;
|
|
1154
|
-
} catch (error) {
|
|
1155
|
-
throw this.wrapError(error);
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
/**
|
|
1159
|
-
* 流式发送消息
|
|
1160
|
-
*/
|
|
1161
|
-
async *streamMessage(messages, options) {
|
|
1162
|
-
if (!this.currentConfig) {
|
|
1163
|
-
throw new Error("\u6A21\u578B\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u6267\u884C /model \u547D\u4EE4");
|
|
1164
|
-
}
|
|
1165
|
-
const adapter = this.adapters.get(this.currentConfig.provider);
|
|
1166
|
-
if (!adapter || !adapter.isInitialized()) {
|
|
1167
|
-
throw new Error("\u6A21\u578B\u9002\u914D\u5668\u672A\u521D\u59CB\u5316");
|
|
1168
|
-
}
|
|
1169
|
-
let totalTokens = 0;
|
|
1170
|
-
try {
|
|
1171
|
-
for await (const chunk of adapter.streamMessage(messages, options)) {
|
|
1172
|
-
totalTokens += chunk.delta.length;
|
|
1173
|
-
yield chunk;
|
|
1174
|
-
}
|
|
1175
|
-
const inputTokens = adapter.countTokens(
|
|
1176
|
-
messages.map((m) => m.content).join("\n")
|
|
1177
|
-
);
|
|
1178
|
-
this.updateStats(
|
|
1179
|
-
{ inputTokens, outputTokens: Math.ceil(totalTokens / 4), totalTokens: 0 },
|
|
1180
|
-
{ model: this.currentConfig.model, agent: options?.agent }
|
|
1181
|
-
);
|
|
1182
|
-
this.saveStats().catch(() => {
|
|
1183
|
-
});
|
|
1184
|
-
} catch (error) {
|
|
1185
|
-
throw this.wrapError(error);
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
/**
|
|
1189
|
-
* 计算Token数量
|
|
1190
|
-
*/
|
|
1191
|
-
countTokens(text) {
|
|
1192
|
-
if (!this.currentConfig) {
|
|
1193
|
-
return Math.ceil(text.length / 4);
|
|
1194
|
-
}
|
|
1195
|
-
const adapter = this.adapters.get(this.currentConfig.provider);
|
|
1196
|
-
return adapter?.countTokens(text) || Math.ceil(text.length / 4);
|
|
1197
|
-
}
|
|
1198
|
-
/**
|
|
1199
|
-
* 获取当前模型
|
|
1200
|
-
*/
|
|
1201
|
-
getCurrentModel() {
|
|
1202
|
-
return this.currentConfig?.model || null;
|
|
1203
|
-
}
|
|
1204
|
-
/**
|
|
1205
|
-
* 获取当前提供商
|
|
1206
|
-
*/
|
|
1207
|
-
getCurrentProvider() {
|
|
1208
|
-
return this.currentConfig?.provider || null;
|
|
1209
|
-
}
|
|
1210
|
-
/**
|
|
1211
|
-
* 获取Token统计
|
|
1212
|
-
*/
|
|
1213
|
-
getStats() {
|
|
1214
|
-
return { ...this.stats };
|
|
1215
|
-
}
|
|
1216
|
-
/**
|
|
1217
|
-
* 获取今日统计
|
|
1218
|
-
*/
|
|
1219
|
-
getTodayStats() {
|
|
1220
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1221
|
-
return this.stats.byDate[today] || { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
1222
|
-
}
|
|
1223
|
-
/**
|
|
1224
|
-
* 重置统计
|
|
1225
|
-
*/
|
|
1226
|
-
resetStats() {
|
|
1227
|
-
this.stats = {
|
|
1228
|
-
totalInput: 0,
|
|
1229
|
-
totalOutput: 0,
|
|
1230
|
-
byModel: {},
|
|
1231
|
-
byAgent: {},
|
|
1232
|
-
byDate: {}
|
|
1233
|
-
};
|
|
1234
|
-
this.saveStats().catch(() => {
|
|
1235
|
-
});
|
|
1236
|
-
}
|
|
1237
|
-
/**
|
|
1238
|
-
* 验证API Key
|
|
1239
|
-
*/
|
|
1240
|
-
async validateApiKey(provider, apiKey) {
|
|
1241
|
-
let adapter = this.adapters.get(provider);
|
|
1242
|
-
if (!adapter) {
|
|
1243
|
-
adapter = createAdapter(provider);
|
|
1244
|
-
}
|
|
1245
|
-
return adapter.validateApiKey(apiKey);
|
|
1246
|
-
}
|
|
1247
|
-
// ==================== 私有方法 ====================
|
|
1248
|
-
getProviderFromModel(modelId) {
|
|
1249
|
-
const info = getModelInfo(modelId);
|
|
1250
|
-
return info?.provider || "openai";
|
|
1251
|
-
}
|
|
1252
|
-
updateStats(usage, context) {
|
|
1253
|
-
this.stats.totalInput += usage.inputTokens;
|
|
1254
|
-
this.stats.totalOutput += usage.outputTokens;
|
|
1255
|
-
if (!this.stats.byModel[context.model]) {
|
|
1256
|
-
this.stats.byModel[context.model] = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
1257
|
-
}
|
|
1258
|
-
this.stats.byModel[context.model].inputTokens += usage.inputTokens;
|
|
1259
|
-
this.stats.byModel[context.model].outputTokens += usage.outputTokens;
|
|
1260
|
-
this.stats.byModel[context.model].totalTokens += usage.totalTokens;
|
|
1261
|
-
if (context.agent) {
|
|
1262
|
-
if (!this.stats.byAgent[context.agent]) {
|
|
1263
|
-
this.stats.byAgent[context.agent] = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
1264
|
-
}
|
|
1265
|
-
this.stats.byAgent[context.agent].inputTokens += usage.inputTokens;
|
|
1266
|
-
this.stats.byAgent[context.agent].outputTokens += usage.outputTokens;
|
|
1267
|
-
this.stats.byAgent[context.agent].totalTokens += usage.totalTokens;
|
|
1268
|
-
}
|
|
1269
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1270
|
-
if (!this.stats.byDate[today]) {
|
|
1271
|
-
this.stats.byDate[today] = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
1272
|
-
}
|
|
1273
|
-
this.stats.byDate[today].inputTokens += usage.inputTokens;
|
|
1274
|
-
this.stats.byDate[today].outputTokens += usage.outputTokens;
|
|
1275
|
-
this.stats.byDate[today].totalTokens += usage.totalTokens;
|
|
1276
|
-
}
|
|
1277
|
-
async loadStats() {
|
|
1278
|
-
if (!this.statsPath) return;
|
|
1279
|
-
try {
|
|
1280
|
-
const dailyPath = path7__namespace.join(this.statsPath, "daily.json");
|
|
1281
|
-
const totalPath = path7__namespace.join(this.statsPath, "total.json");
|
|
1282
|
-
const [daily, total] = await Promise.all([
|
|
1283
|
-
fs7__namespace.readFile(dailyPath, "utf-8").catch(() => "{}"),
|
|
1284
|
-
fs7__namespace.readFile(totalPath, "utf-8").catch(() => '{"totalInput":0,"totalOutput":0}')
|
|
1285
|
-
]);
|
|
1286
|
-
const dailyData = JSON.parse(daily);
|
|
1287
|
-
const totalData = JSON.parse(total);
|
|
1288
|
-
this.stats.byDate = dailyData;
|
|
1289
|
-
this.stats.totalInput = totalData.totalInput || 0;
|
|
1290
|
-
this.stats.totalOutput = totalData.totalOutput || 0;
|
|
1291
|
-
} catch {
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
async saveStats() {
|
|
1295
|
-
if (!this.statsPath) return;
|
|
1296
|
-
try {
|
|
1297
|
-
await fs7__namespace.mkdir(this.statsPath, { recursive: true });
|
|
1298
|
-
const dailyPath = path7__namespace.join(this.statsPath, "daily.json");
|
|
1299
|
-
const totalPath = path7__namespace.join(this.statsPath, "total.json");
|
|
1300
|
-
await Promise.all([
|
|
1301
|
-
fs7__namespace.writeFile(dailyPath, JSON.stringify(this.stats.byDate, null, 2)),
|
|
1302
|
-
fs7__namespace.writeFile(totalPath, JSON.stringify({
|
|
1303
|
-
totalInput: this.stats.totalInput,
|
|
1304
|
-
totalOutput: this.stats.totalOutput
|
|
1305
|
-
}))
|
|
1306
|
-
]);
|
|
1307
|
-
} catch {
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
wrapError(error) {
|
|
1311
|
-
if (error instanceof ModelError) {
|
|
1312
|
-
return error;
|
|
1313
|
-
}
|
|
1314
|
-
const err = error;
|
|
1315
|
-
if (err.message.includes("timeout")) {
|
|
1316
|
-
return new ModelError("TIMEOUT", err.message, { retryable: true });
|
|
1317
|
-
}
|
|
1318
|
-
if (err.message.includes("network") || err.message.includes("ECONNREFUSED")) {
|
|
1319
|
-
return new ModelError("NETWORK_ERROR", err.message, { retryable: true });
|
|
1320
|
-
}
|
|
1321
|
-
return new ModelError("UNKNOWN_ERROR", err.message, { retryable: false });
|
|
1322
|
-
}
|
|
1323
|
-
};
|
|
1324
|
-
}
|
|
1325
|
-
});
|
|
1326
|
-
|
|
1327
|
-
// src/types/mcp.ts
|
|
1328
|
-
var init_mcp = __esm({
|
|
1329
|
-
"src/types/mcp.ts"() {
|
|
1330
|
-
init_cjs_shims();
|
|
1331
|
-
}
|
|
1332
|
-
});
|
|
1333
|
-
var init_base2 = __esm({
|
|
1334
|
-
"src/services/mcp/base.ts"() {
|
|
1335
|
-
init_cjs_shims();
|
|
1336
|
-
}
|
|
1337
|
-
});
|
|
1338
|
-
|
|
1339
|
-
// src/services/mcp/lanhu.ts
|
|
1340
|
-
var init_lanhu = __esm({
|
|
1341
|
-
"src/services/mcp/lanhu.ts"() {
|
|
1342
|
-
init_cjs_shims();
|
|
1343
|
-
init_base2();
|
|
1344
|
-
}
|
|
1345
|
-
});
|
|
1346
|
-
|
|
1347
|
-
// src/services/mcp/figma.ts
|
|
1348
|
-
var init_figma = __esm({
|
|
1349
|
-
"src/services/mcp/figma.ts"() {
|
|
1350
|
-
init_cjs_shims();
|
|
1351
|
-
init_base2();
|
|
1352
|
-
}
|
|
1353
|
-
});
|
|
1354
|
-
|
|
1355
|
-
// src/services/mcp/manager.ts
|
|
1356
|
-
var init_manager = __esm({
|
|
1357
|
-
"src/services/mcp/manager.ts"() {
|
|
1358
|
-
init_cjs_shims();
|
|
1359
|
-
init_base2();
|
|
1360
|
-
init_lanhu();
|
|
1361
|
-
init_figma();
|
|
1362
|
-
}
|
|
1363
|
-
});
|
|
1364
|
-
|
|
1365
|
-
// src/services/mcp/index.ts
|
|
1366
|
-
var init_mcp2 = __esm({
|
|
1367
|
-
"src/services/mcp/index.ts"() {
|
|
1368
|
-
init_cjs_shims();
|
|
1369
|
-
init_mcp();
|
|
1370
|
-
init_base2();
|
|
1371
|
-
init_lanhu();
|
|
1372
|
-
init_figma();
|
|
1373
|
-
init_manager();
|
|
1374
|
-
}
|
|
1375
|
-
});
|
|
1376
|
-
|
|
1377
|
-
// src/services/index.ts
|
|
1378
|
-
var init_services = __esm({
|
|
1379
|
-
"src/services/index.ts"() {
|
|
1380
|
-
init_cjs_shims();
|
|
1381
|
-
init_config();
|
|
1382
|
-
init_model2();
|
|
1383
|
-
init_adapters();
|
|
1384
|
-
init_mcp2();
|
|
1385
|
-
}
|
|
1386
|
-
});
|
|
1387
949
|
|
|
1388
950
|
// src/commands/model.ts
|
|
1389
951
|
var model_exports = {};
|
|
@@ -1399,17 +961,18 @@ __export(model_exports, {
|
|
|
1399
961
|
async function handleModel(args, ctx) {
|
|
1400
962
|
const subCommand = args[0];
|
|
1401
963
|
const configManager = ctx.configManager;
|
|
964
|
+
const modelService = ctx.modelService;
|
|
1402
965
|
switch (subCommand) {
|
|
1403
966
|
case "list":
|
|
1404
967
|
return listModels();
|
|
1405
968
|
case "current":
|
|
1406
969
|
return showCurrentModel(configManager);
|
|
1407
970
|
case "set":
|
|
1408
|
-
return setModelDirectly(args[1], configManager);
|
|
971
|
+
return setModelDirectly(args[1], configManager, modelService);
|
|
1409
972
|
case "verify":
|
|
1410
973
|
return verifyCurrentModel(configManager);
|
|
1411
974
|
default:
|
|
1412
|
-
return selectModel(configManager);
|
|
975
|
+
return selectModel(configManager, modelService);
|
|
1413
976
|
}
|
|
1414
977
|
}
|
|
1415
978
|
async function listModels() {
|
|
@@ -1450,7 +1013,7 @@ async function showCurrentModel(configManager) {
|
|
|
1450
1013
|
];
|
|
1451
1014
|
return { output: lines.join("\n") };
|
|
1452
1015
|
}
|
|
1453
|
-
async function setModelDirectly(modelId, configManager) {
|
|
1016
|
+
async function setModelDirectly(modelId, configManager, modelService) {
|
|
1454
1017
|
if (!modelId) {
|
|
1455
1018
|
return {
|
|
1456
1019
|
output: chalk9__default.default.red("\u8BF7\u6307\u5B9A\u6A21\u578BID\uFF0C\u4F8B\u5982: /model set gpt-4o")
|
|
@@ -1465,11 +1028,22 @@ async function setModelDirectly(modelId, configManager) {
|
|
|
1465
1028
|
}
|
|
1466
1029
|
configManager.set("model", modelId);
|
|
1467
1030
|
await configManager.save();
|
|
1031
|
+
const apiKey = configManager.get("apiKey");
|
|
1032
|
+
if (apiKey) {
|
|
1033
|
+
try {
|
|
1034
|
+
await modelService.configureModel({
|
|
1035
|
+
provider: modelInfo.provider,
|
|
1036
|
+
model: modelId,
|
|
1037
|
+
apiKey
|
|
1038
|
+
});
|
|
1039
|
+
} catch {
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1468
1042
|
return {
|
|
1469
1043
|
output: chalk9__default.default.green(`\u2713 \u5DF2\u5207\u6362\u5230\u6A21\u578B: ${modelInfo.name} (${modelInfo.provider})`)
|
|
1470
1044
|
};
|
|
1471
1045
|
}
|
|
1472
|
-
async function verifyCurrentModel(configManager) {
|
|
1046
|
+
async function verifyCurrentModel(configManager, modelService) {
|
|
1473
1047
|
const currentModel = configManager.get("model");
|
|
1474
1048
|
const apiKey = configManager.get("apiKey");
|
|
1475
1049
|
if (!apiKey) {
|
|
@@ -1502,7 +1076,7 @@ async function verifyCurrentModel(configManager) {
|
|
|
1502
1076
|
};
|
|
1503
1077
|
}
|
|
1504
1078
|
}
|
|
1505
|
-
async function selectModel(configManager) {
|
|
1079
|
+
async function selectModel(configManager, modelService) {
|
|
1506
1080
|
try {
|
|
1507
1081
|
const currentModel = configManager.get("model");
|
|
1508
1082
|
const currentApiKey = configManager.get("apiKey");
|
|
@@ -1566,6 +1140,11 @@ async function selectModel(configManager) {
|
|
|
1566
1140
|
configManager.set("model", modelResponse.model);
|
|
1567
1141
|
configManager.set("apiKey", apiKeyResponse.apiKey);
|
|
1568
1142
|
await configManager.save();
|
|
1143
|
+
await modelService.configureModel({
|
|
1144
|
+
provider: selectedModel.provider,
|
|
1145
|
+
model: modelResponse.model,
|
|
1146
|
+
apiKey: apiKeyResponse.apiKey
|
|
1147
|
+
});
|
|
1569
1148
|
return {
|
|
1570
1149
|
output: chalk9__default.default.green(`
|
|
1571
1150
|
\u2713 \u6A21\u578B\u914D\u7F6E\u5B8C\u6210
|
|
@@ -1614,10 +1193,10 @@ function createSpinner(message) {
|
|
|
1614
1193
|
};
|
|
1615
1194
|
}
|
|
1616
1195
|
var model_default;
|
|
1617
|
-
var
|
|
1196
|
+
var init_model2 = __esm({
|
|
1618
1197
|
"src/commands/model.ts"() {
|
|
1619
1198
|
init_cjs_shims();
|
|
1620
|
-
|
|
1199
|
+
init_adapters();
|
|
1621
1200
|
init_model();
|
|
1622
1201
|
model_default = selectModel;
|
|
1623
1202
|
}
|
|
@@ -1633,13 +1212,13 @@ __export(update_exports, {
|
|
|
1633
1212
|
});
|
|
1634
1213
|
function getPackageInfo() {
|
|
1635
1214
|
const possiblePaths = [
|
|
1636
|
-
|
|
1637
|
-
|
|
1215
|
+
path5__namespace.resolve(__dirname, "..", "..", "package.json"),
|
|
1216
|
+
path5__namespace.resolve(__dirname, "..", "..", "..", "package.json")
|
|
1638
1217
|
];
|
|
1639
1218
|
for (const pkgPath of possiblePaths) {
|
|
1640
1219
|
try {
|
|
1641
|
-
if (
|
|
1642
|
-
const content =
|
|
1220
|
+
if (fs7__namespace.existsSync(pkgPath)) {
|
|
1221
|
+
const content = fs7__namespace.readFileSync(pkgPath, "utf-8");
|
|
1643
1222
|
return JSON.parse(content);
|
|
1644
1223
|
}
|
|
1645
1224
|
} catch {
|
|
@@ -1835,6 +1414,7 @@ async function handleNew(args, ctx) {
|
|
|
1835
1414
|
context: null,
|
|
1836
1415
|
clarityScore: 0,
|
|
1837
1416
|
clarificationQuestions: [],
|
|
1417
|
+
referenceResources: [],
|
|
1838
1418
|
complexity: 0,
|
|
1839
1419
|
bddScenarios: [],
|
|
1840
1420
|
specItems: [],
|
|
@@ -1877,7 +1457,7 @@ async function executeWorkflow(ctx) {
|
|
|
1877
1457
|
const lines = [];
|
|
1878
1458
|
try {
|
|
1879
1459
|
if (activeSession.phase === "context") {
|
|
1880
|
-
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 1/
|
|
1460
|
+
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 1/9: \u9879\u76EE\u4E0A\u4E0B\u6587\u83B7\u53D6 \u2501\u2501\u2501"));
|
|
1881
1461
|
lines.push("");
|
|
1882
1462
|
activeSession.context = await readProjectContext(ctx.options.workingDirectory);
|
|
1883
1463
|
lines.push(chalk9__default.default.gray(` \u9879\u76EE: ${activeSession.context.name}`));
|
|
@@ -1891,7 +1471,7 @@ async function executeWorkflow(ctx) {
|
|
|
1891
1471
|
}
|
|
1892
1472
|
if (activeSession.phase === "clarify") {
|
|
1893
1473
|
lines.push("");
|
|
1894
|
-
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 2/
|
|
1474
|
+
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 2/9: \u9700\u6C42\u6F84\u6E05 \u2501\u2501\u2501"));
|
|
1895
1475
|
lines.push("");
|
|
1896
1476
|
const clarityResult = analyzeRequirementClarity(
|
|
1897
1477
|
activeSession.requirement,
|
|
@@ -1917,11 +1497,40 @@ async function executeWorkflow(ctx) {
|
|
|
1917
1497
|
return { output: lines.join("\n") };
|
|
1918
1498
|
}
|
|
1919
1499
|
lines.push(chalk9__default.default.green(" \u2713 \u9700\u6C42\u6E05\u6670\uFF0C\u7EE7\u7EED\u4E0B\u4E00\u6B65"));
|
|
1500
|
+
activeSession.phase = "reference";
|
|
1501
|
+
}
|
|
1502
|
+
if (activeSession.phase === "reference") {
|
|
1503
|
+
lines.push("");
|
|
1504
|
+
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 3/9: \u53C2\u8003\u8D44\u6E90\u5206\u6790 \u2501\u2501\u2501"));
|
|
1505
|
+
lines.push("");
|
|
1506
|
+
const urls = extractUrls(activeSession.refinedRequirement);
|
|
1507
|
+
if (urls.length > 0) {
|
|
1508
|
+
lines.push(chalk9__default.default.gray(` \u53D1\u73B0 ${urls.length} \u4E2A\u53C2\u8003\u94FE\u63A5`));
|
|
1509
|
+
lines.push("");
|
|
1510
|
+
for (const url of urls) {
|
|
1511
|
+
lines.push(chalk9__default.default.gray(` \u{1F4CE} ${url}`));
|
|
1512
|
+
try {
|
|
1513
|
+
const resource = await fetchAndAnalyzeReference(url, ctx);
|
|
1514
|
+
activeSession.referenceResources.push(resource);
|
|
1515
|
+
lines.push(chalk9__default.default.green(` \u2713 \u5DF2\u5206\u6790`));
|
|
1516
|
+
activeSession.refinedRequirement += `
|
|
1517
|
+
|
|
1518
|
+
\u3010\u53C2\u8003\u8D44\u6E90\u5206\u6790 - ${url}\u3011
|
|
1519
|
+
${resource.analysis}`;
|
|
1520
|
+
} catch (error) {
|
|
1521
|
+
lines.push(chalk9__default.default.yellow(` \u26A0 \u83B7\u53D6\u5931\u8D25: ${error.message}`));
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
lines.push("");
|
|
1525
|
+
lines.push(chalk9__default.default.green(" \u2713 \u53C2\u8003\u8D44\u6E90\u5206\u6790\u5B8C\u6210"));
|
|
1526
|
+
} else {
|
|
1527
|
+
lines.push(chalk9__default.default.gray(" \u65E0\u5916\u90E8\u53C2\u8003\u94FE\u63A5"));
|
|
1528
|
+
}
|
|
1920
1529
|
activeSession.phase = "analysis";
|
|
1921
1530
|
}
|
|
1922
1531
|
if (activeSession.phase === "analysis") {
|
|
1923
1532
|
lines.push("");
|
|
1924
|
-
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5
|
|
1533
|
+
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 4/9: \u590D\u6742\u5EA6\u8BC4\u4F30 \u2501\u2501\u2501"));
|
|
1925
1534
|
lines.push("");
|
|
1926
1535
|
activeSession.complexity = analyzeComplexity(
|
|
1927
1536
|
activeSession.refinedRequirement,
|
|
@@ -1938,12 +1547,13 @@ async function executeWorkflow(ctx) {
|
|
|
1938
1547
|
}
|
|
1939
1548
|
if (activeSession.phase === "bdd") {
|
|
1940
1549
|
lines.push("");
|
|
1941
|
-
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5
|
|
1550
|
+
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 5/9: BDD \u573A\u666F\u62C6\u89E3 \u2501\u2501\u2501"));
|
|
1942
1551
|
lines.push("");
|
|
1943
1552
|
activeSession.bddScenarios = generateBDDScenarios(
|
|
1944
1553
|
activeSession.refinedRequirement,
|
|
1945
1554
|
activeSession.context,
|
|
1946
|
-
activeSession.clarificationQuestions
|
|
1555
|
+
activeSession.clarificationQuestions,
|
|
1556
|
+
activeSession.referenceResources
|
|
1947
1557
|
);
|
|
1948
1558
|
for (const scenario of activeSession.bddScenarios) {
|
|
1949
1559
|
lines.push(chalk9__default.default.white(` Feature: ${scenario.feature}`));
|
|
@@ -1958,13 +1568,14 @@ async function executeWorkflow(ctx) {
|
|
|
1958
1568
|
}
|
|
1959
1569
|
if (activeSession.phase === "spec") {
|
|
1960
1570
|
lines.push("");
|
|
1961
|
-
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5
|
|
1571
|
+
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 6/9: OpenSpec \u89C4\u683C \u2501\u2501\u2501"));
|
|
1962
1572
|
lines.push("");
|
|
1963
1573
|
activeSession.specItems = generateSpecItems(
|
|
1964
1574
|
activeSession.refinedRequirement,
|
|
1965
1575
|
activeSession.context,
|
|
1966
1576
|
activeSession.bddScenarios,
|
|
1967
|
-
activeSession.clarificationQuestions
|
|
1577
|
+
activeSession.clarificationQuestions,
|
|
1578
|
+
activeSession.referenceResources
|
|
1968
1579
|
);
|
|
1969
1580
|
const specPath = await saveSpecFile(ctx.options.workingDirectory, activeSession);
|
|
1970
1581
|
lines.push(chalk9__default.default.green(" \u2713 \u89C4\u683C\u6587\u4EF6\u5DF2\u751F\u6210"));
|
|
@@ -1988,7 +1599,7 @@ async function executeWorkflow(ctx) {
|
|
|
1988
1599
|
}
|
|
1989
1600
|
if (activeSession.phase === "tdd") {
|
|
1990
1601
|
lines.push("");
|
|
1991
|
-
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5
|
|
1602
|
+
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 7/9: TDD \u6D4B\u8BD5\u751F\u6210 \u2501\u2501\u2501"));
|
|
1992
1603
|
lines.push("");
|
|
1993
1604
|
activeSession.testFiles = await generateTests(ctx.options.workingDirectory, activeSession);
|
|
1994
1605
|
lines.push(chalk9__default.default.green(" \u2713 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u751F\u6210"));
|
|
@@ -1999,7 +1610,7 @@ async function executeWorkflow(ctx) {
|
|
|
1999
1610
|
}
|
|
2000
1611
|
if (activeSession.phase === "develop") {
|
|
2001
1612
|
lines.push("");
|
|
2002
|
-
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5
|
|
1613
|
+
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 8/9: \u5F00\u53D1\u5B9E\u73B0 \u2501\u2501\u2501"));
|
|
2003
1614
|
lines.push("");
|
|
2004
1615
|
lines.push(chalk9__default.default.yellow(" \u{1F680} \u6B63\u5728\u8C03\u7528 AI \u751F\u6210\u4EE3\u7801..."));
|
|
2005
1616
|
try {
|
|
@@ -2024,7 +1635,7 @@ async function executeWorkflow(ctx) {
|
|
|
2024
1635
|
}
|
|
2025
1636
|
if (activeSession.phase === "review") {
|
|
2026
1637
|
lines.push("");
|
|
2027
|
-
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5
|
|
1638
|
+
lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 9/9: \u4EE3\u7801\u5BA1\u6838 \u2501\u2501\u2501"));
|
|
2028
1639
|
lines.push("");
|
|
2029
1640
|
lines.push(chalk9__default.default.yellow(" \u{1F50D} \u6B63\u5728\u8FDB\u884C\u4EE3\u7801\u5BA1\u6838..."));
|
|
2030
1641
|
try {
|
|
@@ -2321,18 +1932,18 @@ ${session.context.devStandards.slice(0, 2e3)}` : ""}`
|
|
|
2321
1932
|
});
|
|
2322
1933
|
const codeBlocks = parseCodeBlocks(response.content);
|
|
2323
1934
|
for (const block of codeBlocks) {
|
|
2324
|
-
const filePath =
|
|
2325
|
-
const dir =
|
|
2326
|
-
await
|
|
2327
|
-
await
|
|
1935
|
+
const filePath = path5__namespace.join(workingDir, block.filename);
|
|
1936
|
+
const dir = path5__namespace.dirname(filePath);
|
|
1937
|
+
await fs5__namespace.mkdir(dir, { recursive: true });
|
|
1938
|
+
await fs5__namespace.writeFile(filePath, block.code, "utf-8");
|
|
2328
1939
|
files.push(block.filename);
|
|
2329
1940
|
}
|
|
2330
1941
|
if (files.length === 0) {
|
|
2331
|
-
const implDir =
|
|
2332
|
-
await
|
|
1942
|
+
const implDir = path5__namespace.join(workingDir, "src", "features");
|
|
1943
|
+
await fs5__namespace.mkdir(implDir, { recursive: true });
|
|
2333
1944
|
const featureName = session.specItems[0]?.title || "feature";
|
|
2334
1945
|
const fileName = `${featureName.replace(/[^a-zA-Z0-9]/g, "_")}.ts`;
|
|
2335
|
-
const filePath =
|
|
1946
|
+
const filePath = path5__namespace.join(implDir, fileName);
|
|
2336
1947
|
const stubCode = `/**
|
|
2337
1948
|
* ${session.requirement}
|
|
2338
1949
|
*
|
|
@@ -2344,7 +1955,7 @@ export function ${featureName.replace(/[^a-zA-Z0-9]/g, "")}() {
|
|
|
2344
1955
|
console.log('${featureName} - \u5F85\u5B9E\u73B0');
|
|
2345
1956
|
}
|
|
2346
1957
|
`;
|
|
2347
|
-
await
|
|
1958
|
+
await fs5__namespace.writeFile(filePath, stubCode, "utf-8");
|
|
2348
1959
|
files.push(`src/features/${fileName}`);
|
|
2349
1960
|
}
|
|
2350
1961
|
return { success: true, files };
|
|
@@ -2399,7 +2010,7 @@ async function executeReview(ctx, session) {
|
|
|
2399
2010
|
const codeContents = [];
|
|
2400
2011
|
for (const file of session.implFiles) {
|
|
2401
2012
|
try {
|
|
2402
|
-
const content = await
|
|
2013
|
+
const content = await fs5__namespace.readFile(path5__namespace.join(workingDir, file), "utf-8");
|
|
2403
2014
|
codeContents.push(`// ${file}
|
|
2404
2015
|
${content}`);
|
|
2405
2016
|
} catch {
|
|
@@ -2408,7 +2019,7 @@ ${content}`);
|
|
|
2408
2019
|
const testContents = [];
|
|
2409
2020
|
for (const file of session.testFiles) {
|
|
2410
2021
|
try {
|
|
2411
|
-
const content = await
|
|
2022
|
+
const content = await fs5__namespace.readFile(path5__namespace.join(workingDir, file), "utf-8");
|
|
2412
2023
|
testContents.push(`// ${file}
|
|
2413
2024
|
${content}`);
|
|
2414
2025
|
} catch {
|
|
@@ -2487,7 +2098,7 @@ ${testContents.join("\n\n") || "\uFF08\u65E0\u6D4B\u8BD5\u6587\u4EF6\uFF09"}
|
|
|
2487
2098
|
}
|
|
2488
2099
|
async function readProjectContext(workingDir) {
|
|
2489
2100
|
const context = {
|
|
2490
|
-
name:
|
|
2101
|
+
name: path5__namespace.basename(workingDir),
|
|
2491
2102
|
type: "unknown",
|
|
2492
2103
|
framework: null,
|
|
2493
2104
|
techStack: [],
|
|
@@ -2496,11 +2107,11 @@ async function readProjectContext(workingDir) {
|
|
|
2496
2107
|
agentsMd: "",
|
|
2497
2108
|
configYaml: ""
|
|
2498
2109
|
};
|
|
2499
|
-
const agentsPath =
|
|
2110
|
+
const agentsPath = path5__namespace.join(workingDir, "AGENTS.md");
|
|
2500
2111
|
try {
|
|
2501
|
-
const stats = await
|
|
2112
|
+
const stats = await fs5__namespace.stat(agentsPath);
|
|
2502
2113
|
if (stats.size <= MAX_FILE_SIZE2) {
|
|
2503
|
-
context.agentsMd = await
|
|
2114
|
+
context.agentsMd = await fs5__namespace.readFile(agentsPath, "utf-8");
|
|
2504
2115
|
const nameMatch = context.agentsMd.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
|
|
2505
2116
|
if (nameMatch) context.name = nameMatch[1];
|
|
2506
2117
|
const typeMatch = context.agentsMd.match(/\|\s*项目类型\s*\|\s*([^\s|]+)/);
|
|
@@ -2512,11 +2123,11 @@ async function readProjectContext(workingDir) {
|
|
|
2512
2123
|
}
|
|
2513
2124
|
} catch {
|
|
2514
2125
|
}
|
|
2515
|
-
const configPath =
|
|
2126
|
+
const configPath = path5__namespace.join(workingDir, "openspec", "config.yaml");
|
|
2516
2127
|
try {
|
|
2517
|
-
const stats = await
|
|
2128
|
+
const stats = await fs5__namespace.stat(configPath);
|
|
2518
2129
|
if (stats.size <= MAX_FILE_SIZE2) {
|
|
2519
|
-
context.configYaml = await
|
|
2130
|
+
context.configYaml = await fs5__namespace.readFile(configPath, "utf-8");
|
|
2520
2131
|
const nameMatch = context.configYaml.match(/name:\s*(.+)/);
|
|
2521
2132
|
if (nameMatch) context.name = nameMatch[1].trim();
|
|
2522
2133
|
const typeMatch = context.configYaml.match(/type:\s*(.+)/);
|
|
@@ -2535,11 +2146,11 @@ async function readProjectContext(workingDir) {
|
|
|
2535
2146
|
}
|
|
2536
2147
|
} catch {
|
|
2537
2148
|
}
|
|
2538
|
-
const devStandardsPath =
|
|
2149
|
+
const devStandardsPath = path5__namespace.join(workingDir, ".sf-cli", "norms", "devstanded.md");
|
|
2539
2150
|
try {
|
|
2540
|
-
const stats = await
|
|
2151
|
+
const stats = await fs5__namespace.stat(devStandardsPath);
|
|
2541
2152
|
if (stats.size <= MAX_FILE_SIZE2) {
|
|
2542
|
-
context.devStandards = await
|
|
2153
|
+
context.devStandards = await fs5__namespace.readFile(devStandardsPath, "utf-8");
|
|
2543
2154
|
}
|
|
2544
2155
|
} catch {
|
|
2545
2156
|
}
|
|
@@ -2566,13 +2177,41 @@ function analyzeComplexity(requirement, context) {
|
|
|
2566
2177
|
if (!context.framework) score += 0.5;
|
|
2567
2178
|
return Math.max(1, Math.min(10, Math.round(score)));
|
|
2568
2179
|
}
|
|
2569
|
-
function generateBDDScenarios(requirement, context, questions) {
|
|
2180
|
+
function generateBDDScenarios(requirement, context, questions, references = []) {
|
|
2570
2181
|
const scenarios = [];
|
|
2571
2182
|
questions.find((q) => q.category === "ui" && q.answered)?.answer;
|
|
2572
2183
|
const interactionAnswer = questions.find((q) => q.category === "interaction" && q.answered)?.answer;
|
|
2573
2184
|
const edgeAnswer = questions.find((q) => q.category === "edge" && q.answered)?.answer;
|
|
2185
|
+
if (references.length > 0) {
|
|
2186
|
+
for (const ref of references) {
|
|
2187
|
+
const refFeatures = extractFeaturesFromReference(ref);
|
|
2188
|
+
for (const feature of refFeatures) {
|
|
2189
|
+
const scenario = {
|
|
2190
|
+
feature: feature.title,
|
|
2191
|
+
description: feature.description,
|
|
2192
|
+
scenarios: []
|
|
2193
|
+
};
|
|
2194
|
+
scenario.scenarios.push({
|
|
2195
|
+
name: `\u6B63\u5E38\u6D41\u7A0B: ${feature.title}`,
|
|
2196
|
+
given: [`\u7528\u6237\u8FDB\u5165\u76F8\u5173\u9875\u9762`],
|
|
2197
|
+
when: [`\u7528\u6237\u6267\u884C "${feature.title}" \u64CD\u4F5C`],
|
|
2198
|
+
then: [`\u7CFB\u7EDF\u5E94\u6B63\u786E\u5904\u7406\u5E76\u8FD4\u56DE\u9884\u671F\u7ED3\u679C`]
|
|
2199
|
+
});
|
|
2200
|
+
if (feature.hasInput) {
|
|
2201
|
+
scenario.scenarios.push({
|
|
2202
|
+
name: `\u8FB9\u754C\u60C5\u51B5: \u8F93\u5165\u9A8C\u8BC1`,
|
|
2203
|
+
given: [`\u7528\u6237\u8FDB\u5165\u8F93\u5165\u754C\u9762`],
|
|
2204
|
+
when: [`\u7528\u6237\u8F93\u5165\u8FB9\u754C\u503C\u6216\u7A7A\u503C`],
|
|
2205
|
+
then: [`\u7CFB\u7EDF\u5E94\u6B63\u786E\u5904\u7406\u8FB9\u754C\u60C5\u51B5`]
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
scenarios.push(scenario);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2574
2212
|
const features = extractFeatures(requirement);
|
|
2575
2213
|
for (const feature of features) {
|
|
2214
|
+
if (scenarios.some((s) => s.feature === feature.title)) continue;
|
|
2576
2215
|
const scenario = {
|
|
2577
2216
|
feature: feature.title,
|
|
2578
2217
|
description: feature.description,
|
|
@@ -2604,6 +2243,46 @@ function generateBDDScenarios(requirement, context, questions) {
|
|
|
2604
2243
|
}
|
|
2605
2244
|
return scenarios;
|
|
2606
2245
|
}
|
|
2246
|
+
function extractFeaturesFromReference(ref) {
|
|
2247
|
+
const features = [];
|
|
2248
|
+
const analysis = ref.analysis.toLowerCase();
|
|
2249
|
+
if (analysis.includes("\u8F93\u5165") || analysis.includes("\u8868\u5355")) {
|
|
2250
|
+
features.push({
|
|
2251
|
+
title: "\u8F93\u5165\u8868\u5355",
|
|
2252
|
+
description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u8F93\u5165\u8868\u5355\u529F\u80FD",
|
|
2253
|
+
hasInput: true
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
if (analysis.includes("\u6309\u94AE") || analysis.includes("\u64CD\u4F5C")) {
|
|
2257
|
+
features.push({
|
|
2258
|
+
title: "\u4EA4\u4E92\u6309\u94AE",
|
|
2259
|
+
description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6309\u94AE\u4EA4\u4E92",
|
|
2260
|
+
hasInput: false
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
if (analysis.includes("\u8868\u683C") || analysis.includes("\u5217\u8868")) {
|
|
2264
|
+
features.push({
|
|
2265
|
+
title: "\u6570\u636E\u5217\u8868",
|
|
2266
|
+
description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6570\u636E\u5C55\u793A",
|
|
2267
|
+
hasInput: false
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
2270
|
+
if (analysis.includes("\u56FE\u8868") || analysis.includes("\u53EF\u89C6\u5316")) {
|
|
2271
|
+
features.push({
|
|
2272
|
+
title: "\u56FE\u8868\u5C55\u793A",
|
|
2273
|
+
description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u56FE\u8868\u53EF\u89C6\u5316",
|
|
2274
|
+
hasInput: false
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
if (features.length === 0) {
|
|
2278
|
+
features.push({
|
|
2279
|
+
title: "\u53C2\u8003\u529F\u80FD\u5B9E\u73B0",
|
|
2280
|
+
description: `\u57FA\u4E8E\u53C2\u8003\u8D44\u6E90 ${ref.url} \u5B9E\u73B0\u7684\u529F\u80FD`,
|
|
2281
|
+
hasInput: true
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
return features;
|
|
2285
|
+
}
|
|
2607
2286
|
function extractFeatures(requirement) {
|
|
2608
2287
|
const features = [];
|
|
2609
2288
|
const urlMatch = requirement.match(/https?:\/\/[^\s]+/);
|
|
@@ -2644,15 +2323,26 @@ function extractFeatures(requirement) {
|
|
|
2644
2323
|
}
|
|
2645
2324
|
return features;
|
|
2646
2325
|
}
|
|
2647
|
-
function generateSpecItems(requirement, context, bddScenarios, questions) {
|
|
2326
|
+
function generateSpecItems(requirement, context, bddScenarios, questions, references = []) {
|
|
2648
2327
|
const items = [];
|
|
2649
2328
|
let id = 1;
|
|
2329
|
+
for (const ref of references) {
|
|
2330
|
+
items.push({
|
|
2331
|
+
id: `T${id.toString().padStart(3, "0")}`,
|
|
2332
|
+
title: `\u53C2\u8003\u5206\u6790: ${ref.type}`,
|
|
2333
|
+
description: `\u5206\u6790\u53C2\u8003\u8D44\u6E90 ${ref.url}`,
|
|
2334
|
+
priority: "high",
|
|
2335
|
+
files: [],
|
|
2336
|
+
tests: []
|
|
2337
|
+
});
|
|
2338
|
+
id++;
|
|
2339
|
+
}
|
|
2650
2340
|
for (const scenario of bddScenarios) {
|
|
2651
2341
|
items.push({
|
|
2652
2342
|
id: `T${id.toString().padStart(3, "0")}`,
|
|
2653
2343
|
title: scenario.feature,
|
|
2654
2344
|
description: scenario.description,
|
|
2655
|
-
priority: id <=
|
|
2345
|
+
priority: id <= 3 ? "high" : "medium",
|
|
2656
2346
|
files: [],
|
|
2657
2347
|
tests: []
|
|
2658
2348
|
});
|
|
@@ -2669,11 +2359,11 @@ function generateSpecItems(requirement, context, bddScenarios, questions) {
|
|
|
2669
2359
|
return items;
|
|
2670
2360
|
}
|
|
2671
2361
|
async function saveSpecFile(workingDir, session) {
|
|
2672
|
-
const specDir =
|
|
2673
|
-
await
|
|
2674
|
-
const specPath =
|
|
2362
|
+
const specDir = path5__namespace.join(workingDir, "openspec", "changes");
|
|
2363
|
+
await fs5__namespace.mkdir(specDir, { recursive: true });
|
|
2364
|
+
const specPath = path5__namespace.join(specDir, `${session.id}-spec.md`);
|
|
2675
2365
|
const content = formatSpecFile(session);
|
|
2676
|
-
await
|
|
2366
|
+
await fs5__namespace.writeFile(specPath, content, "utf-8");
|
|
2677
2367
|
return specPath;
|
|
2678
2368
|
}
|
|
2679
2369
|
function formatSpecFile(session) {
|
|
@@ -2695,6 +2385,19 @@ function formatSpecFile(session) {
|
|
|
2695
2385
|
lines.push("---");
|
|
2696
2386
|
lines.push("");
|
|
2697
2387
|
}
|
|
2388
|
+
if (session.referenceResources.length > 0) {
|
|
2389
|
+
lines.push("## \u53C2\u8003\u8D44\u6E90");
|
|
2390
|
+
lines.push("");
|
|
2391
|
+
for (const ref of session.referenceResources) {
|
|
2392
|
+
lines.push(`### ${ref.url}`);
|
|
2393
|
+
lines.push(`> \u7C7B\u578B: ${ref.type}`);
|
|
2394
|
+
lines.push("");
|
|
2395
|
+
lines.push(ref.analysis);
|
|
2396
|
+
lines.push("");
|
|
2397
|
+
}
|
|
2398
|
+
lines.push("---");
|
|
2399
|
+
lines.push("");
|
|
2400
|
+
}
|
|
2698
2401
|
if (session.clarificationQuestions.some((q) => q.answered)) {
|
|
2699
2402
|
lines.push("## \u9700\u6C42\u6F84\u6E05");
|
|
2700
2403
|
lines.push("");
|
|
@@ -2734,14 +2437,14 @@ function formatSpecFile(session) {
|
|
|
2734
2437
|
return lines.join("\n");
|
|
2735
2438
|
}
|
|
2736
2439
|
async function generateTests(workingDir, session) {
|
|
2737
|
-
const testDir =
|
|
2738
|
-
await
|
|
2440
|
+
const testDir = path5__namespace.join(workingDir, "tests");
|
|
2441
|
+
await fs5__namespace.mkdir(testDir, { recursive: true });
|
|
2739
2442
|
const testFiles = [];
|
|
2740
2443
|
for (const scenario of session.bddScenarios) {
|
|
2741
2444
|
const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
|
|
2742
|
-
const testPath =
|
|
2445
|
+
const testPath = path5__namespace.join(testDir, `${testName}.test.ts`);
|
|
2743
2446
|
const content = generateTestFile(scenario);
|
|
2744
|
-
await
|
|
2447
|
+
await fs5__namespace.writeFile(testPath, content, "utf-8");
|
|
2745
2448
|
testFiles.push(`tests/${testName}.test.ts`);
|
|
2746
2449
|
}
|
|
2747
2450
|
return testFiles;
|
|
@@ -2765,9 +2468,9 @@ function generateTestFile(scenario) {
|
|
|
2765
2468
|
}
|
|
2766
2469
|
async function archiveWorkflow(workingDir) {
|
|
2767
2470
|
if (!activeSession) return;
|
|
2768
|
-
const archiveDir =
|
|
2769
|
-
await
|
|
2770
|
-
const archivePath =
|
|
2471
|
+
const archiveDir = path5__namespace.join(workingDir, "openspec", "spec");
|
|
2472
|
+
await fs5__namespace.mkdir(archiveDir, { recursive: true });
|
|
2473
|
+
const archivePath = path5__namespace.join(archiveDir, `${activeSession.id}.md`);
|
|
2771
2474
|
const content = `# \u5F52\u6863: ${activeSession.requirement.slice(0, 50)}
|
|
2772
2475
|
|
|
2773
2476
|
> \u5F52\u6863\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
@@ -2793,13 +2496,103 @@ ${activeSession.refinedRequirement}
|
|
|
2793
2496
|
|
|
2794
2497
|
${activeSession.testFiles.map((f) => `- ${f}`).join("\n") || "\u65E0"}
|
|
2795
2498
|
`;
|
|
2796
|
-
await
|
|
2499
|
+
await fs5__namespace.writeFile(archivePath, content, "utf-8");
|
|
2797
2500
|
}
|
|
2798
2501
|
function generateSessionId() {
|
|
2799
2502
|
const timestamp = Date.now().toString(36);
|
|
2800
2503
|
const random = Math.random().toString(36).slice(2, 6);
|
|
2801
2504
|
return `WF-${timestamp}-${random}`.toUpperCase();
|
|
2802
2505
|
}
|
|
2506
|
+
function extractUrls(text) {
|
|
2507
|
+
const urlRegex = /https?:\/\/[^\s<>"{}|\\^`\[\]]+/gi;
|
|
2508
|
+
const matches = text.match(urlRegex);
|
|
2509
|
+
return matches ? [...new Set(matches)] : [];
|
|
2510
|
+
}
|
|
2511
|
+
async function fetchAndAnalyzeReference(url, ctx) {
|
|
2512
|
+
const type = detectResourceType(url);
|
|
2513
|
+
let content = "";
|
|
2514
|
+
let analysis = "";
|
|
2515
|
+
try {
|
|
2516
|
+
const response = await fetch(url, {
|
|
2517
|
+
headers: {
|
|
2518
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
2519
|
+
}
|
|
2520
|
+
});
|
|
2521
|
+
if (!response.ok) {
|
|
2522
|
+
throw new Error(`HTTP ${response.status}`);
|
|
2523
|
+
}
|
|
2524
|
+
content = await response.text();
|
|
2525
|
+
if (ctx.modelService.getCurrentModel()) {
|
|
2526
|
+
analysis = await analyzeReferenceContent(url, content, type, ctx);
|
|
2527
|
+
} else {
|
|
2528
|
+
analysis = extractBasicInfo(content, type);
|
|
2529
|
+
}
|
|
2530
|
+
} catch (error) {
|
|
2531
|
+
throw new Error(`\u65E0\u6CD5\u83B7\u53D6\u53C2\u8003\u8D44\u6E90: ${error.message}`);
|
|
2532
|
+
}
|
|
2533
|
+
return { url, type, content: content.slice(0, 1e4), analysis };
|
|
2534
|
+
}
|
|
2535
|
+
function detectResourceType(url) {
|
|
2536
|
+
if (url.includes("figma.com") || url.includes("lanhuapp.com")) {
|
|
2537
|
+
return "design";
|
|
2538
|
+
}
|
|
2539
|
+
if (/\.(png|jpg|jpeg|gif|webp|svg)$/i.test(url)) {
|
|
2540
|
+
return "image";
|
|
2541
|
+
}
|
|
2542
|
+
if (/api\//i.test(url)) {
|
|
2543
|
+
return "api";
|
|
2544
|
+
}
|
|
2545
|
+
return "webpage";
|
|
2546
|
+
}
|
|
2547
|
+
async function analyzeReferenceContent(url, content, type, ctx) {
|
|
2548
|
+
const typePrompts = {
|
|
2549
|
+
webpage: "\u5206\u6790\u8FD9\u4E2A\u7F51\u9875\u7684\u529F\u80FD\u3001UI\u7EC4\u4EF6\u548C\u4EA4\u4E92\u65B9\u5F0F",
|
|
2550
|
+
design: "\u5206\u6790\u8FD9\u4E2A\u8BBE\u8BA1\u7A3F\u7684\u5E03\u5C40\u3001\u7EC4\u4EF6\u548C\u6837\u5F0F",
|
|
2551
|
+
image: "\u63CF\u8FF0\u8FD9\u4E2A\u56FE\u7247\u7684\u5185\u5BB9\u548C\u8BBE\u8BA1\u5143\u7D20",
|
|
2552
|
+
api: "\u5206\u6790\u8FD9\u4E2AAPI\u7684\u7ED3\u6784\u548C\u53C2\u6570"
|
|
2553
|
+
};
|
|
2554
|
+
const prompt2 = `
|
|
2555
|
+
\u8BF7\u5206\u6790\u4EE5\u4E0B\u53C2\u8003\u8D44\u6E90\u7684 URL\uFF0C\u63D0\u53D6\u5BF9\u5F00\u53D1\u6709\u7528\u7684\u4FE1\u606F\uFF1A
|
|
2556
|
+
|
|
2557
|
+
URL: ${url}
|
|
2558
|
+
\u7C7B\u578B: ${type}
|
|
2559
|
+
|
|
2560
|
+
\u5185\u5BB9\u6458\u8981:
|
|
2561
|
+
${content.slice(0, 5e3)}
|
|
2562
|
+
|
|
2563
|
+
${typePrompts[type]}
|
|
2564
|
+
|
|
2565
|
+
\u8BF7\u63D0\u53D6\uFF1A
|
|
2566
|
+
1. \u4E3B\u8981\u529F\u80FD\u70B9
|
|
2567
|
+
2. UI\u7EC4\u4EF6\u7ED3\u6784
|
|
2568
|
+
3. \u4EA4\u4E92\u65B9\u5F0F
|
|
2569
|
+
4. \u6570\u636E\u7ED3\u6784\uFF08\u5982\u679C\u6709\uFF09
|
|
2570
|
+
5. \u6280\u672F\u5B9E\u73B0\u5EFA\u8BAE
|
|
2571
|
+
|
|
2572
|
+
\u4EE5\u7B80\u6D01\u7684\u8981\u70B9\u5F62\u5F0F\u8F93\u51FA\u3002
|
|
2573
|
+
`;
|
|
2574
|
+
try {
|
|
2575
|
+
const response = await ctx.modelService.sendMessage([
|
|
2576
|
+
{ role: "user", content: prompt2 }
|
|
2577
|
+
], { temperature: 0.3, maxTokens: 2e3 });
|
|
2578
|
+
return response.content;
|
|
2579
|
+
} catch {
|
|
2580
|
+
return extractBasicInfo(content, type);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
function extractBasicInfo(content, type) {
|
|
2584
|
+
const titleMatch = content.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
2585
|
+
const descMatch = content.match(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']+)["']/i);
|
|
2586
|
+
const parts = [];
|
|
2587
|
+
if (titleMatch) {
|
|
2588
|
+
parts.push(`\u6807\u9898: ${titleMatch[1]}`);
|
|
2589
|
+
}
|
|
2590
|
+
if (descMatch) {
|
|
2591
|
+
parts.push(`\u63CF\u8FF0: ${descMatch[1]}`);
|
|
2592
|
+
}
|
|
2593
|
+
parts.push(`\u8D44\u6E90\u7C7B\u578B: ${type}`);
|
|
2594
|
+
return parts.join("\n");
|
|
2595
|
+
}
|
|
2803
2596
|
function generateComplexityBar(score) {
|
|
2804
2597
|
const filled = Math.round(score / 2);
|
|
2805
2598
|
const empty = 5 - filled;
|
|
@@ -2809,6 +2602,7 @@ function getPhaseLabel(phase) {
|
|
|
2809
2602
|
const labels = {
|
|
2810
2603
|
context: "\u9879\u76EE\u4E0A\u4E0B\u6587\u83B7\u53D6",
|
|
2811
2604
|
clarify: "\u9700\u6C42\u6F84\u6E05",
|
|
2605
|
+
reference: "\u53C2\u8003\u8D44\u6E90\u5206\u6790",
|
|
2812
2606
|
analysis: "\u590D\u6742\u5EA6\u8BC4\u4F30",
|
|
2813
2607
|
bdd: "BDD \u573A\u666F\u62C6\u89E3",
|
|
2814
2608
|
spec: "OpenSpec \u89C4\u683C",
|
|
@@ -3051,11 +2845,11 @@ var NormsManager = class {
|
|
|
3051
2845
|
const files = await this.collectProjectFiles(projectPath);
|
|
3052
2846
|
for (const filePath of files.slice(0, MAX_FILES_TO_SCAN)) {
|
|
3053
2847
|
try {
|
|
3054
|
-
const stats = await
|
|
2848
|
+
const stats = await fs5__namespace.stat(filePath);
|
|
3055
2849
|
if (stats.size > MAX_FILE_SIZE) {
|
|
3056
2850
|
continue;
|
|
3057
2851
|
}
|
|
3058
|
-
const content = await
|
|
2852
|
+
const content = await fs5__namespace.readFile(filePath, "utf-8");
|
|
3059
2853
|
const patterns = await this.learnFromFile(filePath, content);
|
|
3060
2854
|
result.patternsFound += patterns.length;
|
|
3061
2855
|
result.filesScanned++;
|
|
@@ -3078,7 +2872,7 @@ var NormsManager = class {
|
|
|
3078
2872
|
* 从单个文件学习规范
|
|
3079
2873
|
*/
|
|
3080
2874
|
async learnFromFile(filePath, content) {
|
|
3081
|
-
const ext =
|
|
2875
|
+
const ext = path5__namespace.extname(filePath);
|
|
3082
2876
|
const patterns = this.extractPatterns(filePath, content, ext);
|
|
3083
2877
|
for (const pattern of patterns) {
|
|
3084
2878
|
await this.recordOccurrence({
|
|
@@ -3681,16 +3475,16 @@ ${response}`;
|
|
|
3681
3475
|
const files = [];
|
|
3682
3476
|
async function scan(dir) {
|
|
3683
3477
|
try {
|
|
3684
|
-
const entries = await
|
|
3478
|
+
const entries = await fs5__namespace.readdir(dir, { withFileTypes: true });
|
|
3685
3479
|
for (const entry of entries) {
|
|
3686
3480
|
if (entry.isDirectory()) {
|
|
3687
3481
|
if (!IGNORED_DIRS.includes(entry.name)) {
|
|
3688
|
-
await scan(
|
|
3482
|
+
await scan(path5__namespace.join(dir, entry.name));
|
|
3689
3483
|
}
|
|
3690
3484
|
} else if (entry.isFile()) {
|
|
3691
|
-
const ext =
|
|
3485
|
+
const ext = path5__namespace.extname(entry.name);
|
|
3692
3486
|
if (SCAN_EXTENSIONS.includes(ext)) {
|
|
3693
|
-
files.push(
|
|
3487
|
+
files.push(path5__namespace.join(dir, entry.name));
|
|
3694
3488
|
}
|
|
3695
3489
|
}
|
|
3696
3490
|
}
|
|
@@ -3724,16 +3518,16 @@ ${response}`;
|
|
|
3724
3518
|
* 确保规范目录存在
|
|
3725
3519
|
*/
|
|
3726
3520
|
async ensureNormsDir() {
|
|
3727
|
-
await
|
|
3728
|
-
await
|
|
3521
|
+
await fs5__namespace.mkdir(this.config.normsDir, { recursive: true });
|
|
3522
|
+
await fs5__namespace.mkdir(path5__namespace.join(this.config.normsDir, "weekly"), { recursive: true });
|
|
3729
3523
|
}
|
|
3730
3524
|
/**
|
|
3731
3525
|
* 加载已有规范
|
|
3732
3526
|
*/
|
|
3733
3527
|
async loadStandards() {
|
|
3734
|
-
const standardsPath =
|
|
3528
|
+
const standardsPath = path5__namespace.join(this.config.normsDir, "patterns.json");
|
|
3735
3529
|
try {
|
|
3736
|
-
const content = await
|
|
3530
|
+
const content = await fs5__namespace.readFile(standardsPath, "utf-8");
|
|
3737
3531
|
const data = JSON.parse(content);
|
|
3738
3532
|
for (const standard of data) {
|
|
3739
3533
|
this.standards.set(standard.id, {
|
|
@@ -3749,9 +3543,9 @@ ${response}`;
|
|
|
3749
3543
|
* 加载权重数据
|
|
3750
3544
|
*/
|
|
3751
3545
|
async loadWeights() {
|
|
3752
|
-
const weightsPath =
|
|
3546
|
+
const weightsPath = path5__namespace.join(this.config.normsDir, "weights.json");
|
|
3753
3547
|
try {
|
|
3754
|
-
const content = await
|
|
3548
|
+
const content = await fs5__namespace.readFile(weightsPath, "utf-8");
|
|
3755
3549
|
const data = JSON.parse(content);
|
|
3756
3550
|
for (const weight of data) {
|
|
3757
3551
|
this.weights.set(weight.standardId, {
|
|
@@ -3766,8 +3560,8 @@ ${response}`;
|
|
|
3766
3560
|
* 保存规范
|
|
3767
3561
|
*/
|
|
3768
3562
|
async saveStandards() {
|
|
3769
|
-
const standardsPath =
|
|
3770
|
-
await
|
|
3563
|
+
const standardsPath = path5__namespace.join(this.config.normsDir, "patterns.json");
|
|
3564
|
+
await fs5__namespace.writeFile(
|
|
3771
3565
|
standardsPath,
|
|
3772
3566
|
JSON.stringify(Array.from(this.standards.values()), null, 2),
|
|
3773
3567
|
"utf-8"
|
|
@@ -3777,8 +3571,8 @@ ${response}`;
|
|
|
3777
3571
|
* 保存权重
|
|
3778
3572
|
*/
|
|
3779
3573
|
async saveWeights() {
|
|
3780
|
-
const weightsPath =
|
|
3781
|
-
await
|
|
3574
|
+
const weightsPath = path5__namespace.join(this.config.normsDir, "weights.json");
|
|
3575
|
+
await fs5__namespace.writeFile(
|
|
3782
3576
|
weightsPath,
|
|
3783
3577
|
JSON.stringify(Array.from(this.weights.values()), null, 2),
|
|
3784
3578
|
"utf-8"
|
|
@@ -3805,8 +3599,8 @@ ${response}`;
|
|
|
3805
3599
|
* 保存周报
|
|
3806
3600
|
*/
|
|
3807
3601
|
async saveWeeklyReport(weekId, report) {
|
|
3808
|
-
const reportPath =
|
|
3809
|
-
await
|
|
3602
|
+
const reportPath = path5__namespace.join(this.config.normsDir, "weekly", `${weekId}.json`);
|
|
3603
|
+
await fs5__namespace.writeFile(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
3810
3604
|
}
|
|
3811
3605
|
/**
|
|
3812
3606
|
* 更新 devstanded.md
|
|
@@ -3814,8 +3608,8 @@ ${response}`;
|
|
|
3814
3608
|
async updateDevStandedMd() {
|
|
3815
3609
|
const standards = this.getEffectiveStandards();
|
|
3816
3610
|
const content = this.generateDevStandedMd(standards);
|
|
3817
|
-
const mdPath =
|
|
3818
|
-
await
|
|
3611
|
+
const mdPath = path5__namespace.join(this.config.normsDir, "devstanded.md");
|
|
3612
|
+
await fs5__namespace.writeFile(mdPath, content, "utf-8");
|
|
3819
3613
|
}
|
|
3820
3614
|
/**
|
|
3821
3615
|
* 生成 devstanded.md 内容
|
|
@@ -3954,7 +3748,7 @@ async function handleInit(args, ctx) {
|
|
|
3954
3748
|
async function initProject(options = {}, workingDir) {
|
|
3955
3749
|
const cwd = workingDir || process.cwd();
|
|
3956
3750
|
try {
|
|
3957
|
-
const stats = await
|
|
3751
|
+
const stats = await fs5__namespace.stat(cwd);
|
|
3958
3752
|
if (!stats.isDirectory()) {
|
|
3959
3753
|
return {
|
|
3960
3754
|
output: chalk9__default.default.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
|
|
@@ -3965,9 +3759,9 @@ async function initProject(options = {}, workingDir) {
|
|
|
3965
3759
|
output: chalk9__default.default.red(`\u9519\u8BEF: \u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6743\u9650\u8BBF\u95EE ${cwd}`)
|
|
3966
3760
|
};
|
|
3967
3761
|
}
|
|
3968
|
-
const sfCliDir =
|
|
3969
|
-
const openspecDir =
|
|
3970
|
-
const agentsMdPath =
|
|
3762
|
+
const sfCliDir = path5__namespace.join(cwd, ".sf-cli");
|
|
3763
|
+
const openspecDir = path5__namespace.join(cwd, "openspec");
|
|
3764
|
+
const agentsMdPath = path5__namespace.join(cwd, "AGENTS.md");
|
|
3971
3765
|
try {
|
|
3972
3766
|
const agentsExists = await fileExists(agentsMdPath);
|
|
3973
3767
|
if (agentsExists && !options.force) {
|
|
@@ -3986,7 +3780,7 @@ async function initProject(options = {}, workingDir) {
|
|
|
3986
3780
|
await normsManager.initialize();
|
|
3987
3781
|
const scanResult = await normsManager.scanProject(cwd);
|
|
3988
3782
|
const agentsContent = generateAgentsMd(projectInfo, scanResult);
|
|
3989
|
-
await
|
|
3783
|
+
await fs5__namespace.writeFile(agentsMdPath, agentsContent, "utf-8");
|
|
3990
3784
|
await generateOpenSpecConfig(openspecDir, projectInfo);
|
|
3991
3785
|
return {
|
|
3992
3786
|
output: chalk9__default.default.green("\u2713 \u9879\u76EE\u521D\u59CB\u5316\u5B8C\u6210\n") + chalk9__default.default.gray(` \u9879\u76EE\u7C7B\u578B: ${projectInfo.type}
|
|
@@ -4027,7 +3821,7 @@ async function createSfCliDirectory(basePath) {
|
|
|
4027
3821
|
"logs"
|
|
4028
3822
|
];
|
|
4029
3823
|
for (const dir of dirs) {
|
|
4030
|
-
await
|
|
3824
|
+
await fs5__namespace.mkdir(path5__namespace.join(basePath, dir), { recursive: true });
|
|
4031
3825
|
}
|
|
4032
3826
|
const config = {
|
|
4033
3827
|
version: "1.0.0",
|
|
@@ -4035,37 +3829,37 @@ async function createSfCliDirectory(basePath) {
|
|
|
4035
3829
|
yolo: false,
|
|
4036
3830
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4037
3831
|
};
|
|
4038
|
-
await
|
|
4039
|
-
|
|
3832
|
+
await fs5__namespace.writeFile(
|
|
3833
|
+
path5__namespace.join(basePath, "config.json"),
|
|
4040
3834
|
JSON.stringify(config, null, 2),
|
|
4041
3835
|
"utf-8"
|
|
4042
3836
|
);
|
|
4043
|
-
await
|
|
4044
|
-
|
|
3837
|
+
await fs5__namespace.writeFile(
|
|
3838
|
+
path5__namespace.join(basePath, "agents", "registry.json"),
|
|
4045
3839
|
JSON.stringify({ version: "1.0.0", agents: {} }, null, 2),
|
|
4046
3840
|
"utf-8"
|
|
4047
3841
|
);
|
|
4048
|
-
await
|
|
4049
|
-
|
|
3842
|
+
await fs5__namespace.writeFile(
|
|
3843
|
+
path5__namespace.join(basePath, "skills", "registry.json"),
|
|
4050
3844
|
JSON.stringify({ version: "1.0.0", skills: {} }, null, 2),
|
|
4051
3845
|
"utf-8"
|
|
4052
3846
|
);
|
|
4053
|
-
await
|
|
4054
|
-
|
|
3847
|
+
await fs5__namespace.writeFile(
|
|
3848
|
+
path5__namespace.join(basePath, "health", "health.md"),
|
|
4055
3849
|
generateHealthTemplate(),
|
|
4056
3850
|
"utf-8"
|
|
4057
3851
|
);
|
|
4058
3852
|
}
|
|
4059
3853
|
async function createOpenSpecDirectory(basePath) {
|
|
4060
|
-
const changesDir =
|
|
4061
|
-
const archiveDir =
|
|
4062
|
-
const specDir =
|
|
4063
|
-
await
|
|
4064
|
-
await
|
|
3854
|
+
const changesDir = path5__namespace.join(basePath, "changes");
|
|
3855
|
+
const archiveDir = path5__namespace.join(changesDir, "archive");
|
|
3856
|
+
const specDir = path5__namespace.join(basePath, "spec");
|
|
3857
|
+
await fs5__namespace.mkdir(archiveDir, { recursive: true });
|
|
3858
|
+
await fs5__namespace.mkdir(specDir, { recursive: true });
|
|
4065
3859
|
}
|
|
4066
3860
|
async function analyzeProject(cwd) {
|
|
4067
3861
|
const result = {
|
|
4068
|
-
name:
|
|
3862
|
+
name: path5__namespace.basename(cwd),
|
|
4069
3863
|
type: "unknown",
|
|
4070
3864
|
framework: null,
|
|
4071
3865
|
techStack: [],
|
|
@@ -4084,9 +3878,9 @@ async function analyzeProject(cwd) {
|
|
|
4084
3878
|
hasEslint: false,
|
|
4085
3879
|
hasPrettier: false
|
|
4086
3880
|
};
|
|
4087
|
-
const pkgPath =
|
|
3881
|
+
const pkgPath = path5__namespace.join(cwd, "package.json");
|
|
4088
3882
|
try {
|
|
4089
|
-
const pkgContent = await
|
|
3883
|
+
const pkgContent = await fs5__namespace.readFile(pkgPath, "utf-8");
|
|
4090
3884
|
const pkg = JSON.parse(pkgContent);
|
|
4091
3885
|
result.name = pkg.name || result.name;
|
|
4092
3886
|
result.dependencies = pkg.dependencies || {};
|
|
@@ -4169,7 +3963,7 @@ async function analyzeDirectoryStructure(cwd) {
|
|
|
4169
3963
|
others: []
|
|
4170
3964
|
};
|
|
4171
3965
|
try {
|
|
4172
|
-
const entries = await
|
|
3966
|
+
const entries = await fs5__namespace.readdir(cwd, { withFileTypes: true });
|
|
4173
3967
|
for (const entry of entries) {
|
|
4174
3968
|
if (!entry.isDirectory()) continue;
|
|
4175
3969
|
const name = entry.name;
|
|
@@ -4205,7 +3999,7 @@ async function findEntryPoints(cwd) {
|
|
|
4205
3999
|
"index.js"
|
|
4206
4000
|
];
|
|
4207
4001
|
for (const entry of possibleEntries) {
|
|
4208
|
-
const fullPath =
|
|
4002
|
+
const fullPath = path5__namespace.join(cwd, entry);
|
|
4209
4003
|
if (await fileExists(fullPath)) {
|
|
4210
4004
|
entryPoints.push(entry);
|
|
4211
4005
|
}
|
|
@@ -4213,15 +4007,15 @@ async function findEntryPoints(cwd) {
|
|
|
4213
4007
|
return entryPoints;
|
|
4214
4008
|
}
|
|
4215
4009
|
async function saveProjectAnalysis(sfCliDir, analysis) {
|
|
4216
|
-
const analysisPath =
|
|
4217
|
-
await
|
|
4010
|
+
const analysisPath = path5__namespace.join(sfCliDir, "cache", "analysis", "project-analysis.json");
|
|
4011
|
+
await fs5__namespace.writeFile(
|
|
4218
4012
|
analysisPath,
|
|
4219
4013
|
JSON.stringify(analysis, null, 2),
|
|
4220
4014
|
"utf-8"
|
|
4221
4015
|
);
|
|
4222
4016
|
}
|
|
4223
4017
|
async function generateOpenSpecConfig(openspecDir, analysis) {
|
|
4224
|
-
const configPath =
|
|
4018
|
+
const configPath = path5__namespace.join(openspecDir, "config.yaml");
|
|
4225
4019
|
const content = `# OpenSpec \u9879\u76EE\u914D\u7F6E
|
|
4226
4020
|
# \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
|
|
4227
4021
|
|
|
@@ -4255,7 +4049,7 @@ architecture:
|
|
|
4255
4049
|
# \u5F00\u53D1\u89C4\u8303 (\u4ECE\u4EE3\u7801\u4E2D\u5B66\u4E60)
|
|
4256
4050
|
standards: []
|
|
4257
4051
|
`;
|
|
4258
|
-
await
|
|
4052
|
+
await fs5__namespace.writeFile(configPath, content, "utf-8");
|
|
4259
4053
|
}
|
|
4260
4054
|
function generateAgentsMd(info, scanResult) {
|
|
4261
4055
|
const techStackList = info.techStack.length > 0 ? info.techStack.map((t) => `| ${t} | \u2713 |`).join("\n") : "| \u5F85\u8BC6\u522B | - |";
|
|
@@ -4377,7 +4171,7 @@ function generateHealthTemplate() {
|
|
|
4377
4171
|
}
|
|
4378
4172
|
async function fileExists(filePath) {
|
|
4379
4173
|
try {
|
|
4380
|
-
await
|
|
4174
|
+
await fs5__namespace.access(filePath);
|
|
4381
4175
|
return true;
|
|
4382
4176
|
} catch {
|
|
4383
4177
|
return false;
|
|
@@ -4541,7 +4335,7 @@ ${chalk9__default.default.yellow("\u793A\u4F8B:")}
|
|
|
4541
4335
|
}
|
|
4542
4336
|
|
|
4543
4337
|
// src/commands/runner.ts
|
|
4544
|
-
|
|
4338
|
+
init_model2();
|
|
4545
4339
|
init_update();
|
|
4546
4340
|
|
|
4547
4341
|
// src/commands/clear.ts
|
|
@@ -4832,7 +4626,7 @@ var WorkflowEngine = class {
|
|
|
4832
4626
|
*/
|
|
4833
4627
|
async initialize(projectPath) {
|
|
4834
4628
|
this.projectPath = projectPath;
|
|
4835
|
-
this.openspecPath =
|
|
4629
|
+
this.openspecPath = path5__namespace.join(projectPath, "openspec");
|
|
4836
4630
|
await this.ensureDirectories();
|
|
4837
4631
|
await this.loadProjectContext();
|
|
4838
4632
|
await this.restoreState();
|
|
@@ -4842,21 +4636,21 @@ var WorkflowEngine = class {
|
|
|
4842
4636
|
* 加载项目上下文(AGENTS.md 和 config.yaml)
|
|
4843
4637
|
*/
|
|
4844
4638
|
async loadProjectContext() {
|
|
4845
|
-
const agentsMdPath =
|
|
4639
|
+
const agentsMdPath = path5__namespace.join(this.projectPath, "AGENTS.md");
|
|
4846
4640
|
try {
|
|
4847
|
-
this.projectContext = await
|
|
4641
|
+
this.projectContext = await fs5__namespace.readFile(agentsMdPath, "utf-8");
|
|
4848
4642
|
} catch {
|
|
4849
4643
|
this.projectContext = "";
|
|
4850
4644
|
}
|
|
4851
|
-
const configPath =
|
|
4645
|
+
const configPath = path5__namespace.join(this.openspecPath, "config.yaml");
|
|
4852
4646
|
try {
|
|
4853
|
-
this.projectConfig = await
|
|
4647
|
+
this.projectConfig = await fs5__namespace.readFile(configPath, "utf-8");
|
|
4854
4648
|
} catch {
|
|
4855
4649
|
this.projectConfig = "";
|
|
4856
4650
|
}
|
|
4857
|
-
const devstandedPath =
|
|
4651
|
+
const devstandedPath = path5__namespace.join(this.projectPath, ".sf-cli", "norms", "devstanded.md");
|
|
4858
4652
|
try {
|
|
4859
|
-
this.devStandards = await
|
|
4653
|
+
this.devStandards = await fs5__namespace.readFile(devstandedPath, "utf-8");
|
|
4860
4654
|
} catch {
|
|
4861
4655
|
this.devStandards = "";
|
|
4862
4656
|
}
|
|
@@ -4876,7 +4670,7 @@ var WorkflowEngine = class {
|
|
|
4876
4670
|
*/
|
|
4877
4671
|
getSpecFilePath() {
|
|
4878
4672
|
if (!this.state) return null;
|
|
4879
|
-
return
|
|
4673
|
+
return path5__namespace.join(this.openspecPath, "changes", `${this.state.id}-spec.md`);
|
|
4880
4674
|
}
|
|
4881
4675
|
/**
|
|
4882
4676
|
* 检查规格文件是否存在
|
|
@@ -4885,7 +4679,7 @@ var WorkflowEngine = class {
|
|
|
4885
4679
|
const specPath = this.getSpecFilePath();
|
|
4886
4680
|
if (!specPath) return false;
|
|
4887
4681
|
try {
|
|
4888
|
-
await
|
|
4682
|
+
await fs5__namespace.access(specPath);
|
|
4889
4683
|
return true;
|
|
4890
4684
|
} catch {
|
|
4891
4685
|
return false;
|
|
@@ -5117,13 +4911,13 @@ var WorkflowEngine = class {
|
|
|
5117
4911
|
*/
|
|
5118
4912
|
async getAllActiveWorkflows() {
|
|
5119
4913
|
const workflows = [];
|
|
5120
|
-
const changesDir =
|
|
4914
|
+
const changesDir = path5__namespace.join(this.openspecPath, "changes");
|
|
5121
4915
|
try {
|
|
5122
|
-
const files = await
|
|
4916
|
+
const files = await fs5__namespace.readdir(changesDir);
|
|
5123
4917
|
for (const file of files) {
|
|
5124
4918
|
if (!file.endsWith(".md") || file.includes("-spec.md")) continue;
|
|
5125
|
-
const filePath =
|
|
5126
|
-
const content = await
|
|
4919
|
+
const filePath = path5__namespace.join(changesDir, file);
|
|
4920
|
+
const content = await fs5__namespace.readFile(filePath, "utf-8");
|
|
5127
4921
|
const state = this.parseChangeRecord(content);
|
|
5128
4922
|
if (state && state.status === "running") {
|
|
5129
4923
|
workflows.push(state);
|
|
@@ -5172,9 +4966,9 @@ var WorkflowEngine = class {
|
|
|
5172
4966
|
if (this.state) {
|
|
5173
4967
|
await this.saveState();
|
|
5174
4968
|
}
|
|
5175
|
-
const statePath =
|
|
4969
|
+
const statePath = path5__namespace.join(this.openspecPath, ".workflow-states", `${changeId}.json`);
|
|
5176
4970
|
try {
|
|
5177
|
-
const content = await
|
|
4971
|
+
const content = await fs5__namespace.readFile(statePath, "utf-8");
|
|
5178
4972
|
this.state = JSON.parse(content, (key, value) => {
|
|
5179
4973
|
if (key.endsWith("At") && typeof value === "string") {
|
|
5180
4974
|
return new Date(value);
|
|
@@ -5184,10 +4978,10 @@ var WorkflowEngine = class {
|
|
|
5184
4978
|
await this.restoreSnapshots();
|
|
5185
4979
|
return true;
|
|
5186
4980
|
} catch {
|
|
5187
|
-
const changesDir =
|
|
5188
|
-
const changeFile =
|
|
4981
|
+
const changesDir = path5__namespace.join(this.openspecPath, "changes");
|
|
4982
|
+
const changeFile = path5__namespace.join(changesDir, `${changeId}.md`);
|
|
5189
4983
|
try {
|
|
5190
|
-
const content = await
|
|
4984
|
+
const content = await fs5__namespace.readFile(changeFile, "utf-8");
|
|
5191
4985
|
const parsed = this.parseChangeRecord(content);
|
|
5192
4986
|
if (parsed && parsed.status === "running") {
|
|
5193
4987
|
this.state = parsed;
|
|
@@ -5248,12 +5042,12 @@ var WorkflowEngine = class {
|
|
|
5248
5042
|
await this.createSpecDocument(summary);
|
|
5249
5043
|
await this.updateChangeRecord("archived");
|
|
5250
5044
|
await this.saveState();
|
|
5251
|
-
const changesDir =
|
|
5252
|
-
const archiveDir =
|
|
5253
|
-
const changeFile =
|
|
5254
|
-
const archiveFile =
|
|
5255
|
-
await
|
|
5256
|
-
await
|
|
5045
|
+
const changesDir = path5__namespace.join(this.openspecPath, "changes");
|
|
5046
|
+
const archiveDir = path5__namespace.join(changesDir, "archive");
|
|
5047
|
+
const changeFile = path5__namespace.join(changesDir, `${changeId}.md`);
|
|
5048
|
+
const archiveFile = path5__namespace.join(archiveDir, `${changeId}.md`);
|
|
5049
|
+
await fs5__namespace.mkdir(archiveDir, { recursive: true });
|
|
5050
|
+
await fs5__namespace.rename(changeFile, archiveFile).catch(() => {
|
|
5257
5051
|
});
|
|
5258
5052
|
this.state = null;
|
|
5259
5053
|
this.snapshots.clear();
|
|
@@ -5275,27 +5069,27 @@ var WorkflowEngine = class {
|
|
|
5275
5069
|
}
|
|
5276
5070
|
// ==================== 私有方法 ====================
|
|
5277
5071
|
async ensureDirectories() {
|
|
5278
|
-
const changesDir =
|
|
5279
|
-
const archiveDir =
|
|
5280
|
-
const specDir =
|
|
5281
|
-
const statesDir =
|
|
5282
|
-
await
|
|
5283
|
-
await
|
|
5284
|
-
await
|
|
5072
|
+
const changesDir = path5__namespace.join(this.openspecPath, "changes");
|
|
5073
|
+
const archiveDir = path5__namespace.join(changesDir, "archive");
|
|
5074
|
+
const specDir = path5__namespace.join(this.openspecPath, "spec");
|
|
5075
|
+
const statesDir = path5__namespace.join(this.openspecPath, ".workflow-states");
|
|
5076
|
+
await fs5__namespace.mkdir(archiveDir, { recursive: true });
|
|
5077
|
+
await fs5__namespace.mkdir(specDir, { recursive: true });
|
|
5078
|
+
await fs5__namespace.mkdir(statesDir, { recursive: true });
|
|
5285
5079
|
}
|
|
5286
5080
|
async restoreState() {
|
|
5287
|
-
const activePath =
|
|
5081
|
+
const activePath = path5__namespace.join(this.openspecPath, ".workflow-active.json");
|
|
5288
5082
|
let activeId = null;
|
|
5289
5083
|
try {
|
|
5290
|
-
const activeContent = await
|
|
5084
|
+
const activeContent = await fs5__namespace.readFile(activePath, "utf-8");
|
|
5291
5085
|
const activeData = JSON.parse(activeContent);
|
|
5292
5086
|
activeId = activeData.activeId;
|
|
5293
5087
|
} catch {
|
|
5294
5088
|
}
|
|
5295
5089
|
if (activeId) {
|
|
5296
|
-
const statePath =
|
|
5090
|
+
const statePath = path5__namespace.join(this.openspecPath, ".workflow-states", `${activeId}.json`);
|
|
5297
5091
|
try {
|
|
5298
|
-
const content = await
|
|
5092
|
+
const content = await fs5__namespace.readFile(statePath, "utf-8");
|
|
5299
5093
|
this.state = JSON.parse(content, (key, value) => {
|
|
5300
5094
|
if (key.endsWith("At") && typeof value === "string") {
|
|
5301
5095
|
return new Date(value);
|
|
@@ -5306,9 +5100,9 @@ var WorkflowEngine = class {
|
|
|
5306
5100
|
} catch {
|
|
5307
5101
|
}
|
|
5308
5102
|
}
|
|
5309
|
-
const oldStatePath =
|
|
5103
|
+
const oldStatePath = path5__namespace.join(this.openspecPath, ".workflow-state.json");
|
|
5310
5104
|
try {
|
|
5311
|
-
const content = await
|
|
5105
|
+
const content = await fs5__namespace.readFile(oldStatePath, "utf-8");
|
|
5312
5106
|
this.state = JSON.parse(content, (key, value) => {
|
|
5313
5107
|
if (key.endsWith("At") && typeof value === "string") {
|
|
5314
5108
|
return new Date(value);
|
|
@@ -5317,7 +5111,7 @@ var WorkflowEngine = class {
|
|
|
5317
5111
|
});
|
|
5318
5112
|
if (this.state) {
|
|
5319
5113
|
await this.saveState();
|
|
5320
|
-
await
|
|
5114
|
+
await fs5__namespace.unlink(oldStatePath).catch(() => {
|
|
5321
5115
|
});
|
|
5322
5116
|
}
|
|
5323
5117
|
} catch (e) {
|
|
@@ -5330,17 +5124,17 @@ var WorkflowEngine = class {
|
|
|
5330
5124
|
}
|
|
5331
5125
|
async saveState() {
|
|
5332
5126
|
if (!this.state) return;
|
|
5333
|
-
const statesDir =
|
|
5334
|
-
await
|
|
5335
|
-
const statePath =
|
|
5336
|
-
await
|
|
5337
|
-
const activePath =
|
|
5338
|
-
await
|
|
5127
|
+
const statesDir = path5__namespace.join(this.openspecPath, ".workflow-states");
|
|
5128
|
+
await fs5__namespace.mkdir(statesDir, { recursive: true });
|
|
5129
|
+
const statePath = path5__namespace.join(statesDir, `${this.state.id}.json`);
|
|
5130
|
+
await fs5__namespace.writeFile(statePath, JSON.stringify(this.state, null, 2));
|
|
5131
|
+
const activePath = path5__namespace.join(this.openspecPath, ".workflow-active.json");
|
|
5132
|
+
await fs5__namespace.writeFile(activePath, JSON.stringify({ activeId: this.state.id }, null, 2));
|
|
5339
5133
|
}
|
|
5340
5134
|
async restoreSnapshots() {
|
|
5341
|
-
const snapshotsPath =
|
|
5135
|
+
const snapshotsPath = path5__namespace.join(this.openspecPath, ".workflow-snapshots.json");
|
|
5342
5136
|
try {
|
|
5343
|
-
const content = await
|
|
5137
|
+
const content = await fs5__namespace.readFile(snapshotsPath, "utf-8");
|
|
5344
5138
|
const data = JSON.parse(content, (key, value) => {
|
|
5345
5139
|
if (key === "timestamp" && typeof value === "string") {
|
|
5346
5140
|
return new Date(value);
|
|
@@ -5355,9 +5149,9 @@ var WorkflowEngine = class {
|
|
|
5355
5149
|
}
|
|
5356
5150
|
}
|
|
5357
5151
|
async saveSnapshots() {
|
|
5358
|
-
const snapshotsPath =
|
|
5152
|
+
const snapshotsPath = path5__namespace.join(this.openspecPath, ".workflow-snapshots.json");
|
|
5359
5153
|
const data = Array.from(this.snapshots.values());
|
|
5360
|
-
await
|
|
5154
|
+
await fs5__namespace.writeFile(snapshotsPath, JSON.stringify(data, null, 2));
|
|
5361
5155
|
}
|
|
5362
5156
|
async createSnapshot() {
|
|
5363
5157
|
if (!this.state) return;
|
|
@@ -5377,16 +5171,16 @@ var WorkflowEngine = class {
|
|
|
5377
5171
|
}
|
|
5378
5172
|
async createChangeRecord() {
|
|
5379
5173
|
if (!this.state) return;
|
|
5380
|
-
const changePath =
|
|
5381
|
-
await
|
|
5174
|
+
const changePath = path5__namespace.join(this.openspecPath, "changes", `${this.state.id}.md`);
|
|
5175
|
+
await fs5__namespace.writeFile(changePath, this.formatChangeRecord());
|
|
5382
5176
|
}
|
|
5383
5177
|
async updateChangeRecord(status) {
|
|
5384
5178
|
if (!this.state) return;
|
|
5385
5179
|
if (status) {
|
|
5386
5180
|
this.state.status = status;
|
|
5387
5181
|
}
|
|
5388
|
-
const changePath =
|
|
5389
|
-
await
|
|
5182
|
+
const changePath = path5__namespace.join(this.openspecPath, "changes", `${this.state.id}.md`);
|
|
5183
|
+
await fs5__namespace.writeFile(changePath, this.formatChangeRecord());
|
|
5390
5184
|
}
|
|
5391
5185
|
formatChangeRecord() {
|
|
5392
5186
|
if (!this.state) return "";
|
|
@@ -5425,7 +5219,7 @@ ${this.state.artifacts.map((a) => `- ${a}`).join("\n") || "\u6682\u65E0"}
|
|
|
5425
5219
|
}
|
|
5426
5220
|
async createSpecDocument(summary) {
|
|
5427
5221
|
if (!this.state) return;
|
|
5428
|
-
const specPath =
|
|
5222
|
+
const specPath = path5__namespace.join(this.openspecPath, "spec", `${this.state.id}.md`);
|
|
5429
5223
|
const content = `# Spec: ${this.state.title}
|
|
5430
5224
|
|
|
5431
5225
|
> \u53D8\u66F4ID: ${this.state.id}
|
|
@@ -5450,7 +5244,7 @@ ${this.state.steps.map((s) => `- [${s.status === "completed" ? "x" : " "}] ${s.s
|
|
|
5450
5244
|
|
|
5451
5245
|
${this.state.artifacts.map((a) => `- ${a}`).join("\n") || "\u6682\u65E0"}
|
|
5452
5246
|
`;
|
|
5453
|
-
await
|
|
5247
|
+
await fs5__namespace.writeFile(specPath, content);
|
|
5454
5248
|
}
|
|
5455
5249
|
};
|
|
5456
5250
|
var ConfirmationRequiredError = class extends Error {
|
|
@@ -5947,15 +5741,15 @@ async function loadProjectContext(workingDirectory) {
|
|
|
5947
5741
|
devStandards: ""
|
|
5948
5742
|
};
|
|
5949
5743
|
try {
|
|
5950
|
-
context.agentsMd = await
|
|
5744
|
+
context.agentsMd = await fs5__namespace.readFile(path5__namespace.join(workingDirectory, "AGENTS.md"), "utf-8");
|
|
5951
5745
|
} catch {
|
|
5952
5746
|
}
|
|
5953
5747
|
try {
|
|
5954
|
-
context.configYaml = await
|
|
5748
|
+
context.configYaml = await fs5__namespace.readFile(path5__namespace.join(workingDirectory, "openspec", "config.yaml"), "utf-8");
|
|
5955
5749
|
} catch {
|
|
5956
5750
|
}
|
|
5957
5751
|
try {
|
|
5958
|
-
context.devStandards = await
|
|
5752
|
+
context.devStandards = await fs5__namespace.readFile(path5__namespace.join(workingDirectory, ".sf-cli", "norms", "devstanded.md"), "utf-8");
|
|
5959
5753
|
} catch {
|
|
5960
5754
|
}
|
|
5961
5755
|
return context;
|
|
@@ -6422,11 +6216,11 @@ ${generateConfirmationPrompt(confirmation.point)}`) + chalk9__default.default.cy
|
|
|
6422
6216
|
}
|
|
6423
6217
|
async function updateChangelog(workingDirectory, summary, changeId) {
|
|
6424
6218
|
try {
|
|
6425
|
-
const changelogPath =
|
|
6426
|
-
const pkgPath =
|
|
6219
|
+
const changelogPath = path5__namespace.join(workingDirectory, "CHANGELOG.md");
|
|
6220
|
+
const pkgPath = path5__namespace.join(workingDirectory, "package.json");
|
|
6427
6221
|
let version = "1.0.0";
|
|
6428
6222
|
try {
|
|
6429
|
-
const pkgContent =
|
|
6223
|
+
const pkgContent = fs7__namespace.readFileSync(pkgPath, "utf-8");
|
|
6430
6224
|
const pkg = JSON.parse(pkgContent);
|
|
6431
6225
|
version = pkg.version || "1.0.0";
|
|
6432
6226
|
} catch {
|
|
@@ -6440,22 +6234,22 @@ async function updateChangelog(workingDirectory, summary, changeId) {
|
|
|
6440
6234
|
|
|
6441
6235
|
- ${summary} (${changeId})
|
|
6442
6236
|
`;
|
|
6443
|
-
if (
|
|
6444
|
-
const content =
|
|
6237
|
+
if (fs7__namespace.existsSync(changelogPath)) {
|
|
6238
|
+
const content = fs7__namespace.readFileSync(changelogPath, "utf-8");
|
|
6445
6239
|
const versionPattern = /^## v\d+\.\d+\.\d+/m;
|
|
6446
6240
|
const match = content.match(versionPattern);
|
|
6447
6241
|
if (match && match.index !== void 0) {
|
|
6448
6242
|
const newContent = content.slice(0, match.index) + entry + content.slice(match.index);
|
|
6449
|
-
|
|
6243
|
+
fs7__namespace.writeFileSync(changelogPath, newContent, "utf-8");
|
|
6450
6244
|
} else {
|
|
6451
|
-
|
|
6245
|
+
fs7__namespace.appendFileSync(changelogPath, entry, "utf-8");
|
|
6452
6246
|
}
|
|
6453
6247
|
} else {
|
|
6454
6248
|
const header = `# Changelog
|
|
6455
6249
|
|
|
6456
6250
|
All notable changes to this project will be documented in this file.
|
|
6457
6251
|
`;
|
|
6458
|
-
|
|
6252
|
+
fs7__namespace.writeFileSync(changelogPath, header + entry, "utf-8");
|
|
6459
6253
|
}
|
|
6460
6254
|
} catch (error) {
|
|
6461
6255
|
}
|
|
@@ -6585,7 +6379,7 @@ async function handleRegenerateSpec(workflow, ctx) {
|
|
|
6585
6379
|
return { output: chalk9__default.default.red("\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") };
|
|
6586
6380
|
}
|
|
6587
6381
|
const workingDir = ctx.options.workingDirectory;
|
|
6588
|
-
const specPath =
|
|
6382
|
+
const specPath = path5__namespace.join(workingDir, "openspec", "changes", `${state.id}-spec.md`);
|
|
6589
6383
|
workflow.confirm("spec-review", "regenerate");
|
|
6590
6384
|
const lines = [];
|
|
6591
6385
|
lines.push(chalk9__default.default.cyan("\u{1F504} \u91CD\u65B0\u751F\u6210\u89C4\u683C..."));
|
|
@@ -6594,7 +6388,7 @@ async function handleRegenerateSpec(workflow, ctx) {
|
|
|
6594
6388
|
const context = await readProjectContext2(workingDir);
|
|
6595
6389
|
const analysis = analyzeComplexity2(state.requirement, context);
|
|
6596
6390
|
const specContent = await generateSpecContent(state.id, state.requirement, analysis, context);
|
|
6597
|
-
await
|
|
6391
|
+
await fs7__namespace.promises.writeFile(specPath, specContent, "utf-8");
|
|
6598
6392
|
lines.push(chalk9__default.default.green("\u2713 \u89C4\u683C\u5DF2\u91CD\u65B0\u751F\u6210"));
|
|
6599
6393
|
lines.push(chalk9__default.default.gray(`\u8DEF\u5F84: ${specPath}`));
|
|
6600
6394
|
lines.push("");
|
|
@@ -6688,9 +6482,9 @@ ${generateConfirmationPrompt(e.point)}`) + chalk9__default.default.cyan(`
|
|
|
6688
6482
|
return { output: lines.join("\n") };
|
|
6689
6483
|
}
|
|
6690
6484
|
async function checkPendingSpec(workingDirectory, changeId) {
|
|
6691
|
-
const specPath =
|
|
6485
|
+
const specPath = path5__namespace.join(workingDirectory, "openspec", "changes", `${changeId}-spec.md`);
|
|
6692
6486
|
try {
|
|
6693
|
-
await
|
|
6487
|
+
await fs7__namespace.promises.access(specPath);
|
|
6694
6488
|
return specPath;
|
|
6695
6489
|
} catch {
|
|
6696
6490
|
return null;
|
|
@@ -6804,13 +6598,13 @@ async function handleSwitch(workflow, args, ctx) {
|
|
|
6804
6598
|
// src/commands/runner.ts
|
|
6805
6599
|
function getVersion2() {
|
|
6806
6600
|
const possiblePaths = [
|
|
6807
|
-
|
|
6808
|
-
|
|
6601
|
+
path5__namespace.resolve(__dirname, "..", "..", "package.json"),
|
|
6602
|
+
path5__namespace.resolve(__dirname, "..", "..", "..", "package.json")
|
|
6809
6603
|
];
|
|
6810
6604
|
for (const pkgPath of possiblePaths) {
|
|
6811
6605
|
try {
|
|
6812
|
-
if (
|
|
6813
|
-
const content =
|
|
6606
|
+
if (fs7__namespace.existsSync(pkgPath)) {
|
|
6607
|
+
const content = fs7__namespace.readFileSync(pkgPath, "utf-8");
|
|
6814
6608
|
const pkg = JSON.parse(content);
|
|
6815
6609
|
return pkg.version;
|
|
6816
6610
|
}
|
|
@@ -6867,7 +6661,7 @@ function normalizeCommand(command) {
|
|
|
6867
6661
|
}
|
|
6868
6662
|
|
|
6869
6663
|
// src/commands/index.ts
|
|
6870
|
-
|
|
6664
|
+
init_model2();
|
|
6871
6665
|
init_update();
|
|
6872
6666
|
init_new();
|
|
6873
6667
|
|
|
@@ -6875,11 +6669,11 @@ init_new();
|
|
|
6875
6669
|
init_cjs_shims();
|
|
6876
6670
|
async function handleFileReference(filePath, ctx) {
|
|
6877
6671
|
const cwd = ctx.options.workingDirectory;
|
|
6878
|
-
const absolutePath =
|
|
6672
|
+
const absolutePath = path5__namespace.isAbsolute(filePath) ? filePath : path5__namespace.join(cwd, filePath);
|
|
6879
6673
|
try {
|
|
6880
|
-
const stats = await
|
|
6674
|
+
const stats = await fs5__namespace.stat(absolutePath);
|
|
6881
6675
|
if (stats.isDirectory()) {
|
|
6882
|
-
const files = await
|
|
6676
|
+
const files = await fs5__namespace.readdir(absolutePath);
|
|
6883
6677
|
return {
|
|
6884
6678
|
output: chalk9__default.default.cyan(`\u{1F4C1} ${filePath}/`) + chalk9__default.default.gray(`
|
|
6885
6679
|
${files.slice(0, 20).join("\n")}`) + (files.length > 20 ? chalk9__default.default.gray(`
|
|
@@ -6887,7 +6681,7 @@ ${files.slice(0, 20).join("\n")}`) + (files.length > 20 ? chalk9__default.defaul
|
|
|
6887
6681
|
contextUsed: 0
|
|
6888
6682
|
};
|
|
6889
6683
|
}
|
|
6890
|
-
const content = await
|
|
6684
|
+
const content = await fs5__namespace.readFile(absolutePath, "utf-8");
|
|
6891
6685
|
const lines = content.split("\n");
|
|
6892
6686
|
ctx.contextManager.addMessage({
|
|
6893
6687
|
role: "user",
|
|
@@ -7197,7 +6991,7 @@ var ContextManager = class {
|
|
|
7197
6991
|
}
|
|
7198
6992
|
async initialize(projectPath) {
|
|
7199
6993
|
this.projectPath = projectPath;
|
|
7200
|
-
this.persistPath =
|
|
6994
|
+
this.persistPath = path5__namespace.join(projectPath, ".sf-cli", "cache", "context", "context.json");
|
|
7201
6995
|
await this.loadPersistedContext();
|
|
7202
6996
|
}
|
|
7203
6997
|
/**
|
|
@@ -7438,15 +7232,15 @@ ${summary}`,
|
|
|
7438
7232
|
*/
|
|
7439
7233
|
async persist() {
|
|
7440
7234
|
if (!this.persistPath) return;
|
|
7441
|
-
const dir =
|
|
7442
|
-
await
|
|
7235
|
+
const dir = path5__namespace.dirname(this.persistPath);
|
|
7236
|
+
await fs5__namespace.mkdir(dir, { recursive: true });
|
|
7443
7237
|
const data = {
|
|
7444
7238
|
messages: this.state.messages,
|
|
7445
7239
|
totalTokens: this.state.totalTokens,
|
|
7446
7240
|
limit: this.state.limit,
|
|
7447
7241
|
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7448
7242
|
};
|
|
7449
|
-
await
|
|
7243
|
+
await fs5__namespace.writeFile(this.persistPath, JSON.stringify(data, null, 2), "utf-8");
|
|
7450
7244
|
}
|
|
7451
7245
|
/**
|
|
7452
7246
|
* 加载持久化的上下文
|
|
@@ -7454,7 +7248,7 @@ ${summary}`,
|
|
|
7454
7248
|
async loadPersistedContext() {
|
|
7455
7249
|
if (!this.persistPath) return;
|
|
7456
7250
|
try {
|
|
7457
|
-
const content = await
|
|
7251
|
+
const content = await fs5__namespace.readFile(this.persistPath, "utf-8");
|
|
7458
7252
|
const data = JSON.parse(content);
|
|
7459
7253
|
this.state.messages = data.messages || [];
|
|
7460
7254
|
this.state.totalTokens = data.totalTokens || 0;
|
|
@@ -7483,8 +7277,400 @@ ${summary}`,
|
|
|
7483
7277
|
}
|
|
7484
7278
|
};
|
|
7485
7279
|
|
|
7486
|
-
// src/
|
|
7487
|
-
|
|
7280
|
+
// src/services/index.ts
|
|
7281
|
+
init_cjs_shims();
|
|
7282
|
+
|
|
7283
|
+
// src/services/config.ts
|
|
7284
|
+
init_cjs_shims();
|
|
7285
|
+
var DEFAULT_CONFIG = {
|
|
7286
|
+
model: "GLM-5",
|
|
7287
|
+
apiKey: "",
|
|
7288
|
+
yolo: false,
|
|
7289
|
+
locale: "zh-CN",
|
|
7290
|
+
theme: "auto"
|
|
7291
|
+
};
|
|
7292
|
+
var ALGORITHM = "aes-256-gcm";
|
|
7293
|
+
var IV_LENGTH = 16;
|
|
7294
|
+
var KEY_DIR = ".sf-cli";
|
|
7295
|
+
var KEY_FILE = ".key";
|
|
7296
|
+
function getOrCreateEncryptionKey() {
|
|
7297
|
+
const keyPath = path5__namespace.join(os__namespace.homedir(), KEY_DIR, KEY_FILE);
|
|
7298
|
+
try {
|
|
7299
|
+
if (fs7__namespace.existsSync(keyPath)) {
|
|
7300
|
+
const keyBase64 = fs7__namespace.readFileSync(keyPath, "utf-8").trim();
|
|
7301
|
+
return Buffer.from(keyBase64, "base64");
|
|
7302
|
+
}
|
|
7303
|
+
} catch {
|
|
7304
|
+
}
|
|
7305
|
+
const key = crypto__namespace.randomBytes(32);
|
|
7306
|
+
try {
|
|
7307
|
+
const keyDir = path5__namespace.dirname(keyPath);
|
|
7308
|
+
if (!fs7__namespace.existsSync(keyDir)) {
|
|
7309
|
+
fs7__namespace.mkdirSync(keyDir, { recursive: true, mode: 448 });
|
|
7310
|
+
}
|
|
7311
|
+
fs7__namespace.writeFileSync(keyPath, key.toString("base64"), {
|
|
7312
|
+
mode: 384,
|
|
7313
|
+
// 仅所有者可读写
|
|
7314
|
+
encoding: "utf-8"
|
|
7315
|
+
});
|
|
7316
|
+
return key;
|
|
7317
|
+
} catch {
|
|
7318
|
+
const machineId = [
|
|
7319
|
+
os__namespace.hostname(),
|
|
7320
|
+
os__namespace.platform(),
|
|
7321
|
+
os__namespace.arch(),
|
|
7322
|
+
process.env.USERNAME || process.env.USER || "default"
|
|
7323
|
+
].join("-");
|
|
7324
|
+
return crypto__namespace.createHash("sha256").update(machineId).digest();
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
7327
|
+
var ConfigManager = class {
|
|
7328
|
+
config;
|
|
7329
|
+
configPath = "";
|
|
7330
|
+
projectPath = "";
|
|
7331
|
+
encryptionKey;
|
|
7332
|
+
constructor() {
|
|
7333
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
7334
|
+
this.encryptionKey = getOrCreateEncryptionKey();
|
|
7335
|
+
}
|
|
7336
|
+
async load(projectPath) {
|
|
7337
|
+
this.projectPath = projectPath;
|
|
7338
|
+
this.configPath = path5__namespace.join(projectPath, ".sf-cli", "config.json");
|
|
7339
|
+
try {
|
|
7340
|
+
const content = await fs5__namespace.readFile(this.configPath, "utf-8");
|
|
7341
|
+
const loaded = JSON.parse(content);
|
|
7342
|
+
if (loaded.apiKey && loaded.apiKeyEncrypted) {
|
|
7343
|
+
loaded.apiKey = this.decrypt(loaded.apiKey);
|
|
7344
|
+
delete loaded.apiKeyEncrypted;
|
|
7345
|
+
}
|
|
7346
|
+
this.config = { ...DEFAULT_CONFIG, ...loaded };
|
|
7347
|
+
} catch {
|
|
7348
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
7349
|
+
}
|
|
7350
|
+
}
|
|
7351
|
+
async save() {
|
|
7352
|
+
if (!this.configPath) return;
|
|
7353
|
+
const configToSave = { ...this.config };
|
|
7354
|
+
if (configToSave.apiKey) {
|
|
7355
|
+
const encrypted = this.encrypt(configToSave.apiKey);
|
|
7356
|
+
configToSave.apiKey = encrypted;
|
|
7357
|
+
configToSave.apiKeyEncrypted = true;
|
|
7358
|
+
}
|
|
7359
|
+
await fs5__namespace.mkdir(path5__namespace.dirname(this.configPath), { recursive: true });
|
|
7360
|
+
await fs5__namespace.writeFile(
|
|
7361
|
+
this.configPath,
|
|
7362
|
+
JSON.stringify(configToSave, null, 2),
|
|
7363
|
+
"utf-8"
|
|
7364
|
+
);
|
|
7365
|
+
}
|
|
7366
|
+
get(key) {
|
|
7367
|
+
return this.config[key];
|
|
7368
|
+
}
|
|
7369
|
+
set(key, value) {
|
|
7370
|
+
this.config[key] = value;
|
|
7371
|
+
}
|
|
7372
|
+
getAll() {
|
|
7373
|
+
return { ...this.config };
|
|
7374
|
+
}
|
|
7375
|
+
update(updates) {
|
|
7376
|
+
this.config = { ...this.config, ...updates };
|
|
7377
|
+
}
|
|
7378
|
+
/**
|
|
7379
|
+
* 加密敏感数据
|
|
7380
|
+
*/
|
|
7381
|
+
encrypt(text) {
|
|
7382
|
+
const iv = crypto__namespace.randomBytes(IV_LENGTH);
|
|
7383
|
+
const cipher = crypto__namespace.createCipheriv(ALGORITHM, this.encryptionKey, iv);
|
|
7384
|
+
let encrypted = cipher.update(text, "utf8", "base64");
|
|
7385
|
+
encrypted += cipher.final("base64");
|
|
7386
|
+
const authTag = cipher.getAuthTag();
|
|
7387
|
+
return `${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted}`;
|
|
7388
|
+
}
|
|
7389
|
+
/**
|
|
7390
|
+
* 解密敏感数据
|
|
7391
|
+
*/
|
|
7392
|
+
decrypt(encryptedData) {
|
|
7393
|
+
try {
|
|
7394
|
+
const parts = encryptedData.split(":");
|
|
7395
|
+
if (parts.length !== 3) {
|
|
7396
|
+
return encryptedData;
|
|
7397
|
+
}
|
|
7398
|
+
const [ivBase64, authTagBase64, encrypted] = parts;
|
|
7399
|
+
const iv = Buffer.from(ivBase64, "base64");
|
|
7400
|
+
const authTag = Buffer.from(authTagBase64, "base64");
|
|
7401
|
+
const decipher = crypto__namespace.createDecipheriv(ALGORITHM, this.encryptionKey, iv);
|
|
7402
|
+
decipher.setAuthTag(authTag);
|
|
7403
|
+
let decrypted = decipher.update(encrypted, "base64", "utf8");
|
|
7404
|
+
decrypted += decipher.final("utf8");
|
|
7405
|
+
return decrypted;
|
|
7406
|
+
} catch {
|
|
7407
|
+
return "";
|
|
7408
|
+
}
|
|
7409
|
+
}
|
|
7410
|
+
};
|
|
7411
|
+
|
|
7412
|
+
// src/services/model.ts
|
|
7413
|
+
init_cjs_shims();
|
|
7414
|
+
init_model();
|
|
7415
|
+
init_adapters();
|
|
7416
|
+
var ModelService = class {
|
|
7417
|
+
adapters = /* @__PURE__ */ new Map();
|
|
7418
|
+
currentConfig = null;
|
|
7419
|
+
stats;
|
|
7420
|
+
configManager;
|
|
7421
|
+
statsPath = "";
|
|
7422
|
+
constructor(configManager) {
|
|
7423
|
+
this.configManager = configManager;
|
|
7424
|
+
this.stats = {
|
|
7425
|
+
totalInput: 0,
|
|
7426
|
+
totalOutput: 0,
|
|
7427
|
+
byModel: {},
|
|
7428
|
+
byAgent: {},
|
|
7429
|
+
byDate: {}
|
|
7430
|
+
};
|
|
7431
|
+
}
|
|
7432
|
+
/**
|
|
7433
|
+
* 初始化服务
|
|
7434
|
+
*/
|
|
7435
|
+
async initialize(statsDir) {
|
|
7436
|
+
this.statsPath = path5__namespace.join(statsDir, "tokens");
|
|
7437
|
+
await this.loadStats();
|
|
7438
|
+
const model = this.configManager.get("model");
|
|
7439
|
+
const apiKey = this.configManager.get("apiKey");
|
|
7440
|
+
if (model && apiKey) {
|
|
7441
|
+
await this.configureModel({
|
|
7442
|
+
provider: this.getProviderFromModel(model),
|
|
7443
|
+
model,
|
|
7444
|
+
apiKey
|
|
7445
|
+
});
|
|
7446
|
+
}
|
|
7447
|
+
}
|
|
7448
|
+
/**
|
|
7449
|
+
* 配置模型
|
|
7450
|
+
*/
|
|
7451
|
+
async configureModel(config) {
|
|
7452
|
+
let adapter = this.adapters.get(config.provider);
|
|
7453
|
+
if (!adapter) {
|
|
7454
|
+
adapter = createAdapter(config.provider);
|
|
7455
|
+
this.adapters.set(config.provider, adapter);
|
|
7456
|
+
}
|
|
7457
|
+
await adapter.initialize(config);
|
|
7458
|
+
this.currentConfig = config;
|
|
7459
|
+
this.configManager.set("model", config.model);
|
|
7460
|
+
this.configManager.set("apiKey", config.apiKey);
|
|
7461
|
+
await this.configManager.save();
|
|
7462
|
+
}
|
|
7463
|
+
/**
|
|
7464
|
+
* 发送消息
|
|
7465
|
+
*/
|
|
7466
|
+
async sendMessage(messages, options) {
|
|
7467
|
+
if (!this.currentConfig) {
|
|
7468
|
+
throw new Error("\u6A21\u578B\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u6267\u884C /model \u547D\u4EE4");
|
|
7469
|
+
}
|
|
7470
|
+
const adapter = this.adapters.get(this.currentConfig.provider);
|
|
7471
|
+
if (!adapter || !adapter.isInitialized()) {
|
|
7472
|
+
throw new Error("\u6A21\u578B\u9002\u914D\u5668\u672A\u521D\u59CB\u5316");
|
|
7473
|
+
}
|
|
7474
|
+
try {
|
|
7475
|
+
const response = await adapter.sendMessage(messages, options);
|
|
7476
|
+
this.updateStats(response.usage, {
|
|
7477
|
+
model: this.currentConfig.model,
|
|
7478
|
+
agent: options?.agent
|
|
7479
|
+
});
|
|
7480
|
+
this.saveStats().catch(() => {
|
|
7481
|
+
});
|
|
7482
|
+
return response;
|
|
7483
|
+
} catch (error) {
|
|
7484
|
+
throw this.wrapError(error);
|
|
7485
|
+
}
|
|
7486
|
+
}
|
|
7487
|
+
/**
|
|
7488
|
+
* 流式发送消息
|
|
7489
|
+
*/
|
|
7490
|
+
async *streamMessage(messages, options) {
|
|
7491
|
+
if (!this.currentConfig) {
|
|
7492
|
+
throw new Error("\u6A21\u578B\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u6267\u884C /model \u547D\u4EE4");
|
|
7493
|
+
}
|
|
7494
|
+
const adapter = this.adapters.get(this.currentConfig.provider);
|
|
7495
|
+
if (!adapter || !adapter.isInitialized()) {
|
|
7496
|
+
throw new Error("\u6A21\u578B\u9002\u914D\u5668\u672A\u521D\u59CB\u5316");
|
|
7497
|
+
}
|
|
7498
|
+
let totalTokens = 0;
|
|
7499
|
+
try {
|
|
7500
|
+
for await (const chunk of adapter.streamMessage(messages, options)) {
|
|
7501
|
+
totalTokens += chunk.delta.length;
|
|
7502
|
+
yield chunk;
|
|
7503
|
+
}
|
|
7504
|
+
const inputTokens = adapter.countTokens(
|
|
7505
|
+
messages.map((m) => m.content).join("\n")
|
|
7506
|
+
);
|
|
7507
|
+
this.updateStats(
|
|
7508
|
+
{ inputTokens, outputTokens: Math.ceil(totalTokens / 4), totalTokens: 0 },
|
|
7509
|
+
{ model: this.currentConfig.model, agent: options?.agent }
|
|
7510
|
+
);
|
|
7511
|
+
this.saveStats().catch(() => {
|
|
7512
|
+
});
|
|
7513
|
+
} catch (error) {
|
|
7514
|
+
throw this.wrapError(error);
|
|
7515
|
+
}
|
|
7516
|
+
}
|
|
7517
|
+
/**
|
|
7518
|
+
* 计算Token数量
|
|
7519
|
+
*/
|
|
7520
|
+
countTokens(text) {
|
|
7521
|
+
if (!this.currentConfig) {
|
|
7522
|
+
return Math.ceil(text.length / 4);
|
|
7523
|
+
}
|
|
7524
|
+
const adapter = this.adapters.get(this.currentConfig.provider);
|
|
7525
|
+
return adapter?.countTokens(text) || Math.ceil(text.length / 4);
|
|
7526
|
+
}
|
|
7527
|
+
/**
|
|
7528
|
+
* 获取当前模型
|
|
7529
|
+
*/
|
|
7530
|
+
getCurrentModel() {
|
|
7531
|
+
return this.currentConfig?.model || null;
|
|
7532
|
+
}
|
|
7533
|
+
/**
|
|
7534
|
+
* 获取当前提供商
|
|
7535
|
+
*/
|
|
7536
|
+
getCurrentProvider() {
|
|
7537
|
+
return this.currentConfig?.provider || null;
|
|
7538
|
+
}
|
|
7539
|
+
/**
|
|
7540
|
+
* 获取Token统计
|
|
7541
|
+
*/
|
|
7542
|
+
getStats() {
|
|
7543
|
+
return { ...this.stats };
|
|
7544
|
+
}
|
|
7545
|
+
/**
|
|
7546
|
+
* 获取今日统计
|
|
7547
|
+
*/
|
|
7548
|
+
getTodayStats() {
|
|
7549
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7550
|
+
return this.stats.byDate[today] || { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
7551
|
+
}
|
|
7552
|
+
/**
|
|
7553
|
+
* 重置统计
|
|
7554
|
+
*/
|
|
7555
|
+
resetStats() {
|
|
7556
|
+
this.stats = {
|
|
7557
|
+
totalInput: 0,
|
|
7558
|
+
totalOutput: 0,
|
|
7559
|
+
byModel: {},
|
|
7560
|
+
byAgent: {},
|
|
7561
|
+
byDate: {}
|
|
7562
|
+
};
|
|
7563
|
+
this.saveStats().catch(() => {
|
|
7564
|
+
});
|
|
7565
|
+
}
|
|
7566
|
+
/**
|
|
7567
|
+
* 验证API Key
|
|
7568
|
+
*/
|
|
7569
|
+
async validateApiKey(provider, apiKey) {
|
|
7570
|
+
let adapter = this.adapters.get(provider);
|
|
7571
|
+
if (!adapter) {
|
|
7572
|
+
adapter = createAdapter(provider);
|
|
7573
|
+
}
|
|
7574
|
+
return adapter.validateApiKey(apiKey);
|
|
7575
|
+
}
|
|
7576
|
+
// ==================== 私有方法 ====================
|
|
7577
|
+
getProviderFromModel(modelId) {
|
|
7578
|
+
const info = getModelInfo(modelId);
|
|
7579
|
+
return info?.provider || "openai";
|
|
7580
|
+
}
|
|
7581
|
+
updateStats(usage, context) {
|
|
7582
|
+
this.stats.totalInput += usage.inputTokens;
|
|
7583
|
+
this.stats.totalOutput += usage.outputTokens;
|
|
7584
|
+
if (!this.stats.byModel[context.model]) {
|
|
7585
|
+
this.stats.byModel[context.model] = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
7586
|
+
}
|
|
7587
|
+
this.stats.byModel[context.model].inputTokens += usage.inputTokens;
|
|
7588
|
+
this.stats.byModel[context.model].outputTokens += usage.outputTokens;
|
|
7589
|
+
this.stats.byModel[context.model].totalTokens += usage.totalTokens;
|
|
7590
|
+
if (context.agent) {
|
|
7591
|
+
if (!this.stats.byAgent[context.agent]) {
|
|
7592
|
+
this.stats.byAgent[context.agent] = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
7593
|
+
}
|
|
7594
|
+
this.stats.byAgent[context.agent].inputTokens += usage.inputTokens;
|
|
7595
|
+
this.stats.byAgent[context.agent].outputTokens += usage.outputTokens;
|
|
7596
|
+
this.stats.byAgent[context.agent].totalTokens += usage.totalTokens;
|
|
7597
|
+
}
|
|
7598
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7599
|
+
if (!this.stats.byDate[today]) {
|
|
7600
|
+
this.stats.byDate[today] = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
7601
|
+
}
|
|
7602
|
+
this.stats.byDate[today].inputTokens += usage.inputTokens;
|
|
7603
|
+
this.stats.byDate[today].outputTokens += usage.outputTokens;
|
|
7604
|
+
this.stats.byDate[today].totalTokens += usage.totalTokens;
|
|
7605
|
+
}
|
|
7606
|
+
async loadStats() {
|
|
7607
|
+
if (!this.statsPath) return;
|
|
7608
|
+
try {
|
|
7609
|
+
const dailyPath = path5__namespace.join(this.statsPath, "daily.json");
|
|
7610
|
+
const totalPath = path5__namespace.join(this.statsPath, "total.json");
|
|
7611
|
+
const [daily, total] = await Promise.all([
|
|
7612
|
+
fs5__namespace.readFile(dailyPath, "utf-8").catch(() => "{}"),
|
|
7613
|
+
fs5__namespace.readFile(totalPath, "utf-8").catch(() => '{"totalInput":0,"totalOutput":0}')
|
|
7614
|
+
]);
|
|
7615
|
+
const dailyData = JSON.parse(daily);
|
|
7616
|
+
const totalData = JSON.parse(total);
|
|
7617
|
+
this.stats.byDate = dailyData;
|
|
7618
|
+
this.stats.totalInput = totalData.totalInput || 0;
|
|
7619
|
+
this.stats.totalOutput = totalData.totalOutput || 0;
|
|
7620
|
+
} catch {
|
|
7621
|
+
}
|
|
7622
|
+
}
|
|
7623
|
+
async saveStats() {
|
|
7624
|
+
if (!this.statsPath) return;
|
|
7625
|
+
try {
|
|
7626
|
+
await fs5__namespace.mkdir(this.statsPath, { recursive: true });
|
|
7627
|
+
const dailyPath = path5__namespace.join(this.statsPath, "daily.json");
|
|
7628
|
+
const totalPath = path5__namespace.join(this.statsPath, "total.json");
|
|
7629
|
+
await Promise.all([
|
|
7630
|
+
fs5__namespace.writeFile(dailyPath, JSON.stringify(this.stats.byDate, null, 2)),
|
|
7631
|
+
fs5__namespace.writeFile(totalPath, JSON.stringify({
|
|
7632
|
+
totalInput: this.stats.totalInput,
|
|
7633
|
+
totalOutput: this.stats.totalOutput
|
|
7634
|
+
}))
|
|
7635
|
+
]);
|
|
7636
|
+
} catch {
|
|
7637
|
+
}
|
|
7638
|
+
}
|
|
7639
|
+
wrapError(error) {
|
|
7640
|
+
if (error instanceof ModelError) {
|
|
7641
|
+
return error;
|
|
7642
|
+
}
|
|
7643
|
+
const err = error;
|
|
7644
|
+
if (err.message.includes("timeout")) {
|
|
7645
|
+
return new ModelError("TIMEOUT", err.message, { retryable: true });
|
|
7646
|
+
}
|
|
7647
|
+
if (err.message.includes("network") || err.message.includes("ECONNREFUSED")) {
|
|
7648
|
+
return new ModelError("NETWORK_ERROR", err.message, { retryable: true });
|
|
7649
|
+
}
|
|
7650
|
+
return new ModelError("UNKNOWN_ERROR", err.message, { retryable: false });
|
|
7651
|
+
}
|
|
7652
|
+
};
|
|
7653
|
+
|
|
7654
|
+
// src/services/index.ts
|
|
7655
|
+
init_adapters();
|
|
7656
|
+
|
|
7657
|
+
// src/services/mcp/index.ts
|
|
7658
|
+
init_cjs_shims();
|
|
7659
|
+
|
|
7660
|
+
// src/types/mcp.ts
|
|
7661
|
+
init_cjs_shims();
|
|
7662
|
+
|
|
7663
|
+
// src/services/mcp/base.ts
|
|
7664
|
+
init_cjs_shims();
|
|
7665
|
+
|
|
7666
|
+
// src/services/mcp/lanhu.ts
|
|
7667
|
+
init_cjs_shims();
|
|
7668
|
+
|
|
7669
|
+
// src/services/mcp/figma.ts
|
|
7670
|
+
init_cjs_shims();
|
|
7671
|
+
|
|
7672
|
+
// src/services/mcp/manager.ts
|
|
7673
|
+
init_cjs_shims();
|
|
7488
7674
|
|
|
7489
7675
|
// src/cli/status.ts
|
|
7490
7676
|
init_cjs_shims();
|
|
@@ -7497,6 +7683,7 @@ var STEP_DISPLAY_NAMES = {
|
|
|
7497
7683
|
"archive": "\u5F52\u6863",
|
|
7498
7684
|
"context": "\u4E0A\u4E0B\u6587",
|
|
7499
7685
|
"clarify": "\u6F84\u6E05",
|
|
7686
|
+
"reference": "\u53C2\u8003\u5206\u6790",
|
|
7500
7687
|
"analysis": "\u5206\u6790",
|
|
7501
7688
|
"bdd": "BDD",
|
|
7502
7689
|
"spec": "\u89C4\u683C",
|
|
@@ -7513,6 +7700,7 @@ var STEP_COLORS = {
|
|
|
7513
7700
|
"archive": chalk9__default.default.gray,
|
|
7514
7701
|
"context": chalk9__default.default.magenta,
|
|
7515
7702
|
"clarify": chalk9__default.default.yellow,
|
|
7703
|
+
"reference": chalk9__default.default.blue,
|
|
7516
7704
|
"analysis": chalk9__default.default.blue,
|
|
7517
7705
|
"bdd": chalk9__default.default.cyan,
|
|
7518
7706
|
"spec": chalk9__default.default.green,
|
|
@@ -7649,17 +7837,17 @@ var Completer = class {
|
|
|
7649
7837
|
prefix = filePath;
|
|
7650
7838
|
} else {
|
|
7651
7839
|
const dir = filePath.slice(0, lastSlash);
|
|
7652
|
-
dirPath =
|
|
7840
|
+
dirPath = path5__namespace.resolve(this.workingDirectory, dir);
|
|
7653
7841
|
prefix = filePath.slice(lastSlash + 1);
|
|
7654
7842
|
}
|
|
7655
7843
|
try {
|
|
7656
|
-
const entries = await
|
|
7844
|
+
const entries = await fs5__namespace.readdir(dirPath, { withFileTypes: true });
|
|
7657
7845
|
const matches = [];
|
|
7658
7846
|
for (const entry of entries) {
|
|
7659
7847
|
if (entry.name.startsWith(prefix)) {
|
|
7660
7848
|
const isDir = entry.isDirectory();
|
|
7661
7849
|
matches.push({
|
|
7662
|
-
text: "@" +
|
|
7850
|
+
text: "@" + path5__namespace.relative(this.workingDirectory, path5__namespace.join(dirPath, entry.name)).replace(/\\/g, "/") + (isDir ? "/" : ""),
|
|
7663
7851
|
displayText: entry.name + (isDir ? "/" : ""),
|
|
7664
7852
|
description: isDir ? "\u76EE\u5F55" : "\u6587\u4EF6",
|
|
7665
7853
|
type: isDir ? "directory" : "file"
|
|
@@ -7732,10 +7920,10 @@ var Completer = class {
|
|
|
7732
7920
|
// src/cli/repl.ts
|
|
7733
7921
|
init_new();
|
|
7734
7922
|
async function startInteractiveMode(options) {
|
|
7735
|
-
const historyFile =
|
|
7923
|
+
const historyFile = path5__namespace.join(options.workingDirectory, ".sf-cli", "history.json");
|
|
7736
7924
|
let history = [];
|
|
7737
7925
|
try {
|
|
7738
|
-
const historyData = await
|
|
7926
|
+
const historyData = await fs5__namespace.readFile(historyFile, "utf-8");
|
|
7739
7927
|
history = JSON.parse(historyData);
|
|
7740
7928
|
} catch {
|
|
7741
7929
|
}
|
|
@@ -7768,11 +7956,11 @@ async function startInteractiveMode(options) {
|
|
|
7768
7956
|
const modelService = new ModelService(configManager);
|
|
7769
7957
|
const normsManager = new NormsManager({
|
|
7770
7958
|
projectPath: options.workingDirectory,
|
|
7771
|
-
normsDir:
|
|
7959
|
+
normsDir: path5__namespace.join(options.workingDirectory, ".sf-cli", "norms")
|
|
7772
7960
|
});
|
|
7773
7961
|
await configManager.load(options.workingDirectory);
|
|
7774
7962
|
await contextManager.initialize(options.workingDirectory);
|
|
7775
|
-
const statsDir =
|
|
7963
|
+
const statsDir = path5__namespace.join(options.workingDirectory, ".sf-cli");
|
|
7776
7964
|
await modelService.initialize(statsDir);
|
|
7777
7965
|
await normsManager.initialize();
|
|
7778
7966
|
const state = {
|
|
@@ -7822,8 +8010,8 @@ async function startInteractiveMode(options) {
|
|
|
7822
8010
|
}
|
|
7823
8011
|
const saveHistory = async () => {
|
|
7824
8012
|
try {
|
|
7825
|
-
await
|
|
7826
|
-
await
|
|
8013
|
+
await fs5__namespace.mkdir(path5__namespace.dirname(historyFile), { recursive: true });
|
|
8014
|
+
await fs5__namespace.writeFile(historyFile, JSON.stringify(history.slice(-500)));
|
|
7827
8015
|
} catch {
|
|
7828
8016
|
}
|
|
7829
8017
|
};
|
|
@@ -7894,16 +8082,15 @@ async function startInteractiveMode(options) {
|
|
|
7894
8082
|
}
|
|
7895
8083
|
|
|
7896
8084
|
// src/cli/index.ts
|
|
7897
|
-
init_config();
|
|
7898
8085
|
function getPackageJson() {
|
|
7899
8086
|
const possiblePaths = [
|
|
7900
|
-
|
|
7901
|
-
|
|
8087
|
+
path5__namespace.resolve(__dirname, "..", "..", "package.json"),
|
|
8088
|
+
path5__namespace.resolve(__dirname, "..", "..", "..", "package.json")
|
|
7902
8089
|
];
|
|
7903
8090
|
for (const pkgPath of possiblePaths) {
|
|
7904
8091
|
try {
|
|
7905
|
-
if (
|
|
7906
|
-
const content =
|
|
8092
|
+
if (fs7__namespace.existsSync(pkgPath)) {
|
|
8093
|
+
const content = fs7__namespace.readFileSync(pkgPath, "utf-8");
|
|
7907
8094
|
return JSON.parse(content);
|
|
7908
8095
|
}
|
|
7909
8096
|
} catch {
|
|
@@ -7932,7 +8119,7 @@ commander.program.command("init").description("\u521D\u59CB\u5316\u63A5\u7BA1\u9
|
|
|
7932
8119
|
commander.program.command("model").description("\u9009\u62E9AI\u6A21\u578B").action(async () => {
|
|
7933
8120
|
const configManager = new ConfigManager();
|
|
7934
8121
|
await configManager.load(process.cwd());
|
|
7935
|
-
const { selectModel: selectModel2 } = await Promise.resolve().then(() => (
|
|
8122
|
+
const { selectModel: selectModel2 } = await Promise.resolve().then(() => (init_model2(), model_exports));
|
|
7936
8123
|
await selectModel2(configManager);
|
|
7937
8124
|
});
|
|
7938
8125
|
commander.program.command("update").description("\u66F4\u65B0CLI\u5230\u6700\u65B0\u7248\u672C").action(async () => {
|