@iflow-mcp/promptx
Version:
AI角色创建平台和智能工具开发平台,基于MCP协议提供专业AI能力注入
1,340 lines (1,329 loc) • 202 kB
JavaScript
"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