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/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.resourcePath = null;
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 - 确保resourcePath正确初始化
37
- this.resourcePath = process.env.RESOURCE_PATH;
38
- if (!this.resourcePath) {
39
- throw new Error('RESOURCE_PATH environment variable is required');
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.resourcePath);
43
- console.error(`[PromptManager] Platform: ${this.platform}, Path: ${this.resourcePath}`);
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
- // 🟢 GREEN: Task 3.1 - 多目录资源同步(如果是远程平台)
51
- if (this.platform !== 'local') {
52
- console.error('[PromptManager] Checking multi-directory resources...');
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.resourcePath);
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.resourcePath);
139
+ const pathInfo = parseRemotePath(this.promptPath);
194
140
  if (!pathInfo) {
195
- throw new Error(`Unable to parse RESOURCE_PATH: ${this.resourcePath}`);
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
- * 🟢 GREEN: Task 1.2 - 通用目录文件扫描函数
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 fetchGitLabDirectory(pathInfo) {
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
- return await response.json();
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
- * 🟢 GREEN: Task 1.2 - 从GitHub获取目录文件(原始数据)
225
+ * 从GitHub获取文件列表
296
226
  */
297
- async fetchGitHubDirectory(pathInfo) {
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
- return await response.json();
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 mapped = {
253
+ const baseName = file.name.replace('.prompt.md', '');
254
+ return {
338
255
  filename: file.name,
339
- path: `${pathInfo.path ? pathInfo.path + '/' : ''}${file.name}`,
340
- platform: platform
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
- * 从远程获取prompt内容
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
- const filePath = path.join(this.resourcePath, fileName);
826
- return await fs.readFile(filePath, 'utf8');
827
- } else {
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
- // 🟢 GREEN: Task 1.1 - 添加更详细的错误处理
831
- let pathInfo;
832
- try {
833
- pathInfo = parseRemotePath(this.promptPath);
834
- } catch (error) {
835
- throw new Error(`Failed to parse remote path configuration: ${error.message}`);
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
- console.error(`[PromptManager] GitLab API call: ${url}`);
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: \n
863
- - URL: ${url} \n
864
- - Method: GET \n
865
- - Headers: ${JSON.stringify(debugHeaders, null, 2)} \n
866
- - Timeout: ${requestOptions.timeout}ms \n
867
- - Response Status: ${response.status} \n
868
- - Response StatusText: ${response.statusText} \n`;
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
- const content = Buffer.from(data.content, 'base64').toString('utf8');
429
+ content = Buffer.from(data.content, 'base64').toString('utf8');
875
430
 
876
- // 获取版本信息 - 优先使用last_commit_id(GitLab)或sha(GitHub)
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); // 前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.resourcePath
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
-