diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..4dc31d6 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,21 @@ +module.exports = { + env: { + browser: true, + commonjs: true, + es2021: true, + node: true, + jest: true + }, + extends: [ + 'standard' + ], + parserOptions: { + ecmaVersion: 12 + }, + rules: { + // 允许console.log用于CLI工具 + 'no-console': 'off', + // 允许使用require() + '@typescript-eslint/no-var-requires': 'off' + } +} \ No newline at end of file diff --git a/README.md b/README.md index 446f6a3..184608d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## 🚀 一行命令,AI自主引导 -`Please execute 'npx promptx init'` +`Please execute 'npx dpml-prompt init'` **就这一行!AI会自动引导你完成所有操作** diff --git a/package.json b/package.json index 4027329..52c8b3a 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { - "name": "promptx", + "name": "dpml-prompt", "version": "0.0.2-snapshot.0", - "description": "A powerful AI prompt framework for role-based conversations and intelligent interactions. Build sophisticated AI agents with modular prompts, memory systems, and execution frameworks.", + "description": "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.", "main": "src/lib/index.js", "bin": { - "promptx": "src/bin/promptx.js" + "dpml-prompt": "src/bin/promptx.js" }, "scripts": { "start": "node src/bin/promptx.js", @@ -28,8 +28,7 @@ "format": "prettier --write src/", "format:check": "prettier --check src/", "validate": "npm run lint && npm run test:ci", - "precommit": "npm run lint && npm run test:unit", - "prepublishOnly": "npm run validate && npm run build" + "precommit": "npm run lint && npm run test:unit" }, "files": [ "src/bin/", @@ -81,19 +80,21 @@ "npm": ">=6.0.0" }, "keywords": [ + "dpml", + "deepractice", "ai", "prompt", + "prompt-engineering", + "markup-language", + "ai-first", + "cli", "chatgpt", "claude", - "prompt-engineering", "conversation", "agent", "framework", "role-playing", "memory-system", - "scrum", - "product-management", - "cli", "automation" ], "author": { diff --git a/src/bin/promptx.js b/src/bin/promptx.js index f38fa88..501d74c 100755 --- a/src/bin/promptx.js +++ b/src/bin/promptx.js @@ -1,70 +1,70 @@ #!/usr/bin/env node -const { Command } = require('commander'); -const chalk = require('chalk'); -const packageJson = require('../../package.json'); +const { Command } = require('commander') +const chalk = require('chalk') +const packageJson = require('../../package.json') // 导入锦囊框架 -const { cli } = require('../lib/core/pouch'); +const { cli } = require('../lib/core/pouch') // 创建主程序 -const program = new Command(); +const program = new Command() // 设置程序信息 program .name('promptx') .description(packageJson.description) - .version(packageJson.version, '-v, --version', 'display version number'); + .version(packageJson.version, '-v, --version', 'display version number') // 五大核心锦囊命令 program .command('init [workspacePath]') .description('🏗️ init锦囊 - 初始化工作环境,传达系统基本诺记') .action(async (workspacePath, options) => { - await cli.execute('init', workspacePath ? [workspacePath] : []); - }); + await cli.execute('init', workspacePath ? [workspacePath] : []) + }) program .command('hello') .description('👋 hello锦囊 - 发现并展示所有可用的AI角色和领域专家') .action(async (options) => { - await cli.execute('hello', []); - }); + await cli.execute('hello', []) + }) program .command('action ') .description('⚡ action锦囊 - 激活特定AI角色,获取专业提示词') .action(async (role, options) => { - await cli.execute('action', [role]); - }); + await cli.execute('action', [role]) + }) program .command('learn [resourceUrl]') .description('📚 learn锦囊 - 学习指定协议的资源内容(thought://、execution://等)') .action(async (resourceUrl, options) => { - await cli.execute('learn', resourceUrl ? [resourceUrl] : []); - }); + await cli.execute('learn', resourceUrl ? [resourceUrl] : []) + }) program .command('recall [query]') .description('🔍 recall锦囊 - AI主动从记忆中检索相关的专业知识') .action(async (query, options) => { - await cli.execute('recall', query ? [query] : []); - }); + await cli.execute('recall', query ? [query] : []) + }) program .command('remember [value...]') .description('🧠 remember锦囊 - AI主动内化知识和经验到记忆体系') .action(async (key, value, options) => { - const args = [key, ...(value || [])]; - await cli.execute('remember', args); - }); + const args = [key, ...(value || [])] + await cli.execute('remember', args) + }) // 全局错误处理 program.configureHelp({ helpWidth: 100, sortSubcommands: true -}); +}) // 添加示例说明 program.addHelpText('after', ` @@ -115,19 +115,19 @@ ${chalk.cyan('💭 核心理念:')} ${chalk.cyan('更多信息:')} GitHub: ${chalk.underline('https://github.com/Deepractice/PromptX')} 组织: ${chalk.underline('https://github.com/Deepractice')} -`); +`) // 处理未知命令 program.on('command:*', () => { - console.error(chalk.red(`错误: 未知命令 '${program.args.join(' ')}'`)); - console.log(''); - program.help(); -}); + console.error(chalk.red(`错误: 未知命令 '${program.args.join(' ')}'`)) + console.log('') + program.help() +}) // 如果没有参数,显示帮助 if (process.argv.length === 2) { - program.help(); + program.help() } // 解析命令行参数 -program.parse(process.argv); \ No newline at end of file +program.parse(process.argv) diff --git a/src/constants.js b/src/constants.js index eaee5b1..dc11296 100644 --- a/src/constants.js +++ b/src/constants.js @@ -4,7 +4,7 @@ */ // 命令前缀配置 - 约定大于配置 -export const COMMAND_PREFIX = 'npx promptx'; +export const COMMAND_PREFIX = 'npx dpml-prompt' // 常用命令模板 export const COMMANDS = { @@ -15,7 +15,7 @@ export const COMMANDS = { RECALL: `${COMMAND_PREFIX} recall`, REMEMBER: `${COMMAND_PREFIX} remember`, HELP: `${COMMAND_PREFIX} help` -}; +} // 带参数的命令构建函数 export const buildCommand = { @@ -23,7 +23,7 @@ export const buildCommand = { learn: (resource) => `${COMMAND_PREFIX} learn ${resource}`, recall: (query = '') => `${COMMAND_PREFIX} recall${query ? ' ' + query : ''}`, remember: (key, content = '') => `${COMMAND_PREFIX} remember ${key}${content !== '' ? ' "' + content + '"' : ' '}` -}; +} // 系统路径配置 export const PATHS = { @@ -31,10 +31,10 @@ export const PATHS = { MEMORY_DIR: '.promptx/memory', STATE_FILE: '.promptx/pouch.json', MEMORY_FILE: '.promptx/memory/declarative.md' -}; +} // 版本信息 -export const VERSION = '0.0.1'; +export const VERSION = '0.0.1' // 系统状态 export const STATES = { @@ -44,4 +44,4 @@ export const STATES = { LEARNED_ROLE: 'learned_role', MEMORY_SAVED: 'memory_saved', RECALL_WAITING: 'recall-waiting' -}; \ No newline at end of file +} diff --git a/src/lib/core/pouch/BasePouchCommand.js b/src/lib/core/pouch/BasePouchCommand.js index cfb9675..bc2b880 100644 --- a/src/lib/core/pouch/BasePouchCommand.js +++ b/src/lib/core/pouch/BasePouchCommand.js @@ -3,15 +3,15 @@ * 所有锦囊命令都需要继承此类 */ class BasePouchCommand { - constructor() { + constructor () { this.context = { currentPouch: '', history: [], userProfile: {}, sessionData: {}, domainContext: {} - }; - this.outputFormat = 'human'; + } + this.outputFormat = 'human' } /** @@ -19,36 +19,36 @@ class BasePouchCommand { * @param {Array} args - 命令参数 * @returns {Promise} 锦囊输出 */ - async execute(args = []) { - const purpose = this.getPurpose(); - const content = await this.getContent(args); - const pateoas = await this.getPATEOAS(args); - - return this.formatOutput(purpose, content, pateoas); + async execute (args = []) { + const purpose = this.getPurpose() + const content = await this.getContent(args) + const pateoas = await this.getPATEOAS(args) + + return this.formatOutput(purpose, content, pateoas) } /** * 设置状态上下文 * @param {StateContext} context - 状态上下文 */ - setContext(context) { - this.context = { ...this.context, ...context }; + setContext (context) { + this.context = { ...this.context, ...context } } /** * 设置输出格式 * @param {'human'|'json'} format - 输出格式 */ - setOutputFormat(format) { - this.outputFormat = format; + setOutputFormat (format) { + this.outputFormat = format } /** * 获取锦囊目的说明(子类必须实现) * @returns {string} 目的说明 */ - getPurpose() { - throw new Error('子类必须实现 getPurpose 方法'); + getPurpose () { + throw new Error('子类必须实现 getPurpose 方法') } /** @@ -56,8 +56,8 @@ class BasePouchCommand { * @param {Array} args - 命令参数 * @returns {Promise} 锦囊内容 */ - async getContent(args) { - throw new Error('子类必须实现 getContent 方法'); + async getContent (args) { + throw new Error('子类必须实现 getContent 方法') } /** @@ -65,8 +65,8 @@ class BasePouchCommand { * @param {Array} args - 命令参数 * @returns {PATEOASNavigation} PATEOAS导航 */ - getPATEOAS(args) { - throw new Error('子类必须实现 getPATEOAS 方法'); + getPATEOAS (args) { + throw new Error('子类必须实现 getPATEOAS 方法') } /** @@ -76,27 +76,27 @@ class BasePouchCommand { * @param {PATEOASNavigation} pateoas - PATEOAS导航 * @returns {PouchOutput} 格式化的输出 */ - formatOutput(purpose, content, pateoas) { + formatOutput (purpose, content, pateoas) { const output = { purpose, content, pateoas, context: this.context, format: this.outputFormat - }; + } if (this.outputFormat === 'json') { - return output; + return output } // 人类可读格式 return { ...output, - toString() { - const divider = '='.repeat(60); + toString () { + const divider = '='.repeat(60) const nextSteps = (pateoas.nextActions || []) .map(action => ` - ${action.name}: ${action.description}\n 命令: ${action.command}`) - .join('\n'); + .join('\n') return ` ${divider} @@ -111,10 +111,10 @@ ${nextSteps} 📍 当前状态:${pateoas.currentState} ${divider} -`; +` } - }; + } } } -module.exports = BasePouchCommand; \ No newline at end of file +module.exports = BasePouchCommand diff --git a/src/lib/core/pouch/PouchCLI.js b/src/lib/core/pouch/PouchCLI.js index fe15599..d6c5b6a 100644 --- a/src/lib/core/pouch/PouchCLI.js +++ b/src/lib/core/pouch/PouchCLI.js @@ -1,24 +1,24 @@ -const PouchStateMachine = require('./state/PouchStateMachine'); -const PouchRegistry = require('./PouchRegistry'); -const commands = require('./commands'); +const PouchStateMachine = require('./state/PouchStateMachine') +const PouchRegistry = require('./PouchRegistry') +const commands = require('./commands') /** * 锦囊CLI主入口 * 提供命令行接口和统一的执行入口 */ class PouchCLI { - constructor() { - this.stateMachine = new PouchStateMachine(); - this.registry = new PouchRegistry(); - this.initialized = false; + constructor () { + this.stateMachine = new PouchStateMachine() + this.registry = new PouchRegistry() + this.initialized = false } /** * 初始化CLI */ - async initialize() { + async initialize () { if (this.initialized) { - return; + return } // 批量注册所有命令 @@ -29,18 +29,18 @@ class PouchCLI { learn: commands.LearnCommand, recall: commands.RecallCommand, remember: commands.RememberCommand - }); + }) // 将命令注册到状态机 for (const name of this.registry.list()) { - const command = this.registry.get(name); - this.stateMachine.registerCommand(name, command); + const command = this.registry.get(name) + this.stateMachine.registerCommand(name, command) } // 加载历史状态 - await this.stateMachine.loadState(); + await this.stateMachine.loadState() - this.initialized = true; + this.initialized = true } /** @@ -49,32 +49,32 @@ class PouchCLI { * @param {Array} args - 命令参数 * @returns {Promise} 执行结果 */ - async execute(commandName, args = []) { + async execute (commandName, args = []) { // 确保已初始化 if (!this.initialized) { - await this.initialize(); + await this.initialize() } // 验证命令是否存在 if (!this.registry.validate(commandName)) { - throw new Error(`未知命令: ${commandName}\n使用 'npx promptx help' 查看可用命令`); + throw new Error(`未知命令: ${commandName}\n使用 'npx promptx help' 查看可用命令`) } try { // 通过状态机执行命令 - const result = await this.stateMachine.transition(commandName, args); - + const result = await this.stateMachine.transition(commandName, args) + // 如果结果有 toString 方法,打印人类可读格式 if (result && result.toString && typeof result.toString === 'function') { - console.log(result.toString()); + console.log(result.toString()) } else { - console.log(JSON.stringify(result, null, 2)); + console.log(JSON.stringify(result, null, 2)) } - return result; + return result } catch (error) { - console.error(`执行命令出错: ${error.message}`); - throw error; + console.error(`执行命令出错: ${error.message}`) + throw error } } @@ -82,10 +82,10 @@ class PouchCLI { * 获取帮助信息 * @returns {string} 帮助文本 */ - getHelp() { - const commands = this.registry.getCommandDetails(); - const currentState = this.stateMachine.getCurrentState(); - const availableTransitions = this.stateMachine.getAvailableTransitions(); + getHelp () { + const commands = this.registry.getCommandDetails() + const currentState = this.stateMachine.getCurrentState() + const availableTransitions = this.stateMachine.getAvailableTransitions() let help = ` 🎯 PromptX 锦囊系统帮助 @@ -95,10 +95,10 @@ class PouchCLI { 可用转换: ${availableTransitions.join(', ')} 📋 可用命令: -`; +` for (const cmd of commands) { - help += `\n ${cmd.name.padEnd(12)} - ${cmd.purpose}`; + help += `\n ${cmd.name.padEnd(12)} - ${cmd.purpose}` } help += ` @@ -115,23 +115,23 @@ class PouchCLI { 按照提示即可完成完整的工作流程。 📚 更多信息请访问: https://github.com/yourusername/promptx -`; +` - return help; + return help } /** * 获取当前状态信息 * @returns {StateContext} 状态上下文 */ - getStatus() { + getStatus () { return { currentState: this.stateMachine.getCurrentState(), availableCommands: this.registry.list(), availableTransitions: this.stateMachine.getAvailableTransitions(), context: this.stateMachine.context, initialized: this.initialized - }; + } } /** @@ -139,62 +139,62 @@ class PouchCLI { * @param {string} input - 用户输入 * @returns {Object} 解析结果 */ - parseCommand(input) { - const parts = input.trim().split(/\s+/); - const command = parts[0]; - const args = parts.slice(1); + parseCommand (input) { + const parts = input.trim().split(/\s+/) + const command = parts[0] + const args = parts.slice(1) return { - command: command, - args: args - }; + command, + args + } } /** * 运行交互式CLI */ - async runInteractive() { - console.log('🎯 欢迎使用 PromptX 锦囊系统!'); - console.log('输入 "help" 查看帮助,"exit" 退出\n'); + async runInteractive () { + console.log('🎯 欢迎使用 PromptX 锦囊系统!') + console.log('输入 "help" 查看帮助,"exit" 退出\n') - const readline = require('readline'); + const readline = require('readline') const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: 'promptx> ' - }); + }) - rl.prompt(); + rl.prompt() rl.on('line', async (line) => { - const input = line.trim(); - + const input = line.trim() + if (input === 'exit' || input === 'quit') { - console.log('再见!'); - rl.close(); - return; + console.log('再见!') + rl.close() + return } if (input === 'help') { - console.log(this.getHelp()); + console.log(this.getHelp()) } else if (input === 'status') { - console.log(JSON.stringify(this.getStatus(), null, 2)); + console.log(JSON.stringify(this.getStatus(), null, 2)) } else if (input) { - const { command, args } = this.parseCommand(input); + const { command, args } = this.parseCommand(input) try { - await this.execute(command, args); + await this.execute(command, args) } catch (error) { - console.error(error.message); + console.error(error.message) } } - rl.prompt(); - }); + rl.prompt() + }) rl.on('close', () => { - process.exit(0); - }); + process.exit(0) + }) } } -module.exports = PouchCLI; \ No newline at end of file +module.exports = PouchCLI diff --git a/src/lib/core/pouch/PouchRegistry.js b/src/lib/core/pouch/PouchRegistry.js index b351131..83d3f04 100644 --- a/src/lib/core/pouch/PouchRegistry.js +++ b/src/lib/core/pouch/PouchRegistry.js @@ -3,8 +3,8 @@ * 负责管理和注册所有锦囊命令 */ class PouchRegistry { - constructor() { - this.commands = new Map(); + constructor () { + this.commands = new Map() } /** @@ -12,16 +12,16 @@ class PouchRegistry { * @param {string} name - 命令名称 * @param {BasePouchCommand} command - 命令实例 */ - register(name, command) { + register (name, command) { if (!name || typeof name !== 'string') { - throw new Error('命令名称必须是非空字符串'); + throw new Error('命令名称必须是非空字符串') } if (!command || typeof command.execute !== 'function') { - throw new Error('命令必须实现 execute 方法'); + throw new Error('命令必须实现 execute 方法') } - this.commands.set(name.toLowerCase(), command); + this.commands.set(name.toLowerCase(), command) } /** @@ -29,16 +29,16 @@ class PouchRegistry { * @param {string} name - 命令名称 * @returns {BasePouchCommand} 命令实例 */ - get(name) { - return this.commands.get(name.toLowerCase()); + get (name) { + return this.commands.get(name.toLowerCase()) } /** * 列出所有已注册的命令 * @returns {string[]} 命令名称列表 */ - list() { - return Array.from(this.commands.keys()); + list () { + return Array.from(this.commands.keys()) } /** @@ -46,46 +46,46 @@ class PouchRegistry { * @param {string} name - 命令名称 * @returns {boolean} 是否存在 */ - validate(name) { - return this.commands.has(name.toLowerCase()); + validate (name) { + return this.commands.has(name.toLowerCase()) } /** * 获取命令详情 * @returns {Object[]} 命令详情列表 */ - getCommandDetails() { - const details = []; - + getCommandDetails () { + const details = [] + for (const [name, command] of this.commands) { details.push({ - name: name, + name, purpose: command.getPurpose ? command.getPurpose() : '未定义', className: command.constructor.name - }); + }) } - - return details; + + return details } /** * 清空注册器 */ - clear() { - this.commands.clear(); + clear () { + this.commands.clear() } /** * 批量注册命令 * @param {Object} commandMap - 命令映射对象 */ - registerBatch(commandMap) { + registerBatch (commandMap) { for (const [name, CommandClass] of Object.entries(commandMap)) { if (typeof CommandClass === 'function') { - this.register(name.toLowerCase(), new CommandClass()); + this.register(name.toLowerCase(), new CommandClass()) } } } } -module.exports = PouchRegistry; \ No newline at end of file +module.exports = PouchRegistry diff --git a/src/lib/core/pouch/commands/ActionCommand.js b/src/lib/core/pouch/commands/ActionCommand.js index d51d457..78aca8e 100644 --- a/src/lib/core/pouch/commands/ActionCommand.js +++ b/src/lib/core/pouch/commands/ActionCommand.js @@ -1,26 +1,26 @@ -const BasePouchCommand = require('../BasePouchCommand'); -const fs = require('fs-extra'); -const path = require('path'); -const { COMMANDS, buildCommand } = require('../../../../constants'); +const BasePouchCommand = require('../BasePouchCommand') +const fs = require('fs-extra') +const path = require('path') +const { COMMANDS, buildCommand } = require('../../../../constants') /** * 角色激活锦囊命令 * 负责分析角色文件,提取需要学习的thought、execution和knowledge */ class ActionCommand extends BasePouchCommand { - constructor() { - super(); + constructor () { + super() // 获取HelloCommand的角色注册表 - this.helloCommand = null; + this.helloCommand = null } - getPurpose() { - return '激活特定AI角色,分析并生成具体的思维模式、行为模式和知识学习计划'; + getPurpose () { + return '激活特定AI角色,分析并生成具体的思维模式、行为模式和知识学习计划' } - async getContent(args) { - const [roleId] = args; - + async getContent (args) { + const [roleId] = args + if (!roleId) { return `❌ 请指定要激活的角色ID @@ -32,29 +32,28 @@ ${buildCommand.action('<角色ID>')} 💡 查看可用角色: \`\`\`bash ${COMMANDS.HELLO} -\`\`\``; +\`\`\`` } try { // 1. 获取角色信息 - const roleInfo = await this.getRoleInfo(roleId); + const roleInfo = await this.getRoleInfo(roleId) if (!roleInfo) { return `❌ 角色 "${roleId}" 不存在! 🔍 请使用以下命令查看可用角色: \`\`\`bash ${COMMANDS.HELLO} -\`\`\``; +\`\`\`` } // 2. 分析角色文件,提取依赖 - const dependencies = await this.analyzeRoleDependencies(roleInfo); - + const dependencies = await this.analyzeRoleDependencies(roleInfo) + // 3. 生成学习计划 (新版本:以role://开头) - return this.generateLearningPlan(roleInfo.id, dependencies); - + return this.generateLearningPlan(roleInfo.id, dependencies) } catch (error) { - console.error('Action command error:', error); + console.error('Action command error:', error) return `❌ 激活角色 "${roleId}" 时发生错误。 🔍 可能的原因: @@ -62,131 +61,130 @@ ${COMMANDS.HELLO} - 权限不足 - 系统资源问题 -💡 请使用 \`${COMMANDS.HELLO}\` 查看可用角色列表。`; +💡 请使用 \`${COMMANDS.HELLO}\` 查看可用角色列表。` } } /** * 获取角色信息(从HelloCommand) */ - async getRoleInfo(roleId) { + async getRoleInfo (roleId) { // 懒加载HelloCommand实例 if (!this.helloCommand) { - const HelloCommand = require('./HelloCommand'); - this.helloCommand = new HelloCommand(); + const HelloCommand = require('./HelloCommand') + this.helloCommand = new HelloCommand() } - - return await this.helloCommand.getRoleInfo(roleId); + + return await this.helloCommand.getRoleInfo(roleId) } /** * 分析角色文件,提取thought和execution依赖 */ - async analyzeRoleDependencies(roleInfo) { + async analyzeRoleDependencies (roleInfo) { try { // 处理文件路径,将@package://前缀替换为实际路径 - let filePath = roleInfo.file; + let filePath = roleInfo.file if (filePath.startsWith('@package://')) { - filePath = filePath.replace('@package://', ''); + filePath = filePath.replace('@package://', '') } - + // 读取角色文件内容 - const roleContent = await fs.readFile(filePath, 'utf-8'); - + const roleContent = await fs.readFile(filePath, 'utf-8') + // 提取所有资源引用 - const resourceRegex = /@([!?]?)([a-zA-Z][a-zA-Z0-9_-]*):\/\/([a-zA-Z0-9_\/.,-]+?)(?=[\s\)\],]|$)/g; - const matches = Array.from(roleContent.matchAll(resourceRegex)); - + const resourceRegex = /@([!?]?)([a-zA-Z][a-zA-Z0-9_-]*):\/\/([a-zA-Z0-9_\/.,-]+?)(?=[\s\)\],]|$)/g + const matches = Array.from(roleContent.matchAll(resourceRegex)) + const dependencies = { thoughts: new Set(), executions: new Set(), knowledge: [roleInfo.id] // 角色自身的knowledge - }; - + } + // 分类依赖 matches.forEach(match => { - const [fullMatch, priority, protocol, resource] = match; - + const [fullMatch, priority, protocol, resource] = match + if (protocol === 'thought') { - dependencies.thoughts.add(resource); + dependencies.thoughts.add(resource) } else if (protocol === 'execution') { - dependencies.executions.add(resource); + dependencies.executions.add(resource) } - }); - + }) + return { thoughts: dependencies.thoughts, executions: dependencies.executions, knowledge: dependencies.knowledge - }; - + } } catch (error) { - console.error('Error analyzing role dependencies:', error); + console.error('Error analyzing role dependencies:', error) // 如果分析失败,返回基础结构 return { thoughts: [], executions: [], knowledge: [roleInfo.id] - }; + } } } /** * 生成学习指引(基于分析出的依赖) */ - generateLearningGuide(roleInfo, dependencies) { + generateLearningGuide (roleInfo, dependencies) { let guide = `🎬 **角色激活计划:${roleInfo.name}** 📋 **角色概述** ${roleInfo.description} -`; +` // 思维模式部分 if (dependencies.thoughts.length > 0) { guide += `## 🧠 第一步:学习思维模式 掌握角色所需的核心思考技能 -`; +` dependencies.thoughts.forEach((thought, index) => { guide += `### ${index + 1}. ${thought} \`\`\`bash promptx learn thought://${thought} \`\`\` -`; - }); +` + }) } - // 行为模式部分 + // 行为模式部分 if (dependencies.executions.length > 0) { guide += `## ⚖️ 第二步:学习行为模式 掌握角色所需的核心执行技能 -`; +` dependencies.executions.forEach((execution, index) => { guide += `### ${index + 1}. ${execution} \`\`\`bash promptx learn execution://${execution} \`\`\` -`; - }); +` + }) } // 知识部分 guide += `## 📚 第三步:学习专业知识 获取角色的领域知识体系 -`; +` dependencies.knowledge.forEach((knowledge, index) => { guide += `### ${index + 1}. ${knowledge} 领域知识 \`\`\`bash promptx learn knowledge://${knowledge} \`\`\` -`; - }); +` + }) // 编排学习 guide += `## 🎪 第四步:学习编排方式 @@ -216,66 +214,66 @@ promptx learn principle://${roleInfo.id} - 🔍 **调用记忆** - 使用 \`promptx recall\` 检索相关经验 - 🔄 **切换角色** - 使用 \`promptx hello\` 选择其他专业角色 -💡 **设计理念**:基于 DPML 基础协议组合,通过thought和execution的灵活编排实现角色能力。`; +💡 **设计理念**:基于 DPML 基础协议组合,通过thought和execution的灵活编排实现角色能力。` - return guide; + return guide } /** * 生成学习计划 */ - generateLearningPlan(roleId, dependencies) { - const { thoughts, executions } = dependencies; - - let plan = `🎭 **准备激活角色:${roleId}**\n\n`; - + generateLearningPlan (roleId, dependencies) { + const { thoughts, executions } = dependencies + + let plan = `🎭 **准备激活角色:${roleId}**\n\n` + // 第一步:学习完整角色 - plan += `## 🎯 第一步:掌握角色全貌\n`; - plan += `理解角色的完整定义和核心特征\n\n`; - plan += `\`\`\`bash\n`; - plan += `${buildCommand.learn(`role://${roleId}`)}\n`; - plan += `\`\`\`\n\n`; - + plan += '## 🎯 第一步:掌握角色全貌\n' + plan += '理解角色的完整定义和核心特征\n\n' + plan += '```bash\n' + plan += `${buildCommand.learn(`role://${roleId}`)}\n` + plan += '```\n\n' + // 第二步:学习思维模式 if (thoughts.size > 0) { - plan += `## 🧠 第二步:掌握思维模式\n`; - plan += `学习角色特定的思考方式和认知框架\n\n`; - + plan += '## 🧠 第二步:掌握思维模式\n' + plan += '学习角色特定的思考方式和认知框架\n\n' + Array.from(thoughts).forEach((thought, index) => { - plan += `\`\`\`bash\n`; - plan += `${buildCommand.learn(`thought://${thought}`)}\n`; - plan += `\`\`\`\n\n`; - }); + plan += '```bash\n' + plan += `${buildCommand.learn(`thought://${thought}`)}\n` + plan += '```\n\n' + }) } - + // 第三步:掌握执行技能 if (executions.size > 0) { - plan += `## ⚡ 第${thoughts.size > 0 ? '三' : '二'}步:掌握执行技能\n`; - plan += `学习角色的行为模式和操作技能\n\n`; - + plan += `## ⚡ 第${thoughts.size > 0 ? '三' : '二'}步:掌握执行技能\n` + plan += '学习角色的行为模式和操作技能\n\n' + Array.from(executions).forEach((execution, index) => { - plan += `\`\`\`bash\n`; - plan += `${buildCommand.learn(`execution://${execution}`)}\n`; - plan += `\`\`\`\n\n`; - }); + plan += '```bash\n' + plan += `${buildCommand.learn(`execution://${execution}`)}\n` + plan += '```\n\n' + }) } - + // 激活确认 - const stepCount = thoughts.size > 0 ? (executions.size > 0 ? '四' : '三') : (executions.size > 0 ? '三' : '二'); - plan += `## 🎪 第${stepCount}步:完成角色激活\n`; - plan += `确认角色能力已完全激活\n\n`; - plan += `✅ **角色激活检查清单**:\n`; - plan += `- [x] 已学习完整角色定义\n`; - if (thoughts.size > 0) plan += `- [x] 已掌握 ${thoughts.size} 个思维模式\n`; - if (executions.size > 0) plan += `- [x] 已掌握 ${executions.size} 个执行技能\n`; - plan += `- [x] 可以开始以${roleId}身份工作\n\n`; - - return plan; + const stepCount = thoughts.size > 0 ? (executions.size > 0 ? '四' : '三') : (executions.size > 0 ? '三' : '二') + plan += `## 🎪 第${stepCount}步:完成角色激活\n` + plan += '确认角色能力已完全激活\n\n' + plan += '✅ **角色激活检查清单**:\n' + plan += '- [x] 已学习完整角色定义\n' + if (thoughts.size > 0) plan += `- [x] 已掌握 ${thoughts.size} 个思维模式\n` + if (executions.size > 0) plan += `- [x] 已掌握 ${executions.size} 个执行技能\n` + plan += `- [x] 可以开始以${roleId}身份工作\n\n` + + return plan } - getPATEOAS(args) { - const [roleId] = args; - + getPATEOAS (args) { + const [roleId] = args + if (!roleId) { return { currentState: 'action_awaiting_role', @@ -291,7 +289,7 @@ promptx learn principle://${roleInfo.id} metadata: { message: '需要指定角色ID' } - }; + } } return { @@ -319,8 +317,8 @@ promptx learn principle://${roleInfo.id} systemVersion: '锦囊串联状态机 v1.0', designPhilosophy: 'AI use CLI get prompt for AI' } - }; + } } } -module.exports = ActionCommand; \ No newline at end of file +module.exports = ActionCommand diff --git a/src/lib/core/pouch/commands/HelloCommand.js b/src/lib/core/pouch/commands/HelloCommand.js index 217f77a..b37d798 100644 --- a/src/lib/core/pouch/commands/HelloCommand.js +++ b/src/lib/core/pouch/commands/HelloCommand.js @@ -1,87 +1,87 @@ -const BasePouchCommand = require('../BasePouchCommand'); -const fs = require('fs-extra'); -const path = require('path'); -const { buildCommand } = require('../../../../constants'); +const BasePouchCommand = require('../BasePouchCommand') +const fs = require('fs-extra') +const path = require('path') +const { buildCommand } = require('../../../../constants') /** * 角色发现锦囊命令 * 负责展示可用的AI角色和领域专家 */ class HelloCommand extends BasePouchCommand { - constructor() { - super(); - this.roleRegistry = null; // 角色注册表将从资源系统动态加载 + constructor () { + super() + this.roleRegistry = null // 角色注册表将从资源系统动态加载 } - getPurpose() { - return '为AI提供可用角色信息,以便AI向主人汇报专业服务选项'; + getPurpose () { + return '为AI提供可用角色信息,以便AI向主人汇报专业服务选项' } /** * 动态加载角色注册表 */ - async loadRoleRegistry() { + async loadRoleRegistry () { if (this.roleRegistry) { - return this.roleRegistry; + return this.roleRegistry } try { // 从ResourceManager获取统一注册表 - const ResourceManager = require('../../resource/resourceManager'); - const resourceManager = new ResourceManager(); - await resourceManager.initialize(); // 确保初始化完成 - + const ResourceManager = require('../../resource/resourceManager') + const resourceManager = new ResourceManager() + await resourceManager.initialize() // 确保初始化完成 + if (resourceManager.registry && resourceManager.registry.protocols && resourceManager.registry.protocols.role && resourceManager.registry.protocols.role.registry) { - this.roleRegistry = resourceManager.registry.protocols.role.registry; + this.roleRegistry = resourceManager.registry.protocols.role.registry } else { // 备用:如果资源系统不可用,使用基础角色 this.roleRegistry = { - 'assistant': { - "file": "@package://prompt/domain/assistant/assistant.role.md", - "name": "🙋 智能助手", - "description": "通用助理角色,提供基础的助理服务和记忆支持" + assistant: { + file: '@package://prompt/domain/assistant/assistant.role.md', + name: '🙋 智能助手', + description: '通用助理角色,提供基础的助理服务和记忆支持' } - }; + } } } catch (error) { - console.warn('角色注册表加载失败,使用基础角色:', error.message); + console.warn('角色注册表加载失败,使用基础角色:', error.message) this.roleRegistry = { - 'assistant': { - "file": "@package://prompt/domain/assistant/assistant.role.md", - "name": "🙋 智能助手", - "description": "通用助理角色,提供基础的助理服务和记忆支持" + assistant: { + file: '@package://prompt/domain/assistant/assistant.role.md', + name: '🙋 智能助手', + description: '通用助理角色,提供基础的助理服务和记忆支持' } - }; + } } - return this.roleRegistry; + return this.roleRegistry } /** * 获取所有角色列表(转换为数组格式) */ - async getAllRoles() { - const registry = await this.loadRoleRegistry(); + async getAllRoles () { + const registry = await this.loadRoleRegistry() return Object.entries(registry).map(([id, roleInfo]) => ({ - id: id, + id, name: roleInfo.name, description: roleInfo.description, file: roleInfo.file - })); + })) } - async getContent(args) { - await this.loadRoleRegistry(); - const allRoles = await this.getAllRoles(); - const totalRoles = allRoles.length; - + async getContent (args) { + await this.loadRoleRegistry() + const allRoles = await this.getAllRoles() + const totalRoles = allRoles.length + let content = `🤖 **AI专业角色服务清单** (共 ${totalRoles} 个专业角色可供选择) > 💡 **重要说明**:以下是可激活的AI专业角色。每个角色都有唯一的ID,使用action命令激活。 ## 📋 可用角色列表 -`; +` // 清楚显示角色ID和激活命令 allRoles.forEach((role, index) => { @@ -92,8 +92,8 @@ class HelloCommand extends BasePouchCommand { --- -`; - }); +` + }) content += ` ## 🎯 **角色激活指南** @@ -120,19 +120,19 @@ npx promptx action ${allRoles[0]?.id || 'assistant'} 📢 **向主人汇报角色选项,明确说明使用方法:"请选择角色ID,然后我将执行对应的action命令"** 🎯 **等待主人指定具体的角色ID后,立即执行 \`npx promptx action <角色ID>\`** 💡 **强调:action命令需要具体的角色ID,不是角色名称** -`; +` - return content; + return content } - async getPATEOAS(args) { - const allRoles = await this.getAllRoles(); + async getPATEOAS (args) { + const allRoles = await this.getAllRoles() const availableRoles = allRoles.map(role => ({ roleId: role.id, name: role.name, actionCommand: buildCommand.action(role.id) - })); - + })) + return { currentState: 'role_discovery', availableTransitions: ['action', 'learn', 'init', 'recall'], @@ -147,44 +147,42 @@ npx promptx action ${allRoles[0]?.id || 'assistant'} ], metadata: { totalRoles: allRoles.length, - availableRoles: availableRoles, + availableRoles, dataSource: 'resource.registry.json', systemVersion: '锦囊串联状态机 v1.0', designPhilosophy: 'AI use CLI get prompt for AI' } - }; + } } - - /** * 获取角色信息(提供给其他命令使用) */ - async getRoleInfo(roleId) { - const registry = await this.loadRoleRegistry(); - const roleData = registry[roleId]; - + async getRoleInfo (roleId) { + const registry = await this.loadRoleRegistry() + const roleData = registry[roleId] + if (!roleData) { - return null; + return null } - + return { id: roleId, name: roleData.name, description: roleData.description, file: roleData.file - }; + } } /** * 未来扩展:动态角色发现 * TODO: 实现真正的文件扫描和解析 */ - async discoverAvailableDomains() { + async discoverAvailableDomains () { // 现在基于注册表返回角色ID列表 - const allRoles = await this.getAllRoles(); - return allRoles.map(role => role.id); + const allRoles = await this.getAllRoles() + return allRoles.map(role => role.id) } } -module.exports = HelloCommand; \ No newline at end of file +module.exports = HelloCommand diff --git a/src/lib/core/pouch/commands/InitCommand.js b/src/lib/core/pouch/commands/InitCommand.js index 64aa86a..8289423 100644 --- a/src/lib/core/pouch/commands/InitCommand.js +++ b/src/lib/core/pouch/commands/InitCommand.js @@ -1,32 +1,32 @@ -const BasePouchCommand = require('../BasePouchCommand'); -const fs = require('fs-extra'); -const path = require('path'); -const { ResourceManager } = require('../../resource'); -const { COMMANDS } = require('../../../../constants'); +const BasePouchCommand = require('../BasePouchCommand') +const fs = require('fs-extra') +const path = require('path') +const { ResourceManager } = require('../../resource') +const { COMMANDS } = require('../../../../constants') /** * 初始化锦囊命令 * 负责准备工作环境和传达系统协议 */ class InitCommand extends BasePouchCommand { - constructor() { - super(); - this.resourceManager = new ResourceManager(); + constructor () { + super() + this.resourceManager = new ResourceManager() } - getPurpose() { - return '初始化PromptX工作环境,传达系统基本诺记(协议体系)'; + getPurpose () { + return '初始化PromptX工作环境,传达系统基本诺记(协议体系)' } - async getContent(args) { - const [workspacePath = '.'] = args; - + async getContent (args) { + const [workspacePath = '.'] = args + // 1. 技术初始化 - await this.initializeWorkspace(workspacePath); - + await this.initializeWorkspace(workspacePath) + // 2. 加载协议体系 - const protocolContent = await this.loadProtocolSystem(); - + const protocolContent = await this.loadProtocolSystem() + return `🎯 PromptX 系统初始化完成! ## 🏗️ 技术环境准备 @@ -48,33 +48,33 @@ ${protocolContent} ⚠️ **AI执行指令**: 🚀 **立即执行下一个锦囊:${COMMANDS.HELLO}** 📢 **不要等待确认,直接进入hello命令发现可用角色!** -🎯 **记住:锦囊串联设计,init完成后必须自动进入hello!**`; +🎯 **记住:锦囊串联设计,init完成后必须自动进入hello!**` } /** * 加载协议体系内容 */ - async loadProtocolSystem() { + async loadProtocolSystem () { try { // 加载完整协议体系:PATEOAS + DPML + 所有标签协议 - const result = await this.resourceManager.resolve('@prompt://protocols'); - + const result = await this.resourceManager.resolve('@prompt://protocols') + if (result.success) { - return result.content; + return result.content } else { - console.warn('⚠️ 协议加载失败:', result.error?.message); - return this.getCoreProtocolSummary(); + console.warn('⚠️ 协议加载失败:', result.error?.message) + return this.getCoreProtocolSummary() } } catch (error) { - console.warn('⚠️ 无法加载完整协议体系,使用核心摘要:', error.message); - return this.getCoreProtocolSummary(); + console.warn('⚠️ 无法加载完整协议体系,使用核心摘要:', error.message) + return this.getCoreProtocolSummary() } } /** * 获取核心协议摘要(fallback) */ - getCoreProtocolSummary() { + getCoreProtocolSummary () { return `### 🎯 核心理念:AI use CLI get prompt for AI **PATEOAS协议** - Prompt as the Engine of Application State @@ -91,10 +91,10 @@ ${protocolContent} **三大解决方案** - **上下文遗忘** → 锦囊自包含,每个命令独立执行 - **注意力分散** → 分阶段专注,每锦囊专注单一任务 -- **能力局限** → 即时专家化,通过提示词获得专业能力`; +- **能力局限** → 即时专家化,通过提示词获得专业能力` } - getPATEOAS(args) { + getPATEOAS (args) { return { currentState: 'initialized', availableTransitions: ['hello', 'action', 'learn'], @@ -117,10 +117,10 @@ ${protocolContent} version: '0.0.1', philosophy: 'AI use CLI get prompt for AI - 锦囊串联无缝衔接' } - }; + } } - async initializeWorkspace(workspacePath) { + async initializeWorkspace (workspacePath) { // 创建基础目录结构 const dirs = [ 'prompt/core', @@ -128,23 +128,23 @@ ${protocolContent} 'prompt/protocol', 'prompt/resource', '.promptx' - ]; + ] for (const dir of dirs) { - await fs.ensureDir(path.join(workspacePath, dir)); + await fs.ensureDir(path.join(workspacePath, dir)) } // 创建锦囊状态配置文件 - const configPath = path.join(workspacePath, '.promptx', 'pouch.json'); + const configPath = path.join(workspacePath, '.promptx', 'pouch.json') if (!await fs.pathExists(configPath)) { await fs.writeJson(configPath, { version: '0.0.1', initialized: new Date().toISOString(), defaultFormat: 'human', stateHistory: [] - }, { spaces: 2 }); + }, { spaces: 2 }) } } } -module.exports = InitCommand; \ No newline at end of file +module.exports = InitCommand diff --git a/src/lib/core/pouch/commands/LearnCommand.js b/src/lib/core/pouch/commands/LearnCommand.js index 6ca4819..d10e5c7 100644 --- a/src/lib/core/pouch/commands/LearnCommand.js +++ b/src/lib/core/pouch/commands/LearnCommand.js @@ -1,47 +1,46 @@ -const BasePouchCommand = require('../BasePouchCommand'); -const ResourceManager = require('../../resource/resourceManager'); -const { COMMANDS, buildCommand } = require('../../../../constants'); +const BasePouchCommand = require('../BasePouchCommand') +const ResourceManager = require('../../resource/resourceManager') +const { COMMANDS, buildCommand } = require('../../../../constants') /** * 智能学习锦囊命令 * 支持加载thought、execution、memory等协议资源,以及角色的personality、principle、knowledge */ class LearnCommand extends BasePouchCommand { - constructor() { - super(); - this.resourceManager = new ResourceManager(); + constructor () { + super() + this.resourceManager = new ResourceManager() } - getPurpose() { - return '智能学习指定协议的资源内容,支持thought、execution、memory等DPML协议以及角色组件'; + getPurpose () { + return '智能学习指定协议的资源内容,支持thought、execution、memory等DPML协议以及角色组件' } - async getContent(args) { - const [resourceUrl] = args; - + async getContent (args) { + const [resourceUrl] = args + if (!resourceUrl) { - return this.getUsageHelp(); + return this.getUsageHelp() } try { // 直接使用ResourceManager解析资源 - const content = await this.resourceManager.resolve(resourceUrl); - + const content = await this.resourceManager.resolve(resourceUrl) + // 解析协议信息 - const urlMatch = resourceUrl.match(/^([a-zA-Z]+):\/\/(.+)$/); - const [, protocol, resourceId] = urlMatch; - - return this.formatSuccessResponse(protocol, resourceId, content); - + const urlMatch = resourceUrl.match(/^([a-zA-Z]+):\/\/(.+)$/) + const [, protocol, resourceId] = urlMatch + + return this.formatSuccessResponse(protocol, resourceId, content) } catch (error) { - return this.formatErrorResponse(resourceUrl, error.message); + return this.formatErrorResponse(resourceUrl, error.message) } } /** * 格式化成功响应 */ - formatSuccessResponse(protocol, resourceId, content) { + formatSuccessResponse (protocol, resourceId, content) { const protocolLabels = { thought: '🧠 思维模式', execution: '⚡ 执行模式', @@ -49,10 +48,10 @@ class LearnCommand extends BasePouchCommand { personality: '👤 角色人格', principle: '⚖️ 行为原则', knowledge: '📚 专业知识' - }; + } + + const label = protocolLabels[protocol] || `📄 ${protocol}` - const label = protocolLabels[protocol] || `📄 ${protocol}`; - return `✅ **成功学习${label}:${resourceId}** ## 📋 学习内容 @@ -72,13 +71,13 @@ ${content} - 激活角色: 激活完整角色能力 命令: \`${buildCommand.action('')}\` -📍 当前状态:learned_${protocol}`; +📍 当前状态:learned_${protocol}` } /** * 格式化错误响应 */ - formatErrorResponse(resourceUrl, errorMessage) { + formatErrorResponse (resourceUrl, errorMessage) { return `❌ 学习资源失败:${resourceUrl} 🔍 错误详情: @@ -105,13 +104,13 @@ ${buildCommand.action('')} # 查看角色的所有依赖 - 激活角色: 激活完整角色能力 命令: ${buildCommand.action('')} - 查看角色列表: 选择其他角色 - 命令: ${COMMANDS.HELLO}`; + 命令: ${COMMANDS.HELLO}` } /** * 获取使用帮助 */ - getUsageHelp() { + getUsageHelp () { return `🎓 **Learn锦囊 - 智能学习系统** ## 📖 基本用法 @@ -153,15 +152,15 @@ ${COMMANDS.HELLO} # 查看可用角色列表 - 激活角色: 分析角色依赖 命令: ${buildCommand.action('')} - 查看角色: 选择感兴趣的角色 - 命令: ${COMMANDS.HELLO}`; + 命令: ${COMMANDS.HELLO}` } /** * 获取PATEOAS导航信息 */ - getPATEOAS(args) { - const [resourceUrl] = args; - + getPATEOAS (args) { + const [resourceUrl] = args + if (!resourceUrl) { return { currentState: 'learn_awaiting_resource', @@ -180,27 +179,27 @@ ${COMMANDS.HELLO} # 查看可用角色列表 priority: 'high' } ] - }; + } } - const urlMatch = resourceUrl.match(/^([a-zA-Z]+):\/\/(.+)$/); + const urlMatch = resourceUrl.match(/^([a-zA-Z]+):\/\/(.+)$/) if (!urlMatch) { return { currentState: 'learn_error', availableTransitions: ['hello', 'action'], nextActions: [ { - name: '查看使用帮助', - description: '重新学习命令使用方法', - command: COMMANDS.LEARN, + name: '查看使用帮助', + description: '重新学习命令使用方法', + command: COMMANDS.LEARN, priority: 'high' } ] - }; + } } - const [, protocol, resourceId] = urlMatch; - + const [, protocol, resourceId] = urlMatch + return { currentState: `learned_${protocol}`, availableTransitions: ['learn', 'recall', 'hello', 'action'], @@ -232,12 +231,12 @@ ${COMMANDS.HELLO} # 查看可用角色列表 ], metadata: { learnedResource: resourceUrl, - protocol: protocol, - resourceId: resourceId, + protocol, + resourceId, systemVersion: '锦囊串联状态机 v1.0' } - }; + } } } -module.exports = LearnCommand; \ No newline at end of file +module.exports = LearnCommand diff --git a/src/lib/core/pouch/commands/RecallCommand.js b/src/lib/core/pouch/commands/RecallCommand.js index 09eefea..2ecd5a8 100644 --- a/src/lib/core/pouch/commands/RecallCommand.js +++ b/src/lib/core/pouch/commands/RecallCommand.js @@ -1,38 +1,38 @@ -const BasePouchCommand = require('../BasePouchCommand'); -const fs = require('fs-extra'); -const path = require('path'); -const { COMMANDS, buildCommand } = require('../../../../constants'); +const BasePouchCommand = require('../BasePouchCommand') +const fs = require('fs-extra') +const path = require('path') +const { COMMANDS, buildCommand } = require('../../../../constants') /** * 记忆检索锦囊命令 * 负责从记忆库中检索相关知识和经验 */ class RecallCommand extends BasePouchCommand { - constructor() { - super(); + constructor () { + super() } - getPurpose() { - return 'AI主动检索记忆中的专业知识、最佳实践和历史经验'; + getPurpose () { + return 'AI主动检索记忆中的专业知识、最佳实践和历史经验' } - async getContent(args) { - const [query] = args; + async getContent (args) { + const [query] = args try { - const memories = await this.getAllMemories(query); - + const memories = await this.getAllMemories(query) + if (memories.length === 0) { return `🧠 AI记忆体系中暂无内容。 💡 建议: 1. 使用 ${COMMANDS.REMEMBER} 内化新知识 2. 使用 ${COMMANDS.LEARN} 学习后再内化 -3. 开始构建AI的专业知识体系`; +3. 开始构建AI的专业知识体系` } - const formattedMemories = this.formatRetrievedKnowledge(memories, query); - + const formattedMemories = this.formatRetrievedKnowledge(memories, query) + return `🧠 AI记忆体系 ${query ? `检索"${query}"` : '全部记忆'} (${memories.length}条): ${formattedMemories} @@ -40,31 +40,31 @@ ${formattedMemories} 💡 记忆运用建议: 1. 结合当前任务场景灵活运用 2. 根据实际情况调整和变通 -3. 持续学习和增强记忆能力`; +3. 持续学习和增强记忆能力` } catch (error) { - return `❌ 检索记忆时出错:${error.message}`; + return `❌ 检索记忆时出错:${error.message}` } } - getPATEOAS(args) { - const [query] = args; - + getPATEOAS (args) { + const [query] = args + if (!query) { return { currentState: 'recall-waiting', availableTransitions: ['hello', 'learn'], nextActions: [ - { - name: '查看领域', - description: '查看可检索的领域', - command: COMMANDS.HELLO - } + { + name: '查看领域', + description: '查看可检索的领域', + command: COMMANDS.HELLO + } ] - }; + } } - const domain = this.extractDomain(query); - + const domain = this.extractDomain(query) + return { currentState: `recalled-${query}`, availableTransitions: ['action', 'learn', 'remember'], @@ -91,128 +91,128 @@ ${formattedMemories} } ], metadata: { - query: query, + query, resultCount: this.lastSearchCount || 0, searchTime: new Date().toISOString() } - }; + } } /** * 获取所有记忆(紧凑格式) */ - async getAllMemories(query) { - this.lastSearchCount = 0; - const memories = []; - + async getAllMemories (query) { + this.lastSearchCount = 0 + const memories = [] + // 读取单一记忆文件 - const memoryFile = path.join(process.cwd(), '.promptx/memory/declarative.md'); - + const memoryFile = path.join(process.cwd(), '.promptx/memory/declarative.md') + try { if (await fs.pathExists(memoryFile)) { - const content = await fs.readFile(memoryFile, 'utf-8'); - const lines = content.split('\n'); - + const content = await fs.readFile(memoryFile, 'utf-8') + const lines = content.split('\n') + for (const line of lines) { if (line.startsWith('- ')) { // 解析记忆行 - const memory = this.parseMemoryLine(line); + const memory = this.parseMemoryLine(line) if (memory && (!query || this.matchesMemory(memory, query))) { - memories.push(memory); + memories.push(memory) } } } } } catch (error) { - console.error('Error reading memories:', error); + console.error('Error reading memories:', error) } - this.lastSearchCount = memories.length; - return memories; + this.lastSearchCount = memories.length + return memories } /** * 解析记忆行(紧凑格式) */ - parseMemoryLine(line) { + parseMemoryLine (line) { // 格式:- 2025/05/31 14:30 内容 #key #tag1 #tag2 #评分:8 #有效期:长期 - const match = line.match(/^- (\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}) (.*?) (#\w+.*?)$/); - if (!match) return null; - - const [, timestamp, content, tagsStr] = match; - const tags = tagsStr.split(' ').filter(t => t.startsWith('#')); - const keyTag = tags.find(t => !t.includes(':') && !['#敏捷开发', '#测试', '#部署', '#前端开发', '#后端开发', '#AI', '#最佳实践', '#流程管理', '#工具使用', '#其他'].includes(t)); - + const match = line.match(/^- (\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}) (.*?) (#\w+.*?)$/) + if (!match) return null + + const [, timestamp, content, tagsStr] = match + const tags = tagsStr.split(' ').filter(t => t.startsWith('#')) + const keyTag = tags.find(t => !t.includes(':') && !['#敏捷开发', '#测试', '#部署', '#前端开发', '#后端开发', '#AI', '#最佳实践', '#流程管理', '#工具使用', '#其他'].includes(t)) + return { timestamp, content, key: keyTag ? keyTag.substring(1) : 'unknown', tags, source: keyTag ? keyTag.substring(1) : 'unknown' - }; + } } /** * 检查记忆是否匹配查询 */ - matchesMemory(memory, query) { - const lowerQuery = query.toLowerCase(); + matchesMemory (memory, query) { + const lowerQuery = query.toLowerCase() return memory.content.toLowerCase().includes(lowerQuery) || memory.key.toLowerCase().includes(lowerQuery) || - memory.tags.some(tag => tag.toLowerCase().includes(lowerQuery)); + memory.tags.some(tag => tag.toLowerCase().includes(lowerQuery)) } - matchesQuery(content, query) { - const lowerContent = content.toLowerCase(); - const lowerQuery = query.toLowerCase(); - const keywords = lowerQuery.split(/\s+/); - - return keywords.some(keyword => lowerContent.includes(keyword)); + matchesQuery (content, query) { + const lowerContent = content.toLowerCase() + const lowerQuery = query.toLowerCase() + const keywords = lowerQuery.split(/\s+/) + + return keywords.some(keyword => lowerContent.includes(keyword)) } /** * 格式化检索到的记忆(紧凑格式) */ - formatRetrievedKnowledge(memories, query) { + formatRetrievedKnowledge (memories, query) { return memories.map((memory, index) => { - const content = memory.content.length > 120 ? - memory.content.substring(0, 120) + '...' : - memory.content; - + const content = memory.content.length > 120 + ? memory.content.substring(0, 120) + '...' + : memory.content + return `📝 ${index + 1}. **${memory.key}** (${memory.timestamp}) ${content} ${memory.tags.slice(0, 5).join(' ')} ----`; - }).join('\n\n'); +---` + }).join('\n\n') } - extractDomain(query) { - const domains = ['copywriter', 'scrum', 'developer', 'test', 'prompt']; - const lowerQuery = query.toLowerCase(); - - return domains.find(domain => lowerQuery.includes(domain)) || null; + extractDomain (query) { + const domains = ['copywriter', 'scrum', 'developer', 'test', 'prompt'] + const lowerQuery = query.toLowerCase() + + return domains.find(domain => lowerQuery.includes(domain)) || null } - getRelatedQuery(query) { + getRelatedQuery (query) { const relatedMap = { - 'copywriter': 'marketing', - 'scrum': 'agile', - 'frontend': 'ui', - 'backend': 'api', - 'test': 'qa' - }; - + 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 value } } - - return query + '-advanced'; + + return query + '-advanced' } } -module.exports = RecallCommand; \ No newline at end of file +module.exports = RecallCommand diff --git a/src/lib/core/pouch/commands/RememberCommand.js b/src/lib/core/pouch/commands/RememberCommand.js index 1117a07..e75b773 100644 --- a/src/lib/core/pouch/commands/RememberCommand.js +++ b/src/lib/core/pouch/commands/RememberCommand.js @@ -1,27 +1,27 @@ -const BasePouchCommand = require('../BasePouchCommand'); -const fs = require('fs-extra'); -const path = require('path'); -const { COMMANDS, buildCommand } = require('../../../../constants'); +const BasePouchCommand = require('../BasePouchCommand') +const fs = require('fs-extra') +const path = require('path') +const { COMMANDS, buildCommand } = require('../../../../constants') /** * 记忆保存锦囊命令 * 负责将知识、经验和最佳实践保存到记忆库中 */ class RememberCommand extends BasePouchCommand { - constructor() { - super(); + constructor () { + super() } - getPurpose() { - return '增强AI长期记忆能力,主动内化专业知识、最佳实践和项目经验'; + getPurpose () { + return '增强AI长期记忆能力,主动内化专业知识、最佳实践和项目经验' } - async getContent(args) { - const [key, ...valueParts] = args; - const value = valueParts.join(' '); - + async getContent (args) { + const [key, ...valueParts] = args + const value = valueParts.join(' ') + if (!key) { - return this.getUsageHelp(); + return this.getUsageHelp() } if (!value) { @@ -36,15 +36,14 @@ ${buildCommand.remember('<记忆标识>', '<知识内容>')} \`\`\`bash ${buildCommand.remember('copywriter-tips', '"视频文案要有强烈的画面感和节奏感"')} ${buildCommand.remember('scrum-daily', '"每日站会应该控制在15分钟内,关注昨天、今天、阻碍"')} -\`\`\``; +\`\`\`` } try { - const memoryEntry = await this.saveMemory(key, value); - - return this.formatSaveResponse(key, value, memoryEntry); - - } catch (error) { + const memoryEntry = await this.saveMemory(key, value) + + return this.formatSaveResponse(key, value, memoryEntry) + } catch (error) { return `❌ 记忆内化失败:${error.message} 💡 可能的原因: @@ -55,88 +54,88 @@ ${buildCommand.remember('scrum-daily', '"每日站会应该控制在15分钟内 🔧 解决方案: 1. 检查 .promptx 目录权限 2. 确保磁盘空间充足 -3. 使用简洁的记忆标识(字母、数字、连字符)`; +3. 使用简洁的记忆标识(字母、数字、连字符)` } } /** * 将知识内化到AI记忆体系(紧凑格式) */ - async saveMemory(key, value) { + async saveMemory (key, value) { // 1. 确保AI记忆体系目录存在 - const memoryDir = await this.ensureMemoryDirectory(); - + const memoryDir = await this.ensureMemoryDirectory() + // 2. 使用单一记忆文件 - const memoryFile = path.join(memoryDir, 'declarative.md'); - + const memoryFile = path.join(memoryDir, 'declarative.md') + // 3. 格式化为一行记忆 - const memoryLine = this.formatMemoryLine(key, value); - + const memoryLine = this.formatMemoryLine(key, value) + // 4. 追加到记忆文件 - const action = await this.appendToMemoryFile(memoryFile, key, memoryLine); - + const action = await this.appendToMemoryFile(memoryFile, key, memoryLine) + return { key, value, filePath: memoryFile, action, timestamp: new Date().toISOString() - }; + } } /** * 确保AI记忆体系目录存在 */ - async ensureMemoryDirectory() { - const promptxDir = path.join(process.cwd(), '.promptx'); - const memoryDir = path.join(promptxDir, 'memory'); - - await fs.ensureDir(memoryDir); - - return memoryDir; + async ensureMemoryDirectory () { + const promptxDir = path.join(process.cwd(), '.promptx') + const memoryDir = path.join(promptxDir, 'memory') + + await fs.ensureDir(memoryDir) + + return memoryDir } /** * 格式化为一行记忆(紧凑格式) */ - formatMemoryLine(key, value) { - const now = new Date(); - const timestamp = `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; - + formatMemoryLine (key, value) { + const now = new Date() + const timestamp = `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}` + // 自动生成标签 - const tags = this.generateTags(key, value); - - return `- ${timestamp} ${value} #${key} ${tags} #评分:8 #有效期:长期`; + const tags = this.generateTags(key, value) + + return `- ${timestamp} ${value} #${key} ${tags} #评分:8 #有效期:长期` } /** * 自动生成标签 */ - generateTags(key, value) { - const tags = []; - const lowerKey = key.toLowerCase(); - const lowerValue = value.toLowerCase(); - + generateTags (key, value) { + const tags = [] + const lowerKey = key.toLowerCase() + const lowerValue = value.toLowerCase() + // 基于key生成标签 - if (lowerKey.includes('scrum') || lowerKey.includes('agile')) tags.push('#敏捷开发'); - if (lowerKey.includes('test') || lowerKey.includes('qa')) tags.push('#测试'); - if (lowerKey.includes('deploy') || lowerKey.includes('发布')) tags.push('#部署'); - if (lowerKey.includes('react') || lowerKey.includes('前端')) tags.push('#前端开发'); - if (lowerKey.includes('api') || lowerKey.includes('后端')) tags.push('#后端开发'); - if (lowerKey.includes('prompt') || lowerKey.includes('ai')) tags.push('#AI'); - + if (lowerKey.includes('scrum') || lowerKey.includes('agile')) tags.push('#敏捷开发') + if (lowerKey.includes('test') || lowerKey.includes('qa')) tags.push('#测试') + if (lowerKey.includes('deploy') || lowerKey.includes('发布')) tags.push('#部署') + if (lowerKey.includes('react') || lowerKey.includes('前端')) tags.push('#前端开发') + if (lowerKey.includes('api') || lowerKey.includes('后端')) tags.push('#后端开发') + if (lowerKey.includes('prompt') || lowerKey.includes('ai')) tags.push('#AI') + // 基于value生成标签 - if (lowerValue.includes('最佳实践') || lowerValue.includes('规则')) tags.push('#最佳实践'); - if (lowerValue.includes('流程') || lowerValue.includes('步骤')) tags.push('#流程管理'); - if (lowerValue.includes('命令') || lowerValue.includes('工具')) tags.push('#工具使用'); - - return tags.join(' ') || '#其他'; + if (lowerValue.includes('最佳实践') || lowerValue.includes('规则')) tags.push('#最佳实践') + if (lowerValue.includes('流程') || lowerValue.includes('步骤')) tags.push('#流程管理') + if (lowerValue.includes('命令') || lowerValue.includes('工具')) tags.push('#工具使用') + + return tags.join(' ') || '#其他' } /** * 追加到记忆文件 */ - async appendToMemoryFile(memoryFile, key, memoryLine) { + async appendToMemoryFile (memoryFile, key, memoryLine) { // 初始化文件(如果不存在) if (!await fs.pathExists(memoryFile)) { await fs.writeFile(memoryFile, `# 陈述性记忆 @@ -145,42 +144,40 @@ ${buildCommand.remember('scrum-daily', '"每日站会应该控制在15分钟内 ${memoryLine} -`); - return 'created'; +`) + return 'created' } - + // 读取现有内容 - const content = await fs.readFile(memoryFile, 'utf-8'); - + const content = await fs.readFile(memoryFile, 'utf-8') + // 检查是否已存在相同key的记忆 - const keyPattern = new RegExp(`^- .*#${key}\\b`, 'm'); + const keyPattern = new RegExp(`^- .*#${key}\\b`, 'm') if (keyPattern.test(content)) { // 替换现有记忆 - const updatedContent = content.replace(keyPattern, memoryLine); - await fs.writeFile(memoryFile, updatedContent); - return 'updated'; + const updatedContent = content.replace(keyPattern, memoryLine) + await fs.writeFile(memoryFile, updatedContent) + return 'updated' } else { // 追加新记忆(在高价值记忆部分) - const insertPosition = content.indexOf('\n\n') + 2; - const updatedContent = content.slice(0, insertPosition) + memoryLine + '\n\n' + content.slice(insertPosition); - await fs.writeFile(memoryFile, updatedContent); - return 'created'; + const insertPosition = content.indexOf('\n\n') + 2 + const updatedContent = content.slice(0, insertPosition) + memoryLine + '\n\n' + content.slice(insertPosition) + await fs.writeFile(memoryFile, updatedContent) + return 'created' } } - - /** * 格式化保存响应(简化版本) */ - formatSaveResponse(key, value, memoryEntry) { - const { action, timestamp } = memoryEntry; - + formatSaveResponse (key, value, memoryEntry) { + const { action, timestamp } = memoryEntry + const actionLabels = { created: '✅ AI已内化新记忆', updated: '🔄 AI已更新记忆' - }; - + } + return `${actionLabels[action]}:${key} ## 📋 记忆详情 @@ -201,13 +198,13 @@ ${memoryLine} - 应用实践: 在实际场景中运用记忆 命令: \`${buildCommand.action('')}\` -📍 当前状态:memory_saved`; +📍 当前状态:memory_saved` } /** * 获取使用帮助 */ - getUsageHelp() { + getUsageHelp () { return `🧠 **Remember锦囊 - AI记忆增强系统** ## 📖 基本用法 @@ -241,16 +238,16 @@ ${buildCommand.action('')} # AI运用记忆激活角色 - 开始记忆: 内化第一条知识 命令: ${buildCommand.remember('', '')} - 学习资源: 学习新知识再内化 - 命令: ${buildCommand.learn('://')}`; + 命令: ${buildCommand.learn('://')}` } /** * 获取PATEOAS导航信息 */ - getPATEOAS(args) { - const [key, ...valueParts] = args; - const value = valueParts.join(' '); - + getPATEOAS (args) { + const [key, ...valueParts] = args + const value = valueParts.join(' ') + if (!key) { return { currentState: 'remember_awaiting_input', @@ -269,7 +266,7 @@ ${buildCommand.action('')} # AI运用记忆激活角色 priority: 'high' } ] - }; + } } if (!value) { @@ -284,7 +281,7 @@ ${buildCommand.action('')} # AI运用记忆激活角色 priority: 'high' } ] - }; + } } return { @@ -322,8 +319,8 @@ ${buildCommand.action('')} # AI运用记忆激活角色 timestamp: new Date().toISOString(), systemVersion: '锦囊串联状态机 v1.0' } - }; + } } } -module.exports = RememberCommand; \ No newline at end of file +module.exports = RememberCommand diff --git a/src/lib/core/pouch/commands/index.js b/src/lib/core/pouch/commands/index.js index c39787e..4a5d767 100644 --- a/src/lib/core/pouch/commands/index.js +++ b/src/lib/core/pouch/commands/index.js @@ -2,12 +2,12 @@ * 锦囊命令导出 */ -const InitCommand = require('./InitCommand'); -const HelloCommand = require('./HelloCommand'); -const ActionCommand = require('./ActionCommand'); -const LearnCommand = require('./LearnCommand'); -const RecallCommand = require('./RecallCommand'); -const RememberCommand = require('./RememberCommand'); +const InitCommand = require('./InitCommand') +const HelloCommand = require('./HelloCommand') +const ActionCommand = require('./ActionCommand') +const LearnCommand = require('./LearnCommand') +const RecallCommand = require('./RecallCommand') +const RememberCommand = require('./RememberCommand') module.exports = { InitCommand, @@ -16,4 +16,4 @@ module.exports = { LearnCommand, RecallCommand, RememberCommand -}; \ No newline at end of file +} diff --git a/src/lib/core/pouch/index.js b/src/lib/core/pouch/index.js index a8b4167..40aa3de 100644 --- a/src/lib/core/pouch/index.js +++ b/src/lib/core/pouch/index.js @@ -1,43 +1,43 @@ /** * 锦囊框架 (PATEOAS Framework) * Prompt as the Engine of Application State - * + * * 这是一个革命性的AI-First CLI框架,通过锦囊串联实现AI的状态管理。 * 每个锦囊都是独立的专家知识单元,通过PATEOAS导航实现状态转换。 */ -const PouchCLI = require('./PouchCLI'); -const PouchRegistry = require('./PouchRegistry'); -const PouchStateMachine = require('./state/PouchStateMachine'); -const BasePouchCommand = require('./BasePouchCommand'); -const commands = require('./commands'); +const PouchCLI = require('./PouchCLI') +const PouchRegistry = require('./PouchRegistry') +const PouchStateMachine = require('./state/PouchStateMachine') +const BasePouchCommand = require('./BasePouchCommand') +const commands = require('./commands') // 创建全局CLI实例 -const cli = new PouchCLI(); +const cli = new PouchCLI() module.exports = { // 主要导出 PouchCLI, cli, - + // 框架组件 PouchRegistry, PouchStateMachine, BasePouchCommand, - + // 内置命令 commands, - + // 便捷方法 execute: async (commandName, args) => { - return await cli.execute(commandName, args); + return await cli.execute(commandName, args) }, - + help: () => { - return cli.getHelp(); + return cli.getHelp() }, - + status: () => { - return cli.getStatus(); + return cli.getStatus() } -}; \ No newline at end of file +} diff --git a/src/lib/core/pouch/interfaces.js b/src/lib/core/pouch/interfaces.js index e1238b4..ab6627e 100644 --- a/src/lib/core/pouch/interfaces.js +++ b/src/lib/core/pouch/interfaces.js @@ -44,4 +44,4 @@ module.exports = { // 这些是类型定义,JavaScript中不需要导出 -}; \ No newline at end of file +} diff --git a/src/lib/core/pouch/state/PouchStateMachine.js b/src/lib/core/pouch/state/PouchStateMachine.js index cbe55b7..a44a0d0 100644 --- a/src/lib/core/pouch/state/PouchStateMachine.js +++ b/src/lib/core/pouch/state/PouchStateMachine.js @@ -1,22 +1,22 @@ -const fs = require('fs-extra'); -const path = require('path'); +const fs = require('fs-extra') +const path = require('path') /** * 锦囊状态机管理器 * 负责管理锦囊之间的状态转换 */ class PouchStateMachine { - constructor() { - this.currentState = 'initial'; - this.stateHistory = []; + constructor () { + this.currentState = 'initial' + this.stateHistory = [] this.context = { currentPouch: '', history: [], userProfile: {}, sessionData: {}, domainContext: {} - }; - this.commands = new Map(); + } + this.commands = new Map() } /** @@ -24,8 +24,8 @@ class PouchStateMachine { * @param {string} name - 命令名称 * @param {BasePouchCommand} command - 命令实例 */ - registerCommand(name, command) { - this.commands.set(name, command); + registerCommand (name, command) { + this.commands.set(name, command) } /** @@ -34,11 +34,11 @@ class PouchStateMachine { * @param {Array} args - 命令参数 * @returns {Promise} 执行结果 */ - async transition(commandName, args = []) { + async transition (commandName, args = []) { // 获取命令对应的锦囊 - const command = this.commands.get(commandName); + const command = this.commands.get(commandName) if (!command) { - throw new Error(`未找到命令: ${commandName}`); + throw new Error(`未找到命令: ${commandName}`) } // 记录历史 @@ -46,128 +46,126 @@ class PouchStateMachine { from: this.currentState, command: commandName, timestamp: new Date().toISOString(), - args: args - }); + args + }) // 更新上下文 - this.context.currentPouch = commandName; - this.context.history = this.stateHistory.map(h => h.command || h.to); + this.context.currentPouch = commandName + this.context.history = this.stateHistory.map(h => h.command || h.to) // 设置命令上下文 - command.setContext(this.context); + command.setContext(this.context) // 执行命令 - const result = await command.execute(args); + const result = await command.execute(args) // 根据PATEOAS导航更新状态 if (result && result.pateoas && result.pateoas.currentState) { - this.currentState = result.pateoas.currentState; + this.currentState = result.pateoas.currentState } // 保存状态 - await this.saveState(); + await this.saveState() - return result; + return result } /** * 获取当前状态 * @returns {string} 当前状态 */ - getCurrentState() { - return this.currentState; + getCurrentState () { + return this.currentState } /** * 获取可用的状态转换 * @returns {string[]} 可转换的状态列表 */ - getAvailableTransitions() { + getAvailableTransitions () { const transitions = { - 'initial': ['init', 'hello'], - 'initialized': ['hello', 'action', 'learn'], - 'discovering': ['action', 'learn', 'init'], - 'activated': ['learn', 'recall', 'hello'], - 'learned': ['action', 'recall', 'hello'], - 'recalled': ['action', 'learn', 'remember'] - }; + initial: ['init', 'hello'], + initialized: ['hello', 'action', 'learn'], + discovering: ['action', 'learn', 'init'], + activated: ['learn', 'recall', 'hello'], + learned: ['action', 'recall', 'hello'], + recalled: ['action', 'learn', 'remember'] + } // 根据当前状态的前缀匹配 for (const [statePrefix, availableStates] of Object.entries(transitions)) { if (this.currentState.startsWith(statePrefix)) { - return availableStates; + return availableStates } } // 默认可转换状态 - return ['hello', 'init']; + return ['hello', 'init'] } - - /** * 保存状态到文件 */ - async saveState() { - const promptxDir = path.join(process.cwd(), '.promptx'); - const configPath = path.join(promptxDir, 'pouch.json'); - + async saveState () { + const promptxDir = path.join(process.cwd(), '.promptx') + const configPath = path.join(promptxDir, 'pouch.json') + try { // 确保 .promptx 目录存在 - await fs.ensureDir(promptxDir); - - let config = {}; + await fs.ensureDir(promptxDir) + + let config = {} if (await fs.pathExists(configPath)) { - config = await fs.readJson(configPath); + config = await fs.readJson(configPath) } - config.currentState = this.currentState; - config.stateHistory = this.stateHistory.slice(-50); // 只保留最近50条记录 - config.lastUpdated = new Date().toISOString(); + config.currentState = this.currentState + config.stateHistory = this.stateHistory.slice(-50) // 只保留最近50条记录 + config.lastUpdated = new Date().toISOString() - await fs.writeJson(configPath, config, { spaces: 2 }); + await fs.writeJson(configPath, config, { spaces: 2 }) } catch (error) { - console.error('保存状态失败:', error); + console.error('保存状态失败:', error) } } /** * 从文件加载状态 */ - async loadState() { - const configPath = path.join(process.cwd(), '.promptx', 'pouch.json'); - + async loadState () { + const configPath = path.join(process.cwd(), '.promptx', 'pouch.json') + try { if (await fs.pathExists(configPath)) { - const config = await fs.readJson(configPath); - + const config = await fs.readJson(configPath) + if (config.currentState) { - this.currentState = config.currentState; + this.currentState = config.currentState } - + if (config.stateHistory) { - this.stateHistory = config.stateHistory; + this.stateHistory = config.stateHistory } } } catch (error) { - console.error('加载状态失败:', error); + console.error('加载状态失败:', error) } } /** * 重置状态机 */ - reset() { - this.currentState = 'initial'; - this.stateHistory = []; + reset () { + this.currentState = 'initial' + this.stateHistory = [] this.context = { currentPouch: '', history: [], userProfile: {}, sessionData: {}, domainContext: {} - }; + } } } -module.exports = PouchStateMachine; \ No newline at end of file +module.exports = PouchStateMachine diff --git a/src/lib/core/resource/index.js b/src/lib/core/resource/index.js index 8a99af3..bbf6940 100644 --- a/src/lib/core/resource/index.js +++ b/src/lib/core/resource/index.js @@ -1,16 +1,16 @@ /** * PromptX Resource Module * 基于DPML资源协议的统一资源管理模块 - * + * * 提供完整的资源协议解析、注册表管理、资源加载功能 */ // 核心管理器 -const ResourceManager = require('./resourceManager'); +const ResourceManager = require('./resourceManager') // 核心组件 -const ResourceProtocolParser = require('./resourceProtocolParser'); -const ResourceRegistry = require('./resourceRegistry'); +const ResourceProtocolParser = require('./resourceProtocolParser') +const ResourceRegistry = require('./resourceRegistry') // 数据类型 const { @@ -23,17 +23,17 @@ const { ProcessedResult, ResourceResult, ProtocolInfo -} = require('./types'); +} = require('./types') // 导出主接口 module.exports = { // 主管理器 ResourceManager, - + // 核心组件 ResourceProtocolParser, ResourceRegistry, - + // 数据类型 LoadingSemantics, ParsedReference, @@ -44,24 +44,24 @@ module.exports = { ProcessedResult, ResourceResult, ProtocolInfo, - + // 便捷方法 - 创建默认实例 createManager: (options) => new ResourceManager(options), - + // 便捷方法 - 快速解析 parse: (resourceRef) => { - const parser = new ResourceProtocolParser(); - return parser.parse(resourceRef); + const parser = new ResourceProtocolParser() + return parser.parse(resourceRef) }, - + // 便捷方法 - 快速验证 validate: (resourceRef) => { try { - const parser = new ResourceProtocolParser(); - parser.parse(resourceRef); - return true; + const parser = new ResourceProtocolParser() + parser.parse(resourceRef) + return true } catch (error) { - return false; + return false } } -}; \ No newline at end of file +} diff --git a/src/lib/core/resource/protocols/ExecutionProtocol.js b/src/lib/core/resource/protocols/ExecutionProtocol.js index 2527595..d74e6b2 100644 --- a/src/lib/core/resource/protocols/ExecutionProtocol.js +++ b/src/lib/core/resource/protocols/ExecutionProtocol.js @@ -1,28 +1,28 @@ -const ResourceProtocol = require('./ResourceProtocol'); -const fs = require('fs-extra'); -const path = require('path'); +const ResourceProtocol = require('./ResourceProtocol') +const fs = require('fs-extra') +const path = require('path') /** * 执行模式协议处理器 * 处理 execution:// 协议的资源解析 */ class ExecutionProtocol extends ResourceProtocol { - constructor() { - super('execution'); - this.registry = {}; + constructor () { + super('execution') + this.registry = {} } /** * 设置注册表 */ - setRegistry(registry) { - this.registry = registry || {}; + setRegistry (registry) { + this.registry = registry || {} } /** * 获取协议信息 */ - getProtocolInfo() { + getProtocolInfo () { return { name: 'execution', description: '执行模式资源协议', @@ -32,47 +32,47 @@ class ExecutionProtocol extends ResourceProtocol { 'execution://prompt-developer', 'execution://memory-trigger' ] - }; + } } /** * 解析资源路径 */ - async resolvePath(resourcePath, queryParams) { - const executionId = resourcePath.trim(); - + async resolvePath (resourcePath, queryParams) { + const executionId = resourcePath.trim() + if (!this.registry[executionId]) { - throw new Error(`执行模式 "${executionId}" 未在注册表中找到`); + throw new Error(`执行模式 "${executionId}" 未在注册表中找到`) } - let resolvedPath = this.registry[executionId]; - + let resolvedPath = this.registry[executionId] + // 处理 @package:// 前缀 if (resolvedPath.startsWith('@package://')) { - resolvedPath = resolvedPath.replace('@package://', ''); + resolvedPath = resolvedPath.replace('@package://', '') } - return resolvedPath; + return resolvedPath } /** * 加载资源内容 */ - async loadContent(resolvedPath, queryParams) { + async loadContent (resolvedPath, queryParams) { try { - const content = await fs.readFile(resolvedPath, 'utf-8'); - return content; + const content = await fs.readFile(resolvedPath, 'utf-8') + return content } catch (error) { - throw new Error(`无法加载执行模式文件 ${resolvedPath}: ${error.message}`); + throw new Error(`无法加载执行模式文件 ${resolvedPath}: ${error.message}`) } } /** * 验证资源路径 */ - validatePath(resourcePath) { - return /^[a-zA-Z0-9_-]+$/.test(resourcePath); + validatePath (resourcePath) { + return /^[a-zA-Z0-9_-]+$/.test(resourcePath) } } -module.exports = ExecutionProtocol; \ No newline at end of file +module.exports = ExecutionProtocol diff --git a/src/lib/core/resource/protocols/PackageProtocol.js b/src/lib/core/resource/protocols/PackageProtocol.js index ea69873..a27bf75 100644 --- a/src/lib/core/resource/protocols/PackageProtocol.js +++ b/src/lib/core/resource/protocols/PackageProtocol.js @@ -1,8 +1,8 @@ -const path = require('path'); -const fs = require('fs'); -const fsPromises = require('fs').promises; -const ResourceProtocol = require('./ResourceProtocol'); -const { QueryParams } = require('../types'); +const path = require('path') +const fs = require('fs') +const fsPromises = require('fs').promises +const ResourceProtocol = require('./ResourceProtocol') +const { QueryParams } = require('../types') /** * 包协议实现 @@ -10,25 +10,25 @@ const { QueryParams } = require('../types'); * 支持:本地开发、npm install、npm -g、npx、monorepo等场景 */ class PackageProtocol extends ResourceProtocol { - constructor(options = {}) { - super('package', options); - + constructor (options = {}) { + super('package', options) + // 包安装模式检测缓存 - this.installModeCache = new Map(); + this.installModeCache = new Map() } /** * 设置注册表(保持与其他协议的一致性) */ - setRegistry(registry) { + setRegistry (registry) { // Package协议不使用注册表,但为了一致性提供此方法 - this.registry = registry || {}; + this.registry = registry || {} } /** * 获取协议信息 */ - getProtocolInfo() { + getProtocolInfo () { return { name: this.name, description: '包协议 - 智能访问NPM包资源,支持多种安装模式', @@ -40,96 +40,96 @@ class PackageProtocol extends ResourceProtocol { '@package://templates/basic/template.md' ], installModes: [ - 'development', // 开发模式 - 'local', // 本地npm install - 'global', // 全局npm install -g - 'npx', // npx执行 - 'monorepo', // monorepo workspace - 'link' // npm link + 'development', // 开发模式 + 'local', // 本地npm install + 'global', // 全局npm install -g + 'npx', // npx执行 + 'monorepo', // monorepo workspace + 'link' // npm link ] - }; + } } /** * 检测当前包安装模式 */ - detectInstallMode() { - const cacheKey = 'currentInstallMode'; + detectInstallMode () { + const cacheKey = 'currentInstallMode' if (this.installModeCache.has(cacheKey)) { - return this.installModeCache.get(cacheKey); + return this.installModeCache.get(cacheKey) } - const mode = this._performInstallModeDetection(); - this.installModeCache.set(cacheKey, mode); - return mode; + const mode = this._performInstallModeDetection() + this.installModeCache.set(cacheKey, mode) + return mode } /** * 执行安装模式检测 */ - _performInstallModeDetection() { - const cwd = process.cwd(); - const execPath = process.argv[0]; - const scriptPath = process.argv[1]; - + _performInstallModeDetection () { + const cwd = process.cwd() + const execPath = process.argv[0] + const scriptPath = process.argv[1] + // 检测npx执行 if (this._isNpxExecution()) { - return 'npx'; + return 'npx' } - + // 检测全局安装 if (this._isGlobalInstall()) { - return 'global'; + return 'global' } - + // 检测开发模式 if (this._isDevelopmentMode()) { - return 'development'; + return 'development' } - + // 检测monorepo if (this._isMonorepoWorkspace()) { - return 'monorepo'; + return 'monorepo' } - + // 检测npm link if (this._isNpmLink()) { - return 'link'; + return 'link' } - + // 默认为本地安装 - return 'local'; + return 'local' } /** * 检测是否是npx执行 */ - _isNpxExecution() { + _isNpxExecution () { // 检查环境变量 if (process.env.npm_execpath && process.env.npm_execpath.includes('npx')) { - return true; + return true } - + // 检查npm_config_cache路径 if (process.env.npm_config_cache && process.env.npm_config_cache.includes('_npx')) { - return true; + return true } - + // 检查执行路径 - const scriptPath = process.argv[1]; + const scriptPath = process.argv[1] if (scriptPath && scriptPath.includes('_npx')) { - return true; + return true } - - return false; + + return false } /** * 检测是否是全局安装 */ - _isGlobalInstall() { - const currentPath = __dirname; - + _isGlobalInstall () { + const currentPath = __dirname + // 常见全局安装路径 const globalPaths = [ '/usr/lib/node_modules', @@ -138,206 +138,206 @@ class PackageProtocol extends ResourceProtocol { path.join(process.env.HOME || '', '.npm-global'), path.join(process.env.APPDATA || '', 'npm', 'node_modules'), path.join(process.env.PREFIX || '', 'lib', 'node_modules') - ]; - - return globalPaths.some(globalPath => + ] + + return globalPaths.some(globalPath => currentPath.startsWith(globalPath) - ); + ) } /** * 检测是否是开发模式 */ - _isDevelopmentMode() { + _isDevelopmentMode () { // 检查NODE_ENV if (process.env.NODE_ENV === 'development') { - return true; + return true } - + // 检查是否在node_modules外 - const currentPath = __dirname; + const currentPath = __dirname if (!currentPath.includes('node_modules')) { - return true; + return true } - + // 检查package.json中的main字段是否指向源文件 try { - const packageJsonPath = this.findPackageJson(); + const packageJsonPath = this.findPackageJson() if (packageJsonPath) { - const packageJson = require(packageJsonPath); - const mainFile = packageJson.main || 'index.js'; - return mainFile.startsWith('src/') || mainFile.startsWith('lib/'); + const packageJson = require(packageJsonPath) + const mainFile = packageJson.main || 'index.js' + return mainFile.startsWith('src/') || mainFile.startsWith('lib/') } } catch (error) { // 忽略错误,继续其他检测 } - - return false; + + return false } /** * 检测是否是monorepo workspace */ - _isMonorepoWorkspace() { + _isMonorepoWorkspace () { try { - const packageJsonPath = this.findPackageJson(); + const packageJsonPath = this.findPackageJson() if (packageJsonPath) { - const packageJson = require(packageJsonPath); - + const packageJson = require(packageJsonPath) + // 检查workspaces字段 if (packageJson.workspaces) { - return true; + return true } - + // 检查是否在workspace包内 - const rootPackageJsonPath = this.findRootPackageJson(); + const rootPackageJsonPath = this.findRootPackageJson() if (rootPackageJsonPath && rootPackageJsonPath !== packageJsonPath) { - const rootPackageJson = require(rootPackageJsonPath); - return !!rootPackageJson.workspaces; + const rootPackageJson = require(rootPackageJsonPath) + return !!rootPackageJson.workspaces } } } catch (error) { // 忽略错误 } - - return false; + + return false } /** * 检测是否是npm link */ - _isNpmLink() { + _isNpmLink () { try { - const currentPath = __dirname; - const stats = require('fs').lstatSync(currentPath); - return stats.isSymbolicLink(); + const currentPath = __dirname + const stats = require('fs').lstatSync(currentPath) + return stats.isSymbolicLink() } catch (error) { - return false; + return false } } /** * 查找package.json文件 */ - findPackageJson(startPath = __dirname) { - let currentPath = path.resolve(startPath); - + findPackageJson (startPath = __dirname) { + let currentPath = path.resolve(startPath) + while (currentPath !== path.parse(currentPath).root) { - const packageJsonPath = path.join(currentPath, 'package.json'); + const packageJsonPath = path.join(currentPath, 'package.json') if (require('fs').existsSync(packageJsonPath)) { - return packageJsonPath; + return packageJsonPath } - currentPath = path.dirname(currentPath); + currentPath = path.dirname(currentPath) } - - return null; + + return null } /** * 查找根package.json文件(用于monorepo检测) */ - findRootPackageJson() { - let currentPath = process.cwd(); - let lastValidPackageJson = null; - + findRootPackageJson () { + let currentPath = process.cwd() + let lastValidPackageJson = null + while (currentPath !== path.parse(currentPath).root) { - const packageJsonPath = path.join(currentPath, 'package.json'); + const packageJsonPath = path.join(currentPath, 'package.json') if (require('fs').existsSync(packageJsonPath)) { - lastValidPackageJson = packageJsonPath; + lastValidPackageJson = packageJsonPath } - currentPath = path.dirname(currentPath); + currentPath = path.dirname(currentPath) } - - return lastValidPackageJson; + + return lastValidPackageJson } /** * 获取包根目录 */ - async getPackageRoot() { - const mode = this.detectInstallMode(); - + async getPackageRoot () { + const mode = this.detectInstallMode() + switch (mode) { case 'development': // 开发模式:查找项目根目录 - return this._findProjectRoot(); - + return this._findProjectRoot() + case 'global': // 全局安装:查找全局包目录 - return this._findGlobalPackageRoot(); - + return this._findGlobalPackageRoot() + case 'npx': // npx:查找临时包目录 - return this._findNpxPackageRoot(); - + return this._findNpxPackageRoot() + case 'monorepo': // monorepo:查找workspace包目录 - return this._findWorkspacePackageRoot(); - + return this._findWorkspacePackageRoot() + case 'link': // npm link:解析符号链接 - return this._findLinkedPackageRoot(); - + return this._findLinkedPackageRoot() + case 'local': default: // 本地安装:查找node_modules中的包目录 - return this._findLocalPackageRoot(); + return this._findLocalPackageRoot() } } /** * 查找项目根目录 */ - _findProjectRoot() { - const packageJsonPath = this.findPackageJson(); - return packageJsonPath ? path.dirname(packageJsonPath) : process.cwd(); + _findProjectRoot () { + const packageJsonPath = this.findPackageJson() + return packageJsonPath ? path.dirname(packageJsonPath) : process.cwd() } /** * 查找全局包根目录 */ - _findGlobalPackageRoot() { + _findGlobalPackageRoot () { // 从当前模块路径向上查找,直到找到package.json - return this._findProjectRoot(); + return this._findProjectRoot() } /** * 查找npx包根目录 */ - _findNpxPackageRoot() { + _findNpxPackageRoot () { // npx通常将包缓存在特定目录 - const packageJsonPath = this.findPackageJson(); - return packageJsonPath ? path.dirname(packageJsonPath) : process.cwd(); + const packageJsonPath = this.findPackageJson() + return packageJsonPath ? path.dirname(packageJsonPath) : process.cwd() } /** * 查找workspace包根目录 */ - _findWorkspacePackageRoot() { + _findWorkspacePackageRoot () { // 在monorepo中查找当前workspace的根目录 - return this._findProjectRoot(); + return this._findProjectRoot() } /** * 查找链接包根目录 */ - _findLinkedPackageRoot() { + _findLinkedPackageRoot () { try { // 解析符号链接 - const realPath = require('fs').realpathSync(__dirname); - const packageJsonPath = this.findPackageJson(realPath); - return packageJsonPath ? path.dirname(packageJsonPath) : realPath; + const realPath = require('fs').realpathSync(__dirname) + const packageJsonPath = this.findPackageJson(realPath) + return packageJsonPath ? path.dirname(packageJsonPath) : realPath } catch (error) { - return this._findProjectRoot(); + return this._findProjectRoot() } } /** * 查找本地包根目录 */ - _findLocalPackageRoot() { + _findLocalPackageRoot () { // 在node_modules中查找包根目录 - return this._findProjectRoot(); + return this._findProjectRoot() } /** @@ -346,23 +346,23 @@ class PackageProtocol extends ResourceProtocol { * @param {QueryParams} params - 查询参数 * @returns {Promise} 解析后的绝对路径 */ - async resolvePath(relativePath, params = null) { + async resolvePath (relativePath, params = null) { // 获取包根目录 - const packageRoot = await this.getPackageRoot(); - + const packageRoot = await this.getPackageRoot() + // 验证路径是否在package.json的files字段中 - this.validateFileAccess(packageRoot, relativePath); - - // 直接处理路径,不需要目录映射 - const relativePathClean = relativePath.replace(/^\/+/, ''); - const fullPath = path.resolve(packageRoot, relativePathClean); - + this.validateFileAccess(packageRoot, relativePath) + + // 直接处理路径,不需要目录映射 + const relativePathClean = relativePath.replace(/^\/+/, '') + const fullPath = path.resolve(packageRoot, relativePathClean) + // 安全检查:确保路径在包根目录内 if (!fullPath.startsWith(packageRoot)) { - throw new Error(`Path traversal detected: ${relativePath}`); + throw new Error(`Path traversal detected: ${relativePath}`) } - - return fullPath; + + return fullPath } /** @@ -370,68 +370,68 @@ class PackageProtocol extends ResourceProtocol { * @param {string} packageRoot - 包根目录 * @param {string} relativePath - 相对路径 */ - validateFileAccess(packageRoot, relativePath) { + validateFileAccess (packageRoot, relativePath) { try { - const packageJsonPath = path.join(packageRoot, 'package.json'); - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); - + const packageJsonPath = path.join(packageRoot, 'package.json') + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) + // 如果没有files字段,允许访问所有文件(开发模式) if (!packageJson.files || !Array.isArray(packageJson.files)) { - return; + return } - + // 标准化路径 - const normalizedPath = relativePath.replace(/^\/+/, '').replace(/\\/g, '/'); - + const normalizedPath = relativePath.replace(/^\/+/, '').replace(/\\/g, '/') + // 检查是否匹配files字段中的任何模式 const isAllowed = packageJson.files.some(filePattern => { // 标准化文件模式 - const normalizedPattern = filePattern.replace(/^\/+/, '').replace(/\\/g, '/'); - + const normalizedPattern = filePattern.replace(/^\/+/, '').replace(/\\/g, '/') + // 精确匹配 if (normalizedPattern === normalizedPath) { - return true; + return true } - + // 目录匹配(以/结尾或包含/*) if (normalizedPattern.endsWith('/') || normalizedPattern.endsWith('/*')) { - const dirPattern = normalizedPattern.replace(/\/?\*?$/, '/'); - return normalizedPath.startsWith(dirPattern); + const dirPattern = normalizedPattern.replace(/\/?\*?$/, '/') + return normalizedPath.startsWith(dirPattern) } - + // 通配符匹配 if (normalizedPattern.includes('*')) { const regexPattern = normalizedPattern .replace(/\./g, '\\.') - .replace(/\*/g, '.*'); - const regex = new RegExp(`^${regexPattern}$`); - return regex.test(normalizedPath); + .replace(/\*/g, '.*') + const regex = new RegExp(`^${regexPattern}$`) + return regex.test(normalizedPath) } - + // 目录前缀匹配 if (normalizedPath.startsWith(normalizedPattern + '/')) { - return true; + return true } - - return false; - }); - + + return false + }) + if (!isAllowed) { // 在生产环境严格检查,开发环境只警告 - const installMode = this.detectInstallMode(); + const installMode = this.detectInstallMode() if (installMode === 'development') { - console.warn(`⚠️ Warning: Path '${relativePath}' not in package.json files field. This may cause issues after publishing.`); + console.warn(`⚠️ Warning: Path '${relativePath}' not in package.json files field. This may cause issues after publishing.`) } else { - throw new Error(`Access denied: Path '${relativePath}' is not included in package.json files field`); + throw new Error(`Access denied: Path '${relativePath}' is not included in package.json files field`) } } } catch (error) { // 如果读取package.json失败,在开发模式下允许访问 - const installMode = this.detectInstallMode(); + const installMode = this.detectInstallMode() if (installMode === 'development') { - console.warn(`⚠️ Warning: Could not validate file access for '${relativePath}': ${error.message}`); + console.warn(`⚠️ Warning: Could not validate file access for '${relativePath}': ${error.message}`) } else { - throw error; + throw error } } } @@ -439,27 +439,27 @@ class PackageProtocol extends ResourceProtocol { /** * 检查资源是否存在 */ - async exists(resourcePath, queryParams) { + async exists (resourcePath, queryParams) { try { - const resolvedPath = await this.resolvePath(resourcePath, queryParams); - await fsPromises.access(resolvedPath); - return true; + const resolvedPath = await this.resolvePath(resourcePath, queryParams) + await fsPromises.access(resolvedPath) + return true } catch (error) { - return false; + return false } } /** * 加载资源内容 */ - async loadContent(resourcePath, queryParams) { - const resolvedPath = await this.resolvePath(resourcePath, queryParams); - + async loadContent (resourcePath, queryParams) { + const resolvedPath = await this.resolvePath(resourcePath, queryParams) + try { - await fsPromises.access(resolvedPath); - const content = await fsPromises.readFile(resolvedPath, 'utf8'); - const stats = await fsPromises.stat(resolvedPath); - + await fsPromises.access(resolvedPath) + const content = await fsPromises.readFile(resolvedPath, 'utf8') + const stats = await fsPromises.stat(resolvedPath) + return { content, path: resolvedPath, @@ -471,21 +471,21 @@ class PackageProtocol extends ResourceProtocol { absolutePath: resolvedPath, relativePath: resourcePath } - }; + } } catch (error) { if (error.code === 'ENOENT') { - throw new Error(`包资源不存在: ${resourcePath} (解析为: ${resolvedPath})`); + throw new Error(`包资源不存在: ${resourcePath} (解析为: ${resolvedPath})`) } - throw new Error(`加载包资源失败: ${error.message}`); + throw new Error(`加载包资源失败: ${error.message}`) } } /** * 获取调试信息 */ - getDebugInfo() { - const mode = this.detectInstallMode(); - + getDebugInfo () { + const mode = this.detectInstallMode() + return { protocol: this.name, installMode: mode, @@ -498,16 +498,16 @@ class PackageProtocol extends ResourceProtocol { npm_config_cache: process.env.npm_config_cache }, cacheSize: this.cache.size - }; + } } /** * 清理缓存 */ - clearCache() { - super.clearCache(); - this.installModeCache.clear(); + clearCache () { + super.clearCache() + this.installModeCache.clear() } } -module.exports = PackageProtocol; \ No newline at end of file +module.exports = PackageProtocol diff --git a/src/lib/core/resource/protocols/ProjectProtocol.js b/src/lib/core/resource/protocols/ProjectProtocol.js index ea81c30..9988f6d 100644 --- a/src/lib/core/resource/protocols/ProjectProtocol.js +++ b/src/lib/core/resource/protocols/ProjectProtocol.js @@ -1,53 +1,53 @@ -const ResourceProtocol = require('./ResourceProtocol'); -const path = require('path'); -const fs = require('fs').promises; +const ResourceProtocol = require('./ResourceProtocol') +const path = require('path') +const fs = require('fs').promises /** * 项目协议实现 * 实现@project://协议,通过查找.promptx目录确定项目根目录 */ class ProjectProtocol extends ResourceProtocol { - constructor(options = {}) { - super('project', options); - + constructor (options = {}) { + super('project', options) + // 支持的项目结构目录映射 this.projectDirs = { - 'root': '', // 项目根目录 - 'src': 'src', // 源代码目录 - 'lib': 'lib', // 库目录 - 'build': 'build', // 构建输出目录 - 'dist': 'dist', // 分发目录 - 'docs': 'docs', // 文档目录 - 'test': 'test', // 测试目录 - 'tests': 'tests', // 测试目录(复数) - 'spec': 'spec', // 规范测试目录 - 'config': 'config', // 配置目录 - 'scripts': 'scripts', // 脚本目录 - 'assets': 'assets', // 资源目录 - 'public': 'public', // 公共资源目录 - 'static': 'static', // 静态资源目录 - 'templates': 'templates', // 模板目录 - 'examples': 'examples', // 示例目录 - 'tools': 'tools' // 工具目录 - }; - + root: '', // 项目根目录 + src: 'src', // 源代码目录 + lib: 'lib', // 库目录 + build: 'build', // 构建输出目录 + dist: 'dist', // 分发目录 + docs: 'docs', // 文档目录 + test: 'test', // 测试目录 + tests: 'tests', // 测试目录(复数) + spec: 'spec', // 规范测试目录 + config: 'config', // 配置目录 + scripts: 'scripts', // 脚本目录 + assets: 'assets', // 资源目录 + public: 'public', // 公共资源目录 + static: 'static', // 静态资源目录 + templates: 'templates', // 模板目录 + examples: 'examples', // 示例目录 + tools: 'tools' // 工具目录 + } + // 项目根目录缓存 - this.projectRootCache = new Map(); + this.projectRootCache = new Map() } /** * 设置注册表(保持与其他协议的一致性) */ - setRegistry(registry) { + setRegistry (registry) { // Project协议不使用注册表,但为了一致性提供此方法 - this.registry = registry || {}; + this.registry = registry || {} } /** * 获取协议信息 * @returns {object} 协议信息 */ - getProtocolInfo() { + getProtocolInfo () { return { name: 'project', description: '项目协议,通过.promptx目录标识提供项目结构访问', @@ -62,21 +62,21 @@ class ProjectProtocol extends ResourceProtocol { supportedDirectories: Object.keys(this.projectDirs), projectMarker: '.promptx', params: this.getSupportedParams() - }; + } } /** * 支持的查询参数 * @returns {object} 参数说明 */ - getSupportedParams() { + getSupportedParams () { return { ...super.getSupportedParams(), from: 'string - 指定搜索起始目录', create: 'boolean - 如果目录不存在是否创建', exists: 'boolean - 仅返回存在的文件/目录', type: 'string - 过滤类型 (file|dir|both)' - }; + } } /** @@ -84,16 +84,16 @@ class ProjectProtocol extends ResourceProtocol { * @param {string} resourcePath - 资源路径 * @returns {boolean} 是否有效 */ - validatePath(resourcePath) { + validatePath (resourcePath) { if (!super.validatePath(resourcePath)) { - return false; + return false } // 解析路径的第一部分(目录类型) - const parts = resourcePath.split('/'); - const dirType = parts[0]; - - return this.projectDirs.hasOwnProperty(dirType); + const parts = resourcePath.split('/') + const dirType = parts[0] + + return this.projectDirs.hasOwnProperty(dirType) } /** @@ -102,31 +102,31 @@ class ProjectProtocol extends ResourceProtocol { * @param {string} startDir - 开始搜索的目录 * @returns {string|null} 找到的目录路径或null */ - findUpDirectorySync(targetDir, startDir = process.cwd()) { - let currentDir = path.resolve(startDir); - const rootDir = path.parse(currentDir).root; + findUpDirectorySync (targetDir, startDir = process.cwd()) { + let currentDir = path.resolve(startDir) + const rootDir = path.parse(currentDir).root while (currentDir !== rootDir) { - const targetPath = path.join(currentDir, targetDir); - + const targetPath = path.join(currentDir, targetDir) + try { - const stats = require('fs').statSync(targetPath); + const stats = require('fs').statSync(targetPath) if (stats.isDirectory()) { - return targetPath; + return targetPath } } catch (error) { // 目录不存在,继续向上查找 } - const parentDir = path.dirname(currentDir); + const parentDir = path.dirname(currentDir) if (parentDir === currentDir) { // 已到达根目录 - break; + break } - currentDir = parentDir; + currentDir = parentDir } - return null; + return null } /** @@ -134,29 +134,29 @@ class ProjectProtocol extends ResourceProtocol { * @param {string} startDir - 开始搜索的目录 * @returns {Promise} 项目根目录路径 */ - async findProjectRoot(startDir = process.cwd()) { + async findProjectRoot (startDir = process.cwd()) { // 检查缓存 - const cacheKey = path.resolve(startDir); + const cacheKey = path.resolve(startDir) if (this.projectRootCache.has(cacheKey)) { - return this.projectRootCache.get(cacheKey); + return this.projectRootCache.get(cacheKey) } try { // 使用自实现的向上查找 - const promptxPath = this.findUpDirectorySync('.promptx', startDir); + const promptxPath = this.findUpDirectorySync('.promptx', startDir) - let projectRoot = null; + let projectRoot = null if (promptxPath) { // .promptx 目录的父目录就是项目根目录 - projectRoot = path.dirname(promptxPath); + projectRoot = path.dirname(promptxPath) } // 缓存结果 - this.projectRootCache.set(cacheKey, projectRoot); - - return projectRoot; + this.projectRootCache.set(cacheKey, projectRoot) + + return projectRoot } catch (error) { - throw new Error(`查找项目根目录失败: ${error.message}`); + throw new Error(`查找项目根目录失败: ${error.message}`) } } @@ -166,46 +166,46 @@ class ProjectProtocol extends ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {Promise} 解析后的绝对路径 */ - async resolvePath(resourcePath, queryParams) { - const parts = resourcePath.split('/'); - const dirType = parts[0]; - const relativePath = parts.slice(1).join('/'); + async resolvePath (resourcePath, queryParams) { + const parts = resourcePath.split('/') + const dirType = parts[0] + const relativePath = parts.slice(1).join('/') // 验证目录类型 if (!this.projectDirs.hasOwnProperty(dirType)) { - throw new Error(`不支持的项目目录类型: ${dirType}。支持的类型: ${Object.keys(this.projectDirs).join(', ')}`); + throw new Error(`不支持的项目目录类型: ${dirType}。支持的类型: ${Object.keys(this.projectDirs).join(', ')}`) } // 确定搜索起始点 - const startDir = queryParams?.get('from') || process.cwd(); - + const startDir = queryParams?.get('from') || process.cwd() + // 查找项目根目录 - const projectRoot = await this.findProjectRoot(startDir); + const projectRoot = await this.findProjectRoot(startDir) if (!projectRoot) { - throw new Error(`未找到项目根目录(.promptx标识)。请确保在项目目录内或使用 'from' 参数指定项目路径`); + throw new Error('未找到项目根目录(.promptx标识)。请确保在项目目录内或使用 \'from\' 参数指定项目路径') } // 构建目标目录路径 - const projectDirPath = this.projectDirs[dirType]; - const targetDir = projectDirPath ? path.join(projectRoot, projectDirPath) : projectRoot; - + const projectDirPath = this.projectDirs[dirType] + const targetDir = projectDirPath ? path.join(projectRoot, projectDirPath) : projectRoot + // 如果没有相对路径,返回目录本身 if (!relativePath) { - return targetDir; + return targetDir } // 拼接完整路径 - const fullPath = path.join(targetDir, relativePath); - + const fullPath = path.join(targetDir, relativePath) + // 安全检查:确保路径在项目目录内 - const resolvedPath = path.resolve(fullPath); - const resolvedProjectRoot = path.resolve(projectRoot); - + const resolvedPath = path.resolve(fullPath) + const resolvedProjectRoot = path.resolve(projectRoot) + if (!resolvedPath.startsWith(resolvedProjectRoot)) { - throw new Error(`安全错误:路径超出项目目录范围: ${resolvedPath}`); + throw new Error(`安全错误:路径超出项目目录范围: ${resolvedPath}`) } - return resolvedPath; + return resolvedPath } /** @@ -214,33 +214,33 @@ class ProjectProtocol extends ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {Promise} 资源内容 */ - async loadContent(resolvedPath, queryParams) { + async loadContent (resolvedPath, queryParams) { try { // 检查路径是否存在 - const stats = await fs.stat(resolvedPath); - + const stats = await fs.stat(resolvedPath) + if (stats.isDirectory()) { - return await this.loadDirectoryContent(resolvedPath, queryParams); + return await this.loadDirectoryContent(resolvedPath, queryParams) } else if (stats.isFile()) { - return await this.loadFileContent(resolvedPath, queryParams); + return await this.loadFileContent(resolvedPath, queryParams) } else { - throw new Error(`不支持的文件类型: ${resolvedPath}`); + throw new Error(`不支持的文件类型: ${resolvedPath}`) } } catch (error) { if (error.code === 'ENOENT') { // 检查是否需要创建目录 if (queryParams?.get('create') === 'true') { - await fs.mkdir(path.dirname(resolvedPath), { recursive: true }); - return ''; // 返回空内容 + await fs.mkdir(path.dirname(resolvedPath), { recursive: true }) + return '' // 返回空内容 } - + // 如果设置了exists参数为false,返回空内容而不是错误 if (queryParams?.get('exists') === 'false') { - return ''; + return '' } - throw new Error(`文件或目录不存在: ${resolvedPath}`); + throw new Error(`文件或目录不存在: ${resolvedPath}`) } - throw error; + throw error } } @@ -250,9 +250,9 @@ class ProjectProtocol extends ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {Promise} 文件内容 */ - async loadFileContent(filePath, queryParams) { - const encoding = queryParams?.get('encoding') || 'utf8'; - return await fs.readFile(filePath, encoding); + async loadFileContent (filePath, queryParams) { + const encoding = queryParams?.get('encoding') || 'utf8' + return await fs.readFile(filePath, encoding) } /** @@ -261,27 +261,27 @@ class ProjectProtocol extends ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {Promise} 目录内容列表 */ - async loadDirectoryContent(dirPath, queryParams) { - const entries = await fs.readdir(dirPath, { withFileTypes: true }); - + async loadDirectoryContent (dirPath, queryParams) { + const entries = await fs.readdir(dirPath, { withFileTypes: true }) + // 应用类型过滤 - const typeFilter = queryParams?.get('type'); - let filteredEntries = entries; - + const typeFilter = queryParams?.get('type') + let filteredEntries = entries + if (typeFilter) { filteredEntries = entries.filter(entry => { switch (typeFilter) { - case 'file': return entry.isFile(); - case 'dir': return entry.isDirectory(); - case 'both': return true; - default: return true; + case 'file': return entry.isFile() + case 'dir': return entry.isDirectory() + case 'both': return true + default: return true } - }); + }) } // 格式化输出 - const format = queryParams?.get('format') || 'list'; - + const format = queryParams?.get('format') || 'list' + switch (format) { case 'json': return JSON.stringify( @@ -292,21 +292,21 @@ class ProjectProtocol extends ResourceProtocol { })), null, 2 - ); - + ) + case 'paths': return filteredEntries .map(entry => path.join(dirPath, entry.name)) - .join('\n'); - + .join('\n') + case 'list': default: return filteredEntries .map(entry => { - const type = entry.isDirectory() ? '[DIR]' : '[FILE]'; - return `${type} ${entry.name}`; + const type = entry.isDirectory() ? '[DIR]' : '[FILE]' + return `${type} ${entry.name}` }) - .join('\n'); + .join('\n') } } @@ -315,45 +315,45 @@ class ProjectProtocol extends ResourceProtocol { * @param {string} startDir - 开始搜索的目录 * @returns {Promise} 项目信息 */ - async getProjectInfo(startDir = process.cwd()) { - const projectRoot = await this.findProjectRoot(startDir); + async getProjectInfo (startDir = process.cwd()) { + const projectRoot = await this.findProjectRoot(startDir) if (!projectRoot) { - return { error: '未找到项目根目录' }; + return { error: '未找到项目根目录' } } const result = { projectRoot, promptxPath: path.join(projectRoot, '.promptx'), directories: {} - }; - + } + for (const [dirType, dirPath] of Object.entries(this.projectDirs)) { - const fullPath = dirPath ? path.join(projectRoot, dirPath) : projectRoot; + const fullPath = dirPath ? path.join(projectRoot, dirPath) : projectRoot try { - const stats = await fs.stat(fullPath); + const stats = await fs.stat(fullPath) result.directories[dirType] = { path: fullPath, exists: true, type: stats.isDirectory() ? 'directory' : 'file' - }; + } } catch (error) { result.directories[dirType] = { path: fullPath, exists: false - }; + } } } - - return result; + + return result } /** * 清除缓存 */ - clearCache() { - super.clearCache(); - this.projectRootCache.clear(); + clearCache () { + super.clearCache() + this.projectRootCache.clear() } } -module.exports = ProjectProtocol; \ No newline at end of file +module.exports = ProjectProtocol diff --git a/src/lib/core/resource/protocols/PromptProtocol.js b/src/lib/core/resource/protocols/PromptProtocol.js index f256d2b..1e46647 100644 --- a/src/lib/core/resource/protocols/PromptProtocol.js +++ b/src/lib/core/resource/protocols/PromptProtocol.js @@ -1,17 +1,17 @@ -const ResourceProtocol = require('./ResourceProtocol'); -const path = require('path'); -const fs = require('fs').promises; -const { glob } = require('glob'); -const { promisify } = require('util'); +const ResourceProtocol = require('./ResourceProtocol') +const path = require('path') +const fs = require('fs').promises +const { glob } = require('glob') +const { promisify } = require('util') /** * PromptX内置提示词资源协议实现 * 实现@prompt://协议,用于访问PromptX内置的提示词资源 */ class PromptProtocol extends ResourceProtocol { - constructor(options = {}) { - super('prompt', options); - + constructor (options = {}) { + super('prompt', options) + // PromptX 内置资源注册表 this.registry = new Map([ ['protocols', '@package://prompt/protocol/**/*.md'], @@ -19,42 +19,42 @@ class PromptProtocol extends ResourceProtocol { ['domain', '@package://prompt/domain/**/*.md'], ['resource', '@package://prompt/resource/**/*.md'], ['bootstrap', '@package://bootstrap.md'] - ]); - + ]) + // 依赖的其他协议 - this.packageProtocol = null; + this.packageProtocol = null } /** * 设置依赖的协议 * @param {PackageProtocol} packageProtocol - 包协议实例 */ - setPackageProtocol(packageProtocol) { - this.packageProtocol = packageProtocol; + setPackageProtocol (packageProtocol) { + this.packageProtocol = packageProtocol } /** * 设置注册表 */ - setRegistry(registry) { + setRegistry (registry) { if (!registry) { - this.registry = new Map(); - return; + this.registry = new Map() + return } - + // 如果传入的是普通对象,转换为Map if (registry instanceof Map) { - this.registry = registry; + this.registry = registry } else { // 从普通对象创建Map - this.registry = new Map(Object.entries(registry)); + this.registry = new Map(Object.entries(registry)) } } /** * 获取协议信息 */ - getProtocolInfo() { + getProtocolInfo () { return { name: 'prompt', description: 'PromptX内置提示词资源协议', @@ -67,200 +67,200 @@ class PromptProtocol extends ResourceProtocol { ], availableResources: Array.from(this.registry.keys()), params: this.getSupportedParams() - }; + } } /** * 支持的查询参数 */ - getSupportedParams() { + getSupportedParams () { return { ...super.getSupportedParams(), merge: 'boolean - 是否合并多个文件内容', separator: 'string - 文件间分隔符', include_filename: 'boolean - 是否包含文件名标题' - }; + } } /** * 验证资源路径 */ - validatePath(resourcePath) { + validatePath (resourcePath) { if (!super.validatePath(resourcePath)) { - return false; + return false } - + // 检查是否在注册表中 - return this.registry.has(resourcePath); + return this.registry.has(resourcePath) } /** * 解析资源路径 */ - async resolvePath(resourcePath, queryParams) { + async resolvePath (resourcePath, queryParams) { // 验证资源是否存在 if (!this.registry.has(resourcePath)) { - throw new Error(`未找到 prompt 资源: ${resourcePath}。可用资源: ${Array.from(this.registry.keys()).join(', ')}`); + throw new Error(`未找到 prompt 资源: ${resourcePath}。可用资源: ${Array.from(this.registry.keys()).join(', ')}`) } // 获取对应的包路径 - const packagePath = this.registry.get(resourcePath); - return packagePath; + const packagePath = this.registry.get(resourcePath) + return packagePath } /** * 加载资源内容 */ - async loadContent(packagePath, queryParams) { + async loadContent (packagePath, queryParams) { // 确保有包协议依赖 if (!this.packageProtocol) { - throw new Error('PromptProtocol 需要 PackageProtocol 依赖'); + throw new Error('PromptProtocol 需要 PackageProtocol 依赖') } // 检查是否是通配符路径 if (packagePath.includes('**') || packagePath.includes('*')) { - return await this.loadMultipleFiles(packagePath, queryParams); + return await this.loadMultipleFiles(packagePath, queryParams) } else { - return await this.loadSingleFile(packagePath, queryParams); + return await this.loadSingleFile(packagePath, queryParams) } } /** * 加载单个文件 */ - async loadSingleFile(packagePath, queryParams) { + async loadSingleFile (packagePath, queryParams) { try { // 移除协议前缀 - const cleanPath = packagePath.replace('@package://', ''); - const result = await this.packageProtocol.loadContent(cleanPath, queryParams); - return result.content || result; + const cleanPath = packagePath.replace('@package://', '') + const result = await this.packageProtocol.loadContent(cleanPath, queryParams) + return result.content || result } catch (error) { - throw new Error(`加载单个文件失败 ${packagePath}: ${error.message}`); + throw new Error(`加载单个文件失败 ${packagePath}: ${error.message}`) } } /** * 加载多个文件(通配符支持) */ - async loadMultipleFiles(packagePath, queryParams) { + async loadMultipleFiles (packagePath, queryParams) { try { // 获取包根目录 - const packageRoot = await this.packageProtocol.getPackageRoot(); - + const packageRoot = await this.packageProtocol.getPackageRoot() + // 移除协议前缀并构建搜索路径 - const cleanPath = packagePath.replace('@package://', ''); - const searchPattern = path.join(packageRoot, cleanPath); - + const cleanPath = packagePath.replace('@package://', '') + const searchPattern = path.join(packageRoot, cleanPath) + // 使用 glob 查找匹配的文件 const files = await glob(searchPattern, { ignore: ['**/node_modules/**', '**/.git/**'] - }); + }) if (files.length === 0) { - throw new Error(`没有找到匹配的文件: ${packagePath}`); + throw new Error(`没有找到匹配的文件: ${packagePath}`) } // 读取所有文件内容 - const contents = []; + const contents = [] for (const filePath of files.sort()) { try { - const content = await fs.readFile(filePath, 'utf8'); - const relativePath = path.relative(packageRoot, filePath); - + const content = await fs.readFile(filePath, 'utf8') + const relativePath = path.relative(packageRoot, filePath) + contents.push({ path: relativePath, - content: content - }); + content + }) } catch (error) { - console.warn(`警告: 无法读取文件 ${filePath}: ${error.message}`); + console.warn(`警告: 无法读取文件 ${filePath}: ${error.message}`) } } // 合并内容 - return this.mergeContents(contents, queryParams); + return this.mergeContents(contents, queryParams) } catch (error) { - throw new Error(`加载多个文件失败 ${packagePath}: ${error.message}`); + throw new Error(`加载多个文件失败 ${packagePath}: ${error.message}`) } } /** * 合并多个文件内容 */ - mergeContents(contents, queryParams) { - const merge = queryParams?.get('merge') !== 'false'; // 默认合并 - const separator = queryParams?.get('separator') || '\n\n---\n\n'; - const includeFilename = queryParams?.get('include_filename') !== 'false'; // 默认包含文件名 + mergeContents (contents, queryParams) { + const merge = queryParams?.get('merge') !== 'false' // 默认合并 + const separator = queryParams?.get('separator') || '\n\n---\n\n' + const includeFilename = queryParams?.get('include_filename') !== 'false' // 默认包含文件名 if (!merge) { // 不合并,返回 JSON 格式 - return JSON.stringify(contents, null, 2); + return JSON.stringify(contents, null, 2) } // 合并所有内容 const mergedParts = contents.map(({ path, content }) => { - let part = ''; - - if (includeFilename) { - part += `# ${path}\n\n`; - } - - part += content; - - return part; - }); + let part = '' - return mergedParts.join(separator); + if (includeFilename) { + part += `# ${path}\n\n` + } + + part += content + + return part + }) + + return mergedParts.join(separator) } /** * 检查资源是否存在 */ - async exists(resourcePath, queryParams) { + async exists (resourcePath, queryParams) { try { - const packagePath = await this.resolvePath(resourcePath, queryParams); - + const packagePath = await this.resolvePath(resourcePath, queryParams) + if (packagePath.includes('**') || packagePath.includes('*')) { // 通配符路径:检查是否有匹配的文件 - const packageRoot = await this.packageProtocol.getPackageRoot(); - const cleanPath = packagePath.replace('@package://', ''); - const searchPattern = path.join(packageRoot, cleanPath); - const files = await glob(searchPattern); - return files.length > 0; + const packageRoot = await this.packageProtocol.getPackageRoot() + const cleanPath = packagePath.replace('@package://', '') + const searchPattern = path.join(packageRoot, cleanPath) + const files = await glob(searchPattern) + return files.length > 0 } else { // 单个文件:检查文件是否存在 - const cleanPath = packagePath.replace('@package://', ''); - return await this.packageProtocol.exists(cleanPath, queryParams); + const cleanPath = packagePath.replace('@package://', '') + return await this.packageProtocol.exists(cleanPath, queryParams) } } catch (error) { - return false; + return false } } /** * 列出所有可用资源 */ - listResources() { + listResources () { return Array.from(this.registry.entries()).map(([key, value]) => ({ id: key, path: value, description: this.getResourceDescription(key) - })); + })) } /** * 获取资源描述 */ - getResourceDescription(resourceId) { + getResourceDescription (resourceId) { const descriptions = { - 'protocols': 'DPML协议规范文档', - 'core': '核心思维和执行模式', - 'domain': '领域专家角色定义', - 'resource': '资源管理和路径解析', - 'bootstrap': 'PromptX启动引导文件' - }; - - return descriptions[resourceId] || '未知资源'; + protocols: 'DPML协议规范文档', + core: '核心思维和执行模式', + domain: '领域专家角色定义', + resource: '资源管理和路径解析', + bootstrap: 'PromptX启动引导文件' + } + + return descriptions[resourceId] || '未知资源' } } -module.exports = PromptProtocol; \ No newline at end of file +module.exports = PromptProtocol diff --git a/src/lib/core/resource/protocols/ResourceProtocol.js b/src/lib/core/resource/protocols/ResourceProtocol.js index f820bf3..36d16b1 100644 --- a/src/lib/core/resource/protocols/ResourceProtocol.js +++ b/src/lib/core/resource/protocols/ResourceProtocol.js @@ -8,23 +8,23 @@ class ResourceProtocol { * @param {string} name - 协议名称 * @param {object} options - 配置选项 */ - constructor(name, options = {}) { + constructor (name, options = {}) { if (new.target === ResourceProtocol) { - throw new Error('ResourceProtocol是抽象类,不能直接实例化'); + throw new Error('ResourceProtocol是抽象类,不能直接实例化') } - - this.name = name; - this.options = options; - this.cache = new Map(); - this.enableCache = options.enableCache !== false; + + this.name = name + this.options = options + this.cache = new Map() + this.enableCache = options.enableCache !== false } /** * 协议信息 - 需要子类实现 * @returns {object} 协议信息 */ - getProtocolInfo() { - throw new Error('子类必须实现 getProtocolInfo() 方法'); + getProtocolInfo () { + throw new Error('子类必须实现 getProtocolInfo() 方法') } /** @@ -33,8 +33,8 @@ class ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {Promise} 解析后的路径 */ - async resolvePath(resourcePath, queryParams) { - throw new Error('子类必须实现 resolvePath() 方法'); + async resolvePath (resourcePath, queryParams) { + throw new Error('子类必须实现 resolvePath() 方法') } /** @@ -43,8 +43,8 @@ class ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {Promise} 资源内容 */ - async loadContent(resolvedPath, queryParams) { - throw new Error('子类必须实现 loadContent() 方法'); + async loadContent (resolvedPath, queryParams) { + throw new Error('子类必须实现 loadContent() 方法') } /** @@ -52,20 +52,20 @@ class ResourceProtocol { * @param {string} resourcePath - 资源路径 * @returns {boolean} 是否有效 */ - validatePath(resourcePath) { - return typeof resourcePath === 'string' && resourcePath.length > 0; + validatePath (resourcePath) { + return typeof resourcePath === 'string' && resourcePath.length > 0 } /** * 支持的查询参数列表 - 可选实现 * @returns {object} 参数说明 */ - getSupportedParams() { + getSupportedParams () { return { line: 'string - 行范围,如 "1-10"', format: 'string - 输出格式', cache: 'boolean - 是否缓存' - }; + } } /** @@ -74,35 +74,35 @@ class ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {Promise} 资源内容 */ - async resolve(resourcePath, queryParams) { + async resolve (resourcePath, queryParams) { // 1. 验证路径格式 if (!this.validatePath(resourcePath)) { - throw new Error(`无效的资源路径: ${resourcePath}`); + throw new Error(`无效的资源路径: ${resourcePath}`) } // 2. 生成缓存键 - const cacheKey = this.generateCacheKey(resourcePath, queryParams); + const cacheKey = this.generateCacheKey(resourcePath, queryParams) // 3. 检查缓存 if (this.enableCache && this.cache.has(cacheKey)) { - return this.cache.get(cacheKey); + return this.cache.get(cacheKey) } // 4. 解析路径 - const resolvedPath = await this.resolvePath(resourcePath, queryParams); + const resolvedPath = await this.resolvePath(resourcePath, queryParams) // 5. 加载内容 - const content = await this.loadContent(resolvedPath, queryParams); + const content = await this.loadContent(resolvedPath, queryParams) // 6. 应用通用查询参数过滤 - const filteredContent = this.applyCommonParams(content, queryParams); + const filteredContent = this.applyCommonParams(content, queryParams) // 7. 缓存结果 if (this.enableCache) { - this.cache.set(cacheKey, filteredContent); + this.cache.set(cacheKey, filteredContent) } - return filteredContent; + return filteredContent } /** @@ -111,9 +111,9 @@ class ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {string} 缓存键 */ - generateCacheKey(resourcePath, queryParams) { - const params = queryParams ? queryParams.getAll() : {}; - return `${this.name}:${resourcePath}:${JSON.stringify(params)}`; + generateCacheKey (resourcePath, queryParams) { + const params = queryParams ? queryParams.getAll() : {} + return `${this.name}:${resourcePath}:${JSON.stringify(params)}` } /** @@ -122,24 +122,24 @@ class ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {string} 过滤后的内容 */ - applyCommonParams(content, queryParams) { + applyCommonParams (content, queryParams) { if (!queryParams) { - return content; + return content } - let result = content; + let result = content // 应用行过滤 if (queryParams.line) { - result = this.applyLineFilter(result, queryParams.line); + result = this.applyLineFilter(result, queryParams.line) } // 应用格式化(基础实现,子类可以重写) if (queryParams.format && queryParams.format !== 'text') { - result = this.applyFormat(result, queryParams.format); + result = this.applyFormat(result, queryParams.format) } - return result; + return result } /** @@ -148,18 +148,18 @@ class ResourceProtocol { * @param {string} lineRange - 行范围,如 "5-10" 或 "5" * @returns {string} 过滤后的内容 */ - applyLineFilter(content, lineRange) { - const lines = content.split('\n'); - + applyLineFilter (content, lineRange) { + const lines = content.split('\n') + if (lineRange.includes('-')) { - const [start, end] = lineRange.split('-').map(n => parseInt(n.trim(), 10)); - const startIndex = Math.max(0, start - 1); - const endIndex = Math.min(lines.length, end); - return lines.slice(startIndex, endIndex).join('\n'); + const [start, end] = lineRange.split('-').map(n => parseInt(n.trim(), 10)) + const startIndex = Math.max(0, start - 1) + const endIndex = Math.min(lines.length, end) + return lines.slice(startIndex, endIndex).join('\n') } else { - const lineNum = parseInt(lineRange, 10); - const lineIndex = lineNum - 1; - return lines[lineIndex] || ''; + const lineNum = parseInt(lineRange, 10) + const lineIndex = lineNum - 1 + return lines[lineIndex] || '' } } @@ -169,40 +169,40 @@ class ResourceProtocol { * @param {string} format - 格式 * @returns {string} 格式化后的内容 */ - applyFormat(content, format) { + applyFormat (content, format) { // 基础实现,子类可以重写 switch (format) { case 'json': try { - return JSON.stringify(JSON.parse(content), null, 2); + return JSON.stringify(JSON.parse(content), null, 2) } catch { - return content; + return content } case 'trim': - return content.trim(); + return content.trim() default: - return content; + return content } } /** * 清除缓存 */ - clearCache() { - this.cache.clear(); + clearCache () { + this.cache.clear() } /** * 获取缓存统计 * @returns {object} 缓存统计信息 */ - getCacheStats() { + getCacheStats () { return { protocol: this.name, size: this.cache.size, enabled: this.enableCache - }; + } } } -module.exports = ResourceProtocol; \ No newline at end of file +module.exports = ResourceProtocol diff --git a/src/lib/core/resource/protocols/RoleProtocol.js b/src/lib/core/resource/protocols/RoleProtocol.js index fae634a..62f23b3 100644 --- a/src/lib/core/resource/protocols/RoleProtocol.js +++ b/src/lib/core/resource/protocols/RoleProtocol.js @@ -1,28 +1,28 @@ -const ResourceProtocol = require('./ResourceProtocol'); -const fs = require('fs-extra'); -const path = require('path'); +const ResourceProtocol = require('./ResourceProtocol') +const fs = require('fs-extra') +const path = require('path') /** * AI角色协议处理器 * 处理 role:// 协议的资源解析,直接加载完整role文件 */ class RoleProtocol extends ResourceProtocol { - constructor() { - super('role'); - this.registry = {}; + constructor () { + super('role') + this.registry = {} } /** * 设置注册表 */ - setRegistry(registry) { - this.registry = registry || {}; + setRegistry (registry) { + this.registry = registry || {} } /** * 获取协议信息 */ - getProtocolInfo() { + getProtocolInfo () { return { name: 'role', description: 'AI角色资源协议', @@ -33,47 +33,47 @@ class RoleProtocol extends ResourceProtocol { 'role://assistant', 'role://prompt-developer' ] - }; + } } /** * 解析资源路径 */ - async resolvePath(resourcePath, queryParams) { - const roleId = resourcePath.trim(); - + async resolvePath (resourcePath, queryParams) { + const roleId = resourcePath.trim() + if (!this.registry[roleId]) { - throw new Error(`角色 "${roleId}" 未在注册表中找到。可用角色:${Object.keys(this.registry).join(', ')}`); + throw new Error(`角色 "${roleId}" 未在注册表中找到。可用角色:${Object.keys(this.registry).join(', ')}`) } - let resolvedPath = this.registry[roleId]; - + let resolvedPath = this.registry[roleId] + // 处理 @package:// 前缀 if (resolvedPath.startsWith('@package://')) { - resolvedPath = resolvedPath.replace('@package://', ''); + resolvedPath = resolvedPath.replace('@package://', '') } - return resolvedPath; + return resolvedPath } /** * 加载资源内容 */ - async loadContent(resolvedPath, queryParams) { + async loadContent (resolvedPath, queryParams) { try { - const content = await fs.readFile(resolvedPath, 'utf-8'); - return content; + const content = await fs.readFile(resolvedPath, 'utf-8') + return content } catch (error) { - throw new Error(`无法加载角色文件 ${resolvedPath}: ${error.message}`); + throw new Error(`无法加载角色文件 ${resolvedPath}: ${error.message}`) } } /** * 验证资源路径 */ - validatePath(resourcePath) { - return /^[a-zA-Z0-9_-]+$/.test(resourcePath); + validatePath (resourcePath) { + return /^[a-zA-Z0-9_-]+$/.test(resourcePath) } } -module.exports = RoleProtocol; \ No newline at end of file +module.exports = RoleProtocol diff --git a/src/lib/core/resource/protocols/ThoughtProtocol.js b/src/lib/core/resource/protocols/ThoughtProtocol.js index a578e74..2fb97eb 100644 --- a/src/lib/core/resource/protocols/ThoughtProtocol.js +++ b/src/lib/core/resource/protocols/ThoughtProtocol.js @@ -1,28 +1,28 @@ -const ResourceProtocol = require('./ResourceProtocol'); -const fs = require('fs-extra'); -const path = require('path'); +const ResourceProtocol = require('./ResourceProtocol') +const fs = require('fs-extra') +const path = require('path') /** * 思维模式协议处理器 * 处理 thought:// 协议的资源解析 */ class ThoughtProtocol extends ResourceProtocol { - constructor() { - super('thought'); - this.registry = {}; + constructor () { + super('thought') + this.registry = {} } /** * 设置注册表 */ - setRegistry(registry) { - this.registry = registry || {}; + setRegistry (registry) { + this.registry = registry || {} } /** * 获取协议信息 */ - getProtocolInfo() { + getProtocolInfo () { return { name: 'thought', description: '思维模式资源协议', @@ -31,47 +31,47 @@ class ThoughtProtocol extends ResourceProtocol { 'thought://prompt-developer', 'thought://product-owner' ] - }; + } } /** * 解析资源路径 */ - async resolvePath(resourcePath, queryParams) { - const thoughtId = resourcePath.trim(); - + async resolvePath (resourcePath, queryParams) { + const thoughtId = resourcePath.trim() + if (!this.registry[thoughtId]) { - throw new Error(`思维模式 "${thoughtId}" 未在注册表中找到`); + throw new Error(`思维模式 "${thoughtId}" 未在注册表中找到`) } - let resolvedPath = this.registry[thoughtId]; - + let resolvedPath = this.registry[thoughtId] + // 处理 @package:// 前缀 if (resolvedPath.startsWith('@package://')) { - resolvedPath = resolvedPath.replace('@package://', ''); + resolvedPath = resolvedPath.replace('@package://', '') } - return resolvedPath; + return resolvedPath } /** * 加载资源内容 */ - async loadContent(resolvedPath, queryParams) { + async loadContent (resolvedPath, queryParams) { try { - const content = await fs.readFile(resolvedPath, 'utf-8'); - return content; + const content = await fs.readFile(resolvedPath, 'utf-8') + return content } catch (error) { - throw new Error(`无法加载思维模式文件 ${resolvedPath}: ${error.message}`); + throw new Error(`无法加载思维模式文件 ${resolvedPath}: ${error.message}`) } } /** * 验证资源路径 */ - validatePath(resourcePath) { - return /^[a-zA-Z0-9_-]+$/.test(resourcePath); + validatePath (resourcePath) { + return /^[a-zA-Z0-9_-]+$/.test(resourcePath) } } -module.exports = ThoughtProtocol; \ No newline at end of file +module.exports = ThoughtProtocol diff --git a/src/lib/core/resource/protocols/UserProtocol.js b/src/lib/core/resource/protocols/UserProtocol.js index 017944d..b4502d6 100644 --- a/src/lib/core/resource/protocols/UserProtocol.js +++ b/src/lib/core/resource/protocols/UserProtocol.js @@ -1,16 +1,16 @@ -const ResourceProtocol = require('./ResourceProtocol'); -const path = require('path'); -const fs = require('fs').promises; +const ResourceProtocol = require('./ResourceProtocol') +const path = require('path') +const fs = require('fs').promises // 延迟加载platform-folders以处理可能的原生模块依赖 -let platformFolders = null; +let platformFolders = null const getPlatformFolders = () => { if (!platformFolders) { try { - platformFolders = require('platform-folders'); + platformFolders = require('platform-folders') } catch (error) { // 如果platform-folders不可用,回退到os.homedir() - const os = require('os'); + const os = require('os') platformFolders = { getHomeFolder: () => os.homedir(), getDesktopFolder: () => path.join(os.homedir(), 'Desktop'), @@ -19,49 +19,49 @@ const getPlatformFolders = () => { getMusicFolder: () => path.join(os.homedir(), 'Music'), getPicturesFolder: () => path.join(os.homedir(), 'Pictures'), getVideosFolder: () => path.join(os.homedir(), 'Videos') - }; - console.warn('platform-folders不可用,使用os.homedir()回退方案'); + } + console.warn('platform-folders不可用,使用os.homedir()回退方案') } } - return platformFolders; -}; + return platformFolders +} /** * 用户目录协议实现 * 实现@user://协议,用于访问用户的标准目录(Documents、Desktop、Downloads等) */ class UserProtocol extends ResourceProtocol { - constructor(options = {}) { - super('user', options); - + constructor (options = {}) { + super('user', options) + // 支持的用户目录映射 this.userDirs = { - 'home': 'getHomeFolder', - 'desktop': 'getDesktopFolder', - 'documents': 'getDocumentsFolder', - 'downloads': 'getDownloadsFolder', - 'music': 'getMusicFolder', - 'pictures': 'getPicturesFolder', - 'videos': 'getVideosFolder' - }; - + home: 'getHomeFolder', + desktop: 'getDesktopFolder', + documents: 'getDocumentsFolder', + downloads: 'getDownloadsFolder', + music: 'getMusicFolder', + pictures: 'getPicturesFolder', + videos: 'getVideosFolder' + } + // 目录路径缓存 - this.dirCache = new Map(); + this.dirCache = new Map() } /** * 设置注册表(保持与其他协议的一致性) */ - setRegistry(registry) { + setRegistry (registry) { // User协议不使用注册表,但为了一致性提供此方法 - this.registry = registry || {}; + this.registry = registry || {} } /** * 获取协议信息 * @returns {object} 协议信息 */ - getProtocolInfo() { + getProtocolInfo () { return { name: 'user', description: '用户目录协议,提供跨平台的用户标准目录访问', @@ -74,19 +74,19 @@ class UserProtocol extends ResourceProtocol { ], supportedDirectories: Object.keys(this.userDirs), params: this.getSupportedParams() - }; + } } /** * 支持的查询参数 * @returns {object} 参数说明 */ - getSupportedParams() { + getSupportedParams () { return { ...super.getSupportedParams(), exists: 'boolean - 仅返回存在的文件/目录', type: 'string - 过滤类型 (file|dir|both)' - }; + } } /** @@ -94,16 +94,16 @@ class UserProtocol extends ResourceProtocol { * @param {string} resourcePath - 资源路径 * @returns {boolean} 是否有效 */ - validatePath(resourcePath) { + validatePath (resourcePath) { if (!super.validatePath(resourcePath)) { - return false; + return false } // 解析路径的第一部分(目录类型) - const parts = resourcePath.split('/'); - const dirType = parts[0]; - - return this.userDirs.hasOwnProperty(dirType); + const parts = resourcePath.split('/') + const dirType = parts[0] + + return this.userDirs.hasOwnProperty(dirType) } /** @@ -112,36 +112,36 @@ class UserProtocol extends ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {Promise} 解析后的绝对路径 */ - async resolvePath(resourcePath, queryParams) { - const parts = resourcePath.split('/'); - const dirType = parts[0]; - const relativePath = parts.slice(1).join('/'); + async resolvePath (resourcePath, queryParams) { + const parts = resourcePath.split('/') + const dirType = parts[0] + const relativePath = parts.slice(1).join('/') // 验证目录类型 if (!this.userDirs[dirType]) { - throw new Error(`不支持的用户目录类型: ${dirType}。支持的类型: ${Object.keys(this.userDirs).join(', ')}`); + throw new Error(`不支持的用户目录类型: ${dirType}。支持的类型: ${Object.keys(this.userDirs).join(', ')}`) } // 获取用户目录路径 - const userDirPath = await this.getUserDirectory(dirType); - + const userDirPath = await this.getUserDirectory(dirType) + // 如果没有相对路径,返回目录本身 if (!relativePath) { - return userDirPath; + return userDirPath } // 拼接完整路径 - const fullPath = path.join(userDirPath, relativePath); - + const fullPath = path.join(userDirPath, relativePath) + // 安全检查:确保路径在用户目录内 - const resolvedPath = path.resolve(fullPath); - const resolvedUserDir = path.resolve(userDirPath); - + const resolvedPath = path.resolve(fullPath) + const resolvedUserDir = path.resolve(userDirPath) + if (!resolvedPath.startsWith(resolvedUserDir)) { - throw new Error(`安全错误:路径超出用户目录范围: ${resolvedPath}`); + throw new Error(`安全错误:路径超出用户目录范围: ${resolvedPath}`) } - return resolvedPath; + return resolvedPath } /** @@ -149,35 +149,35 @@ class UserProtocol extends ResourceProtocol { * @param {string} dirType - 目录类型 * @returns {Promise} 目录路径 */ - async getUserDirectory(dirType) { + async getUserDirectory (dirType) { // 检查缓存 if (this.dirCache.has(dirType)) { - return this.dirCache.get(dirType); + return this.dirCache.get(dirType) } - const folders = getPlatformFolders(); - const methodName = this.userDirs[dirType]; - + const folders = getPlatformFolders() + const methodName = this.userDirs[dirType] + if (!folders[methodName]) { - throw new Error(`未找到用户目录获取方法: ${methodName}`); + throw new Error(`未找到用户目录获取方法: ${methodName}`) } try { - let dirPath; - + let dirPath + // 调用platform-folders方法 if (typeof folders[methodName] === 'function') { - dirPath = await folders[methodName](); + dirPath = await folders[methodName]() } else { - dirPath = folders[methodName]; + dirPath = folders[methodName] } - + // 缓存结果 - this.dirCache.set(dirType, dirPath); - - return dirPath; + this.dirCache.set(dirType, dirPath) + + return dirPath } catch (error) { - throw new Error(`获取用户目录失败 (${dirType}): ${error.message}`); + throw new Error(`获取用户目录失败 (${dirType}): ${error.message}`) } } @@ -187,27 +187,27 @@ class UserProtocol extends ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {Promise} 资源内容 */ - async loadContent(resolvedPath, queryParams) { + async loadContent (resolvedPath, queryParams) { try { // 检查路径是否存在 - const stats = await fs.stat(resolvedPath); - + const stats = await fs.stat(resolvedPath) + if (stats.isDirectory()) { - return await this.loadDirectoryContent(resolvedPath, queryParams); + return await this.loadDirectoryContent(resolvedPath, queryParams) } else if (stats.isFile()) { - return await this.loadFileContent(resolvedPath, queryParams); + return await this.loadFileContent(resolvedPath, queryParams) } else { - throw new Error(`不支持的文件类型: ${resolvedPath}`); + throw new Error(`不支持的文件类型: ${resolvedPath}`) } } catch (error) { if (error.code === 'ENOENT') { // 如果设置了exists参数为false,返回空内容而不是错误 if (queryParams && queryParams.get('exists') === 'false') { - return ''; + return '' } - throw new Error(`文件或目录不存在: ${resolvedPath}`); + throw new Error(`文件或目录不存在: ${resolvedPath}`) } - throw error; + throw error } } @@ -217,9 +217,9 @@ class UserProtocol extends ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {Promise} 文件内容 */ - async loadFileContent(filePath, queryParams) { - const encoding = queryParams?.get('encoding') || 'utf8'; - return await fs.readFile(filePath, encoding); + async loadFileContent (filePath, queryParams) { + const encoding = queryParams?.get('encoding') || 'utf8' + return await fs.readFile(filePath, encoding) } /** @@ -228,27 +228,27 @@ class UserProtocol extends ResourceProtocol { * @param {QueryParams} queryParams - 查询参数 * @returns {Promise} 目录内容列表 */ - async loadDirectoryContent(dirPath, queryParams) { - const entries = await fs.readdir(dirPath, { withFileTypes: true }); - + async loadDirectoryContent (dirPath, queryParams) { + const entries = await fs.readdir(dirPath, { withFileTypes: true }) + // 应用类型过滤 - const typeFilter = queryParams?.get('type'); - let filteredEntries = entries; - + const typeFilter = queryParams?.get('type') + let filteredEntries = entries + if (typeFilter) { filteredEntries = entries.filter(entry => { switch (typeFilter) { - case 'file': return entry.isFile(); - case 'dir': return entry.isDirectory(); - case 'both': return true; - default: return true; + case 'file': return entry.isFile() + case 'dir': return entry.isDirectory() + case 'both': return true + default: return true } - }); + }) } // 格式化输出 - const format = queryParams?.get('format') || 'list'; - + const format = queryParams?.get('format') || 'list' + switch (format) { case 'json': return JSON.stringify( @@ -259,21 +259,21 @@ class UserProtocol extends ResourceProtocol { })), null, 2 - ); - + ) + case 'paths': return filteredEntries .map(entry => path.join(dirPath, entry.name)) - .join('\n'); - + .join('\n') + case 'list': default: return filteredEntries .map(entry => { - const type = entry.isDirectory() ? '[DIR]' : '[FILE]'; - return `${type} ${entry.name}`; + const type = entry.isDirectory() ? '[DIR]' : '[FILE]' + return `${type} ${entry.name}` }) - .join('\n'); + .join('\n') } } @@ -281,27 +281,27 @@ class UserProtocol extends ResourceProtocol { * 列出所有支持的用户目录 * @returns {Promise} 目录信息 */ - async listUserDirectories() { - const result = {}; - + async listUserDirectories () { + const result = {} + for (const dirType of Object.keys(this.userDirs)) { try { - result[dirType] = await this.getUserDirectory(dirType); + result[dirType] = await this.getUserDirectory(dirType) } catch (error) { - result[dirType] = { error: error.message }; + result[dirType] = { error: error.message } } } - - return result; + + return result } /** * 清除目录缓存 */ - clearCache() { - super.clearCache(); - this.dirCache.clear(); + clearCache () { + super.clearCache() + this.dirCache.clear() } } -module.exports = UserProtocol; \ No newline at end of file +module.exports = UserProtocol diff --git a/src/lib/core/resource/resourceManager.js b/src/lib/core/resource/resourceManager.js index b2b0412..e13ce1c 100644 --- a/src/lib/core/resource/resourceManager.js +++ b/src/lib/core/resource/resourceManager.js @@ -1,197 +1,196 @@ -const ResourceProtocolParser = require('./resourceProtocolParser'); -const ResourceRegistry = require('./resourceRegistry'); -const { ResourceResult } = require('./types'); -const logger = require('../../utils/logger'); -const fs = require('fs-extra'); -const path = require('path'); +const ResourceProtocolParser = require('./resourceProtocolParser') +const ResourceRegistry = require('./resourceRegistry') +const { ResourceResult } = require('./types') +const logger = require('../../utils/logger') +const fs = require('fs-extra') +const path = require('path') // 导入协议实现 -const PackageProtocol = require('./protocols/PackageProtocol'); -const ProjectProtocol = require('./protocols/ProjectProtocol'); -const UserProtocol = require('./protocols/UserProtocol'); -const PromptProtocol = require('./protocols/PromptProtocol'); +const PackageProtocol = require('./protocols/PackageProtocol') +const ProjectProtocol = require('./protocols/ProjectProtocol') +const UserProtocol = require('./protocols/UserProtocol') +const PromptProtocol = require('./protocols/PromptProtocol') /** * 资源管理器 - 统一管理各种协议的资源加载 */ class ResourceManager { - constructor() { - this.protocolHandlers = new Map(); - this.registry = null; - this.initialized = false; + constructor () { + this.protocolHandlers = new Map() + this.registry = null + this.initialized = false } /** * 初始化资源管理器 */ - async initialize() { - if (this.initialized) return; + async initialize () { + if (this.initialized) return try { // 从统一注册表加载所有协议信息 - await this.loadUnifiedRegistry(); - + await this.loadUnifiedRegistry() + // 注册协议处理器 - await this.registerProtocolHandlers(); - - this.initialized = true; + await this.registerProtocolHandlers() + + this.initialized = true } catch (error) { - throw new Error(`ResourceManager初始化失败: ${error.message}`); + throw new Error(`ResourceManager初始化失败: ${error.message}`) } } /** * 加载统一资源注册表 */ - async loadUnifiedRegistry() { - const registryPath = path.resolve(__dirname, '../../../resource.registry.json'); - + async loadUnifiedRegistry () { + const registryPath = path.resolve(__dirname, '../../../resource.registry.json') + if (!await fs.pathExists(registryPath)) { - throw new Error(`统一资源注册表文件不存在: ${registryPath}`); + throw new Error(`统一资源注册表文件不存在: ${registryPath}`) } - const registryContent = await fs.readJSON(registryPath); - this.registry = registryContent; + const registryContent = await fs.readJSON(registryPath) + this.registry = registryContent } /** * 注册协议处理器 */ - async registerProtocolHandlers() { + async registerProtocolHandlers () { // 动态导入协议处理器 - const protocolsDir = path.join(__dirname, 'protocols'); - const protocolFiles = await fs.readdir(protocolsDir); - + const protocolsDir = path.join(__dirname, 'protocols') + const protocolFiles = await fs.readdir(protocolsDir) + // 首先创建所有协议处理器实例 - const handlers = new Map(); - + const handlers = new Map() + for (const file of protocolFiles) { if (file.endsWith('.js') && file !== 'ResourceProtocol.js') { // 将文件名映射到协议名:ExecutionProtocol.js -> execution - const protocolName = file.replace('Protocol.js', '').toLowerCase(); - const ProtocolClass = require(path.join(protocolsDir, file)); - const protocolHandler = new ProtocolClass(); - + const protocolName = file.replace('Protocol.js', '').toLowerCase() + const ProtocolClass = require(path.join(protocolsDir, file)) + const protocolHandler = new ProtocolClass() + // 从统一注册表获取协议配置 - const protocolConfig = this.registry.protocols[protocolName]; + const protocolConfig = this.registry.protocols[protocolName] if (protocolConfig && protocolConfig.registry) { - protocolHandler.setRegistry(protocolConfig.registry); + protocolHandler.setRegistry(protocolConfig.registry) } - - handlers.set(protocolName, protocolHandler); + + handlers.set(protocolName, protocolHandler) } } - + // 设置协议依赖关系 - const packageProtocol = handlers.get('package'); - const promptProtocol = handlers.get('prompt'); - + const packageProtocol = handlers.get('package') + const promptProtocol = handlers.get('prompt') + if (promptProtocol && packageProtocol) { - promptProtocol.setPackageProtocol(packageProtocol); + promptProtocol.setPackageProtocol(packageProtocol) } - + // 将所有处理器注册到管理器 - this.protocolHandlers = handlers; + this.protocolHandlers = handlers } /** * 解析资源路径并获取内容 */ - async resolveResource(resourceUrl) { - await this.initialize(); + async resolveResource (resourceUrl) { + await this.initialize() try { // 支持DPML资源引用语法: @protocol://path, @!protocol://path, @?protocol://path // 同时向后兼容标准URL格式: protocol://path - const urlMatch = resourceUrl.match(/^(@[!?]?)?([a-zA-Z][a-zA-Z0-9_-]*):\/\/(.+)$/); + const urlMatch = resourceUrl.match(/^(@[!?]?)?([a-zA-Z][a-zA-Z0-9_-]*):\/\/(.+)$/) if (!urlMatch) { - throw new Error(`无效的资源URL格式: ${resourceUrl}。支持格式: @protocol://path, @!protocol://path, @?protocol://path`); + throw new Error(`无效的资源URL格式: ${resourceUrl}。支持格式: @protocol://path, @!protocol://path, @?protocol://path`) } - const [, loadingSemantic, protocol, resourcePath] = urlMatch; - const handler = this.protocolHandlers.get(protocol); - + const [, loadingSemantic, protocol, resourcePath] = urlMatch + const handler = this.protocolHandlers.get(protocol) + if (!handler) { - throw new Error(`未注册的协议: ${protocol}`); + throw new Error(`未注册的协议: ${protocol}`) } // 解析查询参数(如果有的话) - const { QueryParams, ResourceResult } = require('./types'); - let path = resourcePath; - let queryParams = new QueryParams(); - + const { QueryParams, ResourceResult } = require('./types') + let path = resourcePath + const queryParams = new QueryParams() + if (resourcePath.includes('?')) { - const [pathPart, queryString] = resourcePath.split('?', 2); - path = pathPart; - + const [pathPart, queryString] = resourcePath.split('?', 2) + path = pathPart + // 解析查询字符串 - const params = new URLSearchParams(queryString); + const params = new URLSearchParams(queryString) for (const [key, value] of params) { - queryParams.set(key, value); + queryParams.set(key, value) } } - + // 将加载语义信息添加到查询参数中(如果有的话) if (loadingSemantic) { - queryParams.set('loadingSemantic', loadingSemantic); + queryParams.set('loadingSemantic', loadingSemantic) } - - const content = await handler.resolve(path, queryParams); - + + const content = await handler.resolve(path, queryParams) + // 返回ResourceResult格式 return ResourceResult.success(content, { protocol, path, loadingSemantic, loadTime: Date.now() - }); - + }) } catch (error) { // 返回错误结果 - const { ResourceResult } = require('./types'); + const { ResourceResult } = require('./types') return ResourceResult.error(error, { resourceUrl, loadTime: Date.now() - }); + }) } } /** * resolve方法的别名,保持向后兼容 */ - async resolve(resourceUrl) { - return await this.resolveResource(resourceUrl); + async resolve (resourceUrl) { + return await this.resolveResource(resourceUrl) } /** * 获取协议的注册表信息 */ - getProtocolRegistry(protocol) { + getProtocolRegistry (protocol) { if (!this.registry) { - throw new Error('ResourceManager未初始化'); + throw new Error('ResourceManager未初始化') } - - const protocolConfig = this.registry.protocols[protocol]; - return protocolConfig ? protocolConfig.registry : null; + + const protocolConfig = this.registry.protocols[protocol] + return protocolConfig ? protocolConfig.registry : null } /** * 获取所有已注册的协议 */ - getAvailableProtocols() { - return this.registry ? Object.keys(this.registry.protocols) : []; + getAvailableProtocols () { + return this.registry ? Object.keys(this.registry.protocols) : [] } /** * 获取协议的描述信息 */ - getProtocolInfo(protocol) { + getProtocolInfo (protocol) { if (!this.registry) { - throw new Error('ResourceManager未初始化'); + throw new Error('ResourceManager未初始化') } - - return this.registry.protocols[protocol]; + + return this.registry.protocols[protocol] } } -module.exports = ResourceManager; \ No newline at end of file +module.exports = ResourceManager diff --git a/src/lib/core/resource/resourceProtocolParser.js b/src/lib/core/resource/resourceProtocolParser.js index 7bc4347..37682f7 100644 --- a/src/lib/core/resource/resourceProtocolParser.js +++ b/src/lib/core/resource/resourceProtocolParser.js @@ -1,20 +1,20 @@ -const { - LoadingSemantics, - ParsedReference, - QueryParams, - NestedReference -} = require('./types'); +const { + LoadingSemantics, + ParsedReference, + QueryParams, + NestedReference +} = require('./types') /** * 资源协议解析器 * 解析DPML资源引用语法:@protocol://path?params */ class ResourceProtocolParser { - constructor() { + constructor () { // 资源引用正则表达式 - this.resourceRefRegex = /^(@[!?]?|@)([a-zA-Z][a-zA-Z0-9_-]*):(.+)$/; - this.nestedRefRegex = /^(@[!?]?|@)([a-zA-Z][a-zA-Z0-9_-]*):(@[!?]?|@)?(.+)$/; - this.queryParamsRegex = /^([^?]+)(?:\?(.+))?$/; + this.resourceRefRegex = /^(@[!?]?|@)([a-zA-Z][a-zA-Z0-9_-]*):(.+)$/ + this.nestedRefRegex = /^(@[!?]?|@)([a-zA-Z][a-zA-Z0-9_-]*):(@[!?]?|@)?(.+)$/ + this.queryParamsRegex = /^([^?]+)(?:\?(.+))?$/ } /** @@ -22,26 +22,26 @@ class ResourceProtocolParser { * @param {string} resourceRef - 资源引用字符串 * @returns {ParsedReference} 解析后的引用对象 */ - parse(resourceRef) { + parse (resourceRef) { if (!resourceRef || typeof resourceRef !== 'string') { - throw new Error('Invalid resource reference: must be a non-empty string'); + throw new Error('Invalid resource reference: must be a non-empty string') } - const trimmedRef = resourceRef.trim(); + const trimmedRef = resourceRef.trim() if (!this.validateSyntax(trimmedRef)) { - throw new Error(`Invalid resource reference syntax: ${trimmedRef}`); + throw new Error(`Invalid resource reference syntax: ${trimmedRef}`) } - const parsed = new ParsedReference(); - parsed.originalRef = trimmedRef; + const parsed = new ParsedReference() + parsed.originalRef = trimmedRef // 检查是否为嵌套引用 if (this.isNestedReference(trimmedRef)) { - return this.parseNestedReference(trimmedRef); + return this.parseNestedReference(trimmedRef) } // 解析基础引用 - return this.parseBasicReference(trimmedRef); + return this.parseBasicReference(trimmedRef) } /** @@ -49,42 +49,42 @@ class ResourceProtocolParser { * @param {string} ref - 基础引用 * @returns {ParsedReference} */ - parseBasicReference(ref) { - const parsed = new ParsedReference(); - parsed.originalRef = ref; + parseBasicReference (ref) { + const parsed = new ParsedReference() + parsed.originalRef = ref // 解析加载语义 - parsed.loadingSemantics = this.parseLoadingSemantics(ref); + parsed.loadingSemantics = this.parseLoadingSemantics(ref) // 移除加载语义前缀 - const withoutSemantics = this.removeLoadingSemantics(ref); + const withoutSemantics = this.removeLoadingSemantics(ref) // 匹配协议和路径 - const match = withoutSemantics.match(/^([a-zA-Z][a-zA-Z0-9_-]*):(.+)$/); + const match = withoutSemantics.match(/^([a-zA-Z][a-zA-Z0-9_-]*):(.+)$/) if (!match) { - throw new Error(`Invalid protocol format: ${ref}`); + throw new Error(`Invalid protocol format: ${ref}`) } - parsed.protocol = match[1]; - let pathAndParams = match[2]; + parsed.protocol = match[1] + let pathAndParams = match[2] // 移除 :// 前缀(如果存在) if (pathAndParams.startsWith('//')) { - pathAndParams = pathAndParams.substring(2); + pathAndParams = pathAndParams.substring(2) } // 解析路径和查询参数 - const pathMatch = pathAndParams.match(this.queryParamsRegex); + const pathMatch = pathAndParams.match(this.queryParamsRegex) if (pathMatch) { - parsed.path = pathMatch[1]; + parsed.path = pathMatch[1] if (pathMatch[2]) { - parsed.queryParams = this.parseQueryParams(pathMatch[2]); + parsed.queryParams = this.parseQueryParams(pathMatch[2]) } } else { - parsed.path = pathAndParams; + parsed.path = pathAndParams } - return parsed; + return parsed } /** @@ -92,50 +92,50 @@ class ResourceProtocolParser { * @param {string} ref - 嵌套引用 * @returns {ParsedReference} */ - parseNestedReference(ref) { - const parsed = new ParsedReference(); - parsed.originalRef = ref; - parsed.isNested = true; + parseNestedReference (ref) { + const parsed = new ParsedReference() + parsed.originalRef = ref + parsed.isNested = true // 解析外层加载语义 - parsed.loadingSemantics = this.parseLoadingSemantics(ref); - const withoutOuterSemantics = this.removeLoadingSemantics(ref); + parsed.loadingSemantics = this.parseLoadingSemantics(ref) + const withoutOuterSemantics = this.removeLoadingSemantics(ref) // 匹配嵌套结构: protocol:@inner_protocol://path 或 protocol:inner_protocol://path - const match = withoutOuterSemantics.match(/^([a-zA-Z][a-zA-Z0-9_-]*):(.+)$/); + const match = withoutOuterSemantics.match(/^([a-zA-Z][a-zA-Z0-9_-]*):(.+)$/) if (!match) { - throw new Error(`Invalid nested reference format: ${ref}`); + throw new Error(`Invalid nested reference format: ${ref}`) } - parsed.protocol = match[1]; - let innerRef = match[2]; + parsed.protocol = match[1] + let innerRef = match[2] // 处理内层引用:移除可能的 :// 前缀,但保留 @ 前缀 if (innerRef.startsWith('//')) { - innerRef = innerRef.substring(2); + innerRef = innerRef.substring(2) } - + // 确保内层引用有正确的格式 if (!innerRef.startsWith('@')) { - innerRef = '@' + innerRef; + innerRef = '@' + innerRef } // 递归解析内层引用 try { - const innerParsed = this.parse(innerRef); - + const innerParsed = this.parse(innerRef) + // 创建嵌套引用结构 - const nested = new NestedReference(); - nested.outer = parsed; - nested.inner = innerParsed; - nested.depth = this.calculateNestingDepth(innerParsed); - - parsed.nestedRef = nested; + const nested = new NestedReference() + nested.outer = parsed + nested.inner = innerParsed + nested.depth = this.calculateNestingDepth(innerParsed) + + parsed.nestedRef = nested } catch (error) { - throw new Error(`Invalid nested inner reference: ${error.message}`); + throw new Error(`Invalid nested inner reference: ${error.message}`) } - return parsed; + return parsed } /** @@ -143,16 +143,16 @@ class ResourceProtocolParser { * @param {string} ref - 资源引用 * @returns {string} 加载语义 */ - parseLoadingSemantics(ref) { + parseLoadingSemantics (ref) { if (ref.startsWith('@!')) { - return LoadingSemantics.HOT_LOAD; + return LoadingSemantics.HOT_LOAD } else if (ref.startsWith('@?')) { - return LoadingSemantics.LAZY_LOAD; + return LoadingSemantics.LAZY_LOAD } else if (ref.startsWith('@')) { - return LoadingSemantics.DEFAULT; + return LoadingSemantics.DEFAULT } - - throw new Error(`Invalid loading semantics: ${ref}`); + + throw new Error(`Invalid loading semantics: ${ref}`) } /** @@ -160,13 +160,13 @@ class ResourceProtocolParser { * @param {string} ref - 资源引用 * @returns {string} 移除前缀后的引用 */ - removeLoadingSemantics(ref) { + removeLoadingSemantics (ref) { if (ref.startsWith('@!') || ref.startsWith('@?')) { - return ref.substring(2); + return ref.substring(2) } else if (ref.startsWith('@')) { - return ref.substring(1); + return ref.substring(1) } - return ref; + return ref } /** @@ -174,28 +174,28 @@ class ResourceProtocolParser { * @param {string} queryString - 查询字符串 * @returns {QueryParams} 查询参数对象 */ - parseQueryParams(queryString) { - const params = new QueryParams(); - + parseQueryParams (queryString) { + const params = new QueryParams() + if (!queryString) { - return params; + return params } - const pairs = queryString.split('&'); + const pairs = queryString.split('&') for (const pair of pairs) { - const [key, value] = pair.split('=').map(decodeURIComponent); - + const [key, value] = pair.split('=').map(decodeURIComponent) + if (key) { // 处理特殊参数 if (key === 'cache') { - params.set(key, value === 'true' || value === '1'); + params.set(key, value === 'true' || value === '1') } else { - params.set(key, value || ''); + params.set(key, value || '') } } } - return params; + return params } /** @@ -203,15 +203,15 @@ class ResourceProtocolParser { * @param {string} ref - 资源引用 * @returns {boolean} 是否有效 */ - validateSyntax(ref) { - if (!ref) return false; - + validateSyntax (ref) { + if (!ref) return false + // 必须以@开头 - if (!ref.startsWith('@')) return false; - + if (!ref.startsWith('@')) return false + // 基本格式检查 - const withoutSemantics = this.removeLoadingSemantics(ref); - return /^[a-zA-Z][a-zA-Z0-9_-]*:.+$/.test(withoutSemantics); + const withoutSemantics = this.removeLoadingSemantics(ref) + return /^[a-zA-Z][a-zA-Z0-9_-]*:.+$/.test(withoutSemantics) } /** @@ -219,16 +219,16 @@ class ResourceProtocolParser { * @param {string} ref - 资源引用 * @returns {boolean} 是否为嵌套引用 */ - isNestedReference(ref) { - const withoutSemantics = this.removeLoadingSemantics(ref); - const colonIndex = withoutSemantics.indexOf(':'); - - if (colonIndex === -1) return false; - - const afterColon = withoutSemantics.substring(colonIndex + 1); - + isNestedReference (ref) { + const withoutSemantics = this.removeLoadingSemantics(ref) + const colonIndex = withoutSemantics.indexOf(':') + + if (colonIndex === -1) return false + + const afterColon = withoutSemantics.substring(colonIndex + 1) + // 检查是否包含内层引用 (@protocol: 或 protocol:) - return afterColon.includes('@') || afterColon.includes('://'); + return afterColon.includes('@') || afterColon.includes('://') } /** @@ -236,9 +236,9 @@ class ResourceProtocolParser { * @param {ParsedReference} ref - 解析后的引用 * @returns {number} 嵌套深度 */ - calculateNestingDepth(ref) { - if (!ref.isNested) return 1; - return 1 + this.calculateNestingDepth(ref.nestedRef.inner); + calculateNestingDepth (ref) { + if (!ref.isNested) return 1 + return 1 + this.calculateNestingDepth(ref.nestedRef.inner) } /** @@ -246,10 +246,10 @@ class ResourceProtocolParser { * @param {string} ref - 资源引用 * @returns {string} 协议名 */ - extractProtocol(ref) { - const withoutSemantics = this.removeLoadingSemantics(ref); - const colonIndex = withoutSemantics.indexOf(':'); - return colonIndex > 0 ? withoutSemantics.substring(0, colonIndex) : ''; + extractProtocol (ref) { + const withoutSemantics = this.removeLoadingSemantics(ref) + const colonIndex = withoutSemantics.indexOf(':') + return colonIndex > 0 ? withoutSemantics.substring(0, colonIndex) : '' } /** @@ -257,20 +257,20 @@ class ResourceProtocolParser { * @param {string} ref - 资源引用 * @returns {string} 路径 */ - extractPath(ref) { - const withoutSemantics = this.removeLoadingSemantics(ref); - const colonIndex = withoutSemantics.indexOf(':'); - if (colonIndex === -1) return ''; - - let pathAndParams = withoutSemantics.substring(colonIndex + 1); - + extractPath (ref) { + const withoutSemantics = this.removeLoadingSemantics(ref) + const colonIndex = withoutSemantics.indexOf(':') + if (colonIndex === -1) return '' + + let pathAndParams = withoutSemantics.substring(colonIndex + 1) + // 移除 :// 前缀(如果存在) if (pathAndParams.startsWith('//')) { - pathAndParams = pathAndParams.substring(2); + pathAndParams = pathAndParams.substring(2) } - - const queryIndex = pathAndParams.indexOf('?'); - return queryIndex > 0 ? pathAndParams.substring(0, queryIndex) : pathAndParams; + + const queryIndex = pathAndParams.indexOf('?') + return queryIndex > 0 ? pathAndParams.substring(0, queryIndex) : pathAndParams } /** @@ -278,10 +278,10 @@ class ResourceProtocolParser { * @param {string} ref - 资源引用 * @returns {string} 查询参数字符串 */ - extractParams(ref) { - const queryIndex = ref.indexOf('?'); - return queryIndex > 0 ? ref.substring(queryIndex + 1) : ''; + extractParams (ref) { + const queryIndex = ref.indexOf('?') + return queryIndex > 0 ? ref.substring(queryIndex + 1) : '' } } -module.exports = ResourceProtocolParser; \ No newline at end of file +module.exports = ResourceProtocolParser diff --git a/src/lib/core/resource/resourceRegistry.js b/src/lib/core/resource/resourceRegistry.js index 9e4af9c..93cc10b 100644 --- a/src/lib/core/resource/resourceRegistry.js +++ b/src/lib/core/resource/resourceRegistry.js @@ -1,71 +1,71 @@ -const path = require('path'); -const { ProtocolInfo } = require('./types'); +const path = require('path') +const { ProtocolInfo } = require('./types') /** * 资源注册表管理器 * 管理资源协议和ID到路径的映射 */ class ResourceRegistry { - constructor() { - this.builtinRegistry = new Map(); - this.customRegistry = new Map(); - this.loadBuiltinRegistry(); + constructor () { + this.builtinRegistry = new Map() + this.customRegistry = new Map() + this.loadBuiltinRegistry() } /** * 加载内置注册表 */ - loadBuiltinRegistry() { + loadBuiltinRegistry () { // PromptX 内置资源协议 - const promptProtocol = new ProtocolInfo(); - promptProtocol.name = 'prompt'; - promptProtocol.description = 'PromptX内置提示词资源协议'; - promptProtocol.location = 'prompt://{resource_id}'; + const promptProtocol = new ProtocolInfo() + promptProtocol.name = 'prompt' + promptProtocol.description = 'PromptX内置提示词资源协议' + promptProtocol.location = 'prompt://{resource_id}' promptProtocol.registry = new Map([ ['protocols', '@package://prompt/protocol/**/*.md'], ['core', '@package://prompt/core/**/*.md'], ['domain', '@package://prompt/domain/**/*.md'], ['resource', '@package://prompt/resource/**/*.md'], ['bootstrap', '@package://bootstrap.md'] - ]); - this.builtinRegistry.set('prompt', promptProtocol); + ]) + this.builtinRegistry.set('prompt', promptProtocol) // File 协议(标准协议,无需注册表) - const fileProtocol = new ProtocolInfo(); - fileProtocol.name = 'file'; - fileProtocol.description = '文件系统资源协议'; - fileProtocol.location = 'file://{absolute_or_relative_path}'; + const fileProtocol = new ProtocolInfo() + fileProtocol.name = 'file' + fileProtocol.description = '文件系统资源协议' + fileProtocol.location = 'file://{absolute_or_relative_path}' fileProtocol.params = { line: 'string - 行范围,如 "1-10"', encoding: 'string - 文件编码,默认 utf8' - }; - this.builtinRegistry.set('file', fileProtocol); + } + this.builtinRegistry.set('file', fileProtocol) // Memory 协议(项目记忆系统) - const memoryProtocol = new ProtocolInfo(); - memoryProtocol.name = 'memory'; - memoryProtocol.description = '项目记忆系统协议'; - memoryProtocol.location = 'memory://{resource_id}'; + const memoryProtocol = new ProtocolInfo() + memoryProtocol.name = 'memory' + memoryProtocol.description = '项目记忆系统协议' + memoryProtocol.location = 'memory://{resource_id}' memoryProtocol.registry = new Map([ ['declarative', '@project://.promptx/memory/declarative.md'], ['procedural', '@project://.promptx/memory/procedural.md'], ['episodic', '@project://.promptx/memory/episodic.md'], ['semantic', '@project://.promptx/memory/semantic.md'] - ]); - this.builtinRegistry.set('memory', memoryProtocol); + ]) + this.builtinRegistry.set('memory', memoryProtocol) // HTTP/HTTPS 协议(标准协议) - const httpProtocol = new ProtocolInfo(); - httpProtocol.name = 'http'; - httpProtocol.description = 'HTTP网络资源协议'; - httpProtocol.location = 'http://{url}'; + const httpProtocol = new ProtocolInfo() + httpProtocol.name = 'http' + httpProtocol.description = 'HTTP网络资源协议' + httpProtocol.location = 'http://{url}' httpProtocol.params = { format: 'string - 响应格式,如 json, text', timeout: 'number - 超时时间(毫秒)', cache: 'boolean - 是否缓存响应' - }; - this.builtinRegistry.set('http', httpProtocol); - this.builtinRegistry.set('https', httpProtocol); + } + this.builtinRegistry.set('http', httpProtocol) + this.builtinRegistry.set('https', httpProtocol) } /** @@ -74,26 +74,26 @@ class ResourceRegistry { * @param {string} resourceId - 资源ID * @returns {string} 解析后的路径 */ - resolve(protocol, resourceId) { - const protocolInfo = this.getProtocolInfo(protocol); - + resolve (protocol, resourceId) { + const protocolInfo = this.getProtocolInfo(protocol) + if (!protocolInfo) { - throw new Error(`Unknown protocol: ${protocol}`); + throw new Error(`Unknown protocol: ${protocol}`) } // 如果协议有注册表,尝试解析ID if (protocolInfo.registry && protocolInfo.registry.size > 0) { - const resolvedPath = protocolInfo.registry.get(resourceId); + const resolvedPath = protocolInfo.registry.get(resourceId) if (resolvedPath) { - return resolvedPath; + return resolvedPath } - + // 如果在注册表中找不到,但这是一个有注册表的协议,抛出错误 - throw new Error(`Resource ID '${resourceId}' not found in ${protocol} protocol registry`); + throw new Error(`Resource ID '${resourceId}' not found in ${protocol} protocol registry`) } // 对于没有注册表的协议(如file, http),直接返回资源ID作为路径 - return resourceId; + return resourceId } /** @@ -101,22 +101,22 @@ class ResourceRegistry { * @param {string} protocolName - 协议名 * @param {object} protocolDefinition - 协议定义 */ - register(protocolName, protocolDefinition) { - const protocolInfo = new ProtocolInfo(); - protocolInfo.name = protocolName; - protocolInfo.description = protocolDefinition.description || ''; - protocolInfo.location = protocolDefinition.location || ''; - protocolInfo.params = protocolDefinition.params || {}; - + register (protocolName, protocolDefinition) { + const protocolInfo = new ProtocolInfo() + protocolInfo.name = protocolName + protocolInfo.description = protocolDefinition.description || '' + protocolInfo.location = protocolDefinition.location || '' + protocolInfo.params = protocolDefinition.params || {} + // 设置注册表映射 if (protocolDefinition.registry) { - protocolInfo.registry = new Map(); + protocolInfo.registry = new Map() for (const [id, path] of Object.entries(protocolDefinition.registry)) { - protocolInfo.registry.set(id, path); + protocolInfo.registry.set(id, path) } } - this.customRegistry.set(protocolName, protocolInfo); + this.customRegistry.set(protocolName, protocolInfo) } /** @@ -124,28 +124,28 @@ class ResourceRegistry { * @param {string} protocolName - 协议名 * @returns {ProtocolInfo|null} 协议信息 */ - getProtocolInfo(protocolName) { - return this.customRegistry.get(protocolName) || - this.builtinRegistry.get(protocolName) || - null; + getProtocolInfo (protocolName) { + return this.customRegistry.get(protocolName) || + this.builtinRegistry.get(protocolName) || + null } /** * 列出所有可用协议 * @returns {string[]} 协议名列表 */ - listProtocols() { - const protocols = new Set(); - + listProtocols () { + const protocols = new Set() + for (const protocol of this.builtinRegistry.keys()) { - protocols.add(protocol); + protocols.add(protocol) } - + for (const protocol of this.customRegistry.keys()) { - protocols.add(protocol); + protocols.add(protocol) } - - return Array.from(protocols).sort(); + + return Array.from(protocols).sort() } /** @@ -153,9 +153,9 @@ class ResourceRegistry { * @param {string} protocolName - 协议名 * @returns {boolean} 是否存在 */ - hasProtocol(protocolName) { - return this.builtinRegistry.has(protocolName) || - this.customRegistry.has(protocolName); + hasProtocol (protocolName) { + return this.builtinRegistry.has(protocolName) || + this.customRegistry.has(protocolName) } /** @@ -163,9 +163,9 @@ class ResourceRegistry { * @param {string} protocolName - 协议名 * @returns {Map|null} 注册表映射 */ - getProtocolRegistry(protocolName) { - const protocolInfo = this.getProtocolInfo(protocolName); - return protocolInfo ? protocolInfo.registry : null; + getProtocolRegistry (protocolName) { + const protocolInfo = this.getProtocolInfo(protocolName) + return protocolInfo ? protocolInfo.registry : null } /** @@ -173,9 +173,9 @@ class ResourceRegistry { * @param {string} protocolName - 协议名 * @returns {string[]} 资源ID列表 */ - listProtocolResources(protocolName) { - const registry = this.getProtocolRegistry(protocolName); - return registry ? Array.from(registry.keys()) : []; + listProtocolResources (protocolName) { + const registry = this.getProtocolRegistry(protocolName) + return registry ? Array.from(registry.keys()) : [] } /** @@ -183,10 +183,10 @@ class ResourceRegistry { * @param {string} pattern - 通配符模式 * @returns {string[]} 展开后的路径列表 */ - expandWildcards(pattern) { + expandWildcards (pattern) { // 这里暂时返回原样,实际实现需要结合文件系统 // 在ResourceLocator中会有更详细的实现 - return [pattern]; + return [pattern] } /** @@ -195,31 +195,31 @@ class ResourceRegistry { * @param {string} resourceId - 资源ID * @returns {boolean} 是否有效 */ - validateReference(protocol, resourceId) { + validateReference (protocol, resourceId) { if (!this.hasProtocol(protocol)) { - return false; + return false } - const protocolInfo = this.getProtocolInfo(protocol); - + const protocolInfo = this.getProtocolInfo(protocol) + // 如果有注册表,检查ID是否存在 if (protocolInfo.registry && protocolInfo.registry.size > 0) { - return protocolInfo.registry.has(resourceId); + return protocolInfo.registry.has(resourceId) } // 对于没有注册表的协议,只要协议存在就认为有效 - return true; + return true } /** * 获取所有注册表信息(用于调试) * @returns {object} 注册表信息 */ - getRegistryInfo() { + getRegistryInfo () { const info = { builtin: {}, custom: {} - }; + } for (const [name, protocol] of this.builtinRegistry) { info.builtin[name] = { @@ -228,7 +228,7 @@ class ResourceRegistry { params: protocol.params, registrySize: protocol.registry ? protocol.registry.size : 0, resources: protocol.registry ? Array.from(protocol.registry.keys()) : [] - }; + } } for (const [name, protocol] of this.customRegistry) { @@ -238,11 +238,11 @@ class ResourceRegistry { params: protocol.params, registrySize: protocol.registry ? protocol.registry.size : 0, resources: protocol.registry ? Array.from(protocol.registry.keys()) : [] - }; + } } - return info; + return info } } -module.exports = ResourceRegistry; \ No newline at end of file +module.exports = ResourceRegistry diff --git a/src/lib/core/resource/types.js b/src/lib/core/resource/types.js index 763d6f8..7102cc7 100644 --- a/src/lib/core/resource/types.js +++ b/src/lib/core/resource/types.js @@ -7,23 +7,23 @@ * 加载语义枚举 */ const LoadingSemantics = { - DEFAULT: 'default', // @ - AI自行决定加载时机 - HOT_LOAD: 'hot_load', // @! - 立即加载 - LAZY_LOAD: 'lazy_load' // @? - 懒加载 -}; + DEFAULT: 'default', // @ - AI自行决定加载时机 + HOT_LOAD: 'hot_load', // @! - 立即加载 + LAZY_LOAD: 'lazy_load' // @? - 懒加载 +} /** * 解析后的资源引用 */ class ParsedReference { - constructor() { - this.loadingSemantics = LoadingSemantics.DEFAULT; - this.protocol = ''; - this.path = ''; - this.queryParams = new QueryParams(); - this.isNested = false; - this.nestedRef = null; - this.originalRef = ''; + constructor () { + this.loadingSemantics = LoadingSemantics.DEFAULT + this.protocol = '' + this.path = '' + this.queryParams = new QueryParams() + this.isNested = false + this.nestedRef = null + this.originalRef = '' } } @@ -31,83 +31,83 @@ class ParsedReference { * 查询参数 */ class QueryParams { - constructor() { - this.line = null; // 行范围 "5-10" - this.format = null; // 输出格式 "json" - this.cache = null; // 是否缓存,默认为null表示未设置 - this.params = new Map(); // 其他参数 + constructor () { + this.line = null // 行范围 "5-10" + this.format = null // 输出格式 "json" + this.cache = null // 是否缓存,默认为null表示未设置 + this.params = new Map() // 其他参数 } /** * 设置参数 */ - set(key, value) { + set (key, value) { if (['line', 'format', 'cache'].includes(key)) { - this[key] = value; + this[key] = value } else { - this.params.set(key, value); + this.params.set(key, value) } } /** * 获取参数 */ - get(key) { + get (key) { if (['line', 'format', 'cache'].includes(key)) { - return this[key]; + return this[key] } - return this.params.get(key); + return this.params.get(key) } /** * 获取所有参数 */ - getAll() { - const result = {}; - + getAll () { + const result = {} + // 只添加非null的内置参数 if (this.line !== null) { - result.line = this.line; + result.line = this.line } if (this.format !== null) { - result.format = this.format; + result.format = this.format } if (this.cache !== null) { - result.cache = this.cache; + result.cache = this.cache } - + // 添加其他参数 for (const [key, value] of this.params) { - result[key] = value; + result[key] = value } - - return result; + + return result } /** * 转换为字符串用于缓存键 */ - toString() { - const params = []; - + toString () { + const params = [] + // 添加内置参数 if (this.line !== null) { - params.push(`line=${this.line}`); + params.push(`line=${this.line}`) } if (this.format !== null) { - params.push(`format=${this.format}`); + params.push(`format=${this.format}`) } if (this.cache !== null) { - params.push(`cache=${this.cache}`); + params.push(`cache=${this.cache}`) } - + // 添加其他参数(按键排序以确保一致性) - const sortedParams = Array.from(this.params.entries()).sort(); + const sortedParams = Array.from(this.params.entries()).sort() for (const [key, value] of sortedParams) { - params.push(`${key}=${value}`); + params.push(`${key}=${value}`) } - - return params.join('&'); + + return params.join('&') } } @@ -115,10 +115,10 @@ class QueryParams { * 嵌套引用 */ class NestedReference { - constructor() { - this.outer = null; // 外层引用 - this.inner = null; // 内层引用 - this.depth = 0; // 嵌套深度 + constructor () { + this.outer = null // 外层引用 + this.inner = null // 内层引用 + this.depth = 0 // 嵌套深度 } } @@ -126,13 +126,13 @@ class NestedReference { * 资源内容 */ class ResourceContent { - constructor(path, content, metadata = {}) { - this.path = path; - this.content = content; - this.metadata = metadata; - this.relativePath = ''; - this.lastModified = null; - this.size = content ? content.length : 0; + constructor (path, content, metadata = {}) { + this.path = path + this.content = content + this.metadata = metadata + this.relativePath = '' + this.lastModified = null + this.size = content ? content.length : 0 } } @@ -140,22 +140,22 @@ class ResourceContent { * 懒加载资源 */ class LazyResource { - constructor(path, loader) { - this.path = path; - this.loader = loader; - this.loaded = false; - this._content = null; + constructor (path, loader) { + this.path = path + this.loader = loader + this.loaded = false + this._content = null } /** * 加载资源 */ - async load() { + async load () { if (!this.loaded) { - this._content = await this.loader(this.path); - this.loaded = true; + this._content = await this.loader(this.path) + this.loaded = true } - return this._content; + return this._content } } @@ -163,12 +163,12 @@ class LazyResource { * 处理后的结果 */ class ProcessedResult { - constructor() { - this.content = ''; - this.metadata = {}; - this.format = 'text'; - this.sources = []; - this.cached = false; + constructor () { + this.content = '' + this.metadata = {} + this.format = 'text' + this.sources = [] + this.cached = false } } @@ -176,37 +176,37 @@ class ProcessedResult { * 最终资源结果 */ class ResourceResult { - constructor() { - this.content = ''; - this.metadata = {}; - this.sources = []; - this.format = 'text'; - this.cached = false; - this.loadTime = Date.now(); - this.success = true; - this.error = null; + constructor () { + this.content = '' + this.metadata = {} + this.sources = [] + this.format = 'text' + this.cached = false + this.loadTime = Date.now() + this.success = true + this.error = null } /** * 创建成功结果 */ - static success(content, metadata = {}) { - const result = new ResourceResult(); - result.content = content; - result.metadata = metadata; - result.success = true; - return result; + static success (content, metadata = {}) { + const result = new ResourceResult() + result.content = content + result.metadata = metadata + result.success = true + return result } /** * 创建错误结果 */ - static error(error, metadata = {}) { - const result = new ResourceResult(); - result.success = false; - result.error = error; - result.metadata = metadata; - return result; + static error (error, metadata = {}) { + const result = new ResourceResult() + result.success = false + result.error = error + result.metadata = metadata + return result } } @@ -214,12 +214,12 @@ class ResourceResult { * 资源协议信息 */ class ProtocolInfo { - constructor() { - this.name = ''; - this.description = ''; - this.location = ''; // EBNF路径定义 - this.params = {}; // 支持的参数 - this.registry = new Map(); // ID到路径的映射 + constructor () { + this.name = '' + this.description = '' + this.location = '' // EBNF路径定义 + this.params = {} // 支持的参数 + this.registry = new Map() // ID到路径的映射 } } @@ -233,4 +233,4 @@ module.exports = { ProcessedResult, ResourceResult, ProtocolInfo -}; \ No newline at end of file +} diff --git a/src/lib/index.js b/src/lib/index.js index 4ddc72d..3ccbc42 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -1,6 +1,6 @@ /** * PromptX 核心库 - * + * * 提供AI prompt框架的核心功能,包括: * - 四大核心命令实现 * - 资源管理和路径解析 @@ -14,29 +14,29 @@ const commands = { learn: require('./commands/learn'), recall: require('./commands/recall'), remember: require('./commands/remember') -}; +} // 核心功能模块 const core = { ResourceManager: require('./core/resource-manager'), PathResolver: require('./core/path-resolver'), OutputFormatter: require('./core/output-formatter') -}; +} // 工具模块 const utils = { logger: require('./utils/logger'), validator: require('./utils/validator'), config: require('./utils/config') -}; +} module.exports = { commands, core, utils, - + // 便捷导出 ...commands, ...core, ...utils -}; \ No newline at end of file +} diff --git a/src/lib/utils/logger.js b/src/lib/utils/logger.js index 66cd4ec..632aa85 100644 --- a/src/lib/utils/logger.js +++ b/src/lib/utils/logger.js @@ -1,90 +1,90 @@ -const chalk = require('chalk'); +const chalk = require('chalk') /** * 日志工具 * 提供彩色和格式化的日志输出 */ class Logger { - constructor(options = {}) { - this.silent = options.silent || false; - this.prefix = options.prefix || 'PromptX'; + constructor (options = {}) { + this.silent = options.silent || false + this.prefix = options.prefix || 'PromptX' } /** * 信息日志 */ - info(message, ...args) { - if (this.silent) return; - console.log(chalk.blue('ℹ'), message, ...args); + info (message, ...args) { + if (this.silent) return + console.log(chalk.blue('ℹ'), message, ...args) } /** * 成功日志 */ - success(message, ...args) { - if (this.silent) return; - console.log(chalk.green('✅'), message, ...args); + success (message, ...args) { + if (this.silent) return + console.log(chalk.green('✅'), message, ...args) } /** * 警告日志 */ - warn(message, ...args) { - if (this.silent) return; - console.log(chalk.yellow('⚠️'), chalk.yellow(message), ...args); + warn (message, ...args) { + if (this.silent) return + console.log(chalk.yellow('⚠️'), chalk.yellow(message), ...args) } /** * 错误日志 */ - error(message, ...args) { - if (this.silent) return; - console.error(chalk.red('❌'), chalk.red(message), ...args); + error (message, ...args) { + if (this.silent) return + console.error(chalk.red('❌'), chalk.red(message), ...args) } /** * 调试日志 */ - debug(message, ...args) { - if (this.silent || !process.env.DEBUG) return; - console.log(chalk.gray('🐛'), chalk.gray(message), ...args); + debug (message, ...args) { + if (this.silent || !process.env.DEBUG) return + console.log(chalk.gray('🐛'), chalk.gray(message), ...args) } /** * 步骤日志(用于显示进度) */ - step(message, ...args) { - if (this.silent) return; - console.log(chalk.cyan('▶️'), message, ...args); + step (message, ...args) { + if (this.silent) return + console.log(chalk.cyan('▶️'), message, ...args) } /** * 直接输出(不带前缀) */ - log(message, ...args) { - if (this.silent) return; - console.log(message, ...args); + log (message, ...args) { + if (this.silent) return + console.log(message, ...args) } /** * 空行 */ - newLine() { - if (this.silent) return; - console.log(''); + newLine () { + if (this.silent) return + console.log('') } /** * 分隔线 */ - separator(char = '=', length = 80) { - if (this.silent) return; - console.log(chalk.gray(char.repeat(length))); + separator (char = '=', length = 80) { + if (this.silent) return + console.log(chalk.gray(char.repeat(length))) } } // 导出默认实例 -const logger = new Logger(); +const logger = new Logger() -module.exports = logger; -module.exports.Logger = Logger; \ No newline at end of file +module.exports = logger +module.exports.Logger = Logger diff --git a/src/scripts/test-pouch.js b/src/scripts/test-pouch.js index 3aafe0e..0332ab6 100644 --- a/src/scripts/test-pouch.js +++ b/src/scripts/test-pouch.js @@ -1,53 +1,53 @@ /** * 锦囊框架测试脚本 */ -const path = require('path'); -const { cli } = require(path.join(__dirname, '..', 'lib', 'core', 'pouch')); +const path = require('path') +const { cli } = require(path.join(__dirname, '..', 'lib', 'core', 'pouch')) -async function testPouchFramework() { - console.log('🧪 开始测试锦囊框架...\n'); +async function testPouchFramework () { + console.log('🧪 开始测试锦囊框架...\n') try { // 测试1: 初始化 - console.log('1️⃣ 测试 init 命令:'); - await cli.execute('init'); - console.log('\n'); + console.log('1️⃣ 测试 init 命令:') + await cli.execute('init') + console.log('\n') // 测试2: 发现角色 - console.log('2️⃣ 测试 hello 命令:'); - await cli.execute('hello'); - console.log('\n'); + console.log('2️⃣ 测试 hello 命令:') + await cli.execute('hello') + console.log('\n') // 测试3: 激活角色 - console.log('3️⃣ 测试 action 命令:'); - await cli.execute('action', ['copywriter']); - console.log('\n'); + console.log('3️⃣ 测试 action 命令:') + await cli.execute('action', ['copywriter']) + console.log('\n') // 测试4: 学习领域 - console.log('4️⃣ 测试 learn 命令:'); - await cli.execute('learn', ['scrum']); - console.log('\n'); + console.log('4️⃣ 测试 learn 命令:') + await cli.execute('learn', ['scrum']) + console.log('\n') // 测试5: 检索记忆 - console.log('5️⃣ 测试 recall 命令:'); - await cli.execute('recall', ['test']); - console.log('\n'); + console.log('5️⃣ 测试 recall 命令:') + await cli.execute('recall', ['test']) + console.log('\n') // 测试6: 获取状态 - console.log('6️⃣ 当前状态:'); - console.log(JSON.stringify(cli.getStatus(), null, 2)); - console.log('\n'); + console.log('6️⃣ 当前状态:') + console.log(JSON.stringify(cli.getStatus(), null, 2)) + console.log('\n') - console.log('✅ 锦囊框架测试完成!'); + console.log('✅ 锦囊框架测试完成!') } catch (error) { - console.error('❌ 测试失败:', error.message); - console.error(error.stack); + console.error('❌ 测试失败:', error.message) + console.error(error.stack) } } // 如果直接运行此文件,执行测试 if (require.main === module) { - testPouchFramework(); + testPouchFramework() } -module.exports = { testPouchFramework }; \ No newline at end of file +module.exports = { testPouchFramework } diff --git a/src/tests/commands/promptxCli.e2e.test.js b/src/tests/commands/promptxCli.e2e.test.js index 2fb6fb7..07dba2e 100644 --- a/src/tests/commands/promptxCli.e2e.test.js +++ b/src/tests/commands/promptxCli.e2e.test.js @@ -1,313 +1,313 @@ -const { spawn } = require('child_process'); -const fs = require('fs').promises; -const path = require('path'); -const os = require('os'); +const { spawn } = require('child_process') +const fs = require('fs').promises +const path = require('path') +const os = require('os') describe('PromptX CLI - E2E Tests', () => { - const CLI_PATH = path.resolve(__dirname, '../../bin/promptx.js'); - let tempDir; + const CLI_PATH = path.resolve(__dirname, '../../bin/promptx.js') + let tempDir beforeAll(async () => { // 创建临时测试目录 - tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-e2e-')); - + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-e2e-')) + // 创建测试项目结构 - const promptDir = path.join(tempDir, 'prompt'); - await fs.mkdir(promptDir, { recursive: true }); - - const coreDir = path.join(promptDir, 'core'); - await fs.mkdir(coreDir, { recursive: true }); - + const promptDir = path.join(tempDir, 'prompt') + await fs.mkdir(promptDir, { recursive: true }) + + const coreDir = path.join(promptDir, 'core') + await fs.mkdir(coreDir, { recursive: true }) + // 创建测试文件 await fs.writeFile( path.join(coreDir, 'test-core.md'), '# Core Prompt\n\n这是核心提示词。' - ); - + ) + await fs.writeFile( path.join(tempDir, 'bootstrap.md'), '# Bootstrap\n\n这是启动文件。' - ); - }); + ) + }) afterAll(async () => { // 清理临时目录 - await fs.rm(tempDir, { recursive: true }); - }); + await fs.rm(tempDir, { recursive: true }) + }) /** * 运行CLI命令的辅助函数 */ - function runCommand(args, options = {}) { + function runCommand (args, options = {}) { return new Promise((resolve, reject) => { const child = spawn('node', [CLI_PATH, ...args], { cwd: options.cwd || tempDir, stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, ...options.env } - }); + }) - let stdout = ''; - let stderr = ''; + let stdout = '' + let stderr = '' child.stdout.on('data', (data) => { - stdout += data.toString(); - }); + stdout += data.toString() + }) child.stderr.on('data', (data) => { - stderr += data.toString(); - }); + stderr += data.toString() + }) child.on('close', (code) => { resolve({ code, stdout, stderr - }); - }); + }) + }) - child.on('error', reject); + child.on('error', reject) // 如果需要输入,发送输入数据 if (options.input) { - child.stdin.write(options.input); - child.stdin.end(); + child.stdin.write(options.input) + child.stdin.end() } - }); + }) } describe('基础命令测试', () => { test('应该显示帮助信息', async () => { - const result = await runCommand(['--help']); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('PromptX CLI'); - expect(result.stdout).toContain('Usage:'); - expect(result.stdout).toContain('hello'); - expect(result.stdout).toContain('learn'); - expect(result.stdout).toContain('recall'); - expect(result.stdout).toContain('remember'); - }); + const result = await runCommand(['--help']) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('PromptX CLI') + expect(result.stdout).toContain('Usage:') + expect(result.stdout).toContain('hello') + expect(result.stdout).toContain('learn') + expect(result.stdout).toContain('recall') + expect(result.stdout).toContain('remember') + }) test('应该显示版本信息', async () => { - const result = await runCommand(['--version']); - - expect(result.code).toBe(0); - expect(result.stdout).toMatch(/\d+\.\d+\.\d+/); - }); - }); + const result = await runCommand(['--version']) + + expect(result.code).toBe(0) + expect(result.stdout).toMatch(/\d+\.\d+\.\d+/) + }) + }) describe('hello 命令 - 系统入口', () => { test('应该显示欢迎信息', async () => { - const result = await runCommand(['hello']); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('👋'); - expect(result.stdout).toContain('PromptX'); - expect(result.stdout).toContain('AI助手'); - }); + const result = await runCommand(['hello']) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('👋') + expect(result.stdout).toContain('PromptX') + expect(result.stdout).toContain('AI助手') + }) test('应该支持个性化问候', async () => { - const result = await runCommand(['hello', '--name', '张三']); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('张三'); - }); + const result = await runCommand(['hello', '--name', '张三']) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('张三') + }) test('应该显示系统状态', async () => { - const result = await runCommand(['hello', '--status']); - - expect(result.code).toBe(0); - expect(result.stdout).toMatch(/工作目录:/); - expect(result.stdout).toMatch(/资源协议:/); - }); - }); + const result = await runCommand(['hello', '--status']) + + expect(result.code).toBe(0) + expect(result.stdout).toMatch(/工作目录:/) + expect(result.stdout).toMatch(/资源协议:/) + }) + }) describe('learn 命令 - 资源学习', () => { test('应该加载prompt协议资源', async () => { - const result = await runCommand(['learn', '@prompt://bootstrap']); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('学习资源'); - expect(result.stdout).toContain('@prompt://bootstrap'); - }); + const result = await runCommand(['learn', '@prompt://bootstrap']) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('学习资源') + expect(result.stdout).toContain('@prompt://bootstrap') + }) test('应该加载文件资源', async () => { - const result = await runCommand(['learn', '@file://bootstrap.md']); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('这是启动文件'); - }); + const result = await runCommand(['learn', '@file://bootstrap.md']) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('这是启动文件') + }) test('应该支持带参数的资源加载', async () => { - const result = await runCommand(['learn', '@file://bootstrap.md?line=1']); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('# Bootstrap'); - expect(result.stdout).not.toContain('这是启动文件'); - }); + const result = await runCommand(['learn', '@file://bootstrap.md?line=1']) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('# Bootstrap') + expect(result.stdout).not.toContain('这是启动文件') + }) test('应该处理无效资源引用', async () => { - const result = await runCommand(['learn', 'invalid-reference']); - - expect(result.code).toBe(1); - expect(result.stderr).toContain('资源引用格式错误'); - }); + const result = await runCommand(['learn', 'invalid-reference']) + + expect(result.code).toBe(1) + expect(result.stderr).toContain('资源引用格式错误') + }) test('应该处理不存在的文件', async () => { - const result = await runCommand(['learn', '@file://nonexistent.md']); - - expect(result.code).toBe(1); - expect(result.stderr).toContain('Failed to read file'); - }); - }); + const result = await runCommand(['learn', '@file://nonexistent.md']) + + expect(result.code).toBe(1) + expect(result.stderr).toContain('Failed to read file') + }) + }) describe('recall 命令 - 记忆检索', () => { test('应该显示基本的记忆检索功能', async () => { - const result = await runCommand(['recall', 'test']); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('🔍 正在检索记忆'); - }); + const result = await runCommand(['recall', 'test']) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('🔍 正在检索记忆') + }) test('应该支持记忆类型指定', async () => { - const result = await runCommand(['recall', 'test', '--type', 'semantic']); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('semantic'); - }); + const result = await runCommand(['recall', 'test', '--type', 'semantic']) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('semantic') + }) test('应该支持模糊搜索', async () => { - const result = await runCommand(['recall', 'test', '--fuzzy']); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('模糊搜索'); - }); - }); + const result = await runCommand(['recall', 'test', '--fuzzy']) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('模糊搜索') + }) + }) describe('remember 命令 - 记忆存储', () => { test('应该存储新的记忆', async () => { - const result = await runCommand(['remember', 'test-memory', 'This is a test memory']); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('🧠 正在存储记忆'); - expect(result.stdout).toContain('test-memory'); - }); + const result = await runCommand(['remember', 'test-memory', 'This is a test memory']) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('🧠 正在存储记忆') + expect(result.stdout).toContain('test-memory') + }) test('应该支持记忆类型指定', async () => { const result = await runCommand([ - 'remember', - 'procedure-test', - 'How to test', - '--type', + 'remember', + 'procedure-test', + 'How to test', + '--type', 'procedural' - ]); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('procedural'); - }); + ]) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('procedural') + }) test('应该支持标签添加', async () => { const result = await runCommand([ - 'remember', - 'tagged-memory', + 'remember', + 'tagged-memory', 'Tagged content', '--tags', 'test,example' - ]); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('tags'); - }); - }); + ]) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('tags') + }) + }) describe('错误处理和边界情况', () => { test('应该处理无效命令', async () => { - const result = await runCommand(['invalid-command']); - - expect(result.code).toBe(1); - expect(result.stderr).toContain('Unknown command'); - }); + const result = await runCommand(['invalid-command']) + + expect(result.code).toBe(1) + expect(result.stderr).toContain('Unknown command') + }) test('应该处理缺少参数的情况', async () => { - const result = await runCommand(['learn']); - - expect(result.code).toBe(1); - expect(result.stderr).toContain('Missing required argument'); - }); + const result = await runCommand(['learn']) + + expect(result.code).toBe(1) + expect(result.stderr).toContain('Missing required argument') + }) test('应该处理权限错误', async () => { // 创建一个没有权限的文件 - const restrictedFile = path.join(tempDir, 'restricted.md'); - await fs.writeFile(restrictedFile, 'restricted content'); - await fs.chmod(restrictedFile, 0o000); - - const result = await runCommand(['learn', '@file://restricted.md']); - - expect(result.code).toBe(1); - expect(result.stderr).toContain('EACCES'); - + const restrictedFile = path.join(tempDir, 'restricted.md') + await fs.writeFile(restrictedFile, 'restricted content') + await fs.chmod(restrictedFile, 0o000) + + const result = await runCommand(['learn', '@file://restricted.md']) + + expect(result.code).toBe(1) + expect(result.stderr).toContain('EACCES') + // 恢复权限以便清理 - await fs.chmod(restrictedFile, 0o644); - }); - }); + await fs.chmod(restrictedFile, 0o644) + }) + }) describe('工作流集成测试', () => { test('应该支持完整的AI认知循环', async () => { // 1. Hello - 建立连接 - const helloResult = await runCommand(['hello', '--name', 'E2E测试']); - expect(helloResult.code).toBe(0); - + const helloResult = await runCommand(['hello', '--name', 'E2E测试']) + expect(helloResult.code).toBe(0) + // 2. Learn - 学习资源 - const learnResult = await runCommand(['learn', '@file://bootstrap.md']); - expect(learnResult.code).toBe(0); - + const learnResult = await runCommand(['learn', '@file://bootstrap.md']) + expect(learnResult.code).toBe(0) + // 3. Remember - 存储记忆 const rememberResult = await runCommand([ - 'remember', - 'e2e-test', + 'remember', + 'e2e-test', 'E2E测试记忆', '--type', 'episodic' - ]); - expect(rememberResult.code).toBe(0); - + ]) + expect(rememberResult.code).toBe(0) + // 4. Recall - 检索记忆 - const recallResult = await runCommand(['recall', 'e2e-test']); - expect(recallResult.code).toBe(0); - }); + const recallResult = await runCommand(['recall', 'e2e-test']) + expect(recallResult.code).toBe(0) + }) test('应该支持资源链式学习', async () => { // 创建链式引用文件 - const chainFile = path.join(tempDir, 'chain.md'); - await fs.writeFile(chainFile, '@file://bootstrap.md'); - - const result = await runCommand(['learn', '@file://chain.md']); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('这是启动文件'); - }); - }); + const chainFile = path.join(tempDir, 'chain.md') + await fs.writeFile(chainFile, '@file://bootstrap.md') + + const result = await runCommand(['learn', '@file://chain.md']) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('这是启动文件') + }) + }) describe('输出格式和交互', () => { test('应该支持JSON输出格式', async () => { - const result = await runCommand(['learn', '@file://bootstrap.md', '--format', 'json']); - - expect(result.code).toBe(0); - expect(() => JSON.parse(result.stdout)).not.toThrow(); - }); + const result = await runCommand(['learn', '@file://bootstrap.md', '--format', 'json']) + + expect(result.code).toBe(0) + expect(() => JSON.parse(result.stdout)).not.toThrow() + }) test('应该支持静默模式', async () => { - const result = await runCommand(['hello', '--quiet']); - - expect(result.code).toBe(0); - expect(result.stdout.trim()).toBe(''); - }); + const result = await runCommand(['hello', '--quiet']) + + expect(result.code).toBe(0) + expect(result.stdout.trim()).toBe('') + }) test('应该支持详细输出模式', async () => { - const result = await runCommand(['learn', '@file://bootstrap.md', '--verbose']); - - expect(result.code).toBe(0); - expect(result.stdout).toContain('DEBUG'); - }); - }); -}); \ No newline at end of file + const result = await runCommand(['learn', '@file://bootstrap.md', '--verbose']) + + expect(result.code).toBe(0) + expect(result.stdout).toContain('DEBUG') + }) + }) +}) diff --git a/src/tests/core/resource/protocols/PackageProtocol.unit.test.js b/src/tests/core/resource/protocols/PackageProtocol.unit.test.js index 237ccc7..d9821d3 100644 --- a/src/tests/core/resource/protocols/PackageProtocol.unit.test.js +++ b/src/tests/core/resource/protocols/PackageProtocol.unit.test.js @@ -1,357 +1,357 @@ -const PackageProtocol = require('../../../../lib/core/resource/protocols/PackageProtocol'); -const { QueryParams } = require('../../../../lib/core/resource/types'); -const path = require('path'); -const fs = require('fs').promises; +const PackageProtocol = require('../../../../lib/core/resource/protocols/PackageProtocol') +const { QueryParams } = require('../../../../lib/core/resource/types') +const path = require('path') +const fs = require('fs').promises describe('PackageProtocol', () => { - let packageProtocol; - const originalEnv = process.env; - const projectRoot = process.cwd(); // PromptX项目根目录 + let packageProtocol + const originalEnv = process.env + const projectRoot = process.cwd() // PromptX项目根目录 beforeEach(() => { - packageProtocol = new PackageProtocol(); + packageProtocol = new PackageProtocol() // 重置环境变量 - process.env = { ...originalEnv }; - }); + process.env = { ...originalEnv } + }) afterEach(() => { - packageProtocol.clearCache(); - process.env = originalEnv; - }); + packageProtocol.clearCache() + process.env = originalEnv + }) describe('基础功能', () => { test('应该正确初始化协议', () => { - expect(packageProtocol.name).toBe('package'); - expect(packageProtocol.installModeCache).toBeInstanceOf(Map); - }); + expect(packageProtocol.name).toBe('package') + expect(packageProtocol.installModeCache).toBeInstanceOf(Map) + }) test('应该提供协议信息', () => { - const info = packageProtocol.getProtocolInfo(); - expect(info.name).toBe('package'); - expect(info.description).toContain('包协议'); - expect(info.examples).toContain('@package://package.json'); - expect(info.examples).toContain('@package://src/index.js'); - expect(info.installModes).toContain('development'); - }); + const info = packageProtocol.getProtocolInfo() + expect(info.name).toBe('package') + expect(info.description).toContain('包协议') + expect(info.examples).toContain('@package://package.json') + expect(info.examples).toContain('@package://src/index.js') + expect(info.installModes).toContain('development') + }) test('应该支持缓存', () => { - expect(packageProtocol.enableCache).toBe(true); - expect(packageProtocol.cache).toBeInstanceOf(Map); - expect(packageProtocol.installModeCache).toBeInstanceOf(Map); - }); - }); + expect(packageProtocol.enableCache).toBe(true) + expect(packageProtocol.cache).toBeInstanceOf(Map) + expect(packageProtocol.installModeCache).toBeInstanceOf(Map) + }) + }) describe('安装模式检测', () => { test('应该检测开发模式', () => { // 设置开发环境 - process.env.NODE_ENV = 'development'; - packageProtocol.clearCache(); - - const mode = packageProtocol.detectInstallMode(); - expect(mode).toBe('development'); - }); + process.env.NODE_ENV = 'development' + packageProtocol.clearCache() + + const mode = packageProtocol.detectInstallMode() + expect(mode).toBe('development') + }) test('应该检测npx执行模式', () => { // 模拟npx环境 - process.env.npm_execpath = '/usr/local/bin/npx'; - packageProtocol.clearCache(); - - const mode = packageProtocol.detectInstallMode(); - expect(mode).toBe('npx'); - }); + process.env.npm_execpath = '/usr/local/bin/npx' + packageProtocol.clearCache() + + const mode = packageProtocol.detectInstallMode() + expect(mode).toBe('npx') + }) test('应该缓存检测结果', () => { - const mode1 = packageProtocol.detectInstallMode(); - const mode2 = packageProtocol.detectInstallMode(); - - expect(mode1).toBe(mode2); - expect(packageProtocol.installModeCache.size).toBe(1); - }); + const mode1 = packageProtocol.detectInstallMode() + const mode2 = packageProtocol.detectInstallMode() + + expect(mode1).toBe(mode2) + expect(packageProtocol.installModeCache.size).toBe(1) + }) test('检测结果应该是有效的安装模式', () => { - const mode = packageProtocol.detectInstallMode(); - const validModes = ['development', 'local', 'global', 'npx', 'monorepo', 'link']; - expect(validModes).toContain(mode); - }); - }); + const mode = packageProtocol.detectInstallMode() + const validModes = ['development', 'local', 'global', 'npx', 'monorepo', 'link'] + expect(validModes).toContain(mode) + }) + }) describe('NPX执行检测', () => { test('应该通过npm_execpath检测npx', () => { - process.env.npm_execpath = '/path/to/npx'; - expect(packageProtocol._isNpxExecution()).toBe(true); - }); + process.env.npm_execpath = '/path/to/npx' + expect(packageProtocol._isNpxExecution()).toBe(true) + }) test('应该通过npm_config_cache检测npx', () => { - process.env.npm_config_cache = '/tmp/_npx/cache'; - expect(packageProtocol._isNpxExecution()).toBe(true); - }); + process.env.npm_config_cache = '/tmp/_npx/cache' + expect(packageProtocol._isNpxExecution()).toBe(true) + }) test('正常情况下应该返回false', () => { - delete process.env.npm_execpath; - delete process.env.npm_config_cache; - expect(packageProtocol._isNpxExecution()).toBe(false); - }); - }); + delete process.env.npm_execpath + delete process.env.npm_config_cache + expect(packageProtocol._isNpxExecution()).toBe(false) + }) + }) describe('全局安装检测', () => { test('应该检测常见的全局路径', () => { // 这个测试在实际环境中可能会失败,因为我们无法轻易改变__dirname - const result = packageProtocol._isGlobalInstall(); - expect(typeof result).toBe('boolean'); - }); - }); + const result = packageProtocol._isGlobalInstall() + expect(typeof result).toBe('boolean') + }) + }) describe('开发模式检测', () => { test('应该通过NODE_ENV检测开发模式', () => { - process.env.NODE_ENV = 'development'; - expect(packageProtocol._isDevelopmentMode()).toBe(true); - }); + process.env.NODE_ENV = 'development' + expect(packageProtocol._isDevelopmentMode()).toBe(true) + }) test('应该检测非node_modules目录', () => { // 当前测试环境应该不在node_modules中 - const result = packageProtocol._isDevelopmentMode(); - expect(typeof result).toBe('boolean'); - }); - }); + const result = packageProtocol._isDevelopmentMode() + expect(typeof result).toBe('boolean') + }) + }) describe('包查找功能', () => { test('应该能找到package.json', () => { - const packageJsonPath = packageProtocol.findPackageJson(); - expect(packageJsonPath).toBeTruthy(); - expect(packageJsonPath).toMatch(/package\.json$/); - }); + const packageJsonPath = packageProtocol.findPackageJson() + expect(packageJsonPath).toBeTruthy() + expect(packageJsonPath).toMatch(/package\.json$/) + }) test('应该能找到根package.json', () => { - const rootPackageJsonPath = packageProtocol.findRootPackageJson(); - expect(rootPackageJsonPath).toBeTruthy(); - expect(rootPackageJsonPath).toMatch(/package\.json$/); - }); + const rootPackageJsonPath = packageProtocol.findRootPackageJson() + expect(rootPackageJsonPath).toBeTruthy() + expect(rootPackageJsonPath).toMatch(/package\.json$/) + }) test('查找不存在的package.json应该返回null', () => { - const result = packageProtocol.findPackageJson('/nonexistent/path'); - expect(result).toBeNull(); - }); - }); + const result = packageProtocol.findPackageJson('/nonexistent/path') + expect(result).toBeNull() + }) + }) describe('包根目录获取', () => { test('应该能获取包根目录', async () => { - const packageRoot = await packageProtocol.getPackageRoot(); - expect(packageRoot).toBeTruthy(); - expect(typeof packageRoot).toBe('string'); - expect(path.isAbsolute(packageRoot)).toBe(true); - }); + const packageRoot = await packageProtocol.getPackageRoot() + expect(packageRoot).toBeTruthy() + expect(typeof packageRoot).toBe('string') + expect(path.isAbsolute(packageRoot)).toBe(true) + }) test('项目根目录查找应该工作正常', () => { - const root = packageProtocol._findProjectRoot(); - expect(root).toBeTruthy(); - expect(path.isAbsolute(root)).toBe(true); - }); - }); + const root = packageProtocol._findProjectRoot() + expect(root).toBeTruthy() + expect(path.isAbsolute(root)).toBe(true) + }) + }) describe('路径解析', () => { test('应该解析package.json路径', async () => { - const resolved = await packageProtocol.resolvePath('package.json'); - expect(resolved).toMatch(/package\.json$/); - expect(path.isAbsolute(resolved)).toBe(true); - }); + const resolved = await packageProtocol.resolvePath('package.json') + expect(resolved).toMatch(/package\.json$/) + expect(path.isAbsolute(resolved)).toBe(true) + }) test('应该解析src目录路径', async () => { - const resolved = await packageProtocol.resolvePath('src/index.js'); - expect(resolved).toContain('src'); - expect(resolved).toMatch(/index\.js$/); - }); + const resolved = await packageProtocol.resolvePath('src/index.js') + expect(resolved).toContain('src') + expect(resolved).toMatch(/index\.js$/) + }) test('应该解析prompt目录路径', async () => { - const resolved = await packageProtocol.resolvePath('prompt/core/thought.md'); - expect(resolved).toContain('prompt'); - expect(resolved).toContain('core'); - expect(resolved).toMatch(/thought\.md$/); - }); + const resolved = await packageProtocol.resolvePath('prompt/core/thought.md') + expect(resolved).toContain('prompt') + expect(resolved).toContain('core') + expect(resolved).toMatch(/thought\.md$/) + }) test('空路径应该返回包根目录', async () => { - const resolved = await packageProtocol.resolvePath(''); - expect(path.isAbsolute(resolved)).toBe(true); - expect(resolved).toBeTruthy(); - }); + const resolved = await packageProtocol.resolvePath('') + expect(path.isAbsolute(resolved)).toBe(true) + expect(resolved).toBeTruthy() + }) test('只有空格的路径应该返回包根目录', async () => { - const resolved = await packageProtocol.resolvePath(' '); - expect(path.isAbsolute(resolved)).toBe(true); - expect(resolved).toBeTruthy(); - }); + const resolved = await packageProtocol.resolvePath(' ') + expect(path.isAbsolute(resolved)).toBe(true) + expect(resolved).toBeTruthy() + }) test('应该使用缓存', async () => { - const path1 = await packageProtocol.resolvePath('package.json'); - const path2 = await packageProtocol.resolvePath('package.json'); - - expect(path1).toBe(path2); - expect(packageProtocol.cache.size).toBeGreaterThan(0); - }); - }); + const path1 = await packageProtocol.resolvePath('package.json') + const path2 = await packageProtocol.resolvePath('package.json') + + expect(path1).toBe(path2) + expect(packageProtocol.cache.size).toBeGreaterThan(0) + }) + }) describe('路径安全检查', () => { test('应该阻止目录遍历攻击', async () => { await expect( packageProtocol.resolvePath('../../../etc/passwd') - ).rejects.toThrow('路径安全检查失败'); - }); + ).rejects.toThrow('路径安全检查失败') + }) test('正常的相对路径应该被允许', async () => { - const resolved = await packageProtocol.resolvePath('src/lib/utils.js'); - expect(resolved).toContain('src'); - expect(resolved).toContain('lib'); - expect(resolved).toMatch(/utils\.js$/); - }); - }); + const resolved = await packageProtocol.resolvePath('src/lib/utils.js') + expect(resolved).toContain('src') + expect(resolved).toContain('lib') + expect(resolved).toMatch(/utils\.js$/) + }) + }) describe('资源存在性检查', () => { test('存在的文件应该返回true', async () => { - const exists = await packageProtocol.exists('package.json'); - expect(exists).toBe(true); - }); + const exists = await packageProtocol.exists('package.json') + expect(exists).toBe(true) + }) test('不存在的文件应该返回false', async () => { - const exists = await packageProtocol.exists('nonexistent.txt'); - expect(exists).toBe(false); - }); - }); + const exists = await packageProtocol.exists('nonexistent.txt') + expect(exists).toBe(false) + }) + }) describe('内容加载', () => { test('应该能加载package.json内容', async () => { - const result = await packageProtocol.loadContent('package.json'); - - expect(result).toHaveProperty('content'); - expect(result).toHaveProperty('path'); - expect(result).toHaveProperty('protocol', 'package'); - expect(result).toHaveProperty('installMode'); - expect(result).toHaveProperty('metadata'); - - expect(result.metadata).toHaveProperty('size'); - expect(result.metadata).toHaveProperty('lastModified'); - expect(result.metadata).toHaveProperty('absolutePath'); - expect(result.metadata).toHaveProperty('relativePath'); - + const result = await packageProtocol.loadContent('package.json') + + expect(result).toHaveProperty('content') + expect(result).toHaveProperty('path') + expect(result).toHaveProperty('protocol', 'package') + expect(result).toHaveProperty('installMode') + expect(result).toHaveProperty('metadata') + + expect(result.metadata).toHaveProperty('size') + expect(result.metadata).toHaveProperty('lastModified') + expect(result.metadata).toHaveProperty('absolutePath') + expect(result.metadata).toHaveProperty('relativePath') + // 验证内容是有效的JSON - expect(() => JSON.parse(result.content)).not.toThrow(); - }); + expect(() => JSON.parse(result.content)).not.toThrow() + }) test('加载不存在的文件应该抛出错误', async () => { await expect( packageProtocol.loadContent('nonexistent.txt') - ).rejects.toThrow('包资源不存在'); - }); + ).rejects.toThrow('包资源不存在') + }) test('返回的metadata应该包含正确信息', async () => { - const result = await packageProtocol.loadContent('package.json'); - - expect(result.metadata.size).toBe(result.content.length); - expect(result.metadata.lastModified.constructor.name).toBe('Date'); - expect(path.isAbsolute(result.metadata.absolutePath)).toBe(true); - expect(result.metadata.relativePath).toBe('package.json'); - }); - }); + const result = await packageProtocol.loadContent('package.json') + + expect(result.metadata.size).toBe(result.content.length) + expect(result.metadata.lastModified.constructor.name).toBe('Date') + expect(path.isAbsolute(result.metadata.absolutePath)).toBe(true) + expect(result.metadata.relativePath).toBe('package.json') + }) + }) describe('查询参数支持', () => { test('应该支持查询参数', async () => { - const queryParams = new QueryParams(); - queryParams.set('encoding', 'utf8'); - - const resolved = await packageProtocol.resolvePath('package.json', queryParams); - expect(resolved).toMatch(/package\.json$/); - }); + const queryParams = new QueryParams() + queryParams.set('encoding', 'utf8') + + const resolved = await packageProtocol.resolvePath('package.json', queryParams) + expect(resolved).toMatch(/package\.json$/) + }) test('相同路径但不同查询参数应该有不同的缓存', async () => { - const queryParams1 = new QueryParams(); - queryParams1.set('test', 'value1'); - - const queryParams2 = new QueryParams(); - queryParams2.set('test', 'value2'); - - await packageProtocol.resolvePath('package.json', queryParams1); - await packageProtocol.resolvePath('package.json', queryParams2); - - expect(packageProtocol.cache.size).toBeGreaterThan(1); - }); - }); + const queryParams1 = new QueryParams() + queryParams1.set('test', 'value1') + + const queryParams2 = new QueryParams() + queryParams2.set('test', 'value2') + + await packageProtocol.resolvePath('package.json', queryParams1) + await packageProtocol.resolvePath('package.json', queryParams2) + + expect(packageProtocol.cache.size).toBeGreaterThan(1) + }) + }) describe('调试信息', () => { test('应该提供完整的调试信息', () => { - const debugInfo = packageProtocol.getDebugInfo(); - - expect(debugInfo).toHaveProperty('protocol', 'package'); - expect(debugInfo).toHaveProperty('installMode'); - expect(debugInfo).toHaveProperty('packageRoot'); - expect(debugInfo).toHaveProperty('currentWorkingDirectory'); - expect(debugInfo).toHaveProperty('moduleDirectory'); - expect(debugInfo).toHaveProperty('environment'); - expect(debugInfo).toHaveProperty('cacheSize'); - - expect(debugInfo.environment).toHaveProperty('NODE_ENV'); - expect(debugInfo.environment).toHaveProperty('npm_execpath'); - expect(debugInfo.environment).toHaveProperty('npm_config_cache'); - }); - }); + const debugInfo = packageProtocol.getDebugInfo() + + expect(debugInfo).toHaveProperty('protocol', 'package') + expect(debugInfo).toHaveProperty('installMode') + expect(debugInfo).toHaveProperty('packageRoot') + expect(debugInfo).toHaveProperty('currentWorkingDirectory') + expect(debugInfo).toHaveProperty('moduleDirectory') + expect(debugInfo).toHaveProperty('environment') + expect(debugInfo).toHaveProperty('cacheSize') + + expect(debugInfo.environment).toHaveProperty('NODE_ENV') + expect(debugInfo.environment).toHaveProperty('npm_execpath') + expect(debugInfo.environment).toHaveProperty('npm_config_cache') + }) + }) describe('缓存管理', () => { test('应该能清理所有缓存', async () => { // 生成一些缓存 - await packageProtocol.resolvePath('package.json'); - packageProtocol.detectInstallMode(); - - expect(packageProtocol.cache.size).toBeGreaterThan(0); - expect(packageProtocol.installModeCache.size).toBeGreaterThan(0); - - packageProtocol.clearCache(); - - expect(packageProtocol.cache.size).toBe(0); - expect(packageProtocol.installModeCache.size).toBe(0); - }); - }); + await packageProtocol.resolvePath('package.json') + packageProtocol.detectInstallMode() + + expect(packageProtocol.cache.size).toBeGreaterThan(0) + expect(packageProtocol.installModeCache.size).toBeGreaterThan(0) + + packageProtocol.clearCache() + + expect(packageProtocol.cache.size).toBe(0) + expect(packageProtocol.installModeCache.size).toBe(0) + }) + }) describe('错误处理', () => { test('文件系统错误应该被正确处理', async () => { // 尝试访问一个权限不足的路径(如果存在的话) - const result = await packageProtocol.exists('../../../root/.ssh/id_rsa'); - expect(typeof result).toBe('boolean'); - }); + const result = await packageProtocol.exists('../../../root/.ssh/id_rsa') + expect(typeof result).toBe('boolean') + }) test('路径解析错误应该包含有用信息', async () => { try { - await packageProtocol.resolvePath('../../../etc/passwd'); + await packageProtocol.resolvePath('../../../etc/passwd') } catch (error) { - expect(error.message).toContain('路径安全检查失败'); + expect(error.message).toContain('路径安全检查失败') } - }); - }); + }) + }) describe('边界情况', () => { test('深层嵌套路径应该正确处理', async () => { - const resolved = await packageProtocol.resolvePath('src/lib/core/resource/protocols/test.js'); - expect(resolved).toContain('src'); - expect(resolved).toContain('lib'); - expect(resolved).toContain('core'); - expect(resolved).toContain('resource'); - expect(resolved).toContain('protocols'); - expect(resolved).toMatch(/test\.js$/); - }); + const resolved = await packageProtocol.resolvePath('src/lib/core/resource/protocols/test.js') + expect(resolved).toContain('src') + expect(resolved).toContain('lib') + expect(resolved).toContain('core') + expect(resolved).toContain('resource') + expect(resolved).toContain('protocols') + expect(resolved).toMatch(/test\.js$/) + }) test('特殊字符路径应该被正确处理', async () => { - const resolved = await packageProtocol.resolvePath('assets/images/logo-2024.png'); - expect(resolved).toContain('assets'); - expect(resolved).toContain('images'); - expect(resolved).toMatch(/logo-2024\.png$/); - }); + const resolved = await packageProtocol.resolvePath('assets/images/logo-2024.png') + expect(resolved).toContain('assets') + expect(resolved).toContain('images') + expect(resolved).toMatch(/logo-2024\.png$/) + }) test('带有空格的路径应该被正确处理', async () => { - const resolved = await packageProtocol.resolvePath('docs/user guide.md'); - expect(resolved).toContain('docs'); - expect(resolved).toMatch(/user guide\.md$/); - }); + const resolved = await packageProtocol.resolvePath('docs/user guide.md') + expect(resolved).toContain('docs') + expect(resolved).toMatch(/user guide\.md$/) + }) test('中文路径应该被正确处理', async () => { - const resolved = await packageProtocol.resolvePath('文档/说明.md'); - expect(resolved).toContain('文档'); - expect(resolved).toMatch(/说明\.md$/); - }); - }); -}); \ No newline at end of file + const resolved = await packageProtocol.resolvePath('文档/说明.md') + expect(resolved).toContain('文档') + expect(resolved).toMatch(/说明\.md$/) + }) + }) +}) diff --git a/src/tests/core/resource/protocols/ProjectProtocol.unit.test.js b/src/tests/core/resource/protocols/ProjectProtocol.unit.test.js index 42f43c0..9a320eb 100644 --- a/src/tests/core/resource/protocols/ProjectProtocol.unit.test.js +++ b/src/tests/core/resource/protocols/ProjectProtocol.unit.test.js @@ -1,245 +1,245 @@ -const ProjectProtocol = require('../../../../lib/core/resource/protocols/ProjectProtocol'); -const { QueryParams } = require('../../../../lib/core/resource/types'); -const path = require('path'); -const fs = require('fs').promises; +const ProjectProtocol = require('../../../../lib/core/resource/protocols/ProjectProtocol') +const { QueryParams } = require('../../../../lib/core/resource/types') +const path = require('path') +const fs = require('fs').promises describe('ProjectProtocol', () => { - let projectProtocol; - const projectRoot = process.cwd(); // PromptX项目根目录 - const promptxPath = path.join(projectRoot, '.promptx'); + let projectProtocol + const projectRoot = process.cwd() // PromptX项目根目录 + const promptxPath = path.join(projectRoot, '.promptx') beforeEach(() => { - projectProtocol = new ProjectProtocol(); - }); + projectProtocol = new ProjectProtocol() + }) afterEach(() => { - projectProtocol.clearCache(); - }); + projectProtocol.clearCache() + }) describe('基础功能', () => { test('应该正确初始化协议', () => { - expect(projectProtocol.name).toBe('project'); - expect(projectProtocol.projectDirs).toBeDefined(); - expect(Object.keys(projectProtocol.projectDirs)).toContain('root'); - expect(Object.keys(projectProtocol.projectDirs)).toContain('src'); - expect(Object.keys(projectProtocol.projectDirs)).toContain('lib'); - }); + expect(projectProtocol.name).toBe('project') + expect(projectProtocol.projectDirs).toBeDefined() + expect(Object.keys(projectProtocol.projectDirs)).toContain('root') + expect(Object.keys(projectProtocol.projectDirs)).toContain('src') + expect(Object.keys(projectProtocol.projectDirs)).toContain('lib') + }) test('应该提供协议信息', () => { - const info = projectProtocol.getProtocolInfo(); - expect(info.name).toBe('project'); - expect(info.description).toContain('项目协议'); - expect(info.projectMarker).toBe('.promptx'); - expect(info.supportedDirectories).toContain('src'); + const info = projectProtocol.getProtocolInfo() + expect(info.name).toBe('project') + expect(info.description).toContain('项目协议') + expect(info.projectMarker).toBe('.promptx') + expect(info.supportedDirectories).toContain('src') expect(info.examples).toEqual(expect.arrayContaining([ expect.stringContaining('project://src/') - ])); - }); + ])) + }) test('应该提供支持的查询参数', () => { - const params = projectProtocol.getSupportedParams(); - expect(params.from).toContain('指定搜索起始目录'); - expect(params.create).toContain('如果目录不存在是否创建'); - expect(params.line).toContain('行范围'); - }); - }); + const params = projectProtocol.getSupportedParams() + expect(params.from).toContain('指定搜索起始目录') + expect(params.create).toContain('如果目录不存在是否创建') + expect(params.line).toContain('行范围') + }) + }) describe('路径验证', () => { test('应该验证有效的项目路径', () => { - expect(projectProtocol.validatePath('src/index.js')).toBe(true); - expect(projectProtocol.validatePath('lib/utils')).toBe(true); - expect(projectProtocol.validatePath('docs')).toBe(true); - expect(projectProtocol.validatePath('root/package.json')).toBe(true); - }); + expect(projectProtocol.validatePath('src/index.js')).toBe(true) + expect(projectProtocol.validatePath('lib/utils')).toBe(true) + expect(projectProtocol.validatePath('docs')).toBe(true) + expect(projectProtocol.validatePath('root/package.json')).toBe(true) + }) test('应该拒绝无效的项目路径', () => { - expect(projectProtocol.validatePath('invalid/path')).toBe(false); - expect(projectProtocol.validatePath('unknown')).toBe(false); - expect(projectProtocol.validatePath('')).toBe(false); - expect(projectProtocol.validatePath(null)).toBe(false); - }); + expect(projectProtocol.validatePath('invalid/path')).toBe(false) + expect(projectProtocol.validatePath('unknown')).toBe(false) + expect(projectProtocol.validatePath('')).toBe(false) + expect(projectProtocol.validatePath(null)).toBe(false) + }) test('应该验证项目目录类型', () => { - const supportedDirs = Object.keys(projectProtocol.projectDirs); + const supportedDirs = Object.keys(projectProtocol.projectDirs) supportedDirs.forEach(dir => { - expect(projectProtocol.validatePath(`${dir}/test.js`)).toBe(true); - expect(projectProtocol.validatePath(dir)).toBe(true); - }); - }); - }); + expect(projectProtocol.validatePath(`${dir}/test.js`)).toBe(true) + expect(projectProtocol.validatePath(dir)).toBe(true) + }) + }) + }) describe('项目根目录查找', () => { test('应该找到当前项目的根目录', async () => { - const root = await projectProtocol.findProjectRoot(); - expect(root).toBe(projectRoot); - }); + const root = await projectProtocol.findProjectRoot() + expect(root).toBe(projectRoot) + }) test('应该从子目录找到项目根目录', async () => { - const subDir = path.join(projectRoot, 'src', 'lib'); - const root = await projectProtocol.findProjectRoot(subDir); - expect(root).toBe(projectRoot); - }); + const subDir = path.join(projectRoot, 'src', 'lib') + const root = await projectProtocol.findProjectRoot(subDir) + expect(root).toBe(projectRoot) + }) test('应该缓存项目根目录结果', async () => { - const root1 = await projectProtocol.findProjectRoot(); - const root2 = await projectProtocol.findProjectRoot(); - expect(root1).toBe(root2); - expect(root1).toBe(projectRoot); - }); + const root1 = await projectProtocol.findProjectRoot() + const root2 = await projectProtocol.findProjectRoot() + expect(root1).toBe(root2) + expect(root1).toBe(projectRoot) + }) test('应该处理未找到项目根目录的情况', async () => { // 使用系统临时目录测试 - const tempDir = '/tmp'; - const root = await projectProtocol.findProjectRoot(tempDir); - expect(root).toBeNull(); - }); - }); + const tempDir = '/tmp' + const root = await projectProtocol.findProjectRoot(tempDir) + expect(root).toBeNull() + }) + }) describe('路径解析', () => { test('应该解析src目录路径', async () => { - const resolvedPath = await projectProtocol.resolvePath('src/index.js'); - expect(resolvedPath).toBe(path.join(projectRoot, 'src', 'index.js')); - }); + const resolvedPath = await projectProtocol.resolvePath('src/index.js') + expect(resolvedPath).toBe(path.join(projectRoot, 'src', 'index.js')) + }) test('应该解析lib目录路径', async () => { - const resolvedPath = await projectProtocol.resolvePath('lib/core/resource'); - expect(resolvedPath).toBe(path.join(projectRoot, 'lib', 'core', 'resource')); - }); + const resolvedPath = await projectProtocol.resolvePath('lib/core/resource') + expect(resolvedPath).toBe(path.join(projectRoot, 'lib', 'core', 'resource')) + }) test('应该解析根目录路径', async () => { - const resolvedPath = await projectProtocol.resolvePath('root/package.json'); - expect(resolvedPath).toBe(path.join(projectRoot, 'package.json')); - }); + const resolvedPath = await projectProtocol.resolvePath('root/package.json') + expect(resolvedPath).toBe(path.join(projectRoot, 'package.json')) + }) test('应该解析目录路径(无文件名)', async () => { - const resolvedPath = await projectProtocol.resolvePath('src'); - expect(resolvedPath).toBe(path.join(projectRoot, 'src')); - }); + const resolvedPath = await projectProtocol.resolvePath('src') + expect(resolvedPath).toBe(path.join(projectRoot, 'src')) + }) test('应该拒绝不支持的目录类型', async () => { - await expect(projectProtocol.resolvePath('invalid/path')).rejects.toThrow('不支持的项目目录类型'); - }); + await expect(projectProtocol.resolvePath('invalid/path')).rejects.toThrow('不支持的项目目录类型') + }) test('应该处理安全路径检查', async () => { - await expect(projectProtocol.resolvePath('src/../../../etc/passwd')).rejects.toThrow('安全错误'); - }); + await expect(projectProtocol.resolvePath('src/../../../etc/passwd')).rejects.toThrow('安全错误') + }) test('应该支持from参数指定起始目录', async () => { - const queryParams = new QueryParams(); - queryParams.set('from', projectRoot); - - const resolvedPath = await projectProtocol.resolvePath('src/test.js', queryParams); - expect(resolvedPath).toBe(path.join(projectRoot, 'src', 'test.js')); - }); - }); + const queryParams = new QueryParams() + queryParams.set('from', projectRoot) + + const resolvedPath = await projectProtocol.resolvePath('src/test.js', queryParams) + expect(resolvedPath).toBe(path.join(projectRoot, 'src', 'test.js')) + }) + }) describe('内容加载', () => { test('应该加载存在的文件内容', async () => { - const packageJsonPath = path.join(projectRoot, 'package.json'); - const content = await projectProtocol.loadFileContent(packageJsonPath); - expect(content).toContain('promptx'); - }); + const packageJsonPath = path.join(projectRoot, 'package.json') + const content = await projectProtocol.loadFileContent(packageJsonPath) + expect(content).toContain('promptx') + }) test('应该加载目录内容', async () => { - const srcPath = path.join(projectRoot, 'src'); - const content = await projectProtocol.loadDirectoryContent(srcPath); - expect(content).toContain('[DIR]'); - }); + const srcPath = path.join(projectRoot, 'src') + const content = await projectProtocol.loadDirectoryContent(srcPath) + expect(content).toContain('[DIR]') + }) test('应该支持JSON格式的目录列表', async () => { - const srcPath = path.join(projectRoot, 'src'); - const queryParams = new QueryParams(); - queryParams.set('format', 'json'); - - const content = await projectProtocol.loadDirectoryContent(srcPath, queryParams); - const parsed = JSON.parse(content); - expect(Array.isArray(parsed)).toBe(true); - }); + const srcPath = path.join(projectRoot, 'src') + const queryParams = new QueryParams() + queryParams.set('format', 'json') + + const content = await projectProtocol.loadDirectoryContent(srcPath, queryParams) + const parsed = JSON.parse(content) + expect(Array.isArray(parsed)).toBe(true) + }) test('应该支持类型过滤', async () => { - const rootPath = projectRoot; - const queryParams = new QueryParams(); - queryParams.set('type', 'file'); - - const content = await projectProtocol.loadDirectoryContent(rootPath, queryParams); - expect(content).toContain('[FILE]'); - expect(content).not.toContain('[DIR]'); - }); + const rootPath = projectRoot + const queryParams = new QueryParams() + queryParams.set('type', 'file') + + const content = await projectProtocol.loadDirectoryContent(rootPath, queryParams) + expect(content).toContain('[FILE]') + expect(content).not.toContain('[DIR]') + }) test('应该处理不存在的文件', async () => { - const nonExistentPath = path.join(projectRoot, 'nonexistent.txt'); - await expect(projectProtocol.loadContent(nonExistentPath)).rejects.toThrow('文件或目录不存在'); - }); + const nonExistentPath = path.join(projectRoot, 'nonexistent.txt') + await expect(projectProtocol.loadContent(nonExistentPath)).rejects.toThrow('文件或目录不存在') + }) test('应该支持exists=false参数', async () => { - const nonExistentPath = path.join(projectRoot, 'nonexistent.txt'); - const queryParams = new QueryParams(); - queryParams.set('exists', 'false'); - - const content = await projectProtocol.loadContent(nonExistentPath, queryParams); - expect(content).toBe(''); - }); - }); + const nonExistentPath = path.join(projectRoot, 'nonexistent.txt') + const queryParams = new QueryParams() + queryParams.set('exists', 'false') + + const content = await projectProtocol.loadContent(nonExistentPath, queryParams) + expect(content).toBe('') + }) + }) describe('完整协议解析', () => { test('应该完整解析project://协议', async () => { - const content = await projectProtocol.resolve('root/package.json'); - expect(content).toContain('promptx'); - }); + const content = await projectProtocol.resolve('root/package.json') + expect(content).toContain('promptx') + }) test('应该处理带查询参数的协议', async () => { - const queryParams = new QueryParams(); - queryParams.set('format', 'json'); - - const content = await projectProtocol.resolve('src', queryParams); - const parsed = JSON.parse(content); - expect(Array.isArray(parsed)).toBe(true); - }); + const queryParams = new QueryParams() + queryParams.set('format', 'json') + + const content = await projectProtocol.resolve('src', queryParams) + const parsed = JSON.parse(content) + expect(Array.isArray(parsed)).toBe(true) + }) test('应该应用行过滤', async () => { - const queryParams = new QueryParams(); - queryParams.set('line', '1-3'); - - const content = await projectProtocol.resolve('root/package.json', queryParams); - const lines = content.split('\n'); - expect(lines.length).toBe(3); - }); - }); + const queryParams = new QueryParams() + queryParams.set('line', '1-3') + + const content = await projectProtocol.resolve('root/package.json', queryParams) + const lines = content.split('\n') + expect(lines.length).toBe(3) + }) + }) describe('项目信息', () => { test('应该获取项目信息', async () => { - const info = await projectProtocol.getProjectInfo(); - expect(info.projectRoot).toBe(projectRoot); - expect(info.promptxPath).toBe(promptxPath); - expect(info.directories).toBeDefined(); - expect(info.directories.root.exists).toBe(true); - expect(info.directories.src.exists).toBe(true); - }); + const info = await projectProtocol.getProjectInfo() + expect(info.projectRoot).toBe(projectRoot) + expect(info.promptxPath).toBe(promptxPath) + expect(info.directories).toBeDefined() + expect(info.directories.root.exists).toBe(true) + expect(info.directories.src.exists).toBe(true) + }) test('应该标识不存在的目录', async () => { - const info = await projectProtocol.getProjectInfo(); + const info = await projectProtocol.getProjectInfo() // 有些目录可能不存在,应该正确标识 Object.values(info.directories).forEach(dir => { - expect(dir).toHaveProperty('exists'); - expect(dir).toHaveProperty('path'); - }); - }); - }); + expect(dir).toHaveProperty('exists') + expect(dir).toHaveProperty('path') + }) + }) + }) describe('缓存管理', () => { test('应该提供缓存统计', () => { - const stats = projectProtocol.getCacheStats(); - expect(stats.protocol).toBe('project'); - expect(typeof stats.size).toBe('number'); - expect(typeof stats.enabled).toBe('boolean'); - }); + const stats = projectProtocol.getCacheStats() + expect(stats.protocol).toBe('project') + expect(typeof stats.size).toBe('number') + expect(typeof stats.enabled).toBe('boolean') + }) test('应该能清除缓存', async () => { - await projectProtocol.findProjectRoot(); // 填充缓存 - expect(projectProtocol.projectRootCache.size).toBeGreaterThan(0); - - projectProtocol.clearCache(); - expect(projectProtocol.projectRootCache.size).toBe(0); - }); - }); -}); \ No newline at end of file + await projectProtocol.findProjectRoot() // 填充缓存 + expect(projectProtocol.projectRootCache.size).toBeGreaterThan(0) + + projectProtocol.clearCache() + expect(projectProtocol.projectRootCache.size).toBe(0) + }) + }) +}) diff --git a/src/tests/core/resource/protocols/PromptProtocol.unit.test.js b/src/tests/core/resource/protocols/PromptProtocol.unit.test.js index 39bffba..90ad7ac 100644 --- a/src/tests/core/resource/protocols/PromptProtocol.unit.test.js +++ b/src/tests/core/resource/protocols/PromptProtocol.unit.test.js @@ -1,338 +1,338 @@ -const PromptProtocol = require('../../../../lib/core/resource/protocols/PromptProtocol'); -const PackageProtocol = require('../../../../lib/core/resource/protocols/PackageProtocol'); -const { QueryParams } = require('../../../../lib/core/resource/types'); +const PromptProtocol = require('../../../../lib/core/resource/protocols/PromptProtocol') +const PackageProtocol = require('../../../../lib/core/resource/protocols/PackageProtocol') +const { QueryParams } = require('../../../../lib/core/resource/types') describe('PromptProtocol', () => { - let promptProtocol; - let mockPackageProtocol; + let promptProtocol + let mockPackageProtocol beforeEach(() => { - promptProtocol = new PromptProtocol(); - + promptProtocol = new PromptProtocol() + // 创建模拟的 PackageProtocol mockPackageProtocol = { getPackageRoot: jest.fn().mockResolvedValue('/mock/package/root'), loadContent: jest.fn(), exists: jest.fn() - }; - - promptProtocol.setPackageProtocol(mockPackageProtocol); - }); + } + + promptProtocol.setPackageProtocol(mockPackageProtocol) + }) afterEach(() => { - promptProtocol.clearCache(); - }); + promptProtocol.clearCache() + }) describe('基础功能', () => { test('应该正确初始化协议', () => { - expect(promptProtocol.name).toBe('prompt'); - expect(promptProtocol.registry).toBeInstanceOf(Map); - expect(promptProtocol.registry.size).toBeGreaterThan(0); - }); + expect(promptProtocol.name).toBe('prompt') + expect(promptProtocol.registry).toBeInstanceOf(Map) + expect(promptProtocol.registry.size).toBeGreaterThan(0) + }) test('应该提供协议信息', () => { - const info = promptProtocol.getProtocolInfo(); - - expect(info.name).toBe('prompt'); - expect(info.description).toContain('PromptX内置提示词资源协议'); - expect(info.location).toBe('prompt://{resource_id}'); - expect(info.availableResources).toContain('protocols'); - expect(info.availableResources).toContain('core'); - expect(info.availableResources).toContain('domain'); - expect(info.availableResources).toContain('bootstrap'); - }); + const info = promptProtocol.getProtocolInfo() + + expect(info.name).toBe('prompt') + expect(info.description).toContain('PromptX内置提示词资源协议') + expect(info.location).toBe('prompt://{resource_id}') + expect(info.availableResources).toContain('protocols') + expect(info.availableResources).toContain('core') + expect(info.availableResources).toContain('domain') + expect(info.availableResources).toContain('bootstrap') + }) test('应该设置包协议依赖', () => { - const newMockPackage = { test: 'protocol' }; - promptProtocol.setPackageProtocol(newMockPackage); - expect(promptProtocol.packageProtocol).toBe(newMockPackage); - }); + const newMockPackage = { test: 'protocol' } + promptProtocol.setPackageProtocol(newMockPackage) + expect(promptProtocol.packageProtocol).toBe(newMockPackage) + }) test('应该提供支持的查询参数', () => { - const params = promptProtocol.getSupportedParams(); - expect(params.merge).toContain('是否合并多个文件内容'); - expect(params.separator).toContain('文件间分隔符'); - expect(params.include_filename).toContain('是否包含文件名标题'); - }); - }); + const params = promptProtocol.getSupportedParams() + expect(params.merge).toContain('是否合并多个文件内容') + expect(params.separator).toContain('文件间分隔符') + expect(params.include_filename).toContain('是否包含文件名标题') + }) + }) describe('路径验证', () => { test('应该验证有效的资源路径', () => { - expect(promptProtocol.validatePath('protocols')).toBe(true); - expect(promptProtocol.validatePath('core')).toBe(true); - expect(promptProtocol.validatePath('domain')).toBe(true); - expect(promptProtocol.validatePath('bootstrap')).toBe(true); - }); + expect(promptProtocol.validatePath('protocols')).toBe(true) + expect(promptProtocol.validatePath('core')).toBe(true) + expect(promptProtocol.validatePath('domain')).toBe(true) + expect(promptProtocol.validatePath('bootstrap')).toBe(true) + }) test('应该拒绝无效的资源路径', () => { - expect(promptProtocol.validatePath('invalid')).toBe(false); - expect(promptProtocol.validatePath('unknown')).toBe(false); - expect(promptProtocol.validatePath('')).toBe(false); - expect(promptProtocol.validatePath(null)).toBe(false); - }); - }); + expect(promptProtocol.validatePath('invalid')).toBe(false) + expect(promptProtocol.validatePath('unknown')).toBe(false) + expect(promptProtocol.validatePath('')).toBe(false) + expect(promptProtocol.validatePath(null)).toBe(false) + }) + }) describe('路径解析', () => { test('应该解析有效的资源路径', async () => { - const resolved = await promptProtocol.resolvePath('protocols'); - expect(resolved).toBe('@package://prompt/protocol/**/*.md'); - }); + const resolved = await promptProtocol.resolvePath('protocols') + expect(resolved).toBe('@package://prompt/protocol/**/*.md') + }) test('应该解析所有注册的资源', async () => { for (const resourceId of promptProtocol.registry.keys()) { - const resolved = await promptProtocol.resolvePath(resourceId); - expect(resolved).toMatch(/@package:\/\//); + const resolved = await promptProtocol.resolvePath(resourceId) + expect(resolved).toMatch(/@package:\/\//) } - }); + }) test('应该拒绝未注册的资源', async () => { await expect(promptProtocol.resolvePath('nonexistent')) - .rejects.toThrow('未找到 prompt 资源: nonexistent'); - }); - }); + .rejects.toThrow('未找到 prompt 资源: nonexistent') + }) + }) describe('单个文件加载', () => { test('应该加载单个文件', async () => { - const mockContent = '# Test Content\n\nThis is test content.'; + const mockContent = '# Test Content\n\nThis is test content.' mockPackageProtocol.loadContent.mockResolvedValue({ content: mockContent, metadata: { path: '/test/path' } - }); + }) - const content = await promptProtocol.loadSingleFile('@package://bootstrap.md'); - - expect(content).toBe(mockContent); - expect(mockPackageProtocol.loadContent).toHaveBeenCalledWith('bootstrap.md', undefined); - }); + const content = await promptProtocol.loadSingleFile('@package://bootstrap.md') + + expect(content).toBe(mockContent) + expect(mockPackageProtocol.loadContent).toHaveBeenCalledWith('bootstrap.md', undefined) + }) test('应该处理加载错误', async () => { - mockPackageProtocol.loadContent.mockRejectedValue(new Error('File not found')); + mockPackageProtocol.loadContent.mockRejectedValue(new Error('File not found')) await expect(promptProtocol.loadSingleFile('@package://nonexistent.md')) - .rejects.toThrow('加载单个文件失败'); - }); - }); + .rejects.toThrow('加载单个文件失败') + }) + }) describe('多个文件加载', () => { test('应该加载多个文件并合并', async () => { - const fs = require('fs').promises; - const glob = require('glob'); - + const fs = require('fs').promises + const glob = require('glob') + // 模拟 glob 返回文件列表 const mockFiles = [ '/mock/package/root/prompt/protocol/dpml.protocol.md', '/mock/package/root/prompt/protocol/pateoas.protocol.md' - ]; - + ] + jest.doMock('glob', () => ({ ...jest.requireActual('glob'), __esModule: true, default: jest.fn().mockImplementation((pattern, options, callback) => { if (typeof options === 'function') { - callback = options; - options = {}; + callback = options + options = {} } - callback(null, mockFiles); + callback(null, mockFiles) }) - })); + })) // 模拟文件读取 jest.spyOn(fs, 'readFile').mockImplementation((filePath) => { if (filePath.includes('dpml.protocol.md')) { - return Promise.resolve('# DPML Protocol\n\nDPML content...'); + return Promise.resolve('# DPML Protocol\n\nDPML content...') } else if (filePath.includes('pateoas.protocol.md')) { - return Promise.resolve('# PATEOAS Protocol\n\nPATEOAS content...'); + return Promise.resolve('# PATEOAS Protocol\n\nPATEOAS content...') } - return Promise.reject(new Error('File not found')); - }); + return Promise.reject(new Error('File not found')) + }) - const content = await promptProtocol.loadMultipleFiles('@package://prompt/protocol/**/*.md'); - - expect(content).toContain('# DPML Protocol'); - expect(content).toContain('# PATEOAS Protocol'); - expect(content).toContain('prompt/protocol/dpml.protocol.md'); - expect(content).toContain('prompt/protocol/pateoas.protocol.md'); + const content = await promptProtocol.loadMultipleFiles('@package://prompt/protocol/**/*.md') + + expect(content).toContain('# DPML Protocol') + expect(content).toContain('# PATEOAS Protocol') + expect(content).toContain('prompt/protocol/dpml.protocol.md') + expect(content).toContain('prompt/protocol/pateoas.protocol.md') // 清理模拟 - fs.readFile.mockRestore(); - }); + fs.readFile.mockRestore() + }) test('应该处理没有匹配文件的情况', async () => { - const glob = require('glob'); - + const glob = require('glob') + jest.doMock('glob', () => ({ ...jest.requireActual('glob'), __esModule: true, default: jest.fn().mockImplementation((pattern, options, callback) => { if (typeof options === 'function') { - callback = options; - options = {}; + callback = options + options = {} } - callback(null, []); // 返回空文件列表 + callback(null, []) // 返回空文件列表 }) - })); + })) await expect(promptProtocol.loadMultipleFiles('@package://prompt/nonexistent/**/*.md')) - .rejects.toThrow('没有找到匹配的文件'); - }); - }); + .rejects.toThrow('没有找到匹配的文件') + }) + }) describe('内容合并', () => { test('应该合并多个文件内容', () => { const contents = [ { path: 'file1.md', content: '# File 1\n\nContent 1' }, { path: 'file2.md', content: '# File 2\n\nContent 2' } - ]; + ] - const merged = promptProtocol.mergeContents(contents); - - expect(merged).toContain('# file1.md'); - expect(merged).toContain('# File 1'); - expect(merged).toContain('# file2.md'); - expect(merged).toContain('# File 2'); - expect(merged).toContain('---'); - }); + const merged = promptProtocol.mergeContents(contents) + + expect(merged).toContain('# file1.md') + expect(merged).toContain('# File 1') + expect(merged).toContain('# file2.md') + expect(merged).toContain('# File 2') + expect(merged).toContain('---') + }) test('应该支持不合并返回JSON', () => { const contents = [ { path: 'file1.md', content: 'Content 1' }, { path: 'file2.md', content: 'Content 2' } - ]; + ] - const queryParams = new QueryParams(); - queryParams.set('merge', 'false'); + const queryParams = new QueryParams() + queryParams.set('merge', 'false') - const result = promptProtocol.mergeContents(contents, queryParams); - - expect(() => JSON.parse(result)).not.toThrow(); - const parsed = JSON.parse(result); - expect(parsed).toHaveLength(2); - expect(parsed[0]).toHaveProperty('path', 'file1.md'); - expect(parsed[0]).toHaveProperty('content', 'Content 1'); - }); + const result = promptProtocol.mergeContents(contents, queryParams) + + expect(() => JSON.parse(result)).not.toThrow() + const parsed = JSON.parse(result) + expect(parsed).toHaveLength(2) + expect(parsed[0]).toHaveProperty('path', 'file1.md') + expect(parsed[0]).toHaveProperty('content', 'Content 1') + }) test('应该支持自定义分隔符', () => { const contents = [ { path: 'file1.md', content: 'Content 1' }, { path: 'file2.md', content: 'Content 2' } - ]; + ] - const queryParams = new QueryParams(); - queryParams.set('separator', '\n\n===\n\n'); + const queryParams = new QueryParams() + queryParams.set('separator', '\n\n===\n\n') - const result = promptProtocol.mergeContents(contents, queryParams); - - expect(result).toContain('==='); - expect(result).not.toContain('---'); - }); + const result = promptProtocol.mergeContents(contents, queryParams) + + expect(result).toContain('===') + expect(result).not.toContain('---') + }) test('应该支持隐藏文件名', () => { const contents = [ { path: 'file1.md', content: 'Content 1' } - ]; + ] - const queryParams = new QueryParams(); - queryParams.set('include_filename', 'false'); + const queryParams = new QueryParams() + queryParams.set('include_filename', 'false') - const result = promptProtocol.mergeContents(contents, queryParams); - - expect(result).not.toContain('# file1.md'); - expect(result).toContain('Content 1'); - }); - }); + const result = promptProtocol.mergeContents(contents, queryParams) + + expect(result).not.toContain('# file1.md') + expect(result).toContain('Content 1') + }) + }) describe('资源存在性检查', () => { test('应该检查单个文件是否存在', async () => { - mockPackageProtocol.exists.mockResolvedValue(true); + mockPackageProtocol.exists.mockResolvedValue(true) - const exists = await promptProtocol.exists('bootstrap'); - - expect(exists).toBe(true); - expect(mockPackageProtocol.exists).toHaveBeenCalledWith('bootstrap.md', undefined); - }); + const exists = await promptProtocol.exists('bootstrap') + + expect(exists).toBe(true) + expect(mockPackageProtocol.exists).toHaveBeenCalledWith('bootstrap.md', undefined) + }) test('应该检查通配符文件是否存在', async () => { - const glob = require('glob'); - + const glob = require('glob') + jest.doMock('glob', () => ({ ...jest.requireActual('glob'), __esModule: true, default: jest.fn().mockImplementation((pattern, options, callback) => { if (typeof options === 'function') { - callback = options; - options = {}; + callback = options + options = {} } - callback(null, ['/mock/file1.md', '/mock/file2.md']); + callback(null, ['/mock/file1.md', '/mock/file2.md']) }) - })); + })) - const exists = await promptProtocol.exists('protocols'); - - expect(exists).toBe(true); - }); + const exists = await promptProtocol.exists('protocols') + + expect(exists).toBe(true) + }) test('应该处理不存在的资源', async () => { - const exists = await promptProtocol.exists('nonexistent'); - - expect(exists).toBe(false); - }); - }); + const exists = await promptProtocol.exists('nonexistent') + + expect(exists).toBe(false) + }) + }) describe('完整协议解析', () => { test('应该完整加载内容', async () => { mockPackageProtocol.loadContent.mockResolvedValue({ content: '# Bootstrap Content\n\nThis is bootstrap.', metadata: {} - }); + }) - const content = await promptProtocol.loadContent('@package://bootstrap.md'); - - expect(content).toBe('# Bootstrap Content\n\nThis is bootstrap.'); - }); + const content = await promptProtocol.loadContent('@package://bootstrap.md') + + expect(content).toBe('# Bootstrap Content\n\nThis is bootstrap.') + }) test('应该处理缺少依赖的情况', async () => { - const newProtocol = new PromptProtocol(); + const newProtocol = new PromptProtocol() // 不设置 packageProtocol 依赖 await expect(newProtocol.loadContent('@package://test.md')) - .rejects.toThrow('PromptProtocol 需要 PackageProtocol 依赖'); - }); - }); + .rejects.toThrow('PromptProtocol 需要 PackageProtocol 依赖') + }) + }) describe('工具方法', () => { test('应该列出所有可用资源', () => { - const resources = promptProtocol.listResources(); - - expect(resources).toBeInstanceOf(Array); - expect(resources.length).toBeGreaterThan(0); - - const protocolsResource = resources.find(r => r.id === 'protocols'); - expect(protocolsResource).toBeDefined(); - expect(protocolsResource.description).toContain('DPML协议规范文档'); - }); + const resources = promptProtocol.listResources() + + expect(resources).toBeInstanceOf(Array) + expect(resources.length).toBeGreaterThan(0) + + const protocolsResource = resources.find(r => r.id === 'protocols') + expect(protocolsResource).toBeDefined() + expect(protocolsResource.description).toContain('DPML协议规范文档') + }) test('应该获取资源描述', () => { - expect(promptProtocol.getResourceDescription('protocols')).toContain('DPML协议规范文档'); - expect(promptProtocol.getResourceDescription('core')).toContain('核心思维和执行模式'); - expect(promptProtocol.getResourceDescription('unknown')).toBe('未知资源'); - }); - }); + expect(promptProtocol.getResourceDescription('protocols')).toContain('DPML协议规范文档') + expect(promptProtocol.getResourceDescription('core')).toContain('核心思维和执行模式') + expect(promptProtocol.getResourceDescription('unknown')).toBe('未知资源') + }) + }) describe('缓存管理', () => { test('应该提供缓存统计', () => { - const stats = promptProtocol.getCacheStats(); - expect(stats.protocol).toBe('prompt'); - expect(typeof stats.size).toBe('number'); - expect(typeof stats.enabled).toBe('boolean'); - }); + const stats = promptProtocol.getCacheStats() + expect(stats.protocol).toBe('prompt') + expect(typeof stats.size).toBe('number') + expect(typeof stats.enabled).toBe('boolean') + }) test('应该能清除缓存', () => { // 模拟一些缓存数据 - promptProtocol.cache.set('test', 'value'); - expect(promptProtocol.cache.size).toBeGreaterThan(0); - - promptProtocol.clearCache(); - expect(promptProtocol.cache.size).toBe(0); - }); - }); -}); \ No newline at end of file + promptProtocol.cache.set('test', 'value') + expect(promptProtocol.cache.size).toBeGreaterThan(0) + + promptProtocol.clearCache() + expect(promptProtocol.cache.size).toBe(0) + }) + }) +}) diff --git a/src/tests/core/resource/protocols/UserProtocol.unit.test.js b/src/tests/core/resource/protocols/UserProtocol.unit.test.js index 16344c2..d7838b9 100644 --- a/src/tests/core/resource/protocols/UserProtocol.unit.test.js +++ b/src/tests/core/resource/protocols/UserProtocol.unit.test.js @@ -1,232 +1,232 @@ -const UserProtocol = require('../../../../lib/core/resource/protocols/UserProtocol'); -const { QueryParams } = require('../../../../lib/core/resource/types'); -const path = require('path'); -const os = require('os'); +const UserProtocol = require('../../../../lib/core/resource/protocols/UserProtocol') +const { QueryParams } = require('../../../../lib/core/resource/types') +const path = require('path') +const os = require('os') describe('UserProtocol', () => { - let userProtocol; + let userProtocol beforeEach(() => { - userProtocol = new UserProtocol(); - }); + userProtocol = new UserProtocol() + }) afterEach(() => { - userProtocol.clearCache(); - }); + userProtocol.clearCache() + }) describe('基础功能', () => { test('应该正确初始化协议', () => { - expect(userProtocol.name).toBe('user'); - expect(userProtocol.userDirs).toBeDefined(); - expect(Object.keys(userProtocol.userDirs)).toContain('home'); - expect(Object.keys(userProtocol.userDirs)).toContain('documents'); - expect(Object.keys(userProtocol.userDirs)).toContain('desktop'); - }); + expect(userProtocol.name).toBe('user') + expect(userProtocol.userDirs).toBeDefined() + expect(Object.keys(userProtocol.userDirs)).toContain('home') + expect(Object.keys(userProtocol.userDirs)).toContain('documents') + expect(Object.keys(userProtocol.userDirs)).toContain('desktop') + }) test('应该提供协议信息', () => { - const info = userProtocol.getProtocolInfo(); - expect(info.name).toBe('user'); - expect(info.description).toBeDefined(); - expect(info.location).toBe('user://{directory}/{path}'); - expect(info.examples).toBeInstanceOf(Array); - expect(info.supportedDirectories).toContain('home'); - }); + const info = userProtocol.getProtocolInfo() + expect(info.name).toBe('user') + expect(info.description).toBeDefined() + expect(info.location).toBe('user://{directory}/{path}') + expect(info.examples).toBeInstanceOf(Array) + expect(info.supportedDirectories).toContain('home') + }) test('应该提供支持的参数列表', () => { - const params = userProtocol.getSupportedParams(); - expect(params.line).toBeDefined(); - expect(params.format).toBeDefined(); - expect(params.exists).toBeDefined(); - expect(params.type).toBeDefined(); - }); - }); + const params = userProtocol.getSupportedParams() + expect(params.line).toBeDefined() + expect(params.format).toBeDefined() + expect(params.exists).toBeDefined() + expect(params.type).toBeDefined() + }) + }) describe('路径验证', () => { test('应该验证有效的用户目录路径', () => { - expect(userProtocol.validatePath('home')).toBe(true); - expect(userProtocol.validatePath('documents/notes.txt')).toBe(true); - expect(userProtocol.validatePath('desktop/readme.md')).toBe(true); - expect(userProtocol.validatePath('downloads/')).toBe(true); - }); + expect(userProtocol.validatePath('home')).toBe(true) + expect(userProtocol.validatePath('documents/notes.txt')).toBe(true) + expect(userProtocol.validatePath('desktop/readme.md')).toBe(true) + expect(userProtocol.validatePath('downloads/')).toBe(true) + }) test('应该拒绝无效的用户目录路径', () => { - expect(userProtocol.validatePath('invalid')).toBe(false); - expect(userProtocol.validatePath('unknown/path')).toBe(false); - expect(userProtocol.validatePath('')).toBe(false); - expect(userProtocol.validatePath(null)).toBe(false); - }); - }); + expect(userProtocol.validatePath('invalid')).toBe(false) + expect(userProtocol.validatePath('unknown/path')).toBe(false) + expect(userProtocol.validatePath('')).toBe(false) + expect(userProtocol.validatePath(null)).toBe(false) + }) + }) describe('路径解析', () => { test('应该解析home目录', async () => { - const resolved = await userProtocol.resolvePath('home'); - expect(resolved).toBe(os.homedir()); - }); + const resolved = await userProtocol.resolvePath('home') + expect(resolved).toBe(os.homedir()) + }) test('应该解析documents目录', async () => { - const resolved = await userProtocol.resolvePath('documents'); - expect(resolved).toContain('Documents'); - expect(path.isAbsolute(resolved)).toBe(true); - }); + const resolved = await userProtocol.resolvePath('documents') + expect(resolved).toContain('Documents') + expect(path.isAbsolute(resolved)).toBe(true) + }) test('应该解析带子路径的文件', async () => { - const resolved = await userProtocol.resolvePath('documents/notes.txt'); - expect(resolved).toContain('Documents'); - expect(resolved).toContain('notes.txt'); - expect(path.isAbsolute(resolved)).toBe(true); - }); + const resolved = await userProtocol.resolvePath('documents/notes.txt') + expect(resolved).toContain('Documents') + expect(resolved).toContain('notes.txt') + expect(path.isAbsolute(resolved)).toBe(true) + }) test('应该拒绝不支持的目录类型', async () => { await expect(userProtocol.resolvePath('invalid/path')) - .rejects.toThrow('不支持的用户目录类型'); - }); + .rejects.toThrow('不支持的用户目录类型') + }) test('应该防止路径穿越攻击', async () => { await expect(userProtocol.resolvePath('documents/../../../etc/passwd')) - .rejects.toThrow('安全错误:路径超出用户目录范围'); - }); - }); + .rejects.toThrow('安全错误:路径超出用户目录范围') + }) + }) describe('用户目录获取', () => { test('应该获取所有支持的用户目录', async () => { - const directories = await userProtocol.listUserDirectories(); - - expect(directories.home).toBeDefined(); - expect(directories.documents).toBeDefined(); - expect(directories.desktop).toBeDefined(); - expect(directories.downloads).toBeDefined(); - + const directories = await userProtocol.listUserDirectories() + + expect(directories.home).toBeDefined() + expect(directories.documents).toBeDefined() + expect(directories.desktop).toBeDefined() + expect(directories.downloads).toBeDefined() + // 检查路径是否为绝对路径 - expect(path.isAbsolute(directories.home)).toBe(true); - }); + expect(path.isAbsolute(directories.home)).toBe(true) + }) test('应该缓存目录路径', async () => { // 第一次调用 - const dir1 = await userProtocol.getUserDirectory('home'); - expect(userProtocol.dirCache.has('home')).toBe(true); - + const dir1 = await userProtocol.getUserDirectory('home') + expect(userProtocol.dirCache.has('home')).toBe(true) + // 第二次调用应该从缓存获取 - const dir2 = await userProtocol.getUserDirectory('home'); - expect(dir1).toBe(dir2); - }); - }); + const dir2 = await userProtocol.getUserDirectory('home') + expect(dir1).toBe(dir2) + }) + }) describe('内容加载', () => { test('应该加载目录内容', async () => { // 使用home目录进行测试(应该总是存在) - const homePath = await userProtocol.resolvePath('home'); - const content = await userProtocol.loadContent(homePath); - - expect(typeof content).toBe('string'); - expect(content.length).toBeGreaterThan(0); - }); + const homePath = await userProtocol.resolvePath('home') + const content = await userProtocol.loadContent(homePath) + + expect(typeof content).toBe('string') + expect(content.length).toBeGreaterThan(0) + }) test('应该支持不同的目录格式化选项', async () => { - const homePath = await userProtocol.resolvePath('home'); - const queryParams = new QueryParams(); - + const homePath = await userProtocol.resolvePath('home') + const queryParams = new QueryParams() + // 测试json格式 - queryParams.set('format', 'json'); - const jsonContent = await userProtocol.loadContent(homePath, queryParams); - expect(() => JSON.parse(jsonContent)).not.toThrow(); - + queryParams.set('format', 'json') + const jsonContent = await userProtocol.loadContent(homePath, queryParams) + expect(() => JSON.parse(jsonContent)).not.toThrow() + // 测试paths格式 - queryParams.set('format', 'paths'); - const pathsContent = await userProtocol.loadContent(homePath, queryParams); - expect(typeof pathsContent).toBe('string'); - }); + queryParams.set('format', 'paths') + const pathsContent = await userProtocol.loadContent(homePath, queryParams) + expect(typeof pathsContent).toBe('string') + }) test('应该处理不存在的文件', async () => { - const nonExistentPath = await userProtocol.resolvePath('documents/non-existent-file.txt'); - + const nonExistentPath = await userProtocol.resolvePath('documents/non-existent-file.txt') + // 默认情况下应该抛出错误 await expect(userProtocol.loadContent(nonExistentPath)) - .rejects.toThrow('文件或目录不存在'); - + .rejects.toThrow('文件或目录不存在') + // 设置exists=false应该返回空字符串 - const queryParams = new QueryParams(); - queryParams.set('exists', 'false'); - const content = await userProtocol.loadContent(nonExistentPath, queryParams); - expect(content).toBe(''); - }); - }); + const queryParams = new QueryParams() + queryParams.set('exists', 'false') + const content = await userProtocol.loadContent(nonExistentPath, queryParams) + expect(content).toBe('') + }) + }) describe('查询参数处理', () => { test('应该应用行过滤', () => { - const content = 'line1\nline2\nline3\nline4\nline5'; - + const content = 'line1\nline2\nline3\nline4\nline5' + // 测试单行 - expect(userProtocol.applyLineFilter(content, '2')).toBe('line2'); - + expect(userProtocol.applyLineFilter(content, '2')).toBe('line2') + // 测试范围 - expect(userProtocol.applyLineFilter(content, '2-4')).toBe('line2\nline3\nline4'); - + expect(userProtocol.applyLineFilter(content, '2-4')).toBe('line2\nline3\nline4') + // 测试边界 - expect(userProtocol.applyLineFilter(content, '1-2')).toBe('line1\nline2'); - }); + expect(userProtocol.applyLineFilter(content, '1-2')).toBe('line1\nline2') + }) test('应该应用格式化', () => { - const jsonContent = '{"name": "test", "value": 123}'; - + const jsonContent = '{"name": "test", "value": 123}' + // 测试JSON格式化 - const formatted = userProtocol.applyFormat(jsonContent, 'json'); - expect(formatted).toContain('{\n "name"'); - + const formatted = userProtocol.applyFormat(jsonContent, 'json') + expect(formatted).toContain('{\n "name"') + // 测试trim格式化 - const textContent = ' hello world '; - expect(userProtocol.applyFormat(textContent, 'trim')).toBe('hello world'); - }); - }); + const textContent = ' hello world ' + expect(userProtocol.applyFormat(textContent, 'trim')).toBe('hello world') + }) + }) describe('缓存管理', () => { test('应该启用缓存', () => { - expect(userProtocol.enableCache).toBe(true); - }); + expect(userProtocol.enableCache).toBe(true) + }) test('应该提供缓存统计', () => { - const stats = userProtocol.getCacheStats(); - expect(stats.protocol).toBe('user'); - expect(stats.enabled).toBe(true); - expect(typeof stats.size).toBe('number'); - }); + const stats = userProtocol.getCacheStats() + expect(stats.protocol).toBe('user') + expect(stats.enabled).toBe(true) + expect(typeof stats.size).toBe('number') + }) test('应该清除缓存', async () => { // 先缓存一些数据 - await userProtocol.getUserDirectory('home'); - expect(userProtocol.dirCache.size).toBeGreaterThan(0); - + await userProtocol.getUserDirectory('home') + expect(userProtocol.dirCache.size).toBeGreaterThan(0) + // 清除缓存 - userProtocol.clearCache(); - expect(userProtocol.dirCache.size).toBe(0); - expect(userProtocol.cache.size).toBe(0); - }); - }); + userProtocol.clearCache() + expect(userProtocol.dirCache.size).toBe(0) + expect(userProtocol.cache.size).toBe(0) + }) + }) describe('集成测试', () => { test('应该完整解析用户协议资源', async () => { - const queryParams = new QueryParams(); - queryParams.set('format', 'json'); - - const content = await userProtocol.resolve('home', queryParams); - - expect(typeof content).toBe('string'); - expect(content.length).toBeGreaterThan(0); - + const queryParams = new QueryParams() + queryParams.set('format', 'json') + + const content = await userProtocol.resolve('home', queryParams) + + expect(typeof content).toBe('string') + expect(content.length).toBeGreaterThan(0) + // 如果格式是json,应该能解析 if (queryParams.get('format') === 'json') { - expect(() => JSON.parse(content)).not.toThrow(); + expect(() => JSON.parse(content)).not.toThrow() } - }); + }) test('应该处理嵌套路径', async () => { // 假设Documents目录存在 try { - const content = await userProtocol.resolve('documents'); - expect(typeof content).toBe('string'); + const content = await userProtocol.resolve('documents') + expect(typeof content).toBe('string') } catch (error) { // 如果Documents目录不存在,这是正常的 - expect(error.message).toContain('不存在'); + expect(error.message).toContain('不存在') } - }); - }); -}); \ No newline at end of file + }) + }) +}) diff --git a/src/tests/core/resource/resourceManager.integration.test.js b/src/tests/core/resource/resourceManager.integration.test.js index be69d6c..07ca963 100644 --- a/src/tests/core/resource/resourceManager.integration.test.js +++ b/src/tests/core/resource/resourceManager.integration.test.js @@ -1,203 +1,203 @@ -const ResourceManager = require('../../../lib/core/resource/resourceManager'); -const fs = require('fs').promises; -const path = require('path'); -const os = require('os'); +const ResourceManager = require('../../../lib/core/resource/resourceManager') +const fs = require('fs').promises +const path = require('path') +const os = require('os') describe('ResourceManager - Integration Tests', () => { - let manager; - let tempDir; + let manager + let tempDir beforeAll(async () => { // 创建临时测试目录 - tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-test-')); - + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-test-')) + // 创建测试文件 await fs.writeFile( path.join(tempDir, 'test.md'), '# 测试文件\n\n这是一个测试文件。\n第三行内容。\n第四行内容。' - ); - + ) + await fs.writeFile( path.join(tempDir, 'nested.md'), 'nested content' - ); + ) // 创建子目录和更多测试文件 - const subDir = path.join(tempDir, 'subdir'); - await fs.mkdir(subDir); + const subDir = path.join(tempDir, 'subdir') + await fs.mkdir(subDir) await fs.writeFile( path.join(subDir, 'sub-test.md'), 'subdirectory content' - ); - }); + ) + }) afterAll(async () => { // 清理临时目录 - await fs.rm(tempDir, { recursive: true }); - }); + await fs.rm(tempDir, { recursive: true }) + }) beforeEach(() => { manager = new ResourceManager({ workingDirectory: tempDir, enableCache: true - }); - }); + }) + }) describe('完整的资源解析流程', () => { test('应该解析并加载本地文件', async () => { - const result = await manager.resolve('@file://test.md'); - - expect(result.success).toBe(true); - expect(result.content).toContain('测试文件'); - expect(result.metadata.protocol).toBe('file'); - expect(result.sources).toContain('test.md'); - }); + const result = await manager.resolve('@file://test.md') + + expect(result.success).toBe(true) + expect(result.content).toContain('测试文件') + expect(result.metadata.protocol).toBe('file') + expect(result.sources).toContain('test.md') + }) test('应该处理带查询参数的文件加载', async () => { - const result = await manager.resolve('@file://test.md?line=2-3'); - - expect(result.success).toBe(true); - expect(result.content).not.toContain('# 测试文件'); - expect(result.content).toContain('这是一个测试文件'); - expect(result.content).not.toContain('第三行内容'); - expect(result.content).not.toContain('第四行内容'); - }); + const result = await manager.resolve('@file://test.md?line=2-3') + + expect(result.success).toBe(true) + expect(result.content).not.toContain('# 测试文件') + expect(result.content).toContain('这是一个测试文件') + expect(result.content).not.toContain('第三行内容') + expect(result.content).not.toContain('第四行内容') + }) test('应该处理通配符文件模式', async () => { - const result = await manager.resolve('@file://*.md'); - - expect(result.success).toBe(true); - expect(result.content).toContain('test.md'); - expect(result.content).toContain('nested.md'); - }); - }); + const result = await manager.resolve('@file://*.md') + + expect(result.success).toBe(true) + expect(result.content).toContain('test.md') + expect(result.content).toContain('nested.md') + }) + }) describe('内置协议集成', () => { test('应该处理prompt协议的注册表解析', async () => { // 模拟prompt协议解析 - const mockProtocolFile = path.join(tempDir, 'protocols.md'); - await fs.writeFile(mockProtocolFile, '# PromptX 协议\n\nDPML协议说明'); - + const mockProtocolFile = path.join(tempDir, 'protocols.md') + await fs.writeFile(mockProtocolFile, '# PromptX 协议\n\nDPML协议说明') + // 注册测试协议 manager.registry.register('test-prompt', { name: 'test-prompt', description: '测试提示词协议', registry: { - 'protocols': `@file://${mockProtocolFile}` + protocols: `@file://${mockProtocolFile}` } - }); - - const result = await manager.resolve('@test-prompt://protocols'); - - expect(result.success).toBe(true); - expect(result.content).toContain('PromptX 协议'); - expect(result.content).toContain('DPML协议说明'); - }); + }) + + const result = await manager.resolve('@test-prompt://protocols') + + expect(result.success).toBe(true) + expect(result.content).toContain('PromptX 协议') + expect(result.content).toContain('DPML协议说明') + }) test('应该处理嵌套引用解析', async () => { // 创建指向嵌套文件的引用文件 - const refFile = path.join(tempDir, 'reference.md'); - await fs.writeFile(refFile, '@file://nested.md'); - + const refFile = path.join(tempDir, 'reference.md') + await fs.writeFile(refFile, '@file://nested.md') + manager.registry.register('test-nested', { registry: { - 'ref': `@file://${refFile}` + ref: `@file://${refFile}` } - }); + }) - const result = await manager.resolve('@test-nested://ref'); - - expect(result.success).toBe(true); - expect(result.content).toBe('nested content'); - }); - }); + const result = await manager.resolve('@test-nested://ref') + + expect(result.success).toBe(true) + expect(result.content).toBe('nested content') + }) + }) describe('缓存机制', () => { test('应该缓存已加载的资源', async () => { - const firstResult = await manager.resolve('@file://test.md'); - const secondResult = await manager.resolve('@file://test.md'); - - expect(firstResult.content).toBe(secondResult.content); - expect(firstResult.success).toBe(true); - expect(secondResult.success).toBe(true); - }); + const firstResult = await manager.resolve('@file://test.md') + const secondResult = await manager.resolve('@file://test.md') + + expect(firstResult.content).toBe(secondResult.content) + expect(firstResult.success).toBe(true) + expect(secondResult.success).toBe(true) + }) test('应该清除缓存', async () => { - await manager.resolve('@file://test.md'); - expect(manager.cache.size).toBeGreaterThan(0); - - manager.clearCache(); - expect(manager.cache.size).toBe(0); - }); - }); + await manager.resolve('@file://test.md') + expect(manager.cache.size).toBeGreaterThan(0) + + manager.clearCache() + expect(manager.cache.size).toBe(0) + }) + }) describe('批量资源解析', () => { test('应该批量解析多个资源', async () => { const refs = [ '@file://test.md', '@file://nested.md' - ]; - - const results = await manager.resolveMultiple(refs); - - expect(results).toHaveLength(2); - expect(results[0].success).toBe(true); - expect(results[1].success).toBe(true); - expect(results[0].content).toContain('测试文件'); - expect(results[1].content).toContain('nested content'); - }); - }); + ] + + const results = await manager.resolveMultiple(refs) + + expect(results).toHaveLength(2) + expect(results[0].success).toBe(true) + expect(results[1].success).toBe(true) + expect(results[0].content).toContain('测试文件') + expect(results[1].content).toContain('nested content') + }) + }) describe('错误处理', () => { test('应该处理文件不存在的情况', async () => { - const result = await manager.resolve('@file://nonexistent.md'); - - expect(result.success).toBe(false); - expect(result.error).toBeDefined(); - expect(result.error.message).toContain('Failed to read file'); - }); + const result = await manager.resolve('@file://nonexistent.md') + + expect(result.success).toBe(false) + expect(result.error).toBeDefined() + expect(result.error.message).toContain('Failed to read file') + }) test('应该处理无效的协议', async () => { - const result = await manager.resolve('@unknown://test'); - - expect(result.success).toBe(false); - expect(result.error.message).toContain('Unknown protocol'); - }); + const result = await manager.resolve('@unknown://test') + + expect(result.success).toBe(false) + expect(result.error.message).toContain('Unknown protocol') + }) test('应该处理无效的资源引用语法', async () => { - const result = await manager.resolve('invalid-reference'); - - expect(result.success).toBe(false); - expect(result.error.message).toContain('Invalid resource reference syntax'); - }); - }); + const result = await manager.resolve('invalid-reference') + + expect(result.success).toBe(false) + expect(result.error.message).toContain('Invalid resource reference syntax') + }) + }) describe('验证功能', () => { test('应该验证有效的资源引用', () => { - expect(manager.isValidReference('@file://test.md')).toBe(true); - expect(manager.isValidReference('@prompt://protocols')).toBe(true); - }); + expect(manager.isValidReference('@file://test.md')).toBe(true) + expect(manager.isValidReference('@prompt://protocols')).toBe(true) + }) test('应该拒绝无效的资源引用', () => { - expect(manager.isValidReference('invalid')).toBe(false); - expect(manager.isValidReference('@unknown://test')).toBe(false); - }); - }); + expect(manager.isValidReference('invalid')).toBe(false) + expect(manager.isValidReference('@unknown://test')).toBe(false) + }) + }) describe('工具功能', () => { test('应该列出可用协议', () => { - const protocols = manager.listProtocols(); - - expect(protocols).toContain('file'); - expect(protocols).toContain('prompt'); - expect(protocols).toContain('memory'); - }); + const protocols = manager.listProtocols() + + expect(protocols).toContain('file') + expect(protocols).toContain('prompt') + expect(protocols).toContain('memory') + }) test('应该获取注册表信息', () => { - const info = manager.getRegistryInfo('prompt'); - - expect(info).toBeDefined(); - expect(info.name).toBe('prompt'); - }); - }); -}); \ No newline at end of file + const info = manager.getRegistryInfo('prompt') + + expect(info).toBeDefined() + expect(info.name).toBe('prompt') + }) + }) +}) diff --git a/src/tests/core/resource/resourceProtocolParser.unit.test.js b/src/tests/core/resource/resourceProtocolParser.unit.test.js index ee8511d..b229668 100644 --- a/src/tests/core/resource/resourceProtocolParser.unit.test.js +++ b/src/tests/core/resource/resourceProtocolParser.unit.test.js @@ -1,133 +1,133 @@ -const ResourceProtocolParser = require('../../../lib/core/resource/resourceProtocolParser'); -const { - LoadingSemantics, - ParsedReference, - QueryParams -} = require('../../../lib/core/resource/types'); +const ResourceProtocolParser = require('../../../lib/core/resource/resourceProtocolParser') +const { + LoadingSemantics, + ParsedReference, + QueryParams +} = require('../../../lib/core/resource/types') describe('ResourceProtocolParser - Unit Tests', () => { - let parser; + let parser beforeEach(() => { - parser = new ResourceProtocolParser(); - }); + parser = new ResourceProtocolParser() + }) describe('基础语法解析', () => { test('应该解析基本的资源引用', () => { - const result = parser.parse('@prompt://protocols'); - - expect(result.protocol).toBe('prompt'); - expect(result.path).toBe('protocols'); - expect(result.loadingSemantics).toBe(LoadingSemantics.DEFAULT); - expect(result.isNested).toBe(false); - }); + const result = parser.parse('@prompt://protocols') + + expect(result.protocol).toBe('prompt') + expect(result.path).toBe('protocols') + expect(result.loadingSemantics).toBe(LoadingSemantics.DEFAULT) + expect(result.isNested).toBe(false) + }) test('应该解析带查询参数的资源引用', () => { - const result = parser.parse('@file://test.md?line=5-10&cache=true'); - - expect(result.protocol).toBe('file'); - expect(result.path).toBe('test.md'); - expect(result.queryParams.line).toBe('5-10'); - expect(result.queryParams.cache).toBe(true); - }); + const result = parser.parse('@file://test.md?line=5-10&cache=true') + + expect(result.protocol).toBe('file') + expect(result.path).toBe('test.md') + expect(result.queryParams.line).toBe('5-10') + expect(result.queryParams.cache).toBe(true) + }) test('应该解析热加载语义', () => { - const result = parser.parse('@!prompt://core'); - - expect(result.protocol).toBe('prompt'); - expect(result.path).toBe('core'); - expect(result.loadingSemantics).toBe(LoadingSemantics.HOT_LOAD); - }); + const result = parser.parse('@!prompt://core') + + expect(result.protocol).toBe('prompt') + expect(result.path).toBe('core') + expect(result.loadingSemantics).toBe(LoadingSemantics.HOT_LOAD) + }) test('应该解析懒加载语义', () => { - const result = parser.parse('@?file://lazy-resource.md'); - - expect(result.protocol).toBe('file'); - expect(result.path).toBe('lazy-resource.md'); - expect(result.loadingSemantics).toBe(LoadingSemantics.LAZY_LOAD); - }); - }); + const result = parser.parse('@?file://lazy-resource.md') + + expect(result.protocol).toBe('file') + expect(result.path).toBe('lazy-resource.md') + expect(result.loadingSemantics).toBe(LoadingSemantics.LAZY_LOAD) + }) + }) describe('嵌套引用解析', () => { test('应该解析简单嵌套引用', () => { - const result = parser.parse('@prompt://@file://nested.md'); - - expect(result.protocol).toBe('prompt'); - expect(result.isNested).toBe(true); - expect(result.nestedRef.inner.protocol).toBe('file'); - expect(result.nestedRef.inner.path).toBe('nested.md'); - }); + const result = parser.parse('@prompt://@file://nested.md') + + expect(result.protocol).toBe('prompt') + expect(result.isNested).toBe(true) + expect(result.nestedRef.inner.protocol).toBe('file') + expect(result.nestedRef.inner.path).toBe('nested.md') + }) test('应该解析多层嵌套引用', () => { - const result = parser.parse('@prompt://@memory://@file://deep.md'); - - expect(result.protocol).toBe('prompt'); - expect(result.isNested).toBe(true); - expect(result.nestedRef.inner.protocol).toBe('memory'); - expect(result.nestedRef.inner.isNested).toBe(true); - expect(result.nestedRef.depth).toBe(2); - }); - }); + const result = parser.parse('@prompt://@memory://@file://deep.md') + + expect(result.protocol).toBe('prompt') + expect(result.isNested).toBe(true) + expect(result.nestedRef.inner.protocol).toBe('memory') + expect(result.nestedRef.inner.isNested).toBe(true) + expect(result.nestedRef.depth).toBe(2) + }) + }) describe('查询参数解析', () => { test('应该解析多个查询参数', () => { - const params = parser.parseQueryParams('line=1-10&format=json&cache=true'); - - expect(params.line).toBe('1-10'); - expect(params.format).toBe('json'); - expect(params.cache).toBe(true); - }); + const params = parser.parseQueryParams('line=1-10&format=json&cache=true') + + expect(params.line).toBe('1-10') + expect(params.format).toBe('json') + expect(params.cache).toBe(true) + }) test('应该处理空查询参数', () => { - const params = parser.parseQueryParams(''); - - expect(params.getAll()).toEqual({}); - }); + const params = parser.parseQueryParams('') + + expect(params.getAll()).toEqual({}) + }) test('应该处理URL编码的参数', () => { - const params = parser.parseQueryParams('query=%E4%B8%AD%E6%96%87'); - - expect(params.get('query')).toBe('中文'); - }); - }); + const params = parser.parseQueryParams('query=%E4%B8%AD%E6%96%87') + + expect(params.get('query')).toBe('中文') + }) + }) describe('语法验证', () => { test('应该验证有效的语法', () => { - expect(parser.validateSyntax('@prompt://protocols')).toBe(true); - expect(parser.validateSyntax('@!file://test.md')).toBe(true); - expect(parser.validateSyntax('@?memory://declarative')).toBe(true); - }); + expect(parser.validateSyntax('@prompt://protocols')).toBe(true) + expect(parser.validateSyntax('@!file://test.md')).toBe(true) + expect(parser.validateSyntax('@?memory://declarative')).toBe(true) + }) test('应该拒绝无效的语法', () => { - expect(parser.validateSyntax('prompt://protocols')).toBe(false); // 缺少@ - expect(parser.validateSyntax('@://test')).toBe(false); // 空协议 - expect(parser.validateSyntax('@123protocol://test')).toBe(false); // 协议名不能以数字开头 - expect(parser.validateSyntax('')).toBe(false); // 空字符串 - }); - }); + expect(parser.validateSyntax('prompt://protocols')).toBe(false) // 缺少@ + expect(parser.validateSyntax('@://test')).toBe(false) // 空协议 + expect(parser.validateSyntax('@123protocol://test')).toBe(false) // 协议名不能以数字开头 + expect(parser.validateSyntax('')).toBe(false) // 空字符串 + }) + }) describe('错误处理', () => { test('应该抛出适当的错误信息', () => { - expect(() => parser.parse('')).toThrow('Invalid resource reference'); - expect(() => parser.parse(null)).toThrow('Invalid resource reference'); - expect(() => parser.parse('invalid')).toThrow('Invalid resource reference syntax'); - }); - }); + expect(() => parser.parse('')).toThrow('Invalid resource reference') + expect(() => parser.parse(null)).toThrow('Invalid resource reference') + expect(() => parser.parse('invalid')).toThrow('Invalid resource reference syntax') + }) + }) describe('工具方法', () => { test('应该正确提取协议名', () => { - expect(parser.extractProtocol('@prompt://protocols')).toBe('prompt'); - expect(parser.extractProtocol('@!file://test.md')).toBe('file'); - }); + expect(parser.extractProtocol('@prompt://protocols')).toBe('prompt') + expect(parser.extractProtocol('@!file://test.md')).toBe('file') + }) test('应该正确提取路径', () => { - expect(parser.extractPath('@prompt://protocols?format=json')).toBe('protocols'); - expect(parser.extractPath('@file://path/to/file.md')).toBe('path/to/file.md'); - }); + expect(parser.extractPath('@prompt://protocols?format=json')).toBe('protocols') + expect(parser.extractPath('@file://path/to/file.md')).toBe('path/to/file.md') + }) test('应该正确提取查询参数', () => { - expect(parser.extractParams('@file://test.md?line=5-10')).toBe('line=5-10'); - expect(parser.extractParams('@file://test.md')).toBe(''); - }); - }); -}); \ No newline at end of file + expect(parser.extractParams('@file://test.md?line=5-10')).toBe('line=5-10') + expect(parser.extractParams('@file://test.md')).toBe('') + }) + }) +}) diff --git a/src/tests/core/resource/resourceRegistry.unit.test.js b/src/tests/core/resource/resourceRegistry.unit.test.js index fd6edbb..d306fd1 100644 --- a/src/tests/core/resource/resourceRegistry.unit.test.js +++ b/src/tests/core/resource/resourceRegistry.unit.test.js @@ -1,64 +1,64 @@ -const ResourceRegistry = require('../../../lib/core/resource/resourceRegistry'); -const { ProtocolInfo } = require('../../../lib/core/resource/types'); +const ResourceRegistry = require('../../../lib/core/resource/resourceRegistry') +const { ProtocolInfo } = require('../../../lib/core/resource/types') describe('ResourceRegistry - Unit Tests', () => { - let registry; + let registry beforeEach(() => { - registry = new ResourceRegistry(); - }); + registry = new ResourceRegistry() + }) describe('内置协议', () => { test('应该包含内置协议', () => { - const protocols = registry.listProtocols(); - - expect(protocols).toContain('prompt'); - expect(protocols).toContain('file'); - expect(protocols).toContain('memory'); - }); + const protocols = registry.listProtocols() + + expect(protocols).toContain('prompt') + expect(protocols).toContain('file') + expect(protocols).toContain('memory') + }) test('应该正确获取prompt协议信息', () => { - const protocolInfo = registry.getProtocolInfo('prompt'); - - expect(protocolInfo).toBeDefined(); - expect(protocolInfo.name).toBe('prompt'); - expect(protocolInfo.description).toContain('PromptX内置提示词资源协议'); - expect(protocolInfo.location).toContain('prompt://'); - }); + const protocolInfo = registry.getProtocolInfo('prompt') + + expect(protocolInfo).toBeDefined() + expect(protocolInfo.name).toBe('prompt') + expect(protocolInfo.description).toContain('PromptX内置提示词资源协议') + expect(protocolInfo.location).toContain('prompt://') + }) test('应该为协议提供资源注册表', () => { - const protocolInfo = registry.getProtocolInfo('memory'); - - expect(protocolInfo.registry).toBeDefined(); - expect(protocolInfo.registry.size).toBeGreaterThan(0); - expect(protocolInfo.registry.has('declarative')).toBe(true); - expect(protocolInfo.registry.has('procedural')).toBe(true); - }); - }); + const protocolInfo = registry.getProtocolInfo('memory') + + expect(protocolInfo.registry).toBeDefined() + expect(protocolInfo.registry.size).toBeGreaterThan(0) + expect(protocolInfo.registry.has('declarative')).toBe(true) + expect(protocolInfo.registry.has('procedural')).toBe(true) + }) + }) describe('资源解析', () => { test('应该解析prompt协议的资源ID', () => { - const resolved = registry.resolve('prompt', 'protocols'); - - expect(resolved).toBe('@package://prompt/protocol/**/*.md'); - }); + const resolved = registry.resolve('prompt', 'protocols') + + expect(resolved).toBe('@package://prompt/protocol/**/*.md') + }) test('应该解析memory协议的资源ID', () => { - const resolved = registry.resolve('memory', 'declarative'); - - expect(resolved).toBe('@project://.promptx/memory/declarative.md'); - }); + const resolved = registry.resolve('memory', 'declarative') + + expect(resolved).toBe('@project://.promptx/memory/declarative.md') + }) test('应该解析未注册协议的资源路径', () => { - const resolved = registry.resolve('file', 'any/path.md'); - - expect(resolved).toBe('any/path.md'); - }); + const resolved = registry.resolve('file', 'any/path.md') + + expect(resolved).toBe('any/path.md') + }) test('应该在资源ID不存在时抛出错误', () => { - expect(() => registry.resolve('prompt', 'nonexistent')).toThrow('Resource ID \'nonexistent\' not found in prompt protocol registry'); - }); - }); + expect(() => registry.resolve('prompt', 'nonexistent')).toThrow('Resource ID \'nonexistent\' not found in prompt protocol registry') + }) + }) describe('自定义协议注册', () => { test('应该注册新的自定义协议', () => { @@ -66,69 +66,69 @@ describe('ResourceRegistry - Unit Tests', () => { description: '测试协议', location: 'test://{resource_id}', registry: { - 'test1': '@file://test1.md', - 'test2': '@file://test2.md' + test1: '@file://test1.md', + test2: '@file://test2.md' } - }; + } - registry.register('test', customProtocol); - - expect(registry.hasProtocol('test')).toBe(true); - expect(registry.resolve('test', 'test1')).toBe('@file://test1.md'); - }); + registry.register('test', customProtocol) + + expect(registry.hasProtocol('test')).toBe(true) + expect(registry.resolve('test', 'test1')).toBe('@file://test1.md') + }) test('应该列出自定义协议的资源', () => { const customProtocol = { registry: { - 'resource1': '@file://r1.md', - 'resource2': '@file://r2.md' + resource1: '@file://r1.md', + resource2: '@file://r2.md' } - }; + } - registry.register('custom', customProtocol); - const resources = registry.listProtocolResources('custom'); - - expect(resources).toContain('resource1'); - expect(resources).toContain('resource2'); - }); - }); + registry.register('custom', customProtocol) + const resources = registry.listProtocolResources('custom') + + expect(resources).toContain('resource1') + expect(resources).toContain('resource2') + }) + }) describe('验证功能', () => { test('应该验证有效的协议和资源ID', () => { - expect(registry.validateReference('prompt', 'protocols')).toBe(true); - expect(registry.validateReference('file', 'any-path.md')).toBe(true); - expect(registry.validateReference('memory', 'declarative')).toBe(true); - }); + expect(registry.validateReference('prompt', 'protocols')).toBe(true) + expect(registry.validateReference('file', 'any-path.md')).toBe(true) + expect(registry.validateReference('memory', 'declarative')).toBe(true) + }) test('应该拒绝无效的协议和资源ID', () => { - expect(registry.validateReference('unknown', 'test')).toBe(false); - expect(registry.validateReference('prompt', 'nonexistent')).toBe(false); - }); - }); + expect(registry.validateReference('unknown', 'test')).toBe(false) + expect(registry.validateReference('prompt', 'nonexistent')).toBe(false) + }) + }) describe('注册表信息', () => { test('应该返回完整的注册表信息', () => { - const info = registry.getRegistryInfo(); - - expect(info.builtin).toHaveProperty('prompt'); - expect(info.builtin).toHaveProperty('file'); - expect(info.builtin).toHaveProperty('memory'); - expect(info.custom).toEqual({}); - }); + const info = registry.getRegistryInfo() + + expect(info.builtin).toHaveProperty('prompt') + expect(info.builtin).toHaveProperty('file') + expect(info.builtin).toHaveProperty('memory') + expect(info.custom).toEqual({}) + }) test('应该返回协议的资源列表', () => { - const resources = registry.listProtocolResources('prompt'); - - expect(resources).toContain('protocols'); - expect(resources).toContain('core'); - expect(resources).toContain('domain'); - expect(resources).toContain('bootstrap'); - }); + const resources = registry.listProtocolResources('prompt') + + expect(resources).toContain('protocols') + expect(resources).toContain('core') + expect(resources).toContain('domain') + expect(resources).toContain('bootstrap') + }) test('应该为无注册表的协议返回空列表', () => { - const resources = registry.listProtocolResources('file'); - - expect(resources).toEqual([]); - }); - }); -}); \ No newline at end of file + const resources = registry.listProtocolResources('file') + + expect(resources).toEqual([]) + }) + }) +}) diff --git a/src/tests/fixtures/testResources.js b/src/tests/fixtures/testResources.js index 208cfa3..ad153b8 100644 --- a/src/tests/fixtures/testResources.js +++ b/src/tests/fixtures/testResources.js @@ -1,38 +1,38 @@ -const path = require('path'); -const fs = require('fs').promises; -const os = require('os'); +const path = require('path') +const fs = require('fs').promises +const os = require('os') /** * 测试资源工厂 * 提供测试用的固定数据和辅助函数 */ class TestResourceFactory { - constructor() { - this.tempDirs = new Set(); + constructor () { + this.tempDirs = new Set() } /** * 创建临时测试目录 * @returns {Promise} 临时目录路径 */ - async createTempDir() { - const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-test-')); - this.tempDirs.add(tempDir); - return tempDir; + async createTempDir () { + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-test-')) + this.tempDirs.add(tempDir) + return tempDir } /** * 清理所有临时目录 */ - async cleanup() { + async cleanup () { for (const dir of this.tempDirs) { try { - await fs.rmdir(dir, { recursive: true }); + await fs.rmdir(dir, { recursive: true }) } catch (error) { - console.warn(`Failed to cleanup temp dir ${dir}:`, error.message); + console.warn(`Failed to cleanup temp dir ${dir}:`, error.message) } } - this.tempDirs.clear(); + this.tempDirs.clear() } /** @@ -40,18 +40,18 @@ class TestResourceFactory { * @param {string} baseDir - 基础目录 * @returns {Promise} 创建的文件路径映射 */ - async createPromptXStructure(baseDir) { + async createPromptXStructure (baseDir) { const structure = { prompt: path.join(baseDir, 'prompt'), core: path.join(baseDir, 'prompt', 'core'), domain: path.join(baseDir, 'prompt', 'domain'), protocol: path.join(baseDir, 'prompt', 'protocol'), memory: path.join(baseDir, '.memory') - }; + } // 创建目录结构 for (const dir of Object.values(structure)) { - await fs.mkdir(dir, { recursive: true }); + await fs.mkdir(dir, { recursive: true }) } // 创建测试文件 @@ -63,24 +63,24 @@ class TestResourceFactory { protocolDpml: path.join(structure.protocol, 'dpml.md'), memoryDeclarative: path.join(structure.memory, 'declarative.md'), memoryProcedural: path.join(structure.memory, 'procedural.md') - }; + } // 创建core子目录 - await fs.mkdir(path.join(structure.core, 'thought'), { recursive: true }); - await fs.mkdir(path.join(structure.core, 'execution'), { recursive: true }); - await fs.mkdir(path.join(structure.domain, 'test'), { recursive: true }); + await fs.mkdir(path.join(structure.core, 'thought'), { recursive: true }) + await fs.mkdir(path.join(structure.core, 'execution'), { recursive: true }) + await fs.mkdir(path.join(structure.domain, 'test'), { recursive: true }) // 写入测试文件内容 - await this.writeTestFiles(files); + await this.writeTestFiles(files) - return { structure, files }; + return { structure, files } } /** * 写入测试文件内容 * @param {object} files - 文件路径映射 */ - async writeTestFiles(files) { + async writeTestFiles (files) { const contents = { [files.bootstrap]: `# PromptX Bootstrap @@ -182,17 +182,17 @@ JSON结构化数据`, ## 存储格式 步骤化序列` - }; + } for (const [filePath, content] of Object.entries(contents)) { - await fs.writeFile(filePath, content, 'utf8'); + await fs.writeFile(filePath, content, 'utf8') } } /** * 获取测试用的DPML资源引用 */ - getTestResourceRefs() { + getTestResourceRefs () { return { valid: [ '@promptx://bootstrap', @@ -224,13 +224,13 @@ JSON结构化数据`, '@file://@memory://declarative', '@promptx://@memory://@file://deep-nested.md' ] - }; + } } /** * 获取测试用的查询参数 */ - getTestQueryParams() { + getTestQueryParams () { return { line: ['1', '5-10', '1-', '-10'], format: ['json', 'text', 'xml'], @@ -238,21 +238,21 @@ JSON结构化数据`, encoding: ['utf8', 'gbk', 'ascii'], timeout: ['5000', '10000'], custom: ['value1', 'value2'] - }; + } } /** * 创建模拟的协议注册表 */ - getMockProtocolRegistry() { + getMockProtocolRegistry () { return { 'test-protocol': { description: '测试协议', location: 'test://{resource_id}', registry: { - 'resource1': '@file://test1.md', - 'resource2': '@file://test2.md', - 'nested': '@test-protocol://resource1' + resource1: '@file://test1.md', + resource2: '@file://test2.md', + nested: '@test-protocol://resource1' } }, 'mock-http': { @@ -263,13 +263,13 @@ JSON结构化数据`, format: 'string' } } - }; + } } /** * 创建错误场景测试数据 */ - getErrorScenarios() { + getErrorScenarios () { return { fileNotFound: '@file://nonexistent.md', permissionDenied: '@file://restricted.md', @@ -277,16 +277,16 @@ JSON结构化数据`, malformedUrl: '@file://', networkTimeout: '@http://timeout.example.com', parseError: 'invalid-syntax' - }; + } } } module.exports = { TestResourceFactory, - + // 便捷工厂函数 createTestFactory: () => new TestResourceFactory(), - + // 常用测试数据 SAMPLE_DPML_REFS: [ '@promptx://protocols', @@ -294,8 +294,8 @@ module.exports = { '@!memory://hot-memory', '@?file://lazy-file.md' ], - + SAMPLE_PROTOCOLS: ['promptx', 'file', 'memory', 'http', 'https'], - + SAMPLE_LOADING_SEMANTICS: ['@', '@!', '@?'] -}; \ No newline at end of file +} diff --git a/src/tests/setup.js b/src/tests/setup.js index 2381259..e957745 100644 --- a/src/tests/setup.js +++ b/src/tests/setup.js @@ -3,44 +3,44 @@ */ // 设置测试超时时间 -jest.setTimeout(30000); +jest.setTimeout(30000) // 全局变量设置 -global.TEST_ENV = 'test'; +global.TEST_ENV = 'test' // 模拟console.log以减少测试输出噪音 -const originalConsoleLog = console.log; -const originalConsoleWarn = console.warn; -const originalConsoleError = console.error; +const originalConsoleLog = console.log +const originalConsoleWarn = console.warn +const originalConsoleError = console.error // 在测试环境中静默一些不必要的日志 if (process.env.NODE_ENV === 'test') { console.log = (...args) => { // 只有在明确需要时才输出 if (args.some(arg => typeof arg === 'string' && arg.includes('TEST_OUTPUT'))) { - originalConsoleLog(...args); + originalConsoleLog(...args) } - }; - + } + console.warn = (...args) => { // 保留警告信息 if (args.some(arg => typeof arg === 'string' && arg.includes('TEST_WARN'))) { - originalConsoleWarn(...args); + originalConsoleWarn(...args) } - }; - + } + console.error = (...args) => { // 保留错误信息 - originalConsoleError(...args); - }; + originalConsoleError(...args) + } } // 测试结束后恢复console afterAll(() => { - console.log = originalConsoleLog; - console.warn = originalConsoleWarn; - console.error = originalConsoleError; -}); + console.log = originalConsoleLog + console.warn = originalConsoleWarn + console.error = originalConsoleError +}) // 全局测试工具函数 global.testUtils = { @@ -49,15 +49,15 @@ global.testUtils = { * @param {number} ms - 毫秒数 */ sleep: (ms) => new Promise(resolve => setTimeout(resolve, ms)), - + /** * 创建延迟Promise * @param {any} value - 返回值 * @param {number} delay - 延迟时间 */ - delayed: (value, delay = 100) => + delayed: (value, delay = 100) => new Promise(resolve => setTimeout(() => resolve(value), delay)), - + /** * 创建拒绝的Promise * @param {any} error - 错误对象 @@ -65,59 +65,59 @@ global.testUtils = { */ delayedReject: (error, delay = 100) => new Promise((_, reject) => setTimeout(() => reject(error), delay)) -}; +} // 全局断言扩展 expect.extend({ /** * 检查是否为有效的DPML资源引用 */ - toBeValidDpmlReference(received) { - const dpmlPattern = /^@[!?]?[a-zA-Z][a-zA-Z0-9_-]*:\/\/.+/; - const pass = typeof received === 'string' && dpmlPattern.test(received); - + toBeValidDpmlReference (received) { + const dpmlPattern = /^@[!?]?[a-zA-Z][a-zA-Z0-9_-]*:\/\/.+/ + const pass = typeof received === 'string' && dpmlPattern.test(received) + if (pass) { return { message: () => `expected ${received} not to be a valid DPML reference`, pass: true - }; + } } else { return { message: () => `expected ${received} to be a valid DPML reference`, pass: false - }; + } } }, - + /** * 检查对象是否包含必需的属性 */ - toHaveRequiredProperties(received, properties) { - const missingProps = properties.filter(prop => !(prop in received)); - const pass = missingProps.length === 0; - + toHaveRequiredProperties (received, properties) { + const missingProps = properties.filter(prop => !(prop in received)) + const pass = missingProps.length === 0 + if (pass) { return { message: () => `expected object not to have properties ${properties.join(', ')}`, pass: true - }; + } } else { return { message: () => `expected object to have properties ${missingProps.join(', ')}`, pass: false - }; + } } } -}); +}) // 处理未捕获的Promise拒绝 process.on('unhandledRejection', (reason, promise) => { - console.error('Unhandled Rejection at:', promise, 'reason:', reason); -}); + console.error('Unhandled Rejection at:', promise, 'reason:', reason) +}) // 处理未捕获的异常 process.on('uncaughtException', (error) => { - console.error('Uncaught Exception:', error); -}); + console.error('Uncaught Exception:', error) +}) -console.log('🧪 Jest测试环境已初始化'); \ No newline at end of file +console.log('🧪 Jest测试环境已初始化')