UNPKG

dpml-prompt

Version:

DPML-powered AI prompt framework - Revolutionary AI-First CLI system based on Deepractice Prompt Markup Language. Build sophisticated AI agents with structured prompts, memory systems, and execution frameworks.

474 lines (392 loc) 15.9 kB
const BasePouchCommand = require('../BasePouchCommand') const fs = require('fs-extra') const path = require('path') const { COMMANDS } = require('../../../../constants') const { getGlobalResourceManager } = require('../../resource') const { getDirectoryService } = require('../../../utils/DirectoryService') const logger = require('../../../utils/logger') /** * 记忆检索锦囊命令 - 纯XML模式 * 负责从XML格式记忆库中检索相关知识和经验 * 已升级为统一XML架构,移除Markdown兼容逻辑 */ class RecallCommand extends BasePouchCommand { constructor () { super() this.lastSearchCount = 0 this.resourceManager = getGlobalResourceManager() this.FORCE_XML_MODE = true // 🎯 强制XML模式标志 } getPurpose () { return 'AI主动检索记忆中的专业知识、最佳实践和历史经验(纯XML模式)' } async getContent (args) { // 解析参数:--role, query const { role, query } = this.parseArgs(args) if (!role) { return `❌ 错误:缺少必填参数 role 🎯 **使用方法**: recall 角色ID [查询关键词] 📋 **示例**: recall java-developer "React Hooks" recall product-manager # 查看所有产品经理记忆 recall copywriter "A/B测试" 💡 **可用角色ID**:通过 welcome 工具查看所有可用角色` } logger.step('🧠 [RecallCommand] 开始记忆检索流程 (纯XML模式)') logger.info(`🔍 [RecallCommand] 角色: ${role}, 查询内容: ${query ? `"${query}"` : '全部记忆'}`) try { // 🎯 传递role参数到检索方法 const memories = await this.getXMLMemoriesOnly(query, role) logger.success(`✅ [RecallCommand] XML记忆检索完成 - 找到 ${memories.length} 条匹配记忆`) if (memories.length === 0) { if (query) { logger.warn(`⚠️ [RecallCommand] 未找到匹配查询"${query}"的记忆`) return `🔍 记忆检索结果:未找到匹配"${query}"的相关记忆 💡 优化建议: 1. **扩大查询范围**:尝试使用更通用的关键词 2. **换个角度查询**:尝试相关词汇或概念 3. **检查拼写**:确认关键词拼写正确 4. **查看全部记忆**:不使用查询参数,浏览所有记忆寻找灵感 🔄 下一步行动: - 不带参数再次使用 recall 工具查看全部记忆 - 使用 remember 工具记录新的相关知识 - 使用 learn 工具学习相关资源后再检索` } else { logger.warn('⚠️ [RecallCommand] 记忆体系为空') return `🧠 AI记忆体系中暂无内容。 💡 建议: 1. 使用 MCP PromptX remember 工具内化新知识 2. 使用 MCP PromptX learn 工具学习后再内化 3. 开始构建AI的专业知识体系` } } const formattedMemories = this.formatRetrievedKnowledge(memories, query) return `🧠 AI记忆体系 ${query ? `检索"${query}"` : '全部记忆'} (${memories.length}条): ${formattedMemories} 💡 记忆运用建议: 1. 结合当前任务场景灵活运用 2. 根据实际情况调整和变通 3. 持续学习和增强记忆能力` } catch (error) { logger.error(`❌ [RecallCommand] 记忆检索失败: ${error.message}`) logger.debug(`🐛 [RecallCommand] 错误堆栈: ${error.stack}`) return `❌ 检索记忆时出错:${error.message} 🛡️ **数据安全提示**: - 如果是升级后首次使用,数据在 .promptx/backup/ 目录中有备份 - DPML格式记忆文件位置:.promptx/memory/declarative.dpml - 如需帮助,请检查备份数据或重新运行记忆迁移` } } /** * 🎯 解析命令行参数 - role作为第一个位置参数 */ parseArgs(args) { let query = '' let role = '' let argIndex = 0 // 第一个参数是role if (args.length > 0) { role = args[0] argIndex = 1 } // 从第二个参数开始解析查询内容 for (let i = argIndex; i < args.length; i++) { // 查询参数 if (query) { query += ' ' + args[i] } else { query = args[i] } } return { role, query } } getPATEOAS (args) { const [query] = args const currentState = query ? `recalled-${query}` : 'recall-waiting' return { currentState, availableTransitions: ['welcome', 'remember', 'learn', 'recall'], nextActions: [ { name: '选择角色', description: '选择专业角色来应用检索到的知识', method: 'MCP PromptX welcome 工具' }, { name: '记忆新知识', description: '继续内化更多专业知识', method: 'MCP PromptX remember 工具' }, { name: '学习资源', description: '学习相关专业资源', method: 'MCP PromptX learn 工具' }, { name: '继续检索', description: '检索其他相关记忆', method: 'MCP PromptX recall 工具' } ], metadata: { query: query || null, resultCount: this.lastSearchCount || 0, searchTime: new Date().toISOString(), hasResults: (this.lastSearchCount || 0) > 0 } } } /** * 获取XML记忆(纯XML模式,移除Markdown兼容) */ async getXMLMemoriesOnly (query, role) { logger.step('🔧 [RecallCommand] 执行纯XML检索模式') this.lastSearchCount = 0 const memories = [] logger.debug('🔍 [RecallCommand] 初始化ResourceManager...') // 确保ResourceManager已初始化 if (!this.resourceManager.initialized) { logger.info('⚙️ [RecallCommand] ResourceManager未初始化,正在初始化...') await this.resourceManager.initializeWithNewArchitecture() logger.success('⚙️ [RecallCommand] ResourceManager初始化完成') } // 🎯 使用@project协议获取记忆目录(支持HTTP模式) const currentRole = role logger.info(`📁 [RecallCommand] 通过@project协议解析角色记忆目录...`) const projectProtocol = this.resourceManager.protocols.get('project') const roleMemoryDir = await projectProtocol.resolvePath(`.promptx/memory/${currentRole}`) const xmlFile = path.join(roleMemoryDir, 'declarative.dpml') logger.info(`📁 [RecallCommand] @project协议解析结果: ${roleMemoryDir}`) logger.info(`📁 [RecallCommand] 检索角色记忆: ${xmlFile}`) try { // 🎯 只读取XML格式,不再兼容Markdown if (await fs.pathExists(xmlFile)) { logger.info('📄 [RecallCommand] 读取XML格式记忆文件') const xmlMemories = await this.readXMLMemories(xmlFile, query) memories.push(...xmlMemories) logger.success(`📄 [RecallCommand] XML记忆读取完成 - ${xmlMemories.length} 条记忆`) } else { logger.warn('📄 [RecallCommand] 未找到XML记忆文件,可能需要先创建记忆') } } catch (error) { logger.error(`❌ [RecallCommand] 读取XML记忆文件时发生错误: ${error.message}`) logger.debug(`🐛 [RecallCommand] 读取错误堆栈: ${error.stack}`) } this.lastSearchCount = memories.length logger.info(`📊 [RecallCommand] XML记忆检索统计 - 总计: ${memories.length} 条`) return memories } /** * 检查记忆是否匹配查询 - 增强版匹配算法 */ matchesMemory (memory, query) { if (!query) return true logger.debug(`🎯 [RecallCommand] 开始匹配检查 - 查询: "${query}", 记忆: "${memory.content.substring(0, 30)}..."`) const lowerQuery = query.toLowerCase() const lowerContent = memory.content.toLowerCase() // 1. 完全匹配 - 最高优先级 if (lowerContent.includes(lowerQuery) || memory.tags.some(tag => tag.toLowerCase().includes(lowerQuery))) { logger.debug(`✅ [RecallCommand] 完全匹配成功`) return true } // 2. 分词匹配 - 支持多关键词组合查询 const queryWords = lowerQuery.split(/\s+/).filter(word => word.length > 1) if (queryWords.length > 1) { const matchedWords = queryWords.filter(word => lowerContent.includes(word) || memory.tags.some(tag => tag.toLowerCase().includes(word)) ) // 如果匹配了一半以上的关键词,认为相关 if (matchedWords.length >= Math.ceil(queryWords.length / 2)) { logger.debug(`✅ [RecallCommand] 分词匹配成功 - 匹配词数: ${matchedWords.length}/${queryWords.length}`) return true } } // 3. 模糊匹配 - 支持常见同义词和缩写 const synonyms = this.getSynonyms(lowerQuery) for (const synonym of synonyms) { if (lowerContent.includes(synonym) || memory.tags.some(tag => tag.toLowerCase().includes(synonym))) { logger.debug(`✅ [RecallCommand] 同义词匹配成功 - 同义词: "${synonym}"`) return true } } logger.debug(`❌ [RecallCommand] 无匹配`) return false } /** * 获取查询词的同义词和相关词 */ getSynonyms (query) { const synonymMap = { 'mcp': ['model context protocol', '工具'], 'promptx': ['prompt-x', '提示词'], '测试': ['test', 'testing', 'qa', '质量保证'], '工具': ['tool', 'mcp', '功能'], '记忆': ['memory', 'recall', '回忆'], '学习': ['learn', 'study', '学会'], '角色': ['role', 'character', '专家'], '产品': ['product', 'pm', '产品经理'], '开发': ['develop', 'dev', 'coding', '编程'], '前端': ['frontend', 'fe', 'ui'], '后端': ['backend', 'be', 'api', '服务端'], 'github': ['git', '代码仓库', '版本控制'], 'issue': ['问题', 'bug', '需求'], '敏捷': ['agile', 'scrum', '迭代'] } const result = [query] // 包含原查询词 // 查找直接同义词 if (synonymMap[query]) { result.push(...synonymMap[query]) } // 查找包含关系的同义词 for (const [key, values] of Object.entries(synonymMap)) { if (key.includes(query) || query.includes(key)) { result.push(key, ...values) } if (values.some(v => v.includes(query) || query.includes(v))) { result.push(key, ...values) } } return [...new Set(result)] // 去重 } matchesQuery (content, query) { const lowerContent = content.toLowerCase() const lowerQuery = query.toLowerCase() const keywords = lowerQuery.split(/\s+/) return keywords.some(keyword => lowerContent.includes(keyword)) } /** * 格式化检索到的记忆(支持多行显示,确保XML反转义) */ formatRetrievedKnowledge (memories, query) { return memories.map((memory, index) => { // 保持完整的记忆内容,不进行截断 // 陈述性记忆的完整性对于系统价值至关重要 let content = memory.content // 🔧 确保XML转义字符被正确反转义 content = this.unescapeXML(content) // 只对格式进行优化,但不截断内容 // 确保换行符正确显示 content = content.trim() // 🔧 也要对标签进行反转义处理 const unescapedTags = memory.tags.map(tag => this.unescapeXML(tag)) return `📝 ${index + 1}. **记忆** (${memory.timestamp}) ${content} ${unescapedTags.slice(0, 8).join(' ')} ---` }).join('\n') } extractDomain (query) { const domains = ['copywriter', 'scrum', 'developer', 'test', 'prompt'] const lowerQuery = query.toLowerCase() return domains.find(domain => lowerQuery.includes(domain)) || null } getRelatedQuery (query) { const relatedMap = { copywriter: 'marketing', scrum: 'agile', frontend: 'ui', backend: 'api', test: 'qa' } for (const [key, value] of Object.entries(relatedMap)) { if (query.toLowerCase().includes(key)) { return value } } return query + '-advanced' } /** * 读取XML格式记忆 */ async readXMLMemories (xmlFile, query) { logger.step('📄 [RecallCommand] 开始读取XML格式记忆') const memories = [] try { const xmlContent = await fs.readFile(xmlFile, 'utf8') logger.info(`📄 [RecallCommand] XML文件读取成功 - 文件大小: ${xmlContent.length} 字符`) const xmlMemories = this.parseXMLMemories(xmlContent) logger.info(`📄 [RecallCommand] XML解析完成 - 解析出 ${xmlMemories.length} 条记忆`) for (const memory of xmlMemories) { if (!query || this.matchesMemory(memory, query)) { memories.push(memory) if (query) { logger.debug(`🎯 [RecallCommand] 记忆匹配成功: "${memory.content.substring(0, 30)}..."`) } } else if (query) { logger.debug(`❌ [RecallCommand] 记忆不匹配: "${memory.content.substring(0, 30)}..."`) } } logger.success(`📄 [RecallCommand] XML记忆筛选完成 - 匹配: ${memories.length}/${xmlMemories.length} 条`) } catch (error) { logger.error(`❌ [RecallCommand] XML记忆读取失败: ${error.message}`) logger.debug(`🐛 [RecallCommand] XML读取错误堆栈: ${error.stack}`) } return memories } /** * 解析XML格式记忆 */ parseXMLMemories (xmlContent) { logger.debug('🔍 [RecallCommand] 开始解析XML记忆内容') const memories = [] try { // 简单的XML解析(不依赖外部库) const itemRegex = /<item\s+id="([^"]*?)"\s+time="([^"]*?)">(.*?)<\/item>/gs let match let itemCount = 0 while ((match = itemRegex.exec(xmlContent)) !== null) { itemCount++ const [, id, timestamp, itemContent] = match logger.debug(`🔍 [RecallCommand] 解析记忆项 ${itemCount}: ID=${id}, 时间=${timestamp}`) // 解析内容和标签 const contentMatch = itemContent.match(/<content>(.*?)<\/content>/s) const tagsMatch = itemContent.match(/<tags>(.*?)<\/tags>/s) if (contentMatch) { const content = this.unescapeXML(contentMatch[1].trim()) const tagsString = tagsMatch ? this.unescapeXML(tagsMatch[1].trim()) : '' const tags = tagsString ? tagsString.split(/\s+/).filter(t => t) : [] logger.debug(`🔍 [RecallCommand] 记忆项内容: "${content.substring(0, 50)}${content.length > 50 ? '...' : ''}"`) logger.debug(`🔍 [RecallCommand] 记忆项标签: [${tags.join(', ')}]`) memories.push({ id, timestamp, content, tags, source: 'xml' }) } else { logger.warn(`⚠️ [RecallCommand] 记忆项 ${itemCount} 缺少content标签`) } } logger.success(`🔍 [RecallCommand] XML解析完成 - 成功解析 ${memories.length} 条记忆`) } catch (error) { logger.error(`❌ [RecallCommand] XML解析失败: ${error.message}`) logger.debug(`🐛 [RecallCommand] XML解析错误堆栈: ${error.stack}`) } return memories } /** * XML反转义函数(增强版,处理所有常见XML转义字符) */ unescapeXML (text) { if (typeof text !== 'string') { return text } return text .replace(/&lt;/g, '<') .replace(/&gt;/g, '>') .replace(/&quot;/g, '"') .replace(/&#x27;/g, "'") .replace(/&#39;/g, "'") .replace(/&apos;/g, "'") .replace(/&#x2F;/g, '/') .replace(/&#47;/g, '/') .replace(/&nbsp;/g, ' ') .replace(/&amp;/g, '&') } } module.exports = RecallCommand