diff --git a/src/bin/promptx.js b/src/bin/promptx.js index 22fd4e8..8eab3f4 100755 --- a/src/bin/promptx.js +++ b/src/bin/promptx.js @@ -25,7 +25,9 @@ program .command('init [workspacePath]') .description('🏗️ init锦囊 - 初始化工作环境,传达系统基本诺记') .action(async (workspacePath, options) => { - await cli.execute('init', workspacePath ? [workspacePath] : []) + // 如果提供了workspacePath,将其作为workingDirectory参数传递 + const args = workspacePath ? { workingDirectory: workspacePath } : {} + await cli.execute('init', [args]) }) program diff --git a/src/lib/commands/MCPServerCommand.js b/src/lib/commands/MCPServerCommand.js index f89ef00..2744f40 100644 --- a/src/lib/commands/MCPServerCommand.js +++ b/src/lib/commands/MCPServerCommand.js @@ -166,7 +166,7 @@ class MCPServerCommand { */ convertMCPToCliParams(toolName, mcpArgs) { const paramMapping = { - 'promptx_init': () => [], + 'promptx_init': (args) => args.workingDirectory ? [args] : [], 'promptx_hello': () => [], diff --git a/src/lib/core/pouch/commands/ActionCommand.js b/src/lib/core/pouch/commands/ActionCommand.js index 81aaad9..677c244 100644 --- a/src/lib/core/pouch/commands/ActionCommand.js +++ b/src/lib/core/pouch/commands/ActionCommand.js @@ -5,6 +5,7 @@ const { COMMANDS } = require('../../../../constants') const { getGlobalResourceManager } = require('../../resource') const DPMLContentParser = require('../../resource/DPMLContentParser') const SemanticRenderer = require('../../resource/SemanticRenderer') +const CurrentProjectManager = require('../../../utils/CurrentProjectManager') const logger = require('../../../utils/logger') /** @@ -20,6 +21,7 @@ class ActionCommand extends BasePouchCommand { this.resourceManager = getGlobalResourceManager() this.dpmlParser = new DPMLContentParser() this.semanticRenderer = new SemanticRenderer() + this.currentProjectManager = new CurrentProjectManager() } getPurpose () { @@ -27,6 +29,8 @@ class ActionCommand extends BasePouchCommand { } async getContent (args) { + // 智能提示,不阻断服务 + const [roleId] = args if (!roleId) { @@ -477,6 +481,65 @@ ${recallContent} } } } + + /** + * 重写execute方法以添加项目状态检查 + */ + async execute (args = []) { + // 获取项目状态提示 + const projectPrompt = await this.currentProjectManager.generateTopLevelProjectPrompt('action') + + const purpose = this.getPurpose() + const content = await this.getContent(args) + const pateoas = await this.getPATEOAS(args) + + return this.formatOutputWithProjectCheck(purpose, content, pateoas, projectPrompt) + } + + /** + * 格式化带有项目检查的输出 + */ + formatOutputWithProjectCheck(purpose, content, pateoas, projectPrompt) { + const output = { + purpose, + content, + pateoas, + context: this.context, + format: this.outputFormat, + projectPrompt + } + + if (this.outputFormat === 'json') { + return output + } + + // 人类可读格式 + return { + ...output, + toString () { + const divider = '='.repeat(60) + const nextSteps = (pateoas.nextActions || []) + .map(action => ` - ${action.name}: ${action.description}\n 方式: ${action.method || action.command || '通过MCP工具'}`) + .join('\n') + + return `${projectPrompt} + +${divider} +🎯 锦囊目的:${purpose} +${divider} + +📜 锦囊内容: +${content} + +🔄 下一步行动: +${nextSteps} + +📍 当前状态:${pateoas.currentState} +${divider} +` + } + } + } } module.exports = ActionCommand diff --git a/src/lib/core/pouch/commands/HelloCommand.js b/src/lib/core/pouch/commands/HelloCommand.js index 6eb5db0..db4930f 100644 --- a/src/lib/core/pouch/commands/HelloCommand.js +++ b/src/lib/core/pouch/commands/HelloCommand.js @@ -2,6 +2,7 @@ const BasePouchCommand = require('../BasePouchCommand') const fs = require('fs-extra') const path = require('path') const { getGlobalResourceManager } = require('../../resource') +const CurrentProjectManager = require('../../../utils/CurrentProjectManager') const logger = require('../../../utils/logger') /** @@ -13,6 +14,7 @@ class HelloCommand extends BasePouchCommand { super() // 使用全局单例 ResourceManager this.resourceManager = getGlobalResourceManager() + this.currentProjectManager = new CurrentProjectManager() } getPurpose () { @@ -170,7 +172,7 @@ class HelloCommand extends BasePouchCommand { let content = `🤖 **AI专业角色服务清单** (共 ${totalRoles} 个专业角色可供选择) -> 💡 **重要说明**:以下是可激活的AI专业角色。每个角色都有唯一的ID,可通过MCP工具激活。 +> 💡 **使用说明**:以下是可激活的AI专业角色。每个角色都有唯一的ID,可通过MCP工具激活。 ## 📋 可用角色列表 @@ -337,6 +339,65 @@ class HelloCommand extends BasePouchCommand { logger.info('❌ RegistryData 不可用') } } + + /** + * 重写execute方法以添加项目状态检查 + */ + async execute (args = []) { + // 获取项目状态提示 + const projectPrompt = await this.currentProjectManager.generateTopLevelProjectPrompt('list') + + const purpose = this.getPurpose() + const content = await this.getContent(args) + const pateoas = await this.getPATEOAS(args) + + return this.formatOutputWithProjectCheck(purpose, content, pateoas, projectPrompt) + } + + /** + * 格式化带有项目检查的输出 + */ + formatOutputWithProjectCheck(purpose, content, pateoas, projectPrompt) { + const output = { + purpose, + content, + pateoas, + context: this.context, + format: this.outputFormat, + projectPrompt + } + + if (this.outputFormat === 'json') { + return output + } + + // 人类可读格式 + return { + ...output, + toString () { + const divider = '='.repeat(60) + const nextSteps = (pateoas.nextActions || []) + .map(action => ` - ${action.name}: ${action.description}\n 方式: ${action.method || action.command || '通过MCP工具'}`) + .join('\n') + + return `${projectPrompt} + +${divider} +🎯 锦囊目的:${purpose} +${divider} + +📜 锦囊内容: +${content} + +🔄 下一步行动: +${nextSteps} + +📍 当前状态:${pateoas.currentState} +${divider} +` + } + } + } } module.exports = HelloCommand diff --git a/src/lib/core/pouch/commands/InitCommand.js b/src/lib/core/pouch/commands/InitCommand.js index 99ca9fa..66e8ace 100644 --- a/src/lib/core/pouch/commands/InitCommand.js +++ b/src/lib/core/pouch/commands/InitCommand.js @@ -4,6 +4,7 @@ const { COMMANDS } = require('../../../../constants') const { getDirectoryService } = require('../../../utils/DirectoryService') const RegistryData = require('../../resource/RegistryData') const ProjectDiscovery = require('../../resource/discovery/ProjectDiscovery') +const CurrentProjectManager = require('../../../utils/CurrentProjectManager') const logger = require('../../../utils/logger') const path = require('path') const fs = require('fs-extra') @@ -19,6 +20,7 @@ class InitCommand extends BasePouchCommand { this.resourceManager = getGlobalResourceManager() this.projectDiscovery = new ProjectDiscovery() this.directoryService = getDirectoryService() + this.currentProjectManager = new CurrentProjectManager() } getPurpose () { @@ -26,18 +28,70 @@ class InitCommand extends BasePouchCommand { } async getContent (args) { - const [workspacePath = '.'] = args + // 获取工作目录参数,支持两种格式: + // 1. 来自MCP的对象格式:{ workingDirectory: "path" } + // 2. 来自CLI的字符串格式:["path"] + let workingDirectory + + if (args && typeof args[0] === 'object' && args[0].workingDirectory) { + // MCP格式 + workingDirectory = args[0].workingDirectory + } else if (args && typeof args[0] === 'string') { + // CLI格式 + workingDirectory = args[0] + } else if (args && args.length > 0 && args[0]) { + // 兜底:直接取第一个参数 + workingDirectory = args[0] + } + + let projectPath + + if (workingDirectory) { + // AI提供了工作目录,使用AI提供的路径 + projectPath = path.resolve(workingDirectory) + + // 验证AI提供的路径是否有效 + if (!await this.currentProjectManager.validateProjectPath(projectPath)) { + return `❌ 提供的工作目录无效: ${projectPath} + +请确保: +1. 路径存在且为目录 +2. 不是用户主目录 +3. 具有适当的访问权限 - // 构建统一的查找上下文 - // 对于init命令,我们优先使用当前目录,不向上查找现有.promptx +💡 请提供一个有效的项目目录路径。` + } + + // 保存AI提供的项目路径 + await this.currentProjectManager.setCurrentProject(projectPath) + + } else { + // AI没有提供工作目录,检查是否已有保存的项目 + const savedProject = await this.currentProjectManager.getCurrentProject() + + if (savedProject) { + // 使用之前保存的项目路径 + projectPath = savedProject + } else { + // 没有保存的项目,要求AI提供 + return `🎯 PromptX需要知道当前项目的工作目录。 + +请在调用此工具时提供 workingDirectory 参数,例如: +- workingDirectory: "/Users/sean/WorkSpaces/DeepracticeProjects/PromptX" + +💡 你当前工作在哪个项目目录?请提供完整的绝对路径。` + } + } + + // 构建统一的查找上下文,使用确定的项目路径 const context = { - startDir: workspacePath === '.' ? process.cwd() : path.resolve(workspacePath), + startDir: projectPath, platform: process.platform, - avoidUserHome: true, // 特别是Windows环境下避免用户家目录 + avoidUserHome: true, // init命令特有:优先当前目录,不查找现有.promptx strategies: [ 'currentWorkingDirectoryIfHasMarkers', - 'currentWorkingDirectory' // 如果当前目录没有项目标识,就直接使用当前目录 + 'currentWorkingDirectory' ] } diff --git a/src/lib/core/pouch/commands/LearnCommand.js b/src/lib/core/pouch/commands/LearnCommand.js index 1cb7ef3..d4887a3 100644 --- a/src/lib/core/pouch/commands/LearnCommand.js +++ b/src/lib/core/pouch/commands/LearnCommand.js @@ -2,6 +2,7 @@ const BasePouchCommand = require('../BasePouchCommand') const { getGlobalResourceManager } = require('../../resource') const DPMLContentParser = require('../../resource/DPMLContentParser') const SemanticRenderer = require('../../resource/SemanticRenderer') +const CurrentProjectManager = require('../../../utils/CurrentProjectManager') const { COMMANDS } = require('../../../../constants') /** @@ -16,6 +17,7 @@ class LearnCommand extends BasePouchCommand { this.resourceManager = getGlobalResourceManager() this.dpmlParser = new DPMLContentParser() this.semanticRenderer = new SemanticRenderer() + this.currentProjectManager = new CurrentProjectManager() } getPurpose () { @@ -66,7 +68,7 @@ class LearnCommand extends BasePouchCommand { } } - return this.formatSuccessResponse(protocol, resourceId, finalContent) + return await this.formatSuccessResponse(protocol, resourceId, finalContent) } catch (error) { return this.formatErrorResponse(resourceUrl, error.message) } @@ -97,7 +99,7 @@ class LearnCommand extends BasePouchCommand { /** * 格式化成功响应 */ - formatSuccessResponse (protocol, resourceId, content) { + async formatSuccessResponse (protocol, resourceId, content) { const protocolLabels = { thought: '🧠 思维模式', execution: '⚡ 执行模式', @@ -274,6 +276,65 @@ ${errorMessage} } } } + + /** + * 重写execute方法以添加项目状态检查 + */ + async execute (args = []) { + // 获取项目状态提示 + const projectPrompt = await this.currentProjectManager.generateTopLevelProjectPrompt('learn') + + const purpose = this.getPurpose() + const content = await this.getContent(args) + const pateoas = await this.getPATEOAS(args) + + return this.formatOutputWithProjectCheck(purpose, content, pateoas, projectPrompt) + } + + /** + * 格式化带有项目检查的输出 + */ + formatOutputWithProjectCheck(purpose, content, pateoas, projectPrompt) { + const output = { + purpose, + content, + pateoas, + context: this.context, + format: this.outputFormat, + projectPrompt + } + + if (this.outputFormat === 'json') { + return output + } + + // 人类可读格式 + return { + ...output, + toString () { + const divider = '='.repeat(60) + const nextSteps = (pateoas.nextActions || []) + .map(action => ` - ${action.name}: ${action.description}\n 方式: ${action.method || action.command || '通过MCP工具'}`) + .join('\n') + + return `${projectPrompt} + +${divider} +🎯 锦囊目的:${purpose} +${divider} + +📜 锦囊内容: +${content} + +🔄 下一步行动: +${nextSteps} + +📍 当前状态:${pateoas.currentState} +${divider} +` + } + } + } } module.exports = LearnCommand diff --git a/src/lib/core/pouch/commands/LearnCommand.js.backup b/src/lib/core/pouch/commands/LearnCommand.js.backup index 08a8955..dbfef6a 100644 --- a/src/lib/core/pouch/commands/LearnCommand.js.backup +++ b/src/lib/core/pouch/commands/LearnCommand.js.backup @@ -1,6 +1,6 @@ const BasePouchCommand = require('../BasePouchCommand') const ResourceManager = require('../../resource/resourceManager') -const DPMLContentParser = require('../../resource/DPMLContentParser') +const DPMLContentParser = require('../../dpml/DPMLContentParser') const SemanticRenderer = require('../../resource/SemanticRenderer') const { COMMANDS } = require('../../../../constants') diff --git a/src/lib/mcp/toolDefinitions.js b/src/lib/mcp/toolDefinitions.js index f38a949..882d756 100644 --- a/src/lib/mcp/toolDefinitions.js +++ b/src/lib/mcp/toolDefinitions.js @@ -11,12 +11,19 @@ const { z } = require('zod'); const TOOL_DEFINITIONS = [ { name: 'promptx_init', - description: '🎯 [AI专业能力启动器] ⚡ 让你瞬间拥有任何领域的专家级思维和技能 - 一键激活丰富的专业角色库(产品经理/开发者/设计师/营销专家等),获得跨对话记忆能力,30秒内从普通AI变身行业专家。**必须使用场景**:1️⃣系统首次使用时;2️⃣创建新角色后刷新注册表;3️⃣角色激活(action)出错时重新发现角色;4️⃣查看当前版本号。每次需要专业服务时都应该先用这个', + description: '🎯 [AI专业能力启动器] ⚡ 让你瞬间拥有任何领域的专家级思维和技能 - 一键激活丰富的专业角色库(产品经理/开发者/设计师/营销专家等),获得跨对话记忆能力,30秒内从普通AI变身行业专家。**必须使用场景**:1️⃣系统首次使用时;2️⃣创建新角色后刷新注册表;3️⃣角色激活(action)出错时重新发现角色;4️⃣查看当前版本号;5️⃣项目路径发生变化时。每次需要专业服务时都应该先用这个', inputSchema: { type: 'object', - properties: {} + properties: { + workingDirectory: { + type: 'string', + description: '当前项目的工作目录绝对路径。AI应该知道当前工作的项目路径,请提供此参数。' + } + } }, - zodSchema: z.object({}) + zodSchema: z.object({ + workingDirectory: z.string().optional().describe('当前项目的工作目录绝对路径。AI应该知道当前工作的项目路径,请提供此参数。') + }) }, { name: 'promptx_hello', diff --git a/src/lib/utils/CurrentProjectManager.js b/src/lib/utils/CurrentProjectManager.js new file mode 100644 index 0000000..a778d54 --- /dev/null +++ b/src/lib/utils/CurrentProjectManager.js @@ -0,0 +1,252 @@ +const fs = require('fs-extra') +const path = require('path') +const os = require('os') + +/** + * 当前项目管理器 + * 负责管理 ~/.promptx/current-project 文件,持久化当前项目路径 + */ +class CurrentProjectManager { + constructor() { + this.promptxHomeDir = path.join(os.homedir(), '.promptx') + this.currentProjectFile = path.join(this.promptxHomeDir, 'current-project') + } + + /** + * 获取当前保存的项目路径 + * @returns {Promise} 项目路径或null + */ + async getCurrentProject() { + try { + if (await fs.pathExists(this.currentProjectFile)) { + const content = await fs.readFile(this.currentProjectFile, 'utf-8') + return content.trim() + } + } catch (error) { + // 文件不存在或读取失败,返回null + } + return null + } + + /** + * 设置当前项目路径 + * @param {string} projectPath - 项目绝对路径 + */ + async setCurrentProject(projectPath) { + // 确保目录存在 + await fs.ensureDir(this.promptxHomeDir) + + // 保存项目路径 + await fs.writeFile(this.currentProjectFile, projectPath) + } + + /** + * 检查项目一致性,生成AI提示信息 + * @returns {Promise} 项目状态和AI提示信息 + */ + async checkProjectConsistency() { + const savedProject = await this.getCurrentProject() + + if (savedProject) { + return { + hasSaved: true, + savedPath: savedProject, + aiMessage: `📍 PromptX当前设置的项目路径: ${savedProject}`, + aiInstruction: '如果这不是你当前工作的项目路径,请调用 promptx_init 工具并提供正确的 workingDirectory 参数来更新。' + } + } else { + return { + hasSaved: false, + savedPath: null, + aiMessage: '🎯 PromptX尚未设置项目路径', + aiInstruction: '请先调用 promptx_init 工具并提供 workingDirectory 参数来设置当前项目路径。' + } + } + } + + /** + * 生成顶层项目状态提示信息(用于分隔线后的独立层级显示) + * @param {string} contextType - 上下文类型:'list'(角色列表), 'action'(角色激活), 'learn'(学习资源) + * @returns {string} 格式化的顶层项目状态提示 + */ + async generateTopLevelProjectPrompt(contextType = 'list') { + const projectCheck = await this.checkProjectConsistency() + + if (projectCheck.hasSaved) { + // 已设置项目路径的提示 + switch (contextType) { + case 'action': + return `🛑 **项目环境强制验证** 🛑 +${projectCheck.aiMessage} + +⚠️ **执行前必须确认**: +1. 上述路径是否为你当前工作的项目? +2. 如不一致,立即停止所有操作 +3. 使用 \`promptx_init\` 更新正确路径 +4. 验证通过后才能继续角色激活 + +💥 **严重警告**:在错误项目路径下操作将导致不可预知的错误!` + + case 'learn': + return `🛑 **学习环境强制验证** 🛑 +${projectCheck.aiMessage} + +⚠️ **学习前必须确认**: +1. 确保在正确的项目环境中学习 +2. 错误环境将导致知识关联失效 +3. 如需切换项目,立即停止并更新 + +💥 **严重警告**:项目环境不匹配将影响学习效果!` + + case 'list': + default: + return `🛑 **项目环境强制验证** 🛑 +${projectCheck.aiMessage} + +⚠️ **使用前必须确认**: +1. 立即确认上述路径是否为当前工作项目 +2. 如不一致,禁止继续任何操作 +3. 必须使用 \`promptx_init\` 更新正确路径 +4. 验证通过后才能使用角色服务 + +💥 **严重警告**:错误的项目环境将导致服务异常!` + } + } else { + // 未设置项目路径的提示 + return `🛑 **致命错误:项目环境未初始化** 🛑 +${projectCheck.aiMessage} + +💢 **立即执行**: +1. 停止当前操作 +2. 调用 \`promptx_init\` 工具初始化环境 +3. 提供正确的 workingDirectory 参数 +4. 初始化完成后重新开始 + +⛔ **严禁继续**:未初始化环境中的任何操作都可能失败!` + } + } + + /** + * 生成统一的项目状态提示信息 + * @param {string} contextType - 上下文类型:'list'(角色列表), 'action'(角色激活), 'learn'(学习资源) + * @returns {string} 格式化的项目状态提示 + */ + async generateProjectStatusPrompt(contextType = 'list') { + const projectCheck = await this.checkProjectConsistency() + + if (projectCheck.hasSaved) { + // 已设置项目路径的提示 + switch (contextType) { + case 'action': + return `\n🛑 **执行前置检查** +--- +## 📍 项目环境验证 +✅ ${projectCheck.aiMessage} + +⚠️ **强制验证**: +1. 请确认这是你当前工作的项目路径 +2. 如不一致,必须立即停止操作 +3. 使用 \`promptx_init\` 更新正确路径 +4. 仅当路径正确才能继续操作 + +💢 **违规警告**: +- 在错误的项目路径下执行操作将导致不可预知的错误 +- 可能影响项目配置和资源完整性 +---\n` + + case 'learn': + return `\n🛑 **执行前置检查** +--- +## 📍 学习环境验证 +✅ ${projectCheck.aiMessage} + +⚠️ **强制验证**: +1. 确保在正确的项目环境中学习 +2. 错误的项目环境将导致知识关联失效 +3. 如需切换项目,请立即停止并更新 + +💢 **违规警告**: +- 项目环境不匹配将影响学习效果 +- 可能造成知识体系混乱 +---\n` + + case 'list': + default: + return `🛑 **执行前置检查** + +## 📍 项目环境强制验证 +✅ ${projectCheck.aiMessage} + +⚠️ **检查步骤**: +1. 立即确认上述路径是否为当前工作项目 +2. 如不一致,禁止继续任何操作 +3. 必须使用 \`promptx_init\` 更新正确路径 +4. 验证通过后才能使用角色服务 + +💢 **违规警告**: +- 错误的项目环境将导致角色服务异常 +- 可能影响项目资源和配置完整性 +- 禁止在未经验证的环境中继续操作 + +💡 **操作指南**: +1. 一致 → 继续使用角色服务 +2. 不一致 → 立即停止并更新路径 +3. 不确定 → 先确认再操作` + } + } else { + // 未设置项目路径的提示 + return `🛑 **执行终止** + +## ⚠️ 致命错误:项目环境未初始化 +${projectCheck.aiMessage} + +💢 **强制要求**: +1. 立即停止当前操作 +2. 调用 \`promptx_init\` 工具初始化环境: +\`\`\` +workingDirectory: "你当前工作的项目完整路径" +\`\`\` +3. 初始化完成后重新开始操作 + +⛔ **禁止事项**: +- 禁止在未初始化环境中执行任何操作 +- 禁止跳过环境初始化步骤 +- 禁止使用可能不正确的项目路径 + +💥 **违规后果**: +- 操作将失败或产生不可预知的错误 +- 可能破坏项目配置和资源完整性 +- 导致角色服务异常或失效` + } + } + + /** + * 验证路径是否为有效的项目目录 + * @param {string} projectPath - 要验证的路径 + * @returns {Promise} 是否为有效项目目录 + */ + 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 + } + } +} + +module.exports = CurrentProjectManager \ No newline at end of file