UNPKG

@iflow-mcp/promptx

Version:

AI角色创建平台和智能工具开发平台,基于MCP协议提供专业AI能力注入

1,340 lines (1,329 loc) 202 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // ../../node_modules/tsup/assets/cjs_shims.js var init_cjs_shims = __esm({ "../../node_modules/tsup/assets/cjs_shims.js"() { "use strict"; } }); // src/project/ProjectDiscovery.js var require_ProjectDiscovery = __commonJS({ "src/project/ProjectDiscovery.js"(exports2, module2) { "use strict"; init_cjs_shims(); var logger = require("@promptx/logger"); var RegistryData = require_RegistryData(); var ResourceData = require_ResourceData(); var fs = require("fs-extra"); var path = require("path"); var ProjectDiscovery = class { constructor() { this.source = "PROJECT"; this.priority = 2; this.projectProtocol = null; } /** * 获取ProjectProtocol实例 */ getProjectProtocol() { if (!this.projectProtocol) { const { getGlobalResourceManager } = require_resource(); const resourceManager = getGlobalResourceManager(); this.projectProtocol = resourceManager.protocols.get("project"); } return this.projectProtocol; } /** * 发现项目级资源注册表 * @returns {Promise<Map>} 资源注册表 Map<resourceId, reference> */ async discoverRegistry() { try { const registryMap = await this.loadFromRegistry(); if (registryMap.size > 0) { logger.debug(`ProjectDiscovery \u4ECE\u6CE8\u518C\u8868\u52A0\u8F7D ${registryMap.size} \u4E2A\u8D44\u6E90`); return registryMap; } logger.debug("ProjectDiscovery \u6CE8\u518C\u8868\u4E0D\u5B58\u5728\uFF0C\u4F7F\u7528\u52A8\u6001\u626B\u63CF"); const resources = await this.scanProjectResources(); return this.buildRegistryFromResources(resources); } catch (error) { logger.warn(`[ProjectDiscovery] Registry discovery failed: ${error.message}`); return /* @__PURE__ */ new Map(); } } /** * 从注册表文件加载资源 * @returns {Promise<Map>} 资源注册表 */ async loadFromRegistry() { try { const protocol = this.getProjectProtocol(); const registryPath = await protocol.resolvePath(".promptx/resource/project.registry.json"); if (!await fs.pathExists(registryPath)) { return /* @__PURE__ */ new Map(); } const registryData = await RegistryData.fromFile("project", registryPath); return registryData.getResourceMap(true); } catch (error) { logger.warn(`[ProjectDiscovery] Failed to load registry: ${error.message}`); return /* @__PURE__ */ new Map(); } } /** * 动态扫描项目资源 - 恢复重构前的专业扫描逻辑 * @returns {Promise<Array>} 资源列表 */ async scanProjectResources() { try { const protocol = this.getProjectProtocol(); const resourceDir = await protocol.resolvePath(".promptx/resource"); if (!await fs.pathExists(resourceDir)) { logger.debug("ProjectDiscovery \u9879\u76EE\u8D44\u6E90\u76EE\u5F55\u4E0D\u5B58\u5728"); return []; } const tempRegistry = RegistryData.createEmpty("project", null); await this._scanDirectory(resourceDir, tempRegistry); const resources = []; for (const resource of tempRegistry.resources) { resources.push({ id: resource.id, protocol: resource.protocol, reference: resource.reference, source: resource.source }); } logger.info(`[ProjectDiscovery] \u9879\u76EE\u626B\u63CF\u5B8C\u6210\uFF0C\u53D1\u73B0 ${resources.length} \u4E2A\u8D44\u6E90`); return resources; } catch (error) { logger.warn(`[ProjectDiscovery] \u626B\u63CF\u9879\u76EE\u8D44\u6E90\u5931\u8D25: ${error.message}`); return []; } } /** * 扫描目录并添加资源到注册表(通用递归扫描) * @param {string} resourcesDir - 资源目录 * @param {RegistryData} registryData - 注册表数据 * @private */ async _scanDirectory(resourcesDir, registryData) { try { await this._recursiveScan(resourcesDir, "", registryData); } catch (error) { logger.warn(`[ProjectDiscovery] \u626B\u63CF\u8D44\u6E90\u76EE\u5F55\u5931\u8D25: ${error.message}`); } } /** * 递归扫描目录 * @param {string} currentPath - 当前扫描路径 * @param {string} relativePath - 相对于resource目录的路径 * @param {RegistryData} registryData - 注册表数据 * @private */ async _recursiveScan(currentPath, relativePath, registryData) { try { const items = await fs.readdir(currentPath); for (const item of items) { const itemPath = path.join(currentPath, item); const stat = await fs.stat(itemPath); const newRelativePath = relativePath ? `${relativePath}/${item}` : item; if (stat.isDirectory()) { await this._recursiveScan(itemPath, newRelativePath, registryData); } else { await this._processFile(itemPath, newRelativePath, registryData); } } } catch (error) { logger.warn(`[ProjectDiscovery] \u626B\u63CF${currentPath}\u5931\u8D25: ${error.message}`); } } /** * 处理单个文件 * @param {string} filePath - 文件完整路径 * @param {string} relativePath - 相对路径 * @param {RegistryData} registryData - 注册表数据 * @private */ async _processFile(filePath, relativePath, registryData) { const fileName = path.basename(filePath); let protocol = null; let resourceId = null; if (fileName.endsWith(".role.md")) { protocol = "role"; resourceId = path.basename(fileName, ".role.md"); } else if (fileName.endsWith(".thought.md")) { protocol = "thought"; resourceId = path.basename(fileName, ".thought.md"); } else if (fileName.endsWith(".execution.md")) { protocol = "execution"; resourceId = path.basename(fileName, ".execution.md"); } else if (fileName.endsWith(".knowledge.md")) { protocol = "knowledge"; resourceId = path.basename(fileName, ".knowledge.md"); } else if (fileName.endsWith(".tool.js")) { protocol = "tool"; resourceId = path.basename(fileName, ".tool.js"); } else if (fileName.endsWith(".manual.md")) { protocol = "manual"; resourceId = path.basename(fileName, ".manual.md"); } if (protocol && resourceId) { if (await this._validateResourceFile(filePath, protocol)) { const reference = `@project://.promptx/resource/${relativePath}`; const resourceData = new ResourceData({ id: resourceId, source: "project", protocol, name: ResourceData._generateDefaultName(resourceId, protocol), description: ResourceData._generateDefaultDescription(resourceId, protocol), reference, metadata: { scannedAt: (/* @__PURE__ */ new Date()).toISOString(), path: relativePath } }); registryData.addResource(resourceData); logger.debug(`[ProjectDiscovery] \u53D1\u73B0${protocol}\u8D44\u6E90: ${resourceId} at ${relativePath}`); } } } /** * 验证资源文件格式(恢复重构前逻辑) * @param {string} filePath - 文件路径 * @param {string} protocol - 协议类型 * @returns {Promise<boolean>} 是否是有效的资源文件 */ async _validateResourceFile(filePath, protocol) { try { const content = await fs.readFile(filePath, "utf8"); if (!content || typeof content !== "string") { return false; } const trimmedContent = content.trim(); if (trimmedContent.length === 0) { return false; } switch (protocol) { case "role": return /<role[\s>]/.test(trimmedContent) && trimmedContent.includes("</role>"); case "execution": return /<execution[\s>]/.test(trimmedContent) && trimmedContent.includes("</execution>"); case "thought": return /<thought[\s>]/.test(trimmedContent) && trimmedContent.includes("</thought>"); case "knowledge": return true; case "manual": return /<manual[\s>]/.test(trimmedContent) && trimmedContent.includes("</manual>"); case "tool": try { new Function(trimmedContent); return true; } catch (e) { logger.warn(`[ProjectDiscovery] Invalid JavaScript in tool file ${filePath}: ${e.message}`); return false; } default: return false; } } catch (error) { logger.warn(`[ProjectDiscovery] Failed to validate ${filePath}: ${error.message}`); return false; } } /** * 从资源列表构建注册表Map * @param {Array} resources - 资源列表 * @returns {Map} 资源注册表 */ buildRegistryFromResources(resources) { const registryMap = /* @__PURE__ */ new Map(); resources.forEach((resource) => { const key = `project:${resource.id}`; registryMap.set(key, resource.reference); }); return registryMap; } /** * 生成并保存项目注册表文件 * @returns {Promise<RegistryData>} 生成的注册表数据 */ async generateRegistry() { try { const protocol = this.getProjectProtocol(); const registryPath = await protocol.resolvePath(".promptx/resource/project.registry.json"); const registryData = RegistryData.createEmpty("project", registryPath); const resourceDir = await protocol.resolvePath(".promptx/resource"); if (await fs.pathExists(resourceDir)) { await this._scanDirectory(resourceDir, registryData); } await fs.ensureDir(path.dirname(registryPath)); await registryData.save(); logger.info(`[ProjectDiscovery] \u9879\u76EE\u6CE8\u518C\u8868\u751F\u6210\u5B8C\u6210\uFF0C\u53D1\u73B0 ${registryData.size} \u4E2A\u8D44\u6E90`); return registryData; } catch (error) { logger.error(`[ProjectDiscovery] \u751F\u6210\u6CE8\u518C\u8868\u5931\u8D25: ${error.message}`); return RegistryData.createEmpty("project"); } } /** * 获取注册表数据(兼容旧接口) * @returns {Promise<RegistryData>} 注册表数据 */ async getRegistryData() { try { const protocol = this.getProjectProtocol(); const registryPath = await protocol.resolvePath(".promptx/resource/project.registry.json"); if (await fs.pathExists(registryPath)) { const registryData = await RegistryData.fromFile("project", registryPath); if (registryData.size > 0) { logger.info(`[ProjectDiscovery] \u4ECE\u6CE8\u518C\u8868\u52A0\u8F7D ${registryData.size} \u4E2A\u8D44\u6E90`); return registryData; } } logger.info(`[ProjectDiscovery] \u9879\u76EE\u6CE8\u518C\u8868\u65E0\u6548\uFF0C\u91CD\u65B0\u751F\u6210`); return await this.generateRegistry(); } catch (error) { logger.error(`[ProjectDiscovery] \u83B7\u53D6\u6CE8\u518C\u8868\u6570\u636E\u5931\u8D25: ${error.message}`); return RegistryData.createEmpty("project"); } } }; module2.exports = ProjectDiscovery; } }); // src/project/ProjectManager.js var require_ProjectManager = __commonJS({ "src/project/ProjectManager.js"(exports2, module2) { "use strict"; init_cjs_shims(); var fs = require("fs-extra"); var path = require("path"); var os = require("os"); var crypto = require("crypto"); var logger = require("@promptx/logger"); var ProjectDiscovery = require_ProjectDiscovery(); var ProjectManager = class { constructor() { this.promptxHomeDir = path.join(os.homedir(), ".promptx"); this.projectsDir = path.join(this.promptxHomeDir, "project"); } // 🎯 新架构:当前项目状态管理 static currentProject = { workingDirectory: null, mcpId: null, ideType: null, initialized: false }; /** * 获取当前MCP ID * @returns {string} MCP进程ID */ static getCurrentMcpId() { return `mcp-${process.pid}`; } /** * 设置当前项目(init时调用) * @param {string} workingDirectory - 项目工作目录绝对路径 * @param {string} mcpId - MCP进程ID * @param {string} ideType - IDE类型 */ static setCurrentProject(workingDirectory, mcpId, ideType) { this.currentProject = { workingDirectory: path.resolve(workingDirectory), mcpId, ideType, initialized: true }; } /** * 获取当前项目路径(@project协议使用) * @returns {string} 当前项目工作目录 */ static getCurrentProjectPath() { logger.debug(`[ProjectManager DEBUG] getCurrentProjectPath\u88AB\u8C03\u7528`); logger.debug(`[ProjectManager DEBUG] currentProject.initialized: ${this.currentProject.initialized}`); logger.debug(`[ProjectManager DEBUG] currentProject\u72B6\u6001:`, JSON.stringify(this.currentProject, null, 2)); const stack = new Error().stack; const stackLines = stack.split("\n").slice(1, 8); logger.error(`[ProjectManager DEBUG] \u5B8C\u6574\u8C03\u7528\u6808:`); stackLines.forEach((line, index) => { logger.error(`[ProjectManager DEBUG] ${index + 1}. ${line.trim()}`); }); if (!this.currentProject.initialized) { logger.error(`[ProjectManager DEBUG] \u9879\u76EE\u672A\u521D\u59CB\u5316\uFF0C\u5C06\u629B\u51FA\u9519\u8BEF`); throw new Error("\u9879\u76EE\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 init \u547D\u4EE4"); } logger.debug(`[ProjectManager DEBUG] \u8FD4\u56DE\u9879\u76EE\u8DEF\u5F84: ${this.currentProject.workingDirectory}`); return this.currentProject.workingDirectory; } /** * 获取当前项目信息 * @returns {Object} 当前项目完整信息 */ static getCurrentProject() { logger.debug(`[ProjectManager DEBUG] getCurrentProject\u88AB\u8C03\u7528`); logger.debug(`[ProjectManager DEBUG] currentProject.initialized: ${this.currentProject.initialized}`); logger.debug(`[ProjectManager DEBUG] currentProject\u72B6\u6001:`, JSON.stringify(this.currentProject, null, 2)); if (!this.currentProject.initialized) { logger.error(`[ProjectManager DEBUG] \u9879\u76EE\u672A\u521D\u59CB\u5316\uFF0C\u5C06\u629B\u51FA\u9519\u8BEF`); throw new Error("\u9879\u76EE\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 init \u547D\u4EE4"); } logger.debug(`[ProjectManager DEBUG] \u8FD4\u56DE\u9879\u76EE\u4FE1\u606F`); return { ...this.currentProject }; } /** * 检查项目是否已初始化 * @returns {boolean} 是否已初始化 */ static isInitialized() { return this.currentProject.initialized; } /** * 注册项目到MCP实例 - 使用Hash目录结构 * @param {string} projectPath - 项目绝对路径 * @param {string} mcpId - MCP进程ID * @param {string} ideType - IDE类型(cursor/vscode等) * @returns {Promise<Object>} 项目配置对象 */ async registerProject(projectPath, mcpId, ideType) { if (!await this.validateProjectPath(projectPath)) { throw new Error(`\u65E0\u6548\u7684\u9879\u76EE\u8DEF\u5F84: ${projectPath}`); } const projectConfig = { mcpId, ideType: ideType.toLowerCase(), projectPath: path.resolve(projectPath), projectHash: this.generateProjectHash(projectPath) }; const projectHash = this.generateProjectHash(projectPath); const projectConfigDir = path.join(this.projectsDir, projectHash); await fs.ensureDir(projectConfigDir); await fs.ensureDir(path.join(projectConfigDir, ".promptx")); await fs.ensureDir(path.join(projectConfigDir, ".promptx", "memory")); await fs.ensureDir(path.join(projectConfigDir, ".promptx", "resource")); const fileName = this.generateConfigFileName(mcpId, ideType, projectPath); const configPath = path.join(projectConfigDir, fileName); await fs.writeJson(configPath, projectConfig, { spaces: 2 }); return projectConfig; } /** * 根据MCP ID获取单个项目配置(假设只有一个项目) * @param {string} mcpId - MCP进程ID * @returns {Promise<Object|null>} 项目配置对象 */ async getProjectByMcpId(mcpId) { const projects = await this.getProjectsByMcpId(mcpId); return projects.length > 0 ? projects[0] : null; } /** * 根据MCP ID获取所有绑定的项目配置 - 支持Hash目录结构 * @param {string} mcpId - MCP进程ID * @returns {Promise<Array>} 项目配置数组 */ async getProjectsByMcpId(mcpId) { if (!await fs.pathExists(this.projectsDir)) { return []; } const hashDirs = await fs.readdir(this.projectsDir); const projects = []; for (const hashDir of hashDirs) { const hashDirPath = path.join(this.projectsDir, hashDir); if (!(await fs.stat(hashDirPath)).isDirectory()) { continue; } try { const configFiles = await fs.readdir(hashDirPath); for (const file of configFiles) { if (file.startsWith("mcp-") && file.endsWith(".json")) { try { const configPath = path.join(hashDirPath, file); const config = await fs.readJson(configPath); if (config.mcpId === mcpId) { projects.push(config); } } catch (error) { logger.warn(`\u8DF3\u8FC7\u635F\u574F\u7684\u914D\u7F6E\u6587\u4EF6: ${file}`); } } } } catch (error) { logger.warn(`\u8DF3\u8FC7\u65E0\u6CD5\u8BFB\u53D6\u7684\u76EE\u5F55: ${hashDir}`); } } return projects; } /** * 获取特定项目的所有实例(不同IDE/MCP的绑定) - 支持Hash目录结构 * @param {string} projectPath - 项目路径 * @returns {Promise<Array>} 项目实例数组 */ async getProjectInstances(projectPath) { if (!await fs.pathExists(this.projectsDir)) { return []; } const projectHash = this.generateProjectHash(projectPath); const projectConfigDir = path.join(this.projectsDir, projectHash); if (!await fs.pathExists(projectConfigDir)) { return []; } const instances = []; try { const configFiles = await fs.readdir(projectConfigDir); for (const file of configFiles) { if (file.startsWith("mcp-") && file.endsWith(".json")) { try { const configPath = path.join(projectConfigDir, file); const config = await fs.readJson(configPath); if (config.projectHash === projectHash) { instances.push(config); } } catch (error) { logger.warn(`\u8DF3\u8FC7\u635F\u574F\u7684\u914D\u7F6E\u6587\u4EF6: ${file}`); } } } } catch (error) { logger.warn(`\u65E0\u6CD5\u8BFB\u53D6\u9879\u76EE\u914D\u7F6E\u76EE\u5F55: ${projectConfigDir}`); } return instances; } /** * 删除项目绑定 - 支持Hash目录结构 * @param {string} mcpId - MCP进程ID * @param {string} ideType - IDE类型 * @param {string} projectPath - 项目路径 * @returns {Promise<boolean>} 是否删除成功 */ async removeProject(mcpId, ideType, projectPath) { const projectHash = this.generateProjectHash(projectPath); const projectConfigDir = path.join(this.projectsDir, projectHash); const fileName = this.generateConfigFileName(mcpId, ideType, projectPath); const configPath = path.join(projectConfigDir, fileName); if (await fs.pathExists(configPath)) { await fs.remove(configPath); try { const remainingFiles = await fs.readdir(projectConfigDir); const mcpConfigFiles = remainingFiles.filter((file) => file.startsWith("mcp-") && file.endsWith(".json")); if (mcpConfigFiles.length === 0) { await fs.remove(projectConfigDir); } } catch (error) { } return true; } return false; } /** * 清理过期的项目配置 - 支持Hash目录结构 * @returns {Promise<number>} 清理的配置文件数量 */ async cleanupExpiredProjects() { if (!await fs.pathExists(this.projectsDir)) { return 0; } const hashDirs = await fs.readdir(this.projectsDir); let cleanedCount = 0; for (const hashDir of hashDirs) { const hashDirPath = path.join(this.projectsDir, hashDir); if (!(await fs.stat(hashDirPath)).isDirectory()) { continue; } try { const configFiles = await fs.readdir(hashDirPath); let hasValidConfig = false; for (const file of configFiles) { if (file.startsWith("mcp-") && file.endsWith(".json")) { try { const configPath = path.join(hashDirPath, file); const config = await fs.readJson(configPath); if (!await fs.pathExists(config.projectPath)) { await fs.remove(configPath); cleanedCount++; logger.info(`\u6E05\u7406\u8FC7\u671F\u9879\u76EE\u914D\u7F6E: ${file}`); } else { hasValidConfig = true; } } catch (error) { await fs.remove(path.join(hashDirPath, file)); cleanedCount++; logger.info(`\u6E05\u7406\u635F\u574F\u914D\u7F6E\u6587\u4EF6: ${file}`); } } } if (!hasValidConfig) { await fs.remove(hashDirPath); logger.info(`\u6E05\u7406\u7A7A\u7684\u9879\u76EEHash\u76EE\u5F55: ${hashDir}`); } } catch (error) { await fs.remove(hashDirPath); cleanedCount++; logger.info(`\u6E05\u7406\u65E0\u6CD5\u8BBF\u95EE\u7684\u76EE\u5F55: ${hashDir}`); } } return cleanedCount; } /** * 生成多项目环境下的AI提示词 * @param {string} contextType - 上下文类型:'list'/'action'/'learn' * @param {string} mcpId - MCP进程ID * @param {string} ideType - IDE类型 * @returns {Promise<string>} 格式化的AI提示词 */ async generateTopLevelProjectPrompt(contextType = "list", mcpId, ideType) { const projects = await this.getProjectsByMcpId(mcpId); if (projects.length === 0) { return ""; } if (projects.length === 1) { const project = projects[0]; const basePrompt = `\u{1F6D1} **\u9879\u76EE\u73AF\u5883\u9A8C\u8BC1** \u{1F6D1} \u{1F4CD} \u5F53\u524D\u7ED1\u5B9A\u9879\u76EE: ${project.projectPath} \u{1F517} MCP\u5B9E\u4F8B: ${mcpId} (${ideType}) \u26A0\uFE0F **\u6267\u884C\u524D\u786E\u8BA4**\uFF1A\u4E0A\u8FF0\u8DEF\u5F84\u662F\u5426\u4E3A\u4F60\u5F53\u524D\u5DE5\u4F5C\u7684\u9879\u76EE\uFF1F`; switch (contextType) { case "action": return `${basePrompt} \u5982\u4E0D\u4E00\u81F4\uFF0C\u7ACB\u5373\u505C\u6B62\u6240\u6709\u64CD\u4F5C\u5E76\u4F7F\u7528 \`promptx_init\` \u66F4\u65B0\uFF01 \u{1F4A5} **\u4E25\u91CD\u8B66\u544A**\uFF1A\u5728\u9519\u8BEF\u9879\u76EE\u8DEF\u5F84\u4E0B\u64CD\u4F5C\u5C06\u5BFC\u81F4\u4E0D\u53EF\u9884\u77E5\u7684\u9519\u8BEF\uFF01`; case "learn": return `${basePrompt} \u9519\u8BEF\u73AF\u5883\u5C06\u5BFC\u81F4\u77E5\u8BC6\u5173\u8054\u5931\u6548\uFF01 \u{1F4A5} **\u4E25\u91CD\u8B66\u544A**\uFF1A\u9879\u76EE\u73AF\u5883\u4E0D\u5339\u914D\u5C06\u5F71\u54CD\u5B66\u4E60\u6548\u679C\uFF01`; default: return `${basePrompt} \u5982\u4E0D\u4E00\u81F4\uFF0C\u5FC5\u987B\u4F7F\u7528 \`promptx_init\` \u66F4\u65B0\u6B63\u786E\u8DEF\u5F84\uFF01 \u{1F4A5} **\u4E25\u91CD\u8B66\u544A**\uFF1A\u9519\u8BEF\u7684\u9879\u76EE\u73AF\u5883\u5C06\u5BFC\u81F4\u670D\u52A1\u5F02\u5E38\uFF01`; } } const projectList = projects.map( (proj, index) => `${index + 1}. ${path.basename(proj.projectPath)} (${proj.projectPath})` ).join("\n"); return `\u{1F3AF} **\u591A\u9879\u76EE\u73AF\u5883\u68C0\u6D4B** \u{1F3AF} \u{1F4CD} \u5F53\u524DMCP\u5B9E\u4F8B(${mcpId})\u5DF2\u7ED1\u5B9A ${projects.length} \u4E2A\u9879\u76EE\uFF1A ${projectList} \u26A0\uFE0F **\u8BF7\u660E\u786E\u6307\u5B9A**\uFF1A\u4F60\u8981\u5728\u54EA\u4E2A\u9879\u76EE\u4E2D\u6267\u884C\u64CD\u4F5C\uFF1F \u{1F4A1} **\u5EFA\u8BAE**\uFF1A\u5728\u5BF9\u8BDD\u4E2D\u660E\u786E\u8BF4\u660E\u9879\u76EE\u540D\u79F0\u6216\u8DEF\u5F84`; } /** * 验证路径是否为有效的项目目录 * @param {string} projectPath - 要验证的路径 * @returns {Promise<boolean>} 是否为有效项目目录 */ async validateProjectPath(projectPath) { try { const stat = await fs.stat(projectPath); if (!stat.isDirectory()) { return false; } const resolved = path.resolve(projectPath); const homeDir = os.homedir(); if (resolved === homeDir) { return false; } return true; } catch (error) { return false; } } /** * 生成配置文件名 * @param {string} mcpId - MCP进程ID * @param {string} ideType - IDE类型 * @param {string} projectPath - 项目路径 * @returns {string} 配置文件名 */ generateConfigFileName(mcpId, ideType, projectPath) { const projectHash = this.generateProjectHash(projectPath); const projectName = path.basename(projectPath).toLowerCase().replace(/[^a-z0-9-]/g, "-"); const ideTypeSafe = ideType.replace(/[^a-z0-9-]/g, "").toLowerCase() || "unknown"; return `mcp-${mcpId.replace("mcp-", "")}-${ideTypeSafe}-${projectName}-${projectHash}.json`; } /** * 生成项目路径的Hash值 * @param {string} projectPath - 项目路径 * @returns {string} 8位Hash值 */ generateProjectHash(projectPath) { return crypto.createHash("md5").update(path.resolve(projectPath)).digest("hex").substr(0, 8); } /** * 从配置文件中获取IDE类型 * @param {string} mcpId - MCP进程ID * @returns {Promise<string>} IDE类型 */ async getIdeType(mcpId) { const project = await this.getProjectByMcpId(mcpId); return project ? project.ideType : "unknown"; } /** * 生成MCP进程ID - 基于进程ID确保实例唯一 * @param {string} ideType - IDE类型(保留参数兼容性,实际不使用) * @returns {string} MCP进程ID */ static generateMcpId(ideType = "unknown") { const serverEnv = getGlobalServerEnvironment(); if (serverEnv.isInitialized()) { return serverEnv.getMcpId(); } return `mcp-${process.pid}`; } /** * 统一项目注册方法 - 新架构:设置当前项目并持久化配置 * @param {string} workingDirectory - 项目工作目录 * @param {string} ideType - IDE类型(可选,默认'unknown') * @returns {Promise<Object>} 项目配置对象 */ static async registerCurrentProject(workingDirectory, ideType = "unknown") { logger.debug(`[ProjectManager DEBUG] ======= registerCurrentProject\u5F00\u59CB =======`); logger.debug(`[ProjectManager DEBUG] \u53C2\u6570 - workingDirectory: ${workingDirectory}`); logger.debug(`[ProjectManager DEBUG] \u53C2\u6570 - ideType: ${ideType}`); logger.debug(`[ProjectManager DEBUG] \u6CE8\u518C\u524D currentProject\u72B6\u6001:`, JSON.stringify(this.currentProject, null, 2)); const mcpId = this.getCurrentMcpId(); logger.debug(`[ProjectManager DEBUG] MCP ID: ${mcpId}`); logger.debug(`[ProjectManager DEBUG] \u8C03\u7528 setCurrentProject...`); this.setCurrentProject(workingDirectory, mcpId, ideType); logger.debug(`[ProjectManager DEBUG] setCurrentProject\u5B8C\u6210\u540E currentProject\u72B6\u6001:`, JSON.stringify(this.currentProject, null, 2)); logger.debug(`[ProjectManager DEBUG] \u5F00\u59CB\u6301\u4E45\u5316\u9879\u76EE\u914D\u7F6E...`); const projectManager = getGlobalProjectManager(); const result = await projectManager.registerProject(workingDirectory, mcpId, ideType); logger.debug(`[ProjectManager DEBUG] \u9879\u76EE\u914D\u7F6E\u6301\u4E45\u5316\u5B8C\u6210:`, JSON.stringify(result, null, 2)); logger.debug(`[ProjectManager DEBUG] \u5F00\u59CB\u626B\u63CF\u9879\u76EE\u8D44\u6E90...`); try { const discovery = new ProjectDiscovery(); const resources = await discovery.scanProjectResources(); if (resources && Array.isArray(resources)) { const resourceCount = resources.length; logger.info(`[ProjectManager] \u53D1\u73B0\u5E76\u6CE8\u518C\u4E86 ${resourceCount} \u4E2A\u9879\u76EE\u8D44\u6E90`); result.resourcesDiscovered = resourceCount; } } catch (error) { logger.warn(`[ProjectManager] \u9879\u76EE\u8D44\u6E90\u626B\u63CF\u5931\u8D25: ${error.message}`); } logger.debug(`[ProjectManager DEBUG] ======= registerCurrentProject\u7ED3\u675F =======`); return result; } }; var globalProjectManager = null; function getGlobalProjectManager() { if (!globalProjectManager) { globalProjectManager = new ProjectManager(); } return globalProjectManager; } module2.exports = ProjectManager; module2.exports.ProjectManager = ProjectManager; module2.exports.getGlobalProjectManager = getGlobalProjectManager; } }); // src/utils/DirectoryLocator.js var require_DirectoryLocator = __commonJS({ "src/utils/DirectoryLocator.js"(exports2, module2) { "use strict"; init_cjs_shims(); var fs = require("fs-extra"); var path = require("path"); var os = require("os"); var ProjectManager = require_ProjectManager(); var DirectoryLocator = class { constructor(options = {}) { this.options = options; this.cache = /* @__PURE__ */ new Map(); this.platform = process.platform; } /** * 抽象方法:定位目录 * @param {Object} context - 定位上下文 * @returns {Promise<string>} 定位到的目录路径 */ async locate(context = {}) { throw new Error("\u5B50\u7C7B\u5FC5\u987B\u5B9E\u73B0 locate \u65B9\u6CD5"); } /** * 获取缓存 */ getCached(key) { return this.cache.get(key); } /** * 设置缓存 */ setCached(key, value) { this.cache.set(key, value); return value; } /** * 清除缓存 */ clearCache() { this.cache.clear(); } /** * 检查路径是否存在且是目录 */ async isValidDirectory(dirPath) { try { const stat = await fs.stat(dirPath); return stat.isDirectory(); } catch { return false; } } /** * 规范化路径 */ normalizePath(inputPath) { if (!inputPath || typeof inputPath !== "string") { return null; } return path.resolve(inputPath); } /** * 展开家目录路径 */ expandHome(filepath) { if (!filepath || typeof filepath !== "string") { return ""; } if (filepath.startsWith("~/") || filepath === "~") { return path.join(os.homedir(), filepath.slice(2)); } return filepath; } }; var ProjectRootLocator = class extends DirectoryLocator { constructor(options = {}) { super(options); this.projectManager = new ProjectManager(); this.strategies = options.strategies || [ "aiProvidedProjectPath", // 1. AI提供的项目路径(最可靠,由AI告知) "existingPromptxDirectory", // 2. 现有.promptx目录(最可靠的项目标识) "packageJsonDirectory", // 3. 向上查找项目标识文件(最准确的项目边界) "gitRootDirectory", // 4. Git根目录(通用可靠) "currentWorkingDirectoryIfHasMarkers", // 5. 当前目录项目标识(降级策略) "currentWorkingDirectory" // 6. 纯当前目录(最后回退) ]; this.projectMarkers = options.projectMarkers || [ "package.json", ".git", "pyproject.toml", "Cargo.toml", "pom.xml", "build.gradle", "composer.json" ]; } /** * 定位项目根目录 */ async locate(context = {}) { const { startDir = process.cwd() } = context; const cacheKey = `projectRoot:${startDir}`; const cached = this.getCached(cacheKey); if (cached) { return cached; } const strategies = context.strategies || this.strategies; for (const strategy of strategies) { const result = await this._executeStrategy(strategy, startDir, context); if (result && await this._validateProjectRoot(result, context)) { return this.setCached(cacheKey, result); } } return this.setCached(cacheKey, startDir); } /** * 执行特定的查找策略 */ async _executeStrategy(strategy, startDir, context) { switch (strategy) { case "aiProvidedProjectPath": return await this._findByAIProvidedPath(); case "existingPromptxDirectory": return await this._findByExistingPromptx(startDir); case "currentWorkingDirectoryIfHasMarkers": return await this._checkCurrentDirForMarkers(startDir); case "packageJsonDirectory": return await this._findByProjectMarkers(startDir); case "gitRootDirectory": return await this._findByGitRoot(startDir); case "currentWorkingDirectory": return startDir; default: return null; } } /** * 通过AI提供的项目路径查找(最高优先级) */ async _findByAIProvidedPath() { try { const tempMcpId = process.env.PROMPTX_MCP_ID || `temp-${process.pid}`; const projects = await this.projectManager.getProjectsByMcpId(tempMcpId); const aiProvidedPath = projects.length > 0 ? projects[0].projectPath : null; if (aiProvidedPath && await this.isValidDirectory(aiProvidedPath)) { return aiProvidedPath; } } catch (error) { } return null; } /** * 检查当前目录是否包含项目标识文件 */ async _checkCurrentDirForMarkers(startDir) { const currentDir = path.resolve(startDir); for (const marker of this.projectMarkers) { const markerPath = path.join(currentDir, marker); if (await fs.pathExists(markerPath)) { return currentDir; } } return null; } /** * 通过现有.promptx目录查找 */ async _findByExistingPromptx(startDir) { let currentDir = path.resolve(startDir); const root = path.parse(currentDir).root; while (currentDir !== root) { const promptxPath = path.join(currentDir, ".promptx"); if (await this.isValidDirectory(promptxPath)) { return currentDir; } const parentDir = path.dirname(currentDir); if (parentDir === currentDir) break; currentDir = parentDir; } return null; } /** * 通过项目标识文件查找 */ async _findByProjectMarkers(startDir) { let currentDir = path.resolve(startDir); const root = path.parse(currentDir).root; while (currentDir !== root) { for (const marker of this.projectMarkers) { const markerPath = path.join(currentDir, marker); if (await fs.pathExists(markerPath)) { return currentDir; } } const parentDir = path.dirname(currentDir); if (parentDir === currentDir) break; currentDir = parentDir; } return null; } /** * 通过Git根目录查找 */ async _findByGitRoot(startDir) { let currentDir = path.resolve(startDir); const root = path.parse(currentDir).root; while (currentDir !== root) { const gitPath = path.join(currentDir, ".git"); if (await fs.pathExists(gitPath)) { return currentDir; } const parentDir = path.dirname(currentDir); if (parentDir === currentDir) break; currentDir = parentDir; } return null; } /** * 验证项目根目录 */ async _validateProjectRoot(projectRoot, context = {}) { if (this.platform === "win32" && context.avoidUserHome !== false) { const homeDir = os.homedir(); if (path.resolve(projectRoot) === path.resolve(homeDir)) { return false; } } return await this.isValidDirectory(projectRoot); } }; var PromptXWorkspaceLocator = class extends DirectoryLocator { constructor(options = {}) { super(options); this.projectRootLocator = options.projectRootLocator || new ProjectRootLocator(options); this.projectManager = new ProjectManager(); } /** * 定位PromptX工作空间 */ async locate(context = {}) { const cacheKey = `promptxWorkspace:${JSON.stringify(context)}`; const cached = this.getCached(cacheKey); if (cached) { return cached; } const workspaceFromAI = await this._fromAIProvidedPath(); if (workspaceFromAI) { return this.setCached(cacheKey, workspaceFromAI); } const workspaceFromIDE = await this._fromIDEEnvironment(); if (workspaceFromIDE) { return this.setCached(cacheKey, workspaceFromIDE); } const workspaceFromEnv = await this._fromPromptXEnvironment(); if (workspaceFromEnv) { return this.setCached(cacheKey, workspaceFromEnv); } if (context.strategies) { const workspaceFromProject2 = await this._fromProjectRoot(context); if (workspaceFromProject2) { return this.setCached(cacheKey, workspaceFromProject2); } } const workspaceFromExisting = await this._fromExistingDirectory(context.startDir); if (workspaceFromExisting) { return this.setCached(cacheKey, workspaceFromExisting); } const workspaceFromProject = await this._fromProjectRoot(context); if (workspaceFromProject) { return this.setCached(cacheKey, workspaceFromProject); } return this.setCached(cacheKey, await this._getSmartFallback(context)); } /** * 从AI提供的项目路径获取(最高优先级) */ async _fromAIProvidedPath() { try { const tempMcpId = process.env.PROMPTX_MCP_ID || `temp-${process.pid}`; const projects = await this.projectManager.getProjectsByMcpId(tempMcpId); const aiProvidedPath = projects.length > 0 ? projects[0].projectPath : null; if (aiProvidedPath && await this.isValidDirectory(aiProvidedPath)) { return aiProvidedPath; } } catch (error) { } return null; } /** * 从IDE环境变量获取(支持多种IDE) */ async _fromIDEEnvironment() { const ideStrategies = [ // Claude IDE (现有格式) { name: "Claude IDE", vars: ["WORKSPACE_FOLDER_PATHS"], parse: (value, varName) => { try { const folders = JSON.parse(value); return Array.isArray(folders) && folders.length > 0 ? folders[0] : null; } catch { return null; } } }, // VSCode { name: "VSCode", vars: ["VSCODE_WORKSPACE_FOLDER", "VSCODE_CWD"], parse: (value, varName) => value }, // IntelliJ IDEA / WebStorm / PhpStorm { name: "JetBrains IDEs", vars: ["PROJECT_ROOT", "IDEA_INITIAL_DIRECTORY", "WEBSTORM_PROJECT_PATH"], parse: (value, varName) => value }, // Sublime Text { name: "Sublime Text", vars: ["SUBLIME_PROJECT_PATH", "SUBL_PROJECT_DIR"], parse: (value, varName) => value }, // Atom { name: "Atom", vars: ["ATOM_PROJECT_PATH", "ATOM_HOME_PROJECT"], parse: (value, varName) => value }, // Vim/Neovim { name: "Vim/Neovim", vars: ["VIM_PROJECT_ROOT", "NVIM_PROJECT_ROOT"], parse: (value, varName) => value }, // 字节跳动 Trae 和其他基于PWD的IDE { name: "ByteDance Trae & PWD-based IDEs", vars: ["PWD", "TRAE_WORKSPACE", "BYTEDANCE_WORKSPACE"], parse: (value, varName) => { if (varName === "TRAE_WORKSPACE" || varName === "BYTEDANCE_WORKSPACE") { return value; } if (varName === "PWD") { const currentCwd = process.cwd(); if (value && value !== currentCwd) { return value; } } return null; } }, // 通用工作目录 { name: "Generic", vars: ["WORKSPACE_ROOT", "PROJECT_DIR", "WORKING_DIRECTORY"], parse: (value, varName) => value } ]; for (const strategy of ideStrategies) { for (const varName of strategy.vars) { const envValue = process.env[varName]; if (envValue && envValue.trim() !== "") { const parsedPath = strategy.parse(envValue.trim(), varName); if (parsedPath) { const normalizedPath = this.normalizePath(this.expandHome(parsedPath)); if (normalizedPath && await this.isValidDirectory(normalizedPath)) { this._detectedIDE = strategy.name; return normalizedPath; } } } } } return null; } /** * 从PromptX环境变量获取 */ async _fromPromptXEnvironment() { const promptxWorkspaceEnv = process.env.PROMPTX_WORKSPACE; if (promptxWorkspaceEnv && promptxWorkspaceEnv.trim() !== "") { const workspacePath = this.normalizePath(this.expandHome(promptxWorkspaceEnv)); if (workspacePath && await this.isValidDirectory(workspacePath)) { return workspacePath; } } return null; } /** * 从现有.promptx目录获取 */ async _fromExistingDirectory(startDir) { const projectRoot = await this.projectRootLocator._findByExistingPromptx(startDir || process.cwd()); return projectRoot; } /** * 从项目根目录获取 */ async _fromProjectRoot(context) { const projectRoot = await this.projectRootLocator.locate(context); return projectRoot; } /** * 智能回退策略 */ async _getSmartFallback(context) { const argPath = await this._fromProcessArguments(); if (argPath && await this.isValidDirectory(argPath)) { return argPath; } const processCwd = process.cwd(); if (await this.isValidDirectory(processCwd)) { return processCwd; } return os.homedir(); } /** * 从进程参数推断项目路径 */ async _fromProcessArguments() { const args = process.argv; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg.startsWith("--project-path=")) { return arg.split("=")[1]; } if (arg === "--project-path" && i + 1 < args.length) { return args[i + 1]; } if (arg.startsWith("--cwd=")) { return arg.split("=")[1]; } if (arg === "--cwd" && i + 1 < args.length) { return args[i + 1]; } } return null; } /** * 获取检测调试信息 */ getDetectionInfo() { return { detectedIDE: this._detectedIDE || "Unknown", availableEnvVars: this._getAvailableEnvVars(), platform: process.platform, cwd: process.cwd(), args: process.argv }; } /** * 获取可用的环境变量 */ _getAvailableEnvVars() { const relevantVars = [ "WORKSPACE_FOLDER_PATHS", "VSCODE_WORKSPACE_FOLDER", "VSCODE_CWD", "PROJECT_ROOT", "IDEA_INITIAL_DIRECTORY", "WEBSTORM_PROJECT_PATH", "SUBLIME_PROJECT_PATH", "SUBL_PROJECT_DIR", "ATOM_PROJECT_PATH", "ATOM_HOME_PROJECT", "VIM_PROJECT_ROOT", "NVIM_PROJECT_ROOT", "PWD", "TRAE_WORKSPACE", "BYTEDANCE_WORKSPACE", "WORKSPACE_ROOT", "PROJECT_DIR", "WORKING_DIRECTORY", "PROMPTX_WORKSPACE" ]; const available = {}; for (const varName of relevantVars) { if (process.env[varName]) { available[varName] = process.env[varName]; } } return available; } }; var DirectoryLocatorFactory = class { /** * 创建项目根目录定位器 */ static createProjectRootLocator(options = {}) { const platform = process.platform; if (platform === "win32") { return new WindowsProjectRootLocator(options); } else { return new ProjectRootLocator(options); } } /** * 创建PromptX工作空间定位器 */ static createPromptXWorkspaceLocator(options = {}) { const projectRootLocator = this.createProjectRootLocator(options); return new PromptXWorkspaceLocator({ ...options, projectRootLocator }); } /** * 获取平台信息 */ static getPlatform() { return process.platform; } }; var WindowsProjectRootLocator = class extends ProjectRootLocator { constructor(options = {}) { super({ ...options, // Windows默认避免用户家目录 avoidUserHome: options.avoidUserHome !== false }); } /** * Windows特有的项目根目录验证 */ async _validateProjectRoot(projectRoot, context = {}) { const baseValid = await super._validateProjectRoot(projectRoot, context); if (!baseValid) { return false; } const systemPaths = [ "C:\\Windows", "C:\\Program Files", "C:\\Program Files (x86)", "C:\\System Volume Information" ]; const resolvedPath = path.resolve(projectRoot).toUpperCase(); for (const systemPath of systemPaths) { if (resolvedPath.startsWith(systemPath.toUpperCase())) { return false; } } return true; } }; module2.exports = { DirectoryLocator, ProjectRootLocator, PromptXWorkspaceLocator, DirectoryLocatorFactory, WindowsProjectRootLocator }; } }); // src/utils/DirectoryService.js var require_DirectoryService = __commonJS({ "src/utils/DirectoryService.js"(exports2, module2) { "use strict"; init_cjs_shims(); var { DirectoryLocatorFactory } = require_DirectoryLocator(); var logger = require("@promptx/logger"); var DirectoryService = class { constructor() { this.workspaceLocator = null; this.initialized = false; } /** * 初始化服务 */ async initialize(options = {}) { if (this.initialized) { return; } try { this.workspaceLocator = DirectoryLocatorFactory.createPromptXWorkspaceLocator(options); this.initialized = true; l