mcp-osp-prompt 1.0.0 → 1.0.2
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/README.md +22 -78
- package/config.js +12 -17
- package/debug-env.js +7 -0
- package/dialog/browser.js +2 -2
- package/dialog/constants.js +0 -2
- package/dialog/native.js +23 -34
- package/dialog/objc.js +2 -2
- package/fetcher.js +1 -1
- package/package.json +5 -5
- package/platform-utils.js +18 -45
- package/prompt-manager.js +89 -534
- package/server.js +58 -15
- package/test.js +2 -2
- package/tools.js +14 -147
- package/resource-manager.js +0 -358
package/prompt-manager.js
CHANGED
|
@@ -15,9 +15,8 @@ class PromptManager {
|
|
|
15
15
|
this.cacheDir = process.env.CACHE_DIR || path.join(process.env.HOME || '.', '.cache', 'prompts');
|
|
16
16
|
this.ttl = parseInt(process.env.CACHE_TTL_SEC || '10800', 10) * 1000; // 转为毫秒,默认3小时
|
|
17
17
|
this.force = (process.env.FORCE_UPDATE || 'false').toLowerCase() === 'true';
|
|
18
|
-
this.token = process.env.GIT_TOKEN || process.env.GITLAB_TOKEN || process.env.GITHUB_TOKEN; // 支持多种token环境变量
|
|
19
18
|
this.platform = null;
|
|
20
|
-
this.
|
|
19
|
+
this.promptPath = null;
|
|
21
20
|
this.toolsConfig = [];
|
|
22
21
|
this.isInitialized = false;
|
|
23
22
|
}
|
|
@@ -33,74 +32,21 @@ class PromptManager {
|
|
|
33
32
|
|
|
34
33
|
console.error('[PromptManager] Starting synchronous initialization...');
|
|
35
34
|
|
|
36
|
-
// 🟢 GREEN: Task 1.1 - 确保
|
|
37
|
-
this.
|
|
38
|
-
if (!this.
|
|
39
|
-
throw new Error('
|
|
35
|
+
// 🟢 GREEN: Task 1.1 - 确保promptPath正确初始化
|
|
36
|
+
this.promptPath = process.env.PROMPT_PATH;
|
|
37
|
+
if (!this.promptPath) {
|
|
38
|
+
throw new Error('PROMPT_PATH environment variable is required');
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
this.platform = detectPlatformFromPath(this.
|
|
43
|
-
console.error(`[PromptManager] Platform: ${this.platform}, Path: ${this.
|
|
41
|
+
this.platform = detectPlatformFromPath(this.promptPath);
|
|
42
|
+
console.error(`[PromptManager] Platform: ${this.platform}, Path: ${this.promptPath}`);
|
|
44
43
|
|
|
45
|
-
//
|
|
44
|
+
// 确保缓存目录存在
|
|
46
45
|
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
47
|
-
await fs.mkdir(path.join(this.cacheDir, 'projects'), { recursive: true });
|
|
48
|
-
await fs.mkdir(path.join(this.cacheDir, 'rules'), { recursive: true });
|
|
49
46
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// 先尝试读取缓存
|
|
55
|
-
const cachedResources = await this.readCachedMultiDirectoryResources();
|
|
56
|
-
const TTL_12_HOURS = 12 * 60 * 60 * 1000; // 12小时
|
|
57
|
-
|
|
58
|
-
if (cachedResources && cachedResources.lastSync) {
|
|
59
|
-
const cacheAge = Date.now() - new Date(cachedResources.lastSync).getTime();
|
|
60
|
-
|
|
61
|
-
if (cacheAge < TTL_12_HOURS && !this.force) {
|
|
62
|
-
// 缓存未过期,直接使用
|
|
63
|
-
console.error(`[PromptManager] ✅ Using cached resources (age: ${Math.round(cacheAge / 1000 / 60 / 60)}h)`);
|
|
64
|
-
this.toolsConfig = this.generateToolsConfig(cachedResources.prompts);
|
|
65
|
-
} else {
|
|
66
|
-
// 缓存过期,先使用缓存,然后异步更新
|
|
67
|
-
console.error(`[PromptManager] ⏳ Using cached resources (age: ${Math.round(cacheAge / 1000 / 60 / 60)}h), triggering async update...`);
|
|
68
|
-
this.toolsConfig = this.generateToolsConfig(cachedResources.prompts);
|
|
69
|
-
|
|
70
|
-
// 异步更新(不阻塞)
|
|
71
|
-
this.fetchMultiDirectoryResources().then(() => {
|
|
72
|
-
console.error('[PromptManager] ✅ Async resource update complete');
|
|
73
|
-
}).catch(error => {
|
|
74
|
-
console.warn(`[PromptManager] ⚠️ Async resource update failed: ${error.message}`);
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
// 无缓存或缓存损坏,必须同步拉取
|
|
79
|
-
console.error('[PromptManager] 🔄 No cache found, fetching resources...');
|
|
80
|
-
try {
|
|
81
|
-
const resources = await this.fetchMultiDirectoryResources();
|
|
82
|
-
console.error(`[PromptManager] ✅ Resource fetch complete: ${resources.prompts.length} prompts, ${resources.projects.length} projects, ${resources.rules.length} rules`);
|
|
83
|
-
this.toolsConfig = this.generateToolsConfig(resources.prompts);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error(`[PromptManager] ❌ Resource fetch failed: ${error.message}`);
|
|
86
|
-
// 最后的fallback:使用旧的单目录逻辑
|
|
87
|
-
const promptFiles = await this.getPromptFilesList();
|
|
88
|
-
this.toolsConfig = this.generateToolsConfig(promptFiles);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
// 本地模式:支持多目录资源
|
|
93
|
-
try {
|
|
94
|
-
console.error('[PromptManager] Local mode: Scanning multi-directory resources...');
|
|
95
|
-
const resources = await this.fetchLocalMultiDirectoryResources();
|
|
96
|
-
console.error(`[PromptManager] ✅ Local scan complete: ${resources.prompts.length} prompts, ${resources.projects.length} projects, ${resources.rules.length} rules`);
|
|
97
|
-
this.toolsConfig = this.generateToolsConfig(resources.prompts);
|
|
98
|
-
} catch (error) {
|
|
99
|
-
console.warn(`[PromptManager] ⚠️ Local multi-directory scan failed: ${error.message}, using single directory mode`);
|
|
100
|
-
const promptFiles = await this.getPromptFilesList();
|
|
101
|
-
this.toolsConfig = this.generateToolsConfig(promptFiles);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
47
|
+
// 获取prompt文件列表并生成工具配置
|
|
48
|
+
const promptFiles = await this.getPromptFilesList();
|
|
49
|
+
this.toolsConfig = this.generateToolsConfig(promptFiles);
|
|
104
50
|
|
|
105
51
|
// 不再验证核心工具内容可用性,仅确保工具配置存在
|
|
106
52
|
this.validateToolsConfigExists();
|
|
@@ -127,7 +73,7 @@ class PromptManager {
|
|
|
127
73
|
*/
|
|
128
74
|
async scanLocalPromptFiles() {
|
|
129
75
|
try {
|
|
130
|
-
const files = await fs.readdir(this.
|
|
76
|
+
const files = await fs.readdir(this.promptPath);
|
|
131
77
|
const promptFiles = files
|
|
132
78
|
.filter(file => file.endsWith('.prompt.md'))
|
|
133
79
|
.map(filename => {
|
|
@@ -190,9 +136,9 @@ class PromptManager {
|
|
|
190
136
|
const fileListCache = path.join(this.cacheDir, 'file-list.json');
|
|
191
137
|
|
|
192
138
|
console.error('[PromptManager] Fetching remote file list synchronously...');
|
|
193
|
-
const pathInfo = parseRemotePath(this.
|
|
139
|
+
const pathInfo = parseRemotePath(this.promptPath);
|
|
194
140
|
if (!pathInfo) {
|
|
195
|
-
throw new Error(`Unable to parse
|
|
141
|
+
throw new Error(`Unable to parse PROMPT_PATH: ${this.promptPath}`);
|
|
196
142
|
}
|
|
197
143
|
|
|
198
144
|
try {
|
|
@@ -238,45 +184,18 @@ class PromptManager {
|
|
|
238
184
|
}
|
|
239
185
|
|
|
240
186
|
/**
|
|
241
|
-
*
|
|
242
|
-
* @param {Object} pathInfo - 路径信息
|
|
243
|
-
* @param {String} filePattern - 文件模式 (*.prompt.md, *.md, *-code-rules.md)
|
|
244
|
-
* @param {String} dirType - 目录类型 (prompts, projects, rules)
|
|
245
|
-
* @returns {Array} 文件列表
|
|
246
|
-
*/
|
|
247
|
-
async fetchDirectoryFiles(pathInfo, filePattern, dirType = 'prompts') {
|
|
248
|
-
if (!pathInfo || !filePattern) {
|
|
249
|
-
throw new Error('pathInfo and filePattern are required');
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const platform = pathInfo.platform;
|
|
253
|
-
console.error(`[PromptManager] Fetching ${dirType} files (pattern: ${filePattern}) from ${platform}...`);
|
|
254
|
-
|
|
255
|
-
// 根据平台调用不同的API
|
|
256
|
-
let files = [];
|
|
257
|
-
if (platform === 'gitlab') {
|
|
258
|
-
files = await this.fetchGitLabDirectory(pathInfo);
|
|
259
|
-
} else if (platform === 'github') {
|
|
260
|
-
files = await this.fetchGitHubDirectory(pathInfo);
|
|
261
|
-
} else {
|
|
262
|
-
throw new Error(`Unsupported platform: ${platform}`);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// 根据文件模式过滤和映射
|
|
266
|
-
return this.filterAndMapFiles(files, filePattern, dirType, pathInfo);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* 🟢 GREEN: Task 1.2 - 从GitLab获取目录文件(原始数据)
|
|
187
|
+
* 从GitLab获取文件列表
|
|
271
188
|
*/
|
|
272
|
-
async
|
|
189
|
+
async fetchGitLabFileList(pathInfo) {
|
|
273
190
|
const headers = createAuthHeaders('gitlab', process.env.GIT_TOKEN);
|
|
274
191
|
console.error(`[PromptManager] GitLab API call: ${pathInfo.apiUrl}`);
|
|
275
192
|
const requestOptions = { headers, timeout: 10000 };
|
|
276
193
|
const response = await fetch(pathInfo.apiUrl, requestOptions);
|
|
277
194
|
|
|
278
195
|
if (!response.ok) {
|
|
196
|
+
// 构建详细的调试信息,隐藏敏感token信息
|
|
279
197
|
const debugHeaders = { ...headers };
|
|
198
|
+
|
|
280
199
|
const requestInfo = `
|
|
281
200
|
REQUEST INFO: \n
|
|
282
201
|
- URL: ${pathInfo.apiUrl} \n
|
|
@@ -288,13 +207,24 @@ class PromptManager {
|
|
|
288
207
|
throw new Error(`GitLab API error: ${requestInfo}`);
|
|
289
208
|
}
|
|
290
209
|
|
|
291
|
-
|
|
210
|
+
const files = await response.json();
|
|
211
|
+
return files
|
|
212
|
+
.filter(file => file.type === 'blob' && file.name.endsWith('.prompt.md'))
|
|
213
|
+
.map(file => {
|
|
214
|
+
const baseName = file.name.replace('.prompt.md', '');
|
|
215
|
+
return {
|
|
216
|
+
filename: file.name,
|
|
217
|
+
toolName: `dev-${baseName}`,
|
|
218
|
+
source: `${pathInfo.path ? pathInfo.path + '/' : ''}${file.name}`,
|
|
219
|
+
platform: 'gitlab'
|
|
220
|
+
};
|
|
221
|
+
});
|
|
292
222
|
}
|
|
293
223
|
|
|
294
224
|
/**
|
|
295
|
-
*
|
|
225
|
+
* 从GitHub获取文件列表
|
|
296
226
|
*/
|
|
297
|
-
async
|
|
227
|
+
async fetchGitHubFileList(pathInfo) {
|
|
298
228
|
const url = `${pathInfo.apiUrl}?ref=${pathInfo.branch}`;
|
|
299
229
|
const headers = createAuthHeaders('github', process.env.GIT_TOKEN);
|
|
300
230
|
console.error(`[PromptManager] GitHub API call: ${url}`);
|
|
@@ -302,6 +232,7 @@ class PromptManager {
|
|
|
302
232
|
const response = await fetch(url, requestOptions);
|
|
303
233
|
|
|
304
234
|
if (!response.ok) {
|
|
235
|
+
// 构建详细的调试信息,隐藏敏感token信息
|
|
305
236
|
const debugHeaders = { ...headers };
|
|
306
237
|
const requestInfo = `
|
|
307
238
|
REQUEST INFO: \n
|
|
@@ -311,390 +242,22 @@ class PromptManager {
|
|
|
311
242
|
- Timeout: ${requestOptions.timeout}ms \n
|
|
312
243
|
- Response Status: ${response.status} \n
|
|
313
244
|
- Response StatusText: ${response.statusText} \n`;
|
|
245
|
+
|
|
314
246
|
throw new Error(`GitHub API error: ${requestInfo}`);
|
|
315
247
|
}
|
|
316
248
|
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* 🟢 GREEN: Task 1.2 - 根据文件模式过滤和映射文件
|
|
322
|
-
*/
|
|
323
|
-
filterAndMapFiles(files, filePattern, dirType, pathInfo) {
|
|
324
|
-
// 转换filePattern为正则表达式 - 先转义点,再替换星号
|
|
325
|
-
const pattern = filePattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
|
|
326
|
-
const regex = new RegExp(`^${pattern}$`);
|
|
327
|
-
|
|
328
|
-
const platform = pathInfo.platform;
|
|
329
|
-
const fileTypeKey = platform === 'gitlab' ? 'blob' : 'file';
|
|
330
|
-
|
|
249
|
+
const files = await response.json();
|
|
331
250
|
return files
|
|
332
|
-
.filter(file =>
|
|
333
|
-
const isFile = platform === 'gitlab' ? file.type === 'blob' : file.type === 'file';
|
|
334
|
-
return isFile && regex.test(file.name);
|
|
335
|
-
})
|
|
251
|
+
.filter(file => file.type === 'file' && file.name.endsWith('.prompt.md'))
|
|
336
252
|
.map(file => {
|
|
337
|
-
const
|
|
253
|
+
const baseName = file.name.replace('.prompt.md', '');
|
|
254
|
+
return {
|
|
338
255
|
filename: file.name,
|
|
339
|
-
|
|
340
|
-
|
|
256
|
+
toolName: `dev-${baseName}`,
|
|
257
|
+
source: `${pathInfo.path ? pathInfo.path + '/' : ''}${file.name}`,
|
|
258
|
+
platform: 'github'
|
|
341
259
|
};
|
|
342
|
-
|
|
343
|
-
// 根据目录类型添加额外信息
|
|
344
|
-
if (dirType === 'prompts') {
|
|
345
|
-
const baseName = file.name.replace('.prompt.md', '');
|
|
346
|
-
mapped.toolName = `dev-${baseName}`;
|
|
347
|
-
mapped.source = mapped.path;
|
|
348
|
-
} else if (dirType === 'rules') {
|
|
349
|
-
// 提取语言名称 (java-code-rules.md -> java)
|
|
350
|
-
const langMatch = file.name.match(/^(.+)-code-rules\.md$/);
|
|
351
|
-
if (langMatch) {
|
|
352
|
-
mapped.language = langMatch[1];
|
|
353
|
-
}
|
|
354
|
-
} else if (dirType === 'projects') {
|
|
355
|
-
// projects目录,保留原始文件名
|
|
356
|
-
mapped.type = 'project-doc';
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return mapped;
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* 🟢 GREEN: Task 1.2 - 从GitLab获取文件列表(重构为使用通用函数)
|
|
365
|
-
*/
|
|
366
|
-
async fetchGitLabFileList(pathInfo) {
|
|
367
|
-
return await this.fetchDirectoryFiles(pathInfo, '*.prompt.md', 'prompts');
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* 🟢 GREEN: Task 1.2 - 从GitHub获取文件列表(重构为使用通用函数)
|
|
372
|
-
*/
|
|
373
|
-
async fetchGitHubFileList(pathInfo) {
|
|
374
|
-
return await this.fetchDirectoryFiles(pathInfo, '*.prompt.md', 'prompts');
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* 🟢 GREEN: Task 1.4 - 缓存多目录资源
|
|
379
|
-
* @param {Object} resources - 资源对象 { prompts, projects, rules, lastSync }
|
|
380
|
-
*/
|
|
381
|
-
async cacheMultiDirectoryResources(resources) {
|
|
382
|
-
const cacheFile = path.join(this.cacheDir, 'resource-cache.json');
|
|
383
|
-
|
|
384
|
-
try {
|
|
385
|
-
await fs.writeFile(cacheFile, JSON.stringify(resources, null, 2), 'utf8');
|
|
386
|
-
console.error(`[PromptManager] ✅ Resources cached to ${cacheFile}`);
|
|
387
|
-
|
|
388
|
-
// 同时保留旧格式的file-list.json用于向后兼容
|
|
389
|
-
const legacyCache = path.join(this.cacheDir, 'file-list.json');
|
|
390
|
-
await fs.writeFile(legacyCache, JSON.stringify(resources.prompts, null, 2), 'utf8');
|
|
391
|
-
|
|
392
|
-
} catch (error) {
|
|
393
|
-
console.warn(`[PromptManager] ⚠️ Failed to cache resources: ${error.message}`);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* 🟢 GREEN: Task 1.4 - 读取缓存的多目录资源
|
|
399
|
-
* @returns {Object|null} 资源对象或null
|
|
400
|
-
*/
|
|
401
|
-
async readCachedMultiDirectoryResources() {
|
|
402
|
-
const cacheFile = path.join(this.cacheDir, 'resource-cache.json');
|
|
403
|
-
|
|
404
|
-
try {
|
|
405
|
-
const data = await fs.readFile(cacheFile, 'utf8');
|
|
406
|
-
const resources = JSON.parse(data);
|
|
407
|
-
|
|
408
|
-
// 验证缓存结构
|
|
409
|
-
if (resources && resources.prompts && resources.projects && resources.rules) {
|
|
410
|
-
console.error(`[PromptManager] ✅ Loaded cached resources (${resources.prompts.length + resources.projects.length + resources.rules.length} total files)`);
|
|
411
|
-
return resources;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
return null;
|
|
415
|
-
} catch (error) {
|
|
416
|
-
console.warn(`[PromptManager] No valid resource cache found: ${error.message}`);
|
|
417
|
-
return null;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* 🟢 GREEN: Task 1.4 - 检查缓存是否过期
|
|
423
|
-
* @param {Object} resources - 资源对象
|
|
424
|
-
* @returns {boolean} true表示过期
|
|
425
|
-
*/
|
|
426
|
-
isCacheExpired(resources) {
|
|
427
|
-
if (!resources || !resources.lastSync) {
|
|
428
|
-
return true;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const lastSyncTime = new Date(resources.lastSync).getTime();
|
|
432
|
-
const now = Date.now();
|
|
433
|
-
const ttlMs = this.ttl * 1000;
|
|
434
|
-
const age = now - lastSyncTime;
|
|
435
|
-
|
|
436
|
-
const expired = age >= ttlMs;
|
|
437
|
-
console.error(`[PromptManager] Cache age: ${Math.round(age / 1000)}s, TTL: ${this.ttl}s, Expired: ${expired}`);
|
|
438
|
-
|
|
439
|
-
return expired;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* 🟢 GREEN: Task 1.3 - 多目录资源拉取主函数
|
|
444
|
-
* @returns {Object} { prompts: [], projects: [], rules: [], lastSync: ISO时间 }
|
|
445
|
-
*/
|
|
446
|
-
async fetchMultiDirectoryResources() {
|
|
447
|
-
const directories = [
|
|
448
|
-
{ name: 'prompts', pattern: '*.prompt.md', type: 'prompts' },
|
|
449
|
-
{ name: 'projects', pattern: '*.md', type: 'projects' },
|
|
450
|
-
{ name: 'rules', pattern: '*-rules.md', type: 'rules' }
|
|
451
|
-
];
|
|
452
|
-
|
|
453
|
-
const results = {
|
|
454
|
-
prompts: [],
|
|
455
|
-
projects: [],
|
|
456
|
-
rules: [],
|
|
457
|
-
lastSync: new Date().toISOString()
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
console.error('[PromptManager] Fetching multi-directory resources...');
|
|
461
|
-
|
|
462
|
-
// 依次拉取各个目录(错误容错,单个失败不影响其他)
|
|
463
|
-
for (const dir of directories) {
|
|
464
|
-
try {
|
|
465
|
-
console.error(`[PromptManager] Fetching ${dir.name} directory...`);
|
|
466
|
-
|
|
467
|
-
// 使用parseRemotePath的子目录功能
|
|
468
|
-
const pathInfo = parseRemotePath(this.resourcePath, dir.name);
|
|
469
|
-
|
|
470
|
-
// 使用通用扫描函数
|
|
471
|
-
const files = await this.fetchDirectoryFiles(pathInfo, dir.pattern, dir.type);
|
|
472
|
-
|
|
473
|
-
results[dir.type] = files;
|
|
474
|
-
console.error(`[PromptManager] ✅ ${dir.name}: ${files.length} files`);
|
|
475
|
-
|
|
476
|
-
} catch (error) {
|
|
477
|
-
console.warn(`[PromptManager] ⚠️ Failed to fetch ${dir.name}: ${error.message}`);
|
|
478
|
-
results[dir.type] = []; // 失败时使用空数组
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
console.error(`[PromptManager] Multi-directory fetch complete. Total: ${results.prompts.length + results.projects.length + results.rules.length} files`);
|
|
483
|
-
|
|
484
|
-
// 🟢 GREEN: Task 3.1 - 保存文件内容到子目录
|
|
485
|
-
await this.saveResourceContents(results);
|
|
486
|
-
|
|
487
|
-
// 🟢 GREEN: Task 1.4 - 自动缓存结果
|
|
488
|
-
await this.cacheMultiDirectoryResources(results);
|
|
489
|
-
|
|
490
|
-
return results;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* 🟢 GREEN: 本地多目录资源扫描
|
|
495
|
-
* @returns {Object} { prompts: [], projects: [], rules: [], lastSync: ISO时间 }
|
|
496
|
-
*/
|
|
497
|
-
async fetchLocalMultiDirectoryResources() {
|
|
498
|
-
// 假设 RESOURCE_PATH 指向包含 prompts/projects/rules 子目录的基础目录
|
|
499
|
-
// 或者直接指向 prompts 目录,此时需要找到父目录
|
|
500
|
-
|
|
501
|
-
let baseDir = this.resourcePath;
|
|
502
|
-
|
|
503
|
-
// 如果 RESOURCE_PATH 直接指向 prompts 目录,找到父目录
|
|
504
|
-
if (baseDir.endsWith('/prompts') || baseDir.endsWith('\\prompts')) {
|
|
505
|
-
baseDir = path.dirname(baseDir);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
const directories = [
|
|
509
|
-
{ name: 'prompts', pattern: /\.prompt\.md$/, type: 'prompts' },
|
|
510
|
-
{ name: 'projects', pattern: /\.md$/, type: 'projects' },
|
|
511
|
-
{ name: 'rules', pattern: /-rules\.md$/, type: 'rules' }
|
|
512
|
-
];
|
|
513
|
-
|
|
514
|
-
const results = {
|
|
515
|
-
prompts: [],
|
|
516
|
-
projects: [],
|
|
517
|
-
rules: [],
|
|
518
|
-
lastSync: new Date().toISOString()
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
console.error(`[PromptManager] Scanning local directories from base: ${baseDir}`);
|
|
522
|
-
|
|
523
|
-
for (const dir of directories) {
|
|
524
|
-
try {
|
|
525
|
-
const dirPath = path.join(baseDir, dir.name);
|
|
526
|
-
console.error(`[PromptManager] Scanning ${dir.name} directory: ${dirPath}`);
|
|
527
|
-
|
|
528
|
-
// 检查目录是否存在
|
|
529
|
-
try {
|
|
530
|
-
await fs.access(dirPath);
|
|
531
|
-
} catch {
|
|
532
|
-
console.warn(`[PromptManager] ⚠️ Directory not found: ${dirPath}`);
|
|
533
|
-
results[dir.type] = [];
|
|
534
|
-
continue;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const files = await fs.readdir(dirPath);
|
|
538
|
-
const matchedFiles = files
|
|
539
|
-
.filter(file => dir.pattern.test(file))
|
|
540
|
-
.map(filename => {
|
|
541
|
-
const fileInfo = {
|
|
542
|
-
filename,
|
|
543
|
-
type: dir.type,
|
|
544
|
-
path: path.join(dirPath, filename)
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
// 为 prompts 类型添加额外的工具信息
|
|
548
|
-
if (dir.type === 'prompts') {
|
|
549
|
-
const baseName = filename.replace('.prompt.md', '');
|
|
550
|
-
fileInfo.toolName = `dev-${baseName}`;
|
|
551
|
-
fileInfo.source = filename;
|
|
552
|
-
fileInfo.platform = 'local';
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
return fileInfo;
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
results[dir.type] = matchedFiles;
|
|
559
|
-
console.error(`[PromptManager] ✅ ${dir.name}: ${matchedFiles.length} files`);
|
|
560
|
-
|
|
561
|
-
} catch (error) {
|
|
562
|
-
console.warn(`[PromptManager] ⚠️ Failed to scan ${dir.name}: ${error.message}`);
|
|
563
|
-
results[dir.type] = [];
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
console.error(`[PromptManager] Local scan complete. Total: ${results.prompts.length + results.projects.length + results.rules.length} files`);
|
|
568
|
-
|
|
569
|
-
// 本地模式:复制文件到缓存目录
|
|
570
|
-
await this.saveLocalResourceContents(results, baseDir);
|
|
571
|
-
|
|
572
|
-
// 缓存结果
|
|
573
|
-
await this.cacheMultiDirectoryResources(results);
|
|
574
|
-
|
|
575
|
-
return results;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
/**
|
|
579
|
-
* 🟢 GREEN: 保存本地资源文件内容到缓存目录
|
|
580
|
-
* @param {Object} resources - 资源对象
|
|
581
|
-
* @param {string} baseDir - 基础目录
|
|
582
|
-
*/
|
|
583
|
-
async saveLocalResourceContents(resources, baseDir) {
|
|
584
|
-
console.error('[PromptManager] Copying local resource contents to cache...');
|
|
585
|
-
|
|
586
|
-
// 复制 projects 内容
|
|
587
|
-
for (const project of resources.projects) {
|
|
588
|
-
try {
|
|
589
|
-
const sourcePath = path.join(baseDir, 'projects', project.filename);
|
|
590
|
-
const destPath = path.join(this.cacheDir, 'projects', project.filename);
|
|
591
|
-
const content = await fs.readFile(sourcePath, 'utf8');
|
|
592
|
-
await fs.writeFile(destPath, content, 'utf8');
|
|
593
|
-
console.error(`[PromptManager] ✅ Copied ${project.filename} (${content.length} bytes)`);
|
|
594
|
-
} catch (error) {
|
|
595
|
-
console.warn(`[PromptManager] ⚠️ Failed to copy ${project.filename}: ${error.message}`);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// 复制 rules 内容
|
|
600
|
-
for (const rule of resources.rules) {
|
|
601
|
-
try {
|
|
602
|
-
const sourcePath = path.join(baseDir, 'rules', rule.filename);
|
|
603
|
-
const destPath = path.join(this.cacheDir, 'rules', rule.filename);
|
|
604
|
-
const content = await fs.readFile(sourcePath, 'utf8');
|
|
605
|
-
await fs.writeFile(destPath, content, 'utf8');
|
|
606
|
-
console.error(`[PromptManager] ✅ Copied ${rule.filename} (${content.length} bytes)`);
|
|
607
|
-
} catch (error) {
|
|
608
|
-
console.warn(`[PromptManager] ⚠️ Failed to copy ${rule.filename}: ${error.message}`);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
console.error('[PromptManager] ✅ Local resource copy complete');
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/**
|
|
616
|
-
* 🟢 GREEN: Task 3.1 - 保存资源文件内容到本地子目录
|
|
617
|
-
* @param {Object} resources - 资源对象
|
|
618
|
-
*/
|
|
619
|
-
async saveResourceContents(resources) {
|
|
620
|
-
console.error('[PromptManager] Saving resource contents to cache...');
|
|
621
|
-
|
|
622
|
-
// 解析远程路径信息
|
|
623
|
-
const pathInfo = parseRemotePath(this.resourcePath);
|
|
624
|
-
|
|
625
|
-
// 保存projects内容
|
|
626
|
-
for (const project of resources.projects) {
|
|
627
|
-
try {
|
|
628
|
-
const content = await this.fetchFileContent(pathInfo, 'projects', project.filename);
|
|
629
|
-
const filePath = path.join(this.cacheDir, 'projects', project.filename);
|
|
630
|
-
await fs.writeFile(filePath, content, 'utf8');
|
|
631
|
-
console.error(`[PromptManager] ✅ Saved ${project.filename} (${content.length} bytes)`);
|
|
632
|
-
} catch (error) {
|
|
633
|
-
console.warn(`[PromptManager] ⚠️ Failed to save ${project.filename}: ${error.message}`);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// 保存rules内容
|
|
638
|
-
for (const rule of resources.rules) {
|
|
639
|
-
try {
|
|
640
|
-
const content = await this.fetchFileContent(pathInfo, 'rules', rule.filename);
|
|
641
|
-
const filePath = path.join(this.cacheDir, 'rules', rule.filename);
|
|
642
|
-
await fs.writeFile(filePath, content, 'utf8');
|
|
643
|
-
console.error(`[PromptManager] ✅ Saved ${rule.filename} (${content.length} bytes)`);
|
|
644
|
-
} catch (error) {
|
|
645
|
-
console.warn(`[PromptManager] ⚠️ Failed to save ${rule.filename}: ${error.message}`);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
console.error('[PromptManager] ✅ Resource content save complete');
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* 🟢 GREEN: 从远程获取单个文件内容
|
|
654
|
-
* @param {Object} pathInfo - 路径信息
|
|
655
|
-
* @param {string} subDir - 子目录(projects或rules)
|
|
656
|
-
* @param {string} filename - 文件名
|
|
657
|
-
* @returns {Promise<string>} 文件内容
|
|
658
|
-
*/
|
|
659
|
-
async fetchFileContent(pathInfo, subDir, filename) {
|
|
660
|
-
const { platform, project, branch, path: basePath } = pathInfo;
|
|
661
|
-
const filePath = `${basePath}/${subDir}/${filename}`;
|
|
662
|
-
|
|
663
|
-
if (platform === 'gitlab') {
|
|
664
|
-
const encodedPath = encodeURIComponent(filePath);
|
|
665
|
-
const url = `https://gitlab.com/api/v4/projects/${encodeURIComponent(project)}/repository/files/${encodedPath}/raw?ref=${branch}`;
|
|
666
|
-
|
|
667
|
-
const headers = createAuthHeaders(platform, this.token);
|
|
668
|
-
console.error(`[PromptManager] 🔐 Token available: ${this.token ? 'YES' : 'NO'}, Headers: ${JSON.stringify(Object.keys(headers))}`);
|
|
669
|
-
|
|
670
|
-
const response = await fetch(url, {
|
|
671
|
-
headers: headers
|
|
672
260
|
});
|
|
673
|
-
|
|
674
|
-
if (!response.ok) {
|
|
675
|
-
const errorText = await response.text().catch(() => '');
|
|
676
|
-
console.error(`[PromptManager] ⚠️ GitLab API error for ${filename}: ${response.status}`);
|
|
677
|
-
console.error(`[PromptManager] URL: ${url}`);
|
|
678
|
-
throw new Error(`GitLab API error: ${response.status} for ${filePath}`);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
return await response.text();
|
|
682
|
-
} else if (platform === 'github') {
|
|
683
|
-
// GitHub uses owner/repo format (project contains it)
|
|
684
|
-
const url = `https://raw.githubusercontent.com/${project}/${branch}/${filePath}`;
|
|
685
|
-
|
|
686
|
-
const response = await fetch(url, {
|
|
687
|
-
headers: createAuthHeaders(platform, this.token)
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
if (!response.ok) {
|
|
691
|
-
throw new Error(`GitHub raw API error: ${response.status} for ${filePath}`);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
return await response.text();
|
|
695
|
-
} else {
|
|
696
|
-
throw new Error(`Unsupported platform for file content fetch: ${platform}`);
|
|
697
|
-
}
|
|
698
261
|
}
|
|
699
262
|
|
|
700
263
|
/**
|
|
@@ -745,22 +308,6 @@ class PromptManager {
|
|
|
745
308
|
},
|
|
746
309
|
required: ['random_string']
|
|
747
310
|
}
|
|
748
|
-
},
|
|
749
|
-
{
|
|
750
|
-
name: 'load-resource',
|
|
751
|
-
type: 'handler',
|
|
752
|
-
handler: 'handleLoadResource',
|
|
753
|
-
description: 'Load full content of a resource (project documentation or rules)',
|
|
754
|
-
schema: {
|
|
755
|
-
type: 'object',
|
|
756
|
-
properties: {
|
|
757
|
-
resourceName: {
|
|
758
|
-
type: 'string',
|
|
759
|
-
description: 'Resource filename (e.g. "java-rules.md", "kiki-framework-wiki.md")'
|
|
760
|
-
}
|
|
761
|
-
},
|
|
762
|
-
required: ['resourceName']
|
|
763
|
-
}
|
|
764
311
|
}
|
|
765
312
|
];
|
|
766
313
|
|
|
@@ -818,75 +365,84 @@ class PromptManager {
|
|
|
818
365
|
}
|
|
819
366
|
|
|
820
367
|
/**
|
|
821
|
-
*
|
|
368
|
+
* 从远程或本地获取prompt内容
|
|
369
|
+
* 🔧 重构:统一缓存保存逻辑,减少重复代码
|
|
822
370
|
*/
|
|
823
371
|
async fetchPromptContent(fileName) {
|
|
372
|
+
console.error(`[PromptManager] Fetching prompt content: ${fileName} (${this.platform} mode)`);
|
|
373
|
+
|
|
374
|
+
let content, version;
|
|
375
|
+
|
|
376
|
+
// 根据平台类型获取内容和版本信息
|
|
824
377
|
if (this.platform === 'local') {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
console.error(`[PromptManager] Fetching prompt content: ${fileName}`);
|
|
378
|
+
// 本地模式:读取文件并生成基于修改时间的版本号
|
|
379
|
+
const filePath = path.join(this.promptPath, fileName);
|
|
380
|
+
content = await fs.readFile(filePath, 'utf8');
|
|
829
381
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
382
|
+
const stat = await fs.stat(filePath);
|
|
383
|
+
const modTime = new Date(stat.mtimeMs);
|
|
384
|
+
const timeFormat = modTime.getFullYear().toString() +
|
|
385
|
+
(modTime.getMonth() + 1).toString().padStart(2, '0') +
|
|
386
|
+
modTime.getDate().toString().padStart(2, '0') +
|
|
387
|
+
modTime.getHours().toString().padStart(2, '0');
|
|
388
|
+
version = `local-${timeFormat}`;
|
|
389
|
+
|
|
390
|
+
} else {
|
|
391
|
+
// 远程模式:通过API获取内容和commit hash
|
|
392
|
+
const pathInfo = parseRemotePath(this.promptPath);
|
|
393
|
+
if (!pathInfo) {
|
|
394
|
+
throw new Error(`Failed to parse remote path configuration: ${this.promptPath}`);
|
|
836
395
|
}
|
|
837
|
-
let url, headers;
|
|
838
396
|
|
|
397
|
+
let url, headers;
|
|
839
398
|
if (pathInfo.platform === 'github') {
|
|
840
|
-
// fileName 已经包含完整相对路径,不需要再拼接 pathInfo.path
|
|
841
399
|
url = `https://api.github.com/repos/${pathInfo.project}/contents/${fileName}?ref=${pathInfo.branch}`;
|
|
842
400
|
headers = createAuthHeaders('github', process.env.GIT_TOKEN);
|
|
843
|
-
console.error(`[PromptManager] GitHub API call: ${url}`);
|
|
844
401
|
} else if (pathInfo.platform === 'gitlab') {
|
|
845
402
|
const projectEnc = encodeURIComponent(pathInfo.project);
|
|
846
|
-
// fileName 已经包含完整相对路径,不需要再拼接 pathInfo.path
|
|
847
403
|
const filePathEnc = encodeURIComponent(fileName);
|
|
848
404
|
url = `https://gitlab.com/api/v4/projects/${projectEnc}/repository/files/${filePathEnc}?ref=${pathInfo.branch}`;
|
|
849
405
|
headers = createAuthHeaders('gitlab', process.env.GIT_TOKEN);
|
|
850
|
-
|
|
406
|
+
} else {
|
|
407
|
+
throw new Error(`Unsupported platform: ${pathInfo.platform}`);
|
|
851
408
|
}
|
|
852
409
|
|
|
410
|
+
console.error(`[PromptManager] API call: ${url}`);
|
|
411
|
+
|
|
853
412
|
const requestOptions = { headers, timeout: 10000 };
|
|
854
413
|
const response = await fetch(url, requestOptions);
|
|
414
|
+
|
|
855
415
|
if (!response.ok) {
|
|
856
|
-
// 🟢 GREEN: Task 1.2 - 增强错误信息的详细性,包含完整请求信息
|
|
857
|
-
|
|
858
|
-
// 构建详细的调试信息,隐藏敏感token信息
|
|
859
416
|
const debugHeaders = { ...headers };
|
|
860
|
-
|
|
861
417
|
const requestInfo = `
|
|
862
|
-
REQUEST INFO:
|
|
863
|
-
- URL: ${url}
|
|
864
|
-
- Method: GET
|
|
865
|
-
- Headers: ${JSON.stringify(debugHeaders, null, 2)}
|
|
866
|
-
- Timeout: ${requestOptions.timeout}ms
|
|
867
|
-
- Response Status: ${response.status}
|
|
868
|
-
- Response StatusText: ${response.statusText}
|
|
869
|
-
|
|
418
|
+
REQUEST INFO:
|
|
419
|
+
- URL: ${url}
|
|
420
|
+
- Method: GET
|
|
421
|
+
- Headers: ${JSON.stringify(debugHeaders, null, 2)}
|
|
422
|
+
- Timeout: ${requestOptions.timeout}ms
|
|
423
|
+
- Response Status: ${response.status}
|
|
424
|
+
- Response StatusText: ${response.statusText}`;
|
|
870
425
|
throw new Error(`fetchPromptContent Error: ${fileName}, request info: ${requestInfo}`);
|
|
871
426
|
}
|
|
872
427
|
|
|
873
428
|
const data = await response.json();
|
|
874
|
-
|
|
429
|
+
content = Buffer.from(data.content, 'base64').toString('utf8');
|
|
875
430
|
|
|
876
|
-
//
|
|
877
|
-
let version = 'unknown';
|
|
431
|
+
// 提取版本信息:GitLab使用last_commit_id,GitHub使用sha
|
|
878
432
|
if (pathInfo.platform === 'gitlab' && data.last_commit_id) {
|
|
879
|
-
version = data.last_commit_id.substring(0, 8);
|
|
433
|
+
version = data.last_commit_id.substring(0, 8);
|
|
880
434
|
} else if (pathInfo.platform === 'github' && data.sha) {
|
|
881
435
|
version = data.sha.substring(0, 8);
|
|
436
|
+
} else {
|
|
437
|
+
version = 'unknown';
|
|
882
438
|
}
|
|
883
|
-
|
|
884
|
-
// 保存到缓存
|
|
885
|
-
await this.saveToCacheWithVersion(fileName, content, version);
|
|
886
|
-
|
|
887
|
-
console.error(`[PromptManager] Successfully fetched ${fileName} with version ${version}`);
|
|
888
|
-
return content;
|
|
889
439
|
}
|
|
440
|
+
|
|
441
|
+
// 统一保存到缓存
|
|
442
|
+
await this.saveToCacheWithVersion(fileName, content, version);
|
|
443
|
+
console.error(`[PromptManager] ✅ Successfully fetched and cached ${fileName} with version ${version}`);
|
|
444
|
+
|
|
445
|
+
return content;
|
|
890
446
|
}
|
|
891
447
|
|
|
892
448
|
/**
|
|
@@ -926,7 +482,7 @@ class PromptManager {
|
|
|
926
482
|
const versionInfo = {
|
|
927
483
|
version: finalVersion,
|
|
928
484
|
platform: this.platform,
|
|
929
|
-
source: this.
|
|
485
|
+
source: this.promptPath
|
|
930
486
|
};
|
|
931
487
|
await fs.writeFile(versionFile, JSON.stringify(versionInfo, null, 2), 'utf8');
|
|
932
488
|
|
|
@@ -1024,4 +580,3 @@ export async function getLocalPromptInfo(fileName) {
|
|
|
1024
580
|
}
|
|
1025
581
|
}
|
|
1026
582
|
}
|
|
1027
|
-
|