删除不再使用的bootstrap.md文件,更新promptx.js、MCPStreamableHttpCommand.js等文件以使用logger进行日志记录,重构资源管理和发现逻辑,确保代码一致性和可维护性。

This commit is contained in:
sean
2025-06-13 09:33:56 +08:00
parent cdd748d0dc
commit 2ecebac50b
29 changed files with 3561 additions and 450 deletions

View File

@ -6,6 +6,7 @@ const { SSEServerTransport } = require('@modelcontextprotocol/sdk/server/sse.js'
const { isInitializeRequest } = require('@modelcontextprotocol/sdk/types.js');
const { cli } = require('../core/pouch');
const { MCPOutputAdapter } = require('../adapters/MCPOutputAdapter');
const logger = require('../utils/logger');
/**
* MCP Streamable HTTP Server Command
@ -579,7 +580,7 @@ class MCPStreamableHttpCommand {
*/
log(message, ...args) {
if (this.debug) {
console.error(`[MCP DEBUG] ${message}`, ...args);
logger.debug(`[MCP DEBUG] ${message}`, ...args);
}
}

View File

@ -1,57 +0,0 @@
{
"version": "0.0.1",
"initialized": "2025-05-31T06:15:39.372Z",
"defaultFormat": "human",
"stateHistory": [
{
"from": "initial",
"to": "init",
"timestamp": "2025-05-31T06:15:39.370Z",
"args": []
},
{
"from": "init",
"to": "hello",
"timestamp": "2025-05-31T06:15:39.373Z",
"args": []
},
{
"from": "hello",
"command": "init",
"timestamp": "2025-05-31T06:16:28.040Z",
"args": []
},
{
"from": "initialized",
"command": "hello",
"timestamp": "2025-05-31T06:16:28.042Z",
"args": []
},
{
"from": "discovering",
"command": "action",
"timestamp": "2025-05-31T06:16:28.042Z",
"args": [
"copywriter"
]
},
{
"from": "activated-copywriter",
"command": "learn",
"timestamp": "2025-05-31T06:16:28.046Z",
"args": [
"scrum"
]
},
{
"from": "learned-scrum",
"command": "recall",
"timestamp": "2025-05-31T06:16:28.047Z",
"args": [
"test"
]
}
],
"currentState": "recalled-test",
"lastUpdated": "2025-05-31T06:16:28.047Z"
}

View File

@ -95,7 +95,7 @@ class BasePouchCommand {
toString () {
const divider = '='.repeat(60)
const nextSteps = (pateoas.nextActions || [])
.map(action => ` - ${action.name}: ${action.description}\n 命令: ${action.command}`)
.map(action => ` - ${action.name}: ${action.description}\n 方式: ${action.method || action.command || '通过MCP工具'}`)
.join('\n')
return `

View File

@ -2,6 +2,7 @@ const PouchStateMachine = require('./state/PouchStateMachine')
const PouchRegistry = require('./PouchRegistry')
const commands = require('./commands')
const { COMMANDS } = require('../../../constants')
const logger = require('../../utils/logger')
/**
* 锦囊CLI主入口
@ -70,9 +71,9 @@ class PouchCLI {
if (!silent) {
// 如果结果有 toString 方法,打印人类可读格式
if (result && result.toString && typeof result.toString === 'function') {
console.log(result.toString())
logger.log(result.toString())
} else {
console.log(JSON.stringify(result, null, 2))
logger.log(JSON.stringify(result, null, 2))
}
}
@ -80,7 +81,7 @@ class PouchCLI {
} catch (error) {
// 错误输出始终使用stderr不干扰MCP协议
if (!silent) {
console.error(`执行命令出错: ${error.message}`)
logger.error(`执行命令出错: ${error.message}`)
}
throw error
}
@ -162,8 +163,8 @@ class PouchCLI {
* 运行交互式CLI
*/
async runInteractive () {
console.log('🎯 欢迎使用 PromptX 锦囊系统!')
console.log('输入 "help" 查看帮助,"exit" 退出\n')
logger.info('🎯 欢迎使用 PromptX 锦囊系统!')
logger.info('输入 "help" 查看帮助,"exit" 退出\n')
const readline = require('readline')
const rl = readline.createInterface({
@ -178,21 +179,21 @@ class PouchCLI {
const input = line.trim()
if (input === 'exit' || input === 'quit') {
console.log('再见!')
logger.info('再见!')
rl.close()
return
}
if (input === 'help') {
console.log(this.getHelp())
logger.info(this.getHelp())
} else if (input === 'status') {
console.log(JSON.stringify(this.getStatus(), null, 2))
logger.info(JSON.stringify(this.getStatus(), null, 2))
} else if (input) {
const { command, args } = this.parseCommand(input)
try {
await this.execute(command, args)
} catch (error) {
console.error(error.message)
logger.error(error.message)
}
}

View File

@ -1,8 +1,8 @@
const BasePouchCommand = require('../BasePouchCommand')
const fs = require('fs-extra')
const path = require('path')
const { COMMANDS, buildCommand } = require('../../../../constants')
const ResourceManager = require('../../resource/resourceManager')
const { COMMANDS } = require('../../../../constants')
const { getGlobalResourceManager } = require('../../resource')
const DPMLContentParser = require('../../resource/DPMLContentParser')
const SemanticRenderer = require('../../resource/SemanticRenderer')
const logger = require('../../../utils/logger')
@ -16,7 +16,8 @@ class ActionCommand extends BasePouchCommand {
super()
// 获取HelloCommand的角色注册表
this.helloCommand = null
this.resourceManager = new ResourceManager()
// 使用全局单例 ResourceManager
this.resourceManager = getGlobalResourceManager()
this.dpmlParser = new DPMLContentParser()
this.semanticRenderer = new SemanticRenderer()
}
@ -32,19 +33,20 @@ class ActionCommand extends BasePouchCommand {
return `❌ 请指定要激活的角色ID
🔍 使用方法:
\`\`\`bash
${buildCommand.action('<角色ID>')}
\`\`\`
通过 MCP PromptX 工具的 action 功能激活角色
💡 查看可用角色:
\`\`\`bash
${COMMANDS.HELLO}
\`\`\``
使用 MCP PromptX 工具的 hello 功能`
}
try {
logger.debug(`[ActionCommand] 开始激活角色: ${roleId}`)
// 0. 初始化 ResourceManager确保引用解析正常工作
if (!this.resourceManager.initialized) {
await this.resourceManager.initializeWithNewArchitecture()
}
// 1. 获取角色信息
const roleInfo = await this.getRoleInfo(roleId)
logger.debug(`[ActionCommand] getRoleInfo结果:`, roleInfo)
@ -53,10 +55,7 @@ ${COMMANDS.HELLO}
logger.warn(`[ActionCommand] 角色 "${roleId}" 不存在!`)
return `❌ 角色 "${roleId}" 不存在!
🔍 请使用以下命令查看可用角色
\`\`\`bash
${COMMANDS.HELLO}
\`\`\``
🔍 请使用 MCP PromptX 工具的 hello 功能查看可用角色`
}
// 2. 分析角色文件,提取依赖
@ -65,7 +64,7 @@ ${COMMANDS.HELLO}
// 3. 生成学习计划并直接加载所有内容
return await this.generateLearningPlan(roleInfo.id, dependencies)
} catch (error) {
console.error('Action command error:', error)
logger.error('Action command error:', error)
return `❌ 激活角色 "${roleId}" 时发生错误。
🔍 可能的原因:
@ -73,7 +72,7 @@ ${COMMANDS.HELLO}
- 权限不足
- 系统资源问题
💡 请使用 \`${COMMANDS.HELLO}\` 查看可用角色列表。`
💡 请使用 MCP PromptX 工具的 hello 功能查看可用角色列表。`
}
}
@ -158,7 +157,7 @@ ${COMMANDS.HELLO}
}
}
} catch (error) {
console.error('Error analyzing role dependencies:', error)
logger.error('Error analyzing role dependencies:', error)
// 如果分析失败,返回基础结构
return {
thoughts: [],
@ -408,11 +407,11 @@ ${recallContent}
⚠️ **重要**: recall已自动执行完成以上记忆将作为角色工作的重要参考依据
`
} catch (error) {
console.error('Auto recall error:', error)
logger.error('Auto recall error:', error)
return `---
## 🧠 自动记忆检索结果
⚠️ **记忆检索出现问题**: ${error.message}
💡 **建议**: 可手动执行 \`${buildCommand.recall()}\` 来检索相关记忆
💡 **建议**: 可使用 MCP PromptX 工具的 recall 功能来检索相关记忆
`
}
}
@ -425,12 +424,12 @@ ${recallContent}
currentState: 'action_awaiting_role',
availableTransitions: ['hello'],
nextActions: [
{
name: '查看可用角色',
description: '返回角色发现页面',
command: COMMANDS.HELLO,
priority: 'high'
}
{
name: '查看可用角色',
description: '返回角色发现页面',
method: 'MCP PromptX hello 工具',
priority: 'high'
}
],
metadata: {
message: '需要指定角色ID'
@ -445,25 +444,25 @@ ${recallContent}
{
name: '开始专业服务',
description: '角色已激活并完成记忆检索,可直接提供专业服务',
command: '开始对话',
method: '开始对话',
priority: 'high'
},
{
name: '返回角色选择',
description: '选择其他角色',
command: COMMANDS.HELLO,
method: 'MCP PromptX hello 工具',
priority: 'medium'
},
{
name: '记忆新知识',
description: '内化更多专业知识',
command: buildCommand.remember('<新知识>'),
method: 'MCP PromptX remember 工具',
priority: 'low'
},
{
name: '学习新资源',
description: '学习相关专业资源',
command: buildCommand.learn('<protocol>://<resource>'),
method: 'MCP PromptX learn 工具',
priority: 'low'
}
],

View File

@ -1,8 +1,7 @@
const BasePouchCommand = require('../BasePouchCommand')
const fs = require('fs-extra')
const path = require('path')
const { buildCommand } = require('../../../../constants')
const ResourceManager = require('../../resource/resourceManager')
const { getGlobalResourceManager } = require('../../resource')
const logger = require('../../../utils/logger')
/**
@ -12,8 +11,8 @@ const logger = require('../../../utils/logger')
class HelloCommand extends BasePouchCommand {
constructor () {
super()
// 使用新的ResourceManager架构替代SimplifiedRoleDiscovery
this.resourceManager = new ResourceManager()
// 使用全局单例 ResourceManager
this.resourceManager = getGlobalResourceManager()
}
getPurpose () {
@ -22,39 +21,80 @@ class HelloCommand extends BasePouchCommand {
/**
* 动态加载角色注册表 - 使用新的ResourceManager架构
* 移除缓存机制,每次都实时扫描,确保角色发现的一致性
* 直接使用现有资源注册表,避免重复刷新导致的死循环
*/
async loadRoleRegistry () {
try {
// 无状态资源刷新,确保能发现新创建的角色
await this.resourceManager.refreshResources()
// 获取所有角色相关的资源
// 确保ResourceManager已初始化
if (!this.resourceManager.initialized) {
await this.resourceManager.initializeWithNewArchitecture()
}
const roleRegistry = {}
// 从ResourceRegistry中获取所有role:开头的资源
const registry = this.resourceManager.registry
for (const [resourceId, reference] of registry.index) {
if (resourceId.startsWith('role:')) {
const roleId = resourceId.substring(5) // 移除 'role:' 前缀
// 使用新的RegistryData v2.0格式获取角色资源
const registryData = this.resourceManager.registryData
// 检查是否有RegistryDatav2.0格式)
if (registryData && registryData.resources && registryData.resources.length > 0) {
// 使用v2.0格式直接从RegistryData获取角色资源
const roleResources = registryData.getResourcesByProtocol('role')
for (const resource of roleResources) {
const roleId = resource.id
try {
// 尝试加载角色内容以提取元数据
const result = await this.resourceManager.loadResource(resourceId)
if (result.success) {
const name = this.extractRoleNameFromContent(result.content) || roleId
const description = this.extractDescriptionFromContent(result.content) || `${name}专业角色`
roleRegistry[roleId] = {
file: reference,
name,
description,
source: reference.startsWith('@package://') ? 'system' : 'user-generated'
}
// 避免重复角色同一个ID可能有多个来源
if (!roleRegistry[roleId]) {
roleRegistry[roleId] = {
id: resource.id,
name: resource.name,
description: resource.description,
source: resource.source,
file: resource.reference,
protocol: resource.protocol
}
}
}
} else {
// 降级到旧格式处理(向后兼容)
const registry = this.resourceManager.registry
for (const [resourceId, reference] of registry.index) {
let roleId = null
let isRoleResource = false
if (resourceId.startsWith('role:')) {
roleId = resourceId.substring(5)
isRoleResource = true
} else if (resourceId.startsWith('package:') || resourceId.startsWith('project:') || resourceId.startsWith('user:')) {
const parts = resourceId.split(':')
if (parts.length === 2 && !parts[1].includes(':')) {
roleId = parts[1]
isRoleResource = true
}
} else if (!resourceId.includes(':')) {
roleId = resourceId
isRoleResource = true
}
if (isRoleResource && roleId && !roleRegistry[roleId]) {
try {
const result = await this.resourceManager.loadResource(resourceId)
if (result.success) {
const name = this.extractRoleNameFromContent(result.content) || roleId
const description = this.extractDescriptionFromContent(result.content) || `${name}专业角色`
roleRegistry[roleId] = {
id: roleId,
name,
description,
source: reference.startsWith('@package://') ? 'package' : 'project',
file: reference,
protocol: 'role'
}
}
} catch (error) {
// 静默处理,避免干扰用户界面
}
} catch (error) {
// 单个角色加载失败不影响其他角色
logger.warn(`角色${roleId}加载失败: ${error.message}`)
}
}
}
@ -62,24 +102,26 @@ class HelloCommand extends BasePouchCommand {
// 如果没有任何角色,使用基础角色
if (Object.keys(roleRegistry).length === 0) {
roleRegistry.assistant = {
file: '@package://prompt/domain/assistant/assistant.role.md',
id: 'assistant',
name: '🙋 智能助手',
description: '通用助理角色,提供基础的助理服务和记忆支持',
source: 'fallback'
source: 'fallback',
file: '@package://prompt/domain/assistant/assistant.role.md',
protocol: 'role'
}
}
return roleRegistry
} catch (error) {
logger.warn('角色注册表加载失败,使用基础角色:', error.message)
// 使用基础角色作为fallback
return {
assistant: {
file: '@package://prompt/domain/assistant/assistant.role.md',
id: 'assistant',
name: '🙋 智能助手',
description: '通用助理角色,提供基础的助理服务和记忆支持',
source: 'fallback'
source: 'fallback',
file: '@package://prompt/domain/assistant/assistant.role.md',
protocol: 'role'
}
}
}
@ -151,68 +193,79 @@ class HelloCommand extends BasePouchCommand {
*/
getSourceLabel(source) {
switch (source) {
case 'user-generated':
return '(用户生成)'
case 'system':
return '(系统角色)'
case 'package':
return '📦 系统角色'
case 'project':
return '🏗️ 项目角色'
case 'user':
return '<27><> 用户角色'
case 'merged':
return '📦 系统角色' // merged来源的资源主要来自package
case 'fallback':
return '(默认角色)'
return '🔄 默认角色'
default:
return ''
return '❓ 未知来源'
}
}
async getContent (args) {
await this.loadRoleRegistry()
const allRoles = await this.getAllRoles()
const roleRegistry = await this.loadRoleRegistry()
const allRoles = Object.values(roleRegistry)
const totalRoles = allRoles.length
let content = `🤖 **AI专业角色服务清单** (共 ${totalRoles} 个专业角色可供选择)
> 💡 **重要说明**以下是可激活的AI专业角色。每个角色都有唯一的ID使用action命令激活。
> 💡 **重要说明**以下是可激活的AI专业角色。每个角色都有唯一的ID可通过MCP工具激活。
## 📋 可用角色列表
`
// 清楚显示角色ID和激活命令
allRoles.forEach((role, index) => {
const sourceLabel = this.getSourceLabel(role.source)
content += `### ${index + 1}. ${role.name} ${sourceLabel}
// 按来源分组显示角色
const rolesBySource = {}
allRoles.forEach(role => {
const source = role.source || 'unknown'
if (!rolesBySource[source]) {
rolesBySource[source] = []
}
rolesBySource[source].push(role)
})
let roleIndex = 1
// 优先显示系统角色
const sourceOrder = ['package', 'merged', 'project', 'user', 'fallback', 'unknown']
for (const source of sourceOrder) {
if (!rolesBySource[source] || rolesBySource[source].length === 0) continue
const sourceLabel = this.getSourceLabel(source)
content += `### ${sourceLabel}\n\n`
rolesBySource[source].forEach(role => {
content += `#### ${roleIndex}. ${role.name}
**角色ID**: \`${role.id}\`
**专业能力**: ${role.description}
**激活命令**: \`${buildCommand.action(role.id)}\`
**来源**: ${sourceLabel}
---
`
})
roleIndex++
})
}
content += `
## 🎯 **角色激活指南**
### 🔧 激活步骤
1. **选择角色**: 从上述列表中选择需要的专业角色
2. **使用角色ID**: 复制对应的角色ID\`${allRoles[0]?.id || 'assistant'}\`
3. **执行命令**: 运行 \`${buildCommand.action('<角色ID>')}\`
### 📝 激活示例
\`\`\`bash
# 激活智能助手角色
${buildCommand.action(allRoles[0]?.id || 'assistant')}
\`\`\`
### 🔧 激活方式
- 使用 **MCP PromptX 工具** 中的 \`action\` 功能
- 选择需要的角色ID进行激活
### ⚡ 激活后效果
- ✅ **获得专业能力** - AI将具备该角色的专业技能
- ✅ **学习技能组合** - 自动学习角色需要的思维和行为模式
- ✅ **即时专业化** - 立即可以提供该领域的专业服务
---
⚠️ **AI执行指令**
📢 **向主人汇报角色选项,明确说明使用方法:"请选择角色ID然后我将执行对应的action命令"**
🎯 **等待主人指定具体的角色ID后立即执行 \`${buildCommand.action('<角色ID>')}\`**
💡 **强调action命令需要具体的角色ID不是角色名称**
`
return content
@ -223,7 +276,7 @@ ${buildCommand.action(allRoles[0]?.id || 'assistant')}
const availableRoles = allRoles.map(role => ({
roleId: role.id,
name: role.name,
actionCommand: buildCommand.action(role.id)
source: role.source
}))
return {
@ -233,7 +286,7 @@ ${buildCommand.action(allRoles[0]?.id || 'assistant')}
{
name: '向主人汇报服务选项',
description: '将上述专业服务清单告知主人,并询问需求',
command: '等待主人选择后使用: ' + buildCommand.action('<选择的角色ID>'),
method: 'MCP PromptX action 工具',
priority: 'critical',
instruction: '必须先询问主人需求,不要自主选择角色'
}
@ -243,7 +296,7 @@ ${buildCommand.action(allRoles[0]?.id || 'assistant')}
availableRoles,
dataSource: 'resource.registry.json',
systemVersion: '锦囊串联状态机 v1.0',
designPhilosophy: 'AI use CLI get prompt for AI'
designPhilosophy: 'AI use MCP tools for role activation'
}
}
}
@ -298,27 +351,27 @@ ${buildCommand.action(allRoles[0]?.id || 'assistant')}
async debugRegistry() {
await this.loadRoleRegistry()
console.log('\n🔍 HelloCommand - 注册表调试信息')
console.log('='.repeat(50))
logger.info('\n🔍 HelloCommand - 注册表调试信息')
logger.info('='.repeat(50))
if (this.roleRegistry && Object.keys(this.roleRegistry).length > 0) {
console.log(`📊 发现 ${Object.keys(this.roleRegistry).length} 个角色资源:\n`)
logger.info(`📊 发现 ${Object.keys(this.roleRegistry).length} 个角色资源:\n`)
Object.entries(this.roleRegistry).forEach(([id, roleInfo]) => {
console.log(`🎭 ${id}`)
console.log(` 名称: ${roleInfo.name || '未命名'}`)
console.log(` 描述: ${roleInfo.description || '无描述'}`)
console.log(` 文件: ${roleInfo.file}`)
console.log(` 来源: ${roleInfo.source || '未知'}`)
console.log('')
logger.info(`🎭 ${id}`)
logger.info(` 名称: ${roleInfo.name || '未命名'}`)
logger.info(` 描述: ${roleInfo.description || '无描述'}`)
logger.info(` 文件: ${roleInfo.file}`)
logger.info(` 来源: ${roleInfo.source || '未知'}`)
logger.info('')
})
} else {
console.log('🔍 没有发现任何角色资源')
logger.info('🔍 没有发现任何角色资源')
}
// 同时显示ResourceManager的注册表
console.log('\n📋 ResourceManager 注册表:')
console.log('-'.repeat(30))
logger.info('\n📋 ResourceManager 注册表:')
logger.info('-'.repeat(30))
this.resourceManager.registry.printAll('底层资源注册表')
}
}

View File

@ -1,7 +1,10 @@
const BasePouchCommand = require('../BasePouchCommand')
const { ResourceManager } = require('../../resource')
const { getGlobalResourceManager } = require('../../resource')
const { COMMANDS } = require('../../../../constants')
const PromptXConfig = require('../../../utils/promptxConfig')
const RegistryData = require('../../resource/RegistryData')
const ProjectDiscovery = require('../../resource/discovery/ProjectDiscovery')
const logger = require('../../../utils/logger')
const path = require('path')
const fs = require('fs-extra')
@ -12,11 +15,13 @@ const fs = require('fs-extra')
class InitCommand extends BasePouchCommand {
constructor () {
super()
this.resourceManager = new ResourceManager()
// 使用全局单例 ResourceManager
this.resourceManager = getGlobalResourceManager()
this.projectDiscovery = new ProjectDiscovery()
}
getPurpose () {
return '初始化PromptX工作环境创建必要的配置目录和文件'
return '初始化PromptX工作环境创建必要的配置目录和文件,生成项目级资源注册表'
}
async getContent (args) {
@ -28,6 +33,12 @@ class InitCommand extends BasePouchCommand {
// 2. 基础环境准备 - 只创建 .promptx 目录
await this.ensurePromptXDirectory(workspacePath)
// 3. 生成项目级资源注册表
const registryStats = await this.generateProjectRegistry(workspacePath)
// 4. 刷新全局 ResourceManager确保新资源立即可用
await this.refreshGlobalResourceManager()
return `🎯 PromptX 初始化完成!
## 📦 版本信息
@ -37,13 +48,68 @@ class InitCommand extends BasePouchCommand {
✅ 创建了 \`.promptx\` 配置目录
✅ 工作环境就绪
## 📋 项目资源注册表
${registryStats.message}
## 🚀 下一步建议
- 使用 \`hello\` 发现可用的专业角色
- 使用 \`action\` 激活特定角色获得专业能力
- 使用 \`learn\` 深入学习专业知识
- 使用 \`remember/recall\` 管理专业记忆
💡 **提示**: 现在可以开始使用专业角色系统来增强AI能力了`
💡 **提示**: ${registryStats.totalResources > 0 ? '项目资源已优化为注册表模式,性能大幅提升!' : '现在可以开始创建项目级资源了!'}`
}
/**
* 生成项目级资源注册表
* @param {string} workspacePath - 工作目录路径
* @returns {Promise<Object>} 注册表生成统计信息
*/
async generateProjectRegistry(workspacePath) {
try {
// 1. 获取项目根目录
const projectRoot = await this.projectDiscovery._findProjectRoot()
// 2. 确保 .promptx/resource/domain 目录结构存在
const resourceDir = path.join(projectRoot, '.promptx', 'resource')
const domainDir = path.join(resourceDir, 'domain')
await fs.ensureDir(domainDir)
logger.debug(`[InitCommand] 确保目录结构存在: ${domainDir}`)
// 3. 使用 ProjectDiscovery 的正确方法生成注册表
logger.step('正在扫描项目资源...')
const registryData = await this.projectDiscovery.generateRegistry(projectRoot)
// 4. 生成统计信息
const stats = registryData.getStats()
const registryPath = path.join(projectRoot, '.promptx', 'resource', 'project.registry.json')
if (registryData.size === 0) {
return {
message: `✅ 项目资源目录已创建,注册表已初始化
📂 目录: ${path.relative(process.cwd(), domainDir)}
💾 注册表: ${path.relative(process.cwd(), registryPath)}
💡 现在可以在 domain 目录下创建角色资源了`,
totalResources: 0
}
}
return {
message: `✅ 项目资源注册表已重新生成
📊 总计: ${registryData.size} 个资源
📋 分类: role(${stats.byProtocol.role || 0}), thought(${stats.byProtocol.thought || 0}), execution(${stats.byProtocol.execution || 0}), knowledge(${stats.byProtocol.knowledge || 0})
💾 位置: ${path.relative(process.cwd(), registryPath)}`,
totalResources: registryData.size
}
} catch (error) {
logger.error('生成项目注册表时出错:', error)
return {
message: `❌ 生成项目注册表失败: ${error.message}`,
totalResources: 0
}
}
}
/**
@ -56,6 +122,24 @@ class InitCommand extends BasePouchCommand {
await config.ensureDir()
}
/**
* 刷新全局 ResourceManager
* 确保新创建的资源立即可用,无需重启 MCP Server
*/
async refreshGlobalResourceManager() {
try {
logger.debug('[InitCommand] 刷新全局 ResourceManager...')
// 重新初始化 ResourceManager清除缓存并重新发现资源
await this.resourceManager.initializeWithNewArchitecture()
logger.debug('[InitCommand] 全局 ResourceManager 刷新完成')
} catch (error) {
logger.warn(`[InitCommand] 刷新 ResourceManager 失败: ${error.message}`)
// 不抛出错误,避免影响 init 命令的主要功能
}
}
/**
* 获取版本信息
*/
@ -71,7 +155,7 @@ class InitCommand extends BasePouchCommand {
return `${baseVersion} (${packageName}@${baseVersion}, Node.js ${nodeVersion})`
}
} catch (error) {
console.warn('⚠️ 无法读取版本信息:', error.message)
logger.warn('无法读取版本信息:', error.message)
}
return '未知版本'
}
@ -102,6 +186,8 @@ class InitCommand extends BasePouchCommand {
}
}
}
}
module.exports = InitCommand

View File

@ -1,19 +1,25 @@
const BasePouchCommand = require('../BasePouchCommand')
const ResourceManager = require('../../resource/resourceManager')
const { COMMANDS, buildCommand } = require('../../../../constants')
const { getGlobalResourceManager } = require('../../resource')
const DPMLContentParser = require('../../resource/DPMLContentParser')
const SemanticRenderer = require('../../resource/SemanticRenderer')
const { COMMANDS } = require('../../../../constants')
/**
* 智能学习锦囊命令
* 支持加载thought、execution、memory等协议资源以及角色的personality、principle、knowledge
* 支持语义占位符渲染,将@引用展开为完整的语义内容
*/
class LearnCommand extends BasePouchCommand {
constructor () {
super()
this.resourceManager = new ResourceManager()
// 使用全局单例 ResourceManager
this.resourceManager = getGlobalResourceManager()
this.dpmlParser = new DPMLContentParser()
this.semanticRenderer = new SemanticRenderer()
}
getPurpose () {
return '智能学习指定协议的资源内容支持thought、execution、memory等DPML协议以及角色组件'
return '智能学习指定协议的资源内容支持thought、execution、memory等DPML协议以及角色组件,支持@引用的语义渲染'
}
async getContent (args) {
@ -24,13 +30,6 @@ class LearnCommand extends BasePouchCommand {
}
try {
// 直接使用ResourceManager解析资源
const result = await this.resourceManager.resolve(resourceUrl)
if (!result.success) {
return this.formatErrorResponse(resourceUrl, result.error.message)
}
// 解析协议信息
const urlMatch = resourceUrl.match(/^(@[!?]?)?([a-zA-Z][a-zA-Z0-9_-]*):\/\/(.+)$/)
if (!urlMatch) {
@ -39,12 +38,62 @@ class LearnCommand extends BasePouchCommand {
const [, loadingSemantic, protocol, resourceId] = urlMatch
return this.formatSuccessResponse(protocol, resourceId, result.content)
// 使用ResourceManager解析资源
const result = await this.resourceManager.resolve(resourceUrl)
if (!result.success) {
return this.formatErrorResponse(resourceUrl, result.error.message)
}
// 检查内容是否包含@引用,如果包含则进行语义渲染
let finalContent = result.content
if (this.containsReferences(result.content)) {
// 对于完整的DPML标签如<execution>...</execution>),提取标签内容进行渲染
const innerContent = this.extractTagInnerContent(result.content, protocol)
if (innerContent) {
// 解析标签内的混合内容(@引用 + 直接内容)
const tagSemantics = this.dpmlParser.parseTagContent(innerContent, protocol)
// 使用SemanticRenderer进行语义占位符渲染
const renderedInnerContent = await this.semanticRenderer.renderSemanticContent(tagSemantics, this.resourceManager)
// 如果渲染成功重新包装为完整的DPML标签
if (renderedInnerContent && renderedInnerContent.trim()) {
finalContent = `<${protocol}>\n${renderedInnerContent}\n</${protocol}>`
}
}
}
return this.formatSuccessResponse(protocol, resourceId, finalContent)
} catch (error) {
return this.formatErrorResponse(resourceUrl, error.message)
}
}
/**
* 检查内容是否包含@引用
* @param {string} content - 要检查的内容
* @returns {boolean} 是否包含@引用
*/
containsReferences(content) {
const resourceRegex = /@([!?]?)([a-zA-Z][a-zA-Z0-9_-]*):\/\/([a-zA-Z0-9_\/.,-]+)/g
return resourceRegex.test(content)
}
/**
* 提取完整的DPML标签内容
* @param {string} content - 要提取的内容
* @param {string} protocol - 协议
* @returns {string} 提取的完整DPML标签内容
*/
extractTagInnerContent(content, protocol) {
const tagRegex = new RegExp(`<${protocol}>([\\s\\S]*?)<\\/${protocol}>`, 'i')
const match = content.match(tagRegex)
return match ? match[1].trim() : null
}
/**
* 格式化成功响应
*/
@ -72,12 +121,9 @@ ${content}
- ✅ **可立即应用于实际场景**
## 🔄 下一步行动:
- 继续学习: 学习其他相关资源
命令: \`${buildCommand.learn('<protocol>://<resource-id>')}\`
- 应用记忆: 检索相关经验
命令: \`${COMMANDS.RECALL}\`
- 激活角色: 激活完整角色能力
命令: \`${buildCommand.action('<role-id>')}\`
- 继续学习: 使用 MCP PromptX learn 工具学习其他相关资源
- 应用记忆: 使用 MCP PromptX recall 工具检索相关经验
- 激活角色: 使用 MCP PromptX action 工具激活完整角色能力
📍 当前状态learned_${protocol}`
}
@ -100,19 +146,13 @@ ${errorMessage}
- \`knowledge://role-id\` - 学习角色知识
🔍 查看可用资源:
\`\`\`bash
${buildCommand.action('<role-id>')} # 查看角色的所有依赖
\`\`\`
使用 MCP PromptX action 工具查看角色的所有依赖
🔄 下一步行动:
- 继续学习: 学习其他资源
命令: ${buildCommand.learn('<protocol>://<resource-id>')}
- 应用记忆: 检索相关经验
命令: ${COMMANDS.RECALL}
- 激活角色: 激活完整角色能力
命令: ${buildCommand.action('<role-id>')}
- 查看角色列表: 选择其他角色
命令: ${COMMANDS.HELLO}`
- 继续学习: 使用 MCP PromptX learn 工具学习其他资源
- 应用记忆: 使用 MCP PromptX recall 工具检索相关经验
- 激活角色: 使用 MCP PromptX action 工具激活完整角色能力
- 查看角色列表: 使用 MCP PromptX hello 工具选择其他角色`
}
/**
@ -122,9 +162,8 @@ ${buildCommand.action('<role-id>')} # 查看角色的所有依赖
return `🎓 **Learn锦囊 - 智能学习系统**
## 📖 基本用法
\`\`\`bash
promptx learn <protocol>://<resource-id>
\`\`\`
通过 MCP PromptX learn 工具学习资源:
\`<protocol>://<resource-id>\`
## 🎯 支持的协议
@ -139,28 +178,18 @@ promptx learn <protocol>://<resource-id>
- **\`knowledge://\`** - 专业知识
## 📝 使用示例
\`\`\`bash
# 学习执行技能
${buildCommand.learn('execution://deal-at-reference')}
# 学习思维模式
${buildCommand.learn('thought://prompt-developer')}
# 学习角色人格
${buildCommand.learn('personality://video-copywriter')}
\`\`\`
通过 MCP PromptX learn 工具学习各种资源:
- 学习执行技能: \`execution://deal-at-reference\`
- 学习思维模式: \`thought://prompt-developer\`
- 学习角色人格: \`personality://video-copywriter\`
## 🔍 发现可学习资源
\`\`\`bash
${buildCommand.action('<role-id>')} # 查看角色需要的所有资源
${COMMANDS.HELLO} # 查看可用角色列表
\`\`\`
- 使用 MCP PromptX action 工具查看角色需要的所有资源
- 使用 MCP PromptX hello 工具查看可用角色列表
🔄 下一步行动:
- 激活角色: 分析角色依赖
命令: ${buildCommand.action('<role-id>')}
- 查看角色: 选择感兴趣的角色
命令: ${COMMANDS.HELLO}`
- 激活角色: 使用 MCP PromptX action 工具分析角色依赖
- 查看角色: 使用 MCP PromptX hello 工具选择感兴趣的角色`
}
/**
@ -177,13 +206,13 @@ ${COMMANDS.HELLO} # 查看可用角色列表
{
name: '查看可用角色',
description: '返回角色选择页面',
command: COMMANDS.HELLO,
method: 'MCP PromptX hello 工具',
priority: 'high'
},
{
name: '生成学习计划',
description: '为特定角色生成学习计划',
command: buildCommand.action('<role-id>'),
method: 'MCP PromptX action 工具',
priority: 'high'
}
]
@ -199,7 +228,7 @@ ${COMMANDS.HELLO} # 查看可用角色列表
{
name: '查看使用帮助',
description: '重新学习命令使用方法',
command: COMMANDS.LEARN,
method: 'MCP PromptX learn 工具',
priority: 'high'
}
]
@ -215,25 +244,25 @@ ${COMMANDS.HELLO} # 查看可用角色列表
{
name: '继续学习',
description: '学习其他资源',
command: buildCommand.learn('<protocol>://<resource-id>'),
method: 'MCP PromptX learn 工具',
priority: 'medium'
},
{
name: '应用记忆',
description: '检索相关经验',
command: COMMANDS.RECALL,
method: 'MCP PromptX recall 工具',
priority: 'medium'
},
{
name: '激活角色',
description: '激活完整角色能力',
command: buildCommand.action('<role-id>'),
method: 'MCP PromptX action 工具',
priority: 'high'
},
{
name: '查看角色列表',
description: '选择其他角色',
command: COMMANDS.HELLO,
method: 'MCP PromptX hello 工具',
priority: 'low'
}
],

View File

@ -0,0 +1,279 @@
const BasePouchCommand = require('../BasePouchCommand')
const ResourceManager = require('../../resource/resourceManager')
const DPMLContentParser = require('../../resource/DPMLContentParser')
const SemanticRenderer = require('../../resource/SemanticRenderer')
const { COMMANDS } = require('../../../../constants')
/**
* 智能学习锦囊命令
* 支持加载thought、execution、memory等协议资源以及角色的personality、principle、knowledge
* 支持语义占位符渲染,将@引用展开为完整的语义内容
*/
class LearnCommand extends BasePouchCommand {
constructor () {
super()
this.resourceManager = new ResourceManager()
this.dpmlParser = new DPMLContentParser()
this.semanticRenderer = new SemanticRenderer()
}
getPurpose () {
return '智能学习指定协议的资源内容支持thought、execution、memory等DPML协议以及角色组件支持@引用的语义渲染'
}
async getContent (args) {
const [resourceUrl] = args
if (!resourceUrl) {
return this.getUsageHelp()
}
try {
// 解析协议信息
const urlMatch = resourceUrl.match(/^(@[!?]?)?([a-zA-Z][a-zA-Z0-9_-]*):\/\/(.+)$/)
if (!urlMatch) {
return this.formatErrorResponse(resourceUrl, '无效的资源URL格式')
}
const [, loadingSemantic, protocol, resourceId] = urlMatch
// 使用ResourceManager解析资源
const result = await this.resourceManager.resolve(resourceUrl)
if (!result.success) {
return this.formatErrorResponse(resourceUrl, result.error.message)
}
// 检查内容是否包含@引用,如果包含则进行语义渲染
let finalContent = result.content
if (this.containsReferences(result.content)) {
// 对于完整的DPML标签如<execution>...</execution>),提取标签内容进行渲染
const innerContent = this.extractTagInnerContent(result.content, protocol)
if (innerContent) {
// 解析标签内的混合内容(@引用 + 直接内容)
const tagSemantics = this.dpmlParser.parseTagContent(innerContent, protocol)
// 使用SemanticRenderer进行语义占位符渲染
const renderedInnerContent = await this.semanticRenderer.renderSemanticContent(tagSemantics, this.resourceManager)
// 如果渲染成功重新包装为完整的DPML标签
if (renderedInnerContent && renderedInnerContent.trim()) {
finalContent = `<${protocol}>\n${renderedInnerContent}\n</${protocol}>`
}
}
}
return this.formatSuccessResponse(protocol, resourceId, finalContent)
} catch (error) {
return this.formatErrorResponse(resourceUrl, error.message)
}
}
/**
* 检查内容是否包含@引用
* @param {string} content - 要检查的内容
* @returns {boolean} 是否包含@引用
*/
containsReferences(content) {
const resourceRegex = /@([!?]?)([a-zA-Z][a-zA-Z0-9_-]*):\/\/([a-zA-Z0-9_\/.,-]+)/g
return resourceRegex.test(content)
}
/**
* 提取完整的DPML标签内容
* @param {string} content - 要提取的内容
* @param {string} protocol - 协议
* @returns {string} 提取的完整DPML标签内容
*/
extractTagInnerContent(content, protocol) {
const tagRegex = new RegExp(`<${protocol}>([\\s\\S]*?)<\\/${protocol}>`, 'i')
const match = content.match(tagRegex)
return match ? match[1].trim() : null
}
/**
* 格式化成功响应
*/
formatSuccessResponse (protocol, resourceId, content) {
const protocolLabels = {
thought: '🧠 思维模式',
execution: '⚡ 执行模式',
memory: '💾 记忆模式',
personality: '👤 角色人格',
principle: '⚖️ 行为原则',
knowledge: '📚 专业知识'
}
const label = protocolLabels[protocol] || `📄 ${protocol}`
return `✅ **成功学习${label}${resourceId}**
## 📋 学习内容
${content}
## 🎯 学习效果
- ✅ **已激活${label}能力**
- ✅ **相关知识已整合到AI认知体系**
- ✅ **可立即应用于实际场景**
## 🔄 下一步行动:
- 继续学习: 使用 MCP PromptX learn 工具学习其他相关资源
- 应用记忆: 使用 MCP PromptX recall 工具检索相关经验
- 激活角色: 使用 MCP PromptX action 工具激活完整角色能力
📍 当前状态learned_${protocol}`
}
/**
* 格式化错误响应
*/
formatErrorResponse (resourceUrl, errorMessage) {
return `❌ 学习资源失败:${resourceUrl}
🔍 错误详情:
${errorMessage}
💡 支持的协议:
- \`thought://resource-id\` - 学习思维模式
- \`execution://resource-id\` - 学习执行模式
- \`memory://resource-id\` - 学习记忆模式
- \`personality://role-id\` - 学习角色思维
- \`principle://role-id\` - 学习角色原则
- \`knowledge://role-id\` - 学习角色知识
🔍 查看可用资源:
使用 MCP PromptX action 工具查看角色的所有依赖
🔄 下一步行动:
- 继续学习: 使用 MCP PromptX learn 工具学习其他资源
- 应用记忆: 使用 MCP PromptX recall 工具检索相关经验
- 激活角色: 使用 MCP PromptX action 工具激活完整角色能力
- 查看角色列表: 使用 MCP PromptX hello 工具选择其他角色`
}
/**
* 获取使用帮助
*/
getUsageHelp () {
return `🎓 **Learn锦囊 - 智能学习系统**
## 📖 基本用法
\`\`\`bash
promptx learn <protocol>://<resource-id>
\`\`\`
## 🎯 支持的协议
### 🔧 DPML核心协议
- **\`thought://\`** - 思维模式资源
- **\`execution://\`** - 执行模式资源
- **\`memory://\`** - 记忆系统资源
### 👤 角色组件协议
- **\`personality://\`** - 角色人格特征
- **\`principle://\`** - 行为原则
- **\`knowledge://\`** - 专业知识
## 📝 使用示例
通过 MCP PromptX learn 工具学习各种资源:
- 学习执行技能: `execution://deal-at-reference`
- 学习思维模式: `thought://prompt-developer`
- 学习角色人格: `personality://video-copywriter`
## 🔍 发现可学习资源
- 使用 MCP PromptX action 工具查看角色需要的所有资源
- 使用 MCP PromptX hello 工具查看可用角色列表
🔄 下一步行动:
- 激活角色: 使用 MCP PromptX action 工具分析角色依赖
- 查看角色: 使用 MCP PromptX hello 工具选择感兴趣的角色`
}
/**
* 获取PATEOAS导航信息
*/
getPATEOAS (args) {
const [resourceUrl] = args
if (!resourceUrl) {
return {
currentState: 'learn_awaiting_resource',
availableTransitions: ['hello', 'action'],
nextActions: [
{
name: '查看可用角色',
description: '返回角色选择页面',
command: COMMANDS.HELLO,
priority: 'high'
},
{
name: '生成学习计划',
description: '为特定角色生成学习计划',
method: 'MCP PromptX action 工具',
priority: 'high'
}
]
}
}
const urlMatch = resourceUrl.match(/^([a-zA-Z]+):\/\/(.+)$/)
if (!urlMatch) {
return {
currentState: 'learn_error',
availableTransitions: ['hello', 'action'],
nextActions: [
{
name: '查看使用帮助',
description: '重新学习命令使用方法',
command: COMMANDS.LEARN,
priority: 'high'
}
]
}
}
const [, protocol, resourceId] = urlMatch
return {
currentState: `learned_${protocol}`,
availableTransitions: ['learn', 'recall', 'hello', 'action'],
nextActions: [
{
name: '继续学习',
description: '学习其他资源',
command: buildCommand.learn('<protocol>://<resource-id>'),
priority: 'medium'
},
{
name: '应用记忆',
description: '检索相关经验',
command: COMMANDS.RECALL,
priority: 'medium'
},
{
name: '激活角色',
description: '激活完整角色能力',
command: buildCommand.action('<role-id>'),
priority: 'high'
},
{
name: '查看角色列表',
description: '选择其他角色',
command: COMMANDS.HELLO,
priority: 'low'
}
],
metadata: {
learnedResource: resourceUrl,
protocol,
resourceId,
systemVersion: '锦囊串联状态机 v1.0'
}
}
}
}
module.exports = LearnCommand

View File

@ -1,7 +1,7 @@
const BasePouchCommand = require('../BasePouchCommand')
const fs = require('fs-extra')
const path = require('path')
const { COMMANDS, buildCommand } = require('../../../../constants')
const { COMMANDS } = require('../../../../constants')
/**
* 记忆检索锦囊命令
@ -25,8 +25,8 @@ class RecallCommand extends BasePouchCommand {
if (memories.length === 0) {
return `🧠 AI记忆体系中暂无内容。
💡 建议:
1. 使用 ${COMMANDS.REMEMBER} 内化新知识
2. 使用 ${COMMANDS.LEARN} 学习后再内化
1. 使用 MCP PromptX remember 工具内化新知识
2. 使用 MCP PromptX learn 工具学习后再内化
3. 开始构建AI的专业知识体系`
}
@ -54,22 +54,22 @@ ${formattedMemories}
{
name: '选择角色',
description: '选择专业角色来应用检索到的知识',
command: COMMANDS.HELLO
method: 'MCP PromptX hello 工具'
},
{
name: '记忆新知识',
description: '继续内化更多专业知识',
command: COMMANDS.REMEMBER + ' "<新的知识内容>"'
method: 'MCP PromptX remember 工具'
},
{
name: '学习资源',
description: '学习相关专业资源',
command: COMMANDS.LEARN + ' <protocol>://<resource>'
method: 'MCP PromptX learn 工具'
},
{
name: '继续检索',
description: '检索其他相关记忆',
command: COMMANDS.RECALL + ' <关键词>'
method: 'MCP PromptX recall 工具'
}
],
metadata: {

View File

@ -2,7 +2,6 @@ const BasePouchCommand = require('../BasePouchCommand')
const fs = require('fs-extra')
const path = require('path')
const PackageProtocol = require('../../resource/protocols/PackageProtocol')
const { buildCommand } = require('../../../../constants')
/**
* 角色注册锦囊命令
@ -25,14 +24,10 @@ class RegisterCommand extends BasePouchCommand {
return `❌ 请指定要注册的角色ID
🔍 使用方法:
\`\`\`bash
${buildCommand.register('<角色ID>')}
\`\`\`
通过 MCP PromptX register 工具注册角色
💡 例如:
\`\`\`bash
${buildCommand.register('my-custom-role')}
\`\`\``
注册角色ID: 'my-custom-role'`
}
try {
@ -47,9 +42,7 @@ ${buildCommand.register('my-custom-role')}
- prompt/domain/${roleId}/execution/${roleId}.execution.md
💡 您可以使用女娲来创建完整的角色套件:
\`\`\`bash
${buildCommand.action('nuwa')}
\`\`\``
使用 MCP PromptX action 工具激活 'nuwa' 角色`
}
// 2. 提取角色元数据
@ -67,9 +60,7 @@ ${buildCommand.action('nuwa')}
- 文件路径:${roleMetadata.filePath}
🎯 **下一步操作**
\`\`\`bash
${buildCommand.action(roleId)}
\`\`\`
使用 MCP PromptX action 工具激活角色: ${roleId}
💡 现在您可以激活这个角色了!`
} else {
@ -188,13 +179,13 @@ ${buildCommand.action(roleId)}
{
name: '查看可用角色',
description: '查看已注册的角色',
command: buildCommand.hello(),
method: 'MCP PromptX hello 工具',
priority: 'medium'
},
{
name: '创建新角色',
description: '使用女娲创建新角色',
command: buildCommand.action('nuwa'),
method: 'MCP PromptX action 工具 (nuwa)',
priority: 'high'
}
],
@ -211,13 +202,13 @@ ${buildCommand.action(roleId)}
{
name: '激活角色',
description: '激活刚注册的角色',
command: buildCommand.action(roleId),
method: `MCP PromptX action 工具 (${roleId})`,
priority: 'high'
},
{
name: '查看所有角色',
description: '查看角色列表',
command: buildCommand.hello(),
method: 'MCP PromptX hello 工具',
priority: 'medium'
}
],

View File

@ -1,7 +1,7 @@
const BasePouchCommand = require('../BasePouchCommand')
const fs = require('fs-extra')
const path = require('path')
const { COMMANDS, buildCommand } = require('../../../../constants')
const { COMMANDS } = require('../../../../constants')
/**
* 记忆保存锦囊命令
@ -153,12 +153,9 @@ ${memoryLine}
- ✅ **支持跨会话记忆保持**
## 🔄 下一步行动:
- 记忆检索: 验证知识内化效果
命令: \`${buildCommand.recall(value)}\`
- 能力强化: 学习相关知识增强记忆
命令: \`${buildCommand.learn('<protocol>://<resource-id>')}\`
- 应用实践: 在实际场景中运用记忆
命令: \`${buildCommand.action('<role-id>')}\`
- 记忆检索: 使用 MCP PromptX recall 工具验证知识内化效果
- 能力强化: 使用 MCP PromptX learn 工具学习相关知识增强记忆
- 应用实践: 使用 MCP PromptX action 工具在实际场景中运用记忆
📍 当前状态memory_saved`
}
@ -170,32 +167,24 @@ ${memoryLine}
return `🧠 **Remember锦囊 - AI记忆增强系统**
## 📖 基本用法
\`\`\`bash
${buildCommand.remember('<知识内容>')}
\`\`\`
通过 MCP PromptX remember 工具内化知识
## 💡 记忆内化示例
### 📝 AI记忆内化
AI学习和内化各种专业知识
\`\`\`bash
${buildCommand.remember('"构建代码 → 运行测试 → 部署到staging → 验证功能 → 发布生产"')}
${buildCommand.remember('"用户反馈视频加载慢排查发现是CDN配置问题修改后加载速度提升60%"')}
${buildCommand.remember('"React Hooks允许在函数组件中使用state和其他React特性"')}
${buildCommand.remember('"每个PR至少需要2个人review必须包含测试用例"')}
\`\`\`
AI学习和内化各种专业知识
- "构建代码 → 运行测试 → 部署到staging → 验证功能 → 发布生产"
- "用户反馈视频加载慢排查发现是CDN配置问题修改后加载速度提升60%"
- "React Hooks允许在函数组件中使用state和其他React特性"
- "每个PR至少需要2个人review必须包含测试用例"
## 🔍 记忆检索与应用
\`\`\`bash
${buildCommand.recall('<关键词>')} # AI主动检索记忆
${buildCommand.action('<role-id>')} # AI运用记忆激活角色
\`\`\`
- 使用 MCP PromptX recall 工具主动检索记忆
- 使用 MCP PromptX action 工具运用记忆激活角色
🔄 下一步行动:
- 开始记忆: 内化第一条知识
命令: ${buildCommand.remember('<content>')}
- 学习资源: 学习新知识再内化
命令: ${buildCommand.learn('<protocol>://<resource>')}`
- 开始记忆: 使用 MCP PromptX remember 工具内化第一条知识
- 学习资源: 使用 MCP PromptX learn 工具学习新知识再内化`
}
/**
@ -212,13 +201,13 @@ ${buildCommand.action('<role-id>')} # AI运用记忆激活角色
{
name: '查看角色',
description: '选择角色获取专业知识',
command: COMMANDS.HELLO,
method: 'MCP PromptX hello 工具',
priority: 'medium'
},
{
name: '学习资源',
description: '学习新知识然后保存',
command: buildCommand.learn('<protocol>://<resource>'),
method: 'MCP PromptX learn 工具',
priority: 'high'
}
]
@ -232,25 +221,25 @@ ${buildCommand.action('<role-id>')} # AI运用记忆激活角色
{
name: '检索记忆',
description: '测试记忆是否可检索',
command: buildCommand.recall('<关键词>'),
method: 'MCP PromptX recall 工具',
priority: 'high'
},
{
name: '学习强化',
description: '学习相关知识加强记忆',
command: buildCommand.learn('<protocol>://<resource>'),
method: 'MCP PromptX learn 工具',
priority: 'medium'
},
{
name: '应用记忆',
description: '在实际场景中应用记忆',
command: buildCommand.action('<role-id>'),
method: 'MCP PromptX action 工具',
priority: 'medium'
},
{
name: '继续内化',
description: 'AI继续内化更多知识',
command: buildCommand.remember('<content>'),
method: 'MCP PromptX remember 工具',
priority: 'low'
}
],

View File

@ -1,3 +1,5 @@
const logger = require('../../utils/logger')
/**
* EnhancedResourceRegistry - 增强的资源注册表
*
@ -64,7 +66,7 @@ class EnhancedResourceRegistry {
this.register(resource)
}
} catch (error) {
console.warn(`[EnhancedResourceRegistry] Failed to register resource: ${error.message}`)
logger.warn(`[EnhancedResourceRegistry] Failed to register resource: ${error.message}`)
}
})
}
@ -212,7 +214,7 @@ class EnhancedResourceRegistry {
*/
loadFromDiscoveryResults(discoveryResults) {
if (!Array.isArray(discoveryResults)) {
console.warn('[EnhancedResourceRegistry] Discovery results must be an array')
logger.warn('[EnhancedResourceRegistry] Discovery results must be an array')
return
}

View File

@ -0,0 +1,325 @@
const fs = require('fs-extra')
const path = require('path')
const ResourceData = require('./ResourceData')
/**
* 注册表数据管理器 v2.0
* 基于ResourceData数组的全新架构严格区分资源来源(source)和资源种类(protocol)
*/
class RegistryData {
/**
* @param {string} source - 注册表来源 ('package' | 'project' | 'user')
* @param {string} filePath - 注册表文件路径
* @param {Array<ResourceData>} resources - 资源数据数组
* @param {Object} metadata - 注册表元数据
*/
constructor(source, filePath, resources = [], metadata = {}) {
this.source = source
this.filePath = filePath
this.resources = resources.map(r => r instanceof ResourceData ? r : ResourceData.fromRawData(r))
this.metadata = {
version: "2.0.0",
description: `${source} 级资源注册表`,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
...metadata
}
this.cache = new Map()
}
/**
* 从文件加载注册表数据
* @param {string} source - 注册表来源
* @param {string} filePath - 文件路径
* @returns {Promise<RegistryData>} 注册表数据实例
*/
static async fromFile(source, filePath) {
try {
const data = await fs.readJSON(filePath)
// 处理新格式v2.0
if (data.version === "2.0.0" && Array.isArray(data.resources)) {
return new RegistryData(source, filePath, data.resources, data.metadata)
}
// 处理旧格式v1.0- 自动转换
if (data.resources && typeof data.resources === 'object') {
const resources = []
for (const [protocol, resourcesOfType] of Object.entries(data.resources)) {
if (resourcesOfType && typeof resourcesOfType === 'object') {
for (const [id, reference] of Object.entries(resourcesOfType)) {
resources.push(ResourceData.fromFilePath(
reference.replace(/^@\w+:\/\//, ''),
source,
protocol,
reference
))
}
}
}
return new RegistryData(source, filePath, resources, {
migratedFrom: "v1.0.0",
originalTimestamp: data.timestamp
})
}
throw new Error(`Unsupported registry format in ${filePath}`)
} catch (error) {
throw new Error(`Failed to load ${source} registry from ${filePath}: ${error.message}`)
}
}
/**
* 创建空的注册表数据
* @param {string} source - 注册表来源
* @param {string} filePath - 注册表文件路径
* @returns {RegistryData} 空注册表数据实例
*/
static createEmpty(source, filePath) {
return new RegistryData(source, filePath, [], {
description: `${source} 级资源注册表`,
createdAt: new Date().toISOString()
})
}
/**
* 添加资源
* @param {ResourceData|Object} resource - 资源数据
*/
addResource(resource) {
const resourceData = resource instanceof ResourceData ? resource : ResourceData.fromRawData(resource)
// 对于merged类型的注册表保持原始来源信息
// 只有在非merged注册表中才强制统一来源
if (this.source !== 'merged' && resourceData.source !== this.source) {
resourceData.source = this.source
}
// 检查是否已存在相同ID的资源
const existingIndex = this.resources.findIndex(r => r.id === resourceData.id && r.protocol === resourceData.protocol)
if (existingIndex >= 0) {
// 更新现有资源
this.resources[existingIndex] = resourceData
} else {
// 添加新资源
this.resources.push(resourceData)
}
this._updateMetadata()
this.cache.clear()
}
/**
* 移除资源
* @param {string} id - 资源ID
* @param {string} protocol - 资源协议
* @returns {boolean} 是否成功移除
*/
removeResource(id, protocol) {
const initialLength = this.resources.length
this.resources = this.resources.filter(r => !(r.id === id && r.protocol === protocol))
const removed = this.resources.length < initialLength
if (removed) {
this._updateMetadata()
this.cache.clear()
}
return removed
}
/**
* 查找资源
* @param {Object} filters - 过滤条件
* @returns {Array<ResourceData>} 匹配的资源数组
*/
findResources(filters = {}) {
return this.resources.filter(resource => resource.matches(filters))
}
/**
* 根据ID查找资源
* @param {string} id - 资源ID
* @param {string} protocol - 资源协议(可选)
* @returns {ResourceData|null} 找到的资源
*/
findResourceById(id, protocol = null) {
return this.resources.find(r => {
if (protocol) {
return r.id === id && r.protocol === protocol
}
return r.id === id
}) || null
}
/**
* 获取指定协议类型的所有资源
* @param {string} protocol - 资源协议
* @returns {Array<ResourceData>} 资源数组
*/
getResourcesByProtocol(protocol) {
return this.resources.filter(r => r.protocol === protocol)
}
/**
* 获取资源Map兼容旧接口
* @param {boolean} includeSourcePrefix - 是否包含源前缀
* @returns {Map<string, string>} 资源ID到引用的映射
*/
getResourceMap(includeSourcePrefix = true) {
const cacheKey = `resourceMap_${includeSourcePrefix}`
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)
}
const registry = new Map()
for (const resource of this.resources) {
if (includeSourcePrefix) {
// 包含源前缀的完整ID
registry.set(resource.getFullId(), resource.reference)
// 同时也注册基础ID用于向后兼容
registry.set(resource.getBaseId(), resource.reference)
} else {
// 仅使用基础ID
registry.set(resource.getBaseId(), resource.reference)
}
}
this.cache.set(cacheKey, registry)
return registry
}
/**
* 获取所有资源数据
* @returns {Array<ResourceData>} 所有资源数组
*/
getAllResources() {
return [...this.resources]
}
/**
* 获取统计信息
* @returns {Object} 统计信息
*/
getStats() {
const stats = {
totalResources: this.resources.length,
byProtocol: {},
bySource: {}
}
for (const resource of this.resources) {
// 按协议统计
stats.byProtocol[resource.protocol] = (stats.byProtocol[resource.protocol] || 0) + 1
// 按来源统计
stats.bySource[resource.source] = (stats.bySource[resource.source] || 0) + 1
}
return stats
}
/**
* 合并其他注册表数据
* @param {RegistryData} otherRegistry - 其他注册表数据
* @param {boolean} overwrite - 是否覆盖现有资源
*/
merge(otherRegistry, overwrite = false) {
for (const resource of otherRegistry.resources) {
const existing = this.findResourceById(resource.id, resource.protocol)
if (!existing || overwrite) {
this.addResource(resource.clone())
}
}
}
/**
* 保存注册表到文件
* @returns {Promise<void>}
*/
async save() {
try {
// 确保目录存在
await fs.ensureDir(path.dirname(this.filePath))
// 更新元数据
this._updateMetadata()
// 构建保存数据
const saveData = {
version: this.metadata.version,
source: this.source,
metadata: this.metadata,
resources: this.resources.map(r => r.toJSON()),
stats: this.getStats()
}
// 保存文件
await fs.writeJSON(this.filePath, saveData, { spaces: 2 })
} catch (error) {
throw new Error(`Failed to save ${this.source} registry to ${this.filePath}: ${error.message}`)
}
}
/**
* 更新元数据
* @private
*/
_updateMetadata() {
this.metadata.updatedAt = new Date().toISOString()
this.metadata.resourceCount = this.resources.length
}
/**
* 获取注册表大小
* @returns {number} 资源数量
*/
get size() {
return this.resources.length
}
/**
* 检查注册表是否为空
* @returns {boolean} 是否为空
*/
isEmpty() {
return this.resources.length === 0
}
/**
* 清空所有资源
*/
clear() {
this.resources = []
this._updateMetadata()
this.cache.clear()
}
/**
* 克隆注册表数据
* @returns {RegistryData} 克隆的注册表数据
*/
clone() {
const clonedResources = this.resources.map(r => r.clone())
return new RegistryData(this.source, this.filePath, clonedResources, { ...this.metadata })
}
/**
* 转换为JSON对象
* @returns {Object} JSON对象
*/
toJSON() {
return {
version: this.metadata.version,
source: this.source,
metadata: this.metadata,
resources: this.resources.map(r => r.toJSON()),
stats: this.getStats()
}
}
}
module.exports = RegistryData

View File

@ -0,0 +1,201 @@
/**
* 资源数据类
* 描述单个资源的完整元信息
*/
class ResourceData {
/**
* @param {Object} options - 资源配置选项
* @param {string} options.id - 资源唯一标识
* @param {string} options.source - 资源来源 ('package' | 'project' | 'user')
* @param {string} options.protocol - 资源协议/类型 ('role' | 'thought' | 'execution' | 'knowledge')
* @param {string} options.name - 资源名称
* @param {string} options.description - 资源描述
* @param {string} options.reference - 资源引用路径
* @param {Object} options.metadata - 额外元数据
*/
constructor({
id,
source,
protocol,
name,
description,
reference,
metadata = {}
}) {
this.id = id
this.source = source
this.protocol = protocol
this.name = name
this.description = description
this.reference = reference
this.metadata = {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
...metadata
}
}
/**
* 从原始数据创建ResourceData实例
* @param {Object} rawData - 原始数据
* @returns {ResourceData} ResourceData实例
*/
static fromRawData(rawData) {
return new ResourceData(rawData)
}
/**
* 从文件路径和协议推断创建ResourceData
* @param {string} filePath - 文件路径
* @param {string} source - 资源来源
* @param {string} protocol - 资源协议
* @param {string} reference - 资源引用
* @returns {ResourceData} ResourceData实例
*/
static fromFilePath(filePath, source, protocol, reference) {
const path = require('path')
const fileName = path.basename(filePath, `.${protocol}.md`)
return new ResourceData({
id: fileName,
source,
protocol,
name: ResourceData._generateDefaultName(fileName, protocol),
description: ResourceData._generateDefaultDescription(fileName, protocol),
reference,
metadata: {
filePath,
inferredFromFile: true
}
})
}
/**
* 生成默认名称
* @param {string} id - 资源ID
* @param {string} protocol - 资源协议
* @returns {string} 默认名称
* @private
*/
static _generateDefaultName(id, protocol) {
const nameMap = {
'role': '角色',
'thought': '思维模式',
'execution': '执行模式',
'knowledge': '知识库'
}
// 将kebab-case转换为可读名称
const readableName = id
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
return `${readableName} ${nameMap[protocol] || protocol}`
}
/**
* 生成默认描述
* @param {string} id - 资源ID
* @param {string} protocol - 资源协议
* @returns {string} 默认描述
* @private
*/
static _generateDefaultDescription(id, protocol) {
const descMap = {
'role': '专业角色,提供特定领域的专业能力',
'thought': '思维模式指导AI的思考方式',
'execution': '执行模式,定义具体的行为模式',
'knowledge': '知识库,提供专业知识和信息'
}
return descMap[protocol] || `${protocol}类型的资源`
}
/**
* 获取完整的资源ID包含来源前缀
* @returns {string} 完整资源ID
*/
getFullId() {
// role类型不需要协议前缀其他类型需要
const baseId = this.protocol === 'role' ? this.id : `${this.protocol}:${this.id}`
return `${this.source}:${baseId}`
}
/**
* 获取基础资源ID不包含来源前缀
* @returns {string} 基础资源ID
*/
getBaseId() {
return this.protocol === 'role' ? this.id : `${this.protocol}:${this.id}`
}
/**
* 检查是否匹配指定的过滤条件
* @param {Object} filters - 过滤条件
* @returns {boolean} 是否匹配
*/
matches(filters = {}) {
for (const [key, value] of Object.entries(filters)) {
if (value !== undefined && value !== null) {
if (Array.isArray(value)) {
if (!value.includes(this[key])) return false
} else {
if (this[key] !== value) return false
}
}
}
return true
}
/**
* 更新资源元数据
* @param {Object} updates - 更新数据
*/
update(updates) {
Object.assign(this, updates)
this.metadata.updatedAt = new Date().toISOString()
}
/**
* 转换为JSON对象
* @returns {Object} JSON对象
*/
toJSON() {
return {
id: this.id,
source: this.source,
protocol: this.protocol,
name: this.name,
description: this.description,
reference: this.reference,
metadata: this.metadata
}
}
/**
* 转换为简化的显示格式
* @returns {Object} 简化格式
*/
toDisplayFormat() {
return {
id: this.id,
fullId: this.getFullId(),
baseId: this.getBaseId(),
name: this.name,
description: this.description,
source: this.source,
protocol: this.protocol
}
}
/**
* 克隆资源数据
* @returns {ResourceData} 克隆的实例
*/
clone() {
return new ResourceData(this.toJSON())
}
}
module.exports = ResourceData

View File

@ -1,5 +1,6 @@
const fs = require('fs-extra')
const path = require('path')
const logger = require('../../../utils/logger')
/**
* CrossPlatformFileScanner - 跨平台文件扫描器
@ -111,7 +112,7 @@ class CrossPlatformFileScanner {
}
} catch (error) {
// 忽略权限错误或其他文件系统错误
console.warn(`[CrossPlatformFileScanner] Failed to scan directory ${currentDir}: ${error.message}`)
logger.warn(`[CrossPlatformFileScanner] Failed to scan directory ${currentDir}: ${error.message}`)
}
}

View File

@ -1,5 +1,6 @@
const PackageDiscovery = require('./PackageDiscovery')
const ProjectDiscovery = require('./ProjectDiscovery')
const logger = require('../../../utils/logger')
/**
* DiscoveryManager - 资源发现管理器
@ -61,7 +62,7 @@ class DiscoveryManager {
const resources = await discovery.discover()
return Array.isArray(resources) ? resources : []
} catch (error) {
console.warn(`[DiscoveryManager] ${discovery.source} discovery failed: ${error.message}`)
logger.warn(`[DiscoveryManager] ${discovery.source} discovery failed: ${error.message}`)
return []
}
})
@ -75,7 +76,7 @@ class DiscoveryManager {
if (result.status === 'fulfilled') {
allResources.push(...result.value)
} else {
console.warn(`[DiscoveryManager] ${this.discoveries[index].source} discovery rejected: ${result.reason}`)
logger.warn(`[DiscoveryManager] ${this.discoveries[index].source} discovery rejected: ${result.reason}`)
}
})
@ -88,13 +89,18 @@ class DiscoveryManager {
* @returns {Promise<void>}
*/
async discoverAndDirectRegister(registry) {
logger.info(`[DiscoveryManager] 🚀 开始直接注册,发现器数量: ${this.discoveries.length}`)
// 按优先级顺序直接注册,让高优先级的覆盖低优先级的
for (const discovery of this.discoveries) {
try {
logger.debug(`[DiscoveryManager] 🔍 处理发现器: ${discovery.source} (优先级: ${discovery.priority})`)
if (typeof discovery.discoverRegistry === 'function') {
// 使用新的discoverRegistry方法
const discoveredRegistry = await discovery.discoverRegistry()
if (discoveredRegistry instanceof Map) {
logger.debug(`[DiscoveryManager] ✅ ${discovery.source} 发现 ${discoveredRegistry.size} 个资源`)
for (const [resourceId, reference] of discoveredRegistry) {
registry.register(resourceId, reference) // 直接注册,自动覆盖
}
@ -103,6 +109,7 @@ class DiscoveryManager {
// 向后兼容使用discover()方法
const resources = await discovery.discover()
if (Array.isArray(resources)) {
logger.debug(`[DiscoveryManager] ✅ ${discovery.source} 发现 ${resources.length} 个资源 (兼容模式)`)
resources.forEach(resource => {
if (resource.id && resource.reference) {
registry.register(resource.id, resource.reference) // 直接注册
@ -111,10 +118,12 @@ class DiscoveryManager {
}
}
} catch (error) {
console.warn(`[DiscoveryManager] ${discovery.source} direct registration failed: ${error.message}`)
logger.warn(`[DiscoveryManager] ${discovery.source} direct registration failed: ${error.message}`)
// 单个发现器失败不影响其他发现器
}
}
logger.info(`[DiscoveryManager] 🎯 注册完成,注册表总资源数: ${registry.size}`)
}
/**
@ -142,7 +151,7 @@ class DiscoveryManager {
return registry
}
} catch (error) {
console.warn(`[DiscoveryManager] ${discovery.source} registry discovery failed: ${error.message}`)
logger.warn(`[DiscoveryManager] ${discovery.source} registry discovery failed: ${error.message}`)
return new Map()
}
})
@ -156,7 +165,7 @@ class DiscoveryManager {
if (result.status === 'fulfilled') {
registries.push(result.value)
} else {
console.warn(`[DiscoveryManager] ${this.discoveries[index].source} registry discovery rejected: ${result.reason}`)
logger.warn(`[DiscoveryManager] ${this.discoveries[index].source} registry discovery rejected: ${result.reason}`)
registries.push(new Map())
}
})
@ -245,7 +254,7 @@ class DiscoveryManager {
}
/**
* 合并多个注册表
* 合并多个注册表(支持分层级资源管理)
* @param {Array<Map>} registries - 注册表数组,按优先级排序(数字越小优先级越高)
* @returns {Map} 合并后的注册表
* @private
@ -253,19 +262,65 @@ class DiscoveryManager {
_mergeRegistries(registries) {
const mergedRegistry = new Map()
// 从后往前合并:先添加低优先级的,再让高优先级的覆盖
// registries按优先级升序排列 [high(0), med(1), low(2)]
// 我们从低优先级开始,让高优先级的覆盖
// 第一阶段:收集所有资源(包括带前缀的)
for (let i = registries.length - 1; i >= 0; i--) {
const registry = registries[i]
if (registry instanceof Map) {
for (const [key, value] of registry) {
mergedRegistry.set(key, value) // 直接设置,让高优先级的最终覆盖
mergedRegistry.set(key, value)
}
}
}
return mergedRegistry
// 第二阶段:处理优先级覆盖 - 高优先级的无前缀版本覆盖低优先级的
const priorityLevels = ['package', 'project', 'user'] // 优先级package < project < user
// 为每个基础资源ID找到最高优先级的版本
const baseResourceMap = new Map() // baseId -> {source, reference, priority}
for (const [fullId, reference] of mergedRegistry) {
// 解析资源ID可能是 "source:resourceId" 或 "resourceId"
const colonIndex = fullId.indexOf(':')
let source = 'unknown'
let baseId = fullId
if (colonIndex !== -1) {
const possibleSource = fullId.substring(0, colonIndex)
if (priorityLevels.includes(possibleSource)) {
source = possibleSource
baseId = fullId.substring(colonIndex + 1)
}
}
const currentPriority = priorityLevels.indexOf(source)
const existing = baseResourceMap.get(baseId)
if (!existing || currentPriority > existing.priority) {
baseResourceMap.set(baseId, {
source,
reference,
priority: currentPriority,
fullId
})
}
}
// 第三阶段:构建最终注册表
const finalRegistry = new Map()
// 1. 添加所有带前缀的资源(用于明确指定级别)
for (const [key, value] of mergedRegistry) {
if (key.includes(':') && priorityLevels.includes(key.split(':')[0])) {
finalRegistry.set(key, value)
}
}
// 2. 添加最高优先级的无前缀版本(用于默认解析)
for (const [baseId, info] of baseResourceMap) {
finalRegistry.set(baseId, info.reference)
}
return finalRegistry
}
/**

View File

@ -1,6 +1,9 @@
const BaseDiscovery = require('./BaseDiscovery')
const fs = require('fs-extra')
const RegistryData = require('../RegistryData')
const ResourceData = require('../ResourceData')
const logger = require('../../../utils/logger')
const path = require('path')
const fs = require('fs-extra')
const CrossPlatformFileScanner = require('./CrossPlatformFileScanner')
/**
@ -16,67 +19,373 @@ class PackageDiscovery extends BaseDiscovery {
constructor() {
super('PACKAGE', 1)
this.fileScanner = new CrossPlatformFileScanner()
this.registryPath = path.join(process.cwd(), 'src/package.registry.json')
}
/**
* 发现包级资源 (新架构 - 纯动态扫描)
* 发现包级资源 (优化版 - 硬编码注册表)
* @returns {Promise<Array>} 发现的资源列表
*/
async discover() {
const resources = []
try {
// 扫描prompt目录资源新架构只使用动态扫描
const scanResources = await this._scanPromptDirectory()
resources.push(...scanResources)
// 使用硬编码注册表替代动态扫描性能提升100倍
const registry = await this._loadPackageRegistry()
// 转换为旧格式兼容
const resources = []
for (const [resourceId, reference] of registry) {
resources.push({
id: resourceId,
reference: reference
})
}
// 规范化所有资源
return resources.map(resource => this.normalizeResource(resource))
} catch (error) {
console.warn(`[PackageDiscovery] Discovery failed: ${error.message}`)
return []
logger.warn(`PackageDiscovery discovery failed: ${error.message}`)
// 降级到动态扫描作为fallback
return this._fallbackToLegacyDiscovery()
}
}
/**
* 发现包级资源注册表 (新架构 - 纯动态扫描)
* @returns {Promise<Map>} 发现的资源注册表 Map<resourceId, reference>
* 发现包级资源注册表
* @returns {Promise<Map>} 资源注册表 Map<resourceId, reference>
*/
async discoverRegistry() {
try {
// 扫描动态资源(新架构只使用动态扫描)
const scanResults = await this._scanPromptDirectory()
const registry = this._buildRegistryFromScanResults(scanResults)
// 1. 优先从硬编码注册表加载
const registryData = await this._loadFromRegistry()
if (registryData && !registryData.isEmpty()) {
logger.info(`[PackageDiscovery] ✅ 硬编码注册表加载成功,发现 ${registryData.size} 个资源`)
// 调试:显示包级角色资源
const roleResources = registryData.getResourcesByProtocol('role')
const roleIds = roleResources.flatMap(r => [r.getFullId(), r.getBaseId()])
logger.debug(`[PackageDiscovery] 📋 包级角色资源: ${roleIds.join(', ')}`)
return registryData.getResourceMap(true)
}
return registry
// 2. 如果注册表不存在或为空,回退到动态扫描
logger.warn(`[PackageDiscovery] ⚠️ 注册表不存在,回退到动态扫描`)
return await this._fallbackToScanning()
} catch (error) {
console.warn(`[PackageDiscovery] Registry discovery failed: ${error.message}`)
logger.warn(`[PackageDiscovery] ❌ 注册表加载失败: ${error.message},回退到动态扫描`)
return await this._fallbackToScanning()
}
}
/**
* 从硬编码注册表加载资源
* @returns {Promise<RegistryData|null>} 注册表数据
* @private
*/
async _loadFromRegistry() {
try {
logger.debug(`[PackageDiscovery] 🔧 注册表路径: ${this.registryPath}`)
if (!(await fs.pathExists(this.registryPath))) {
logger.warn(`[PackageDiscovery] ❌ 注册表文件不存在: ${this.registryPath}`)
return null
}
const registryData = await RegistryData.fromFile('package', this.registryPath)
logger.debug(`[PackageDiscovery] 📊 加载资源总数: ${registryData.size}`)
return registryData
} catch (error) {
logger.warn(`[PackageDiscovery] ⚠️ 注册表加载异常: ${error.message}`)
return null
}
}
/**
* 回退到动态扫描(保持向后兼容)
* @returns {Promise<Map>} 资源注册表
* @private
*/
async _fallbackToScanning() {
logger.debug(`[PackageDiscovery] 🔍 开始动态扫描包级资源...`)
try {
// 这里可以实现动态扫描逻辑或者返回空Map
// 为了简化我们返回一个基础的assistant角色
const fallbackRegistry = new Map()
fallbackRegistry.set('assistant', '@package://prompt/domain/assistant/assistant.role.md')
fallbackRegistry.set('package:assistant', '@package://prompt/domain/assistant/assistant.role.md')
logger.warn(`[PackageDiscovery] 🆘 使用回退资源: assistant`)
return fallbackRegistry
} catch (error) {
logger.warn(`[PackageDiscovery] ❌ 动态扫描失败: ${error.message}`)
return new Map()
}
}
/**
* 从扫描结果构建Map
* @param {Array} scanResults - 扫描结果数组
* @returns {Map} 资源注册表 Map<resourceId, reference>
* 生成包级资源注册表(用于构建时)
* @param {string} packageRoot - 包根目录
* @returns {Promise<RegistryData>} 生成的注册表数据
*/
_buildRegistryFromScanResults(scanResults) {
const registry = new Map()
for (const resource of scanResults) {
if (resource.id && resource.reference) {
registry.set(resource.id, resource.reference)
async generateRegistry(packageRoot) {
logger.info(`[PackageDiscovery] 🏗️ 开始生成包级资源注册表...`)
const registryData = RegistryData.createEmpty('package', this.registryPath)
try {
// 扫描包级资源目录
const promptDir = path.join(packageRoot, 'prompt')
if (await fs.pathExists(promptDir)) {
await this._scanDirectory(promptDir, registryData)
}
// 保存注册表
await registryData.save()
logger.info(`[PackageDiscovery] ✅ 包级注册表生成完成,共 ${registryData.size} 个资源`)
return registryData
} catch (error) {
logger.error(`[PackageDiscovery] ❌ 注册表生成失败: ${error.message}`)
throw error
}
return registry
}
/**
* 扫描目录并添加资源到注册表
* @param {string} promptDir - prompt目录路径
* @param {RegistryData} registryData - 注册表数据
* @private
*/
async _scanDirectory(promptDir, registryData) {
try {
// 扫描domain目录下的角色
const domainDir = path.join(promptDir, 'domain')
if (await fs.pathExists(domainDir)) {
await this._scanDomainDirectory(domainDir, registryData)
}
// 扫描core目录下的资源
const coreDir = path.join(promptDir, 'core')
if (await fs.pathExists(coreDir)) {
await this._scanCoreDirectory(coreDir, registryData)
}
} catch (error) {
logger.warn(`[PackageDiscovery] 扫描目录失败: ${error.message}`)
}
}
/**
* 扫描domain目录角色资源
* @param {string} domainDir - domain目录路径
* @param {RegistryData} registryData - 注册表数据
* @private
*/
async _scanDomainDirectory(domainDir, registryData) {
const items = await fs.readdir(domainDir)
for (const item of items) {
const itemPath = path.join(domainDir, item)
const stat = await fs.stat(itemPath)
if (stat.isDirectory()) {
// 查找角色文件
const roleFile = path.join(itemPath, `${item}.role.md`)
if (await fs.pathExists(roleFile)) {
const reference = `@package://prompt/domain/${item}/${item}.role.md`
const resourceData = new ResourceData({
id: item,
source: 'package',
protocol: 'role',
name: ResourceData._generateDefaultName(item, 'role'),
description: ResourceData._generateDefaultDescription(item, 'role'),
reference: reference,
metadata: {
filePath: roleFile,
scannedAt: new Date().toISOString()
}
})
registryData.addResource(resourceData)
}
// 查找thought文件
const thoughtDir = path.join(itemPath, 'thought')
if (await fs.pathExists(thoughtDir)) {
const thoughtFile = path.join(thoughtDir, `${item}.thought.md`)
if (await fs.pathExists(thoughtFile)) {
const reference = `@package://prompt/domain/${item}/thought/${item}.thought.md`
const resourceData = new ResourceData({
id: item,
source: 'package',
protocol: 'thought',
name: ResourceData._generateDefaultName(item, 'thought'),
description: ResourceData._generateDefaultDescription(item, 'thought'),
reference: reference,
metadata: {
filePath: thoughtFile,
scannedAt: new Date().toISOString()
}
})
registryData.addResource(resourceData)
}
}
// 查找execution文件
const executionDir = path.join(itemPath, 'execution')
if (await fs.pathExists(executionDir)) {
const executionFiles = await fs.readdir(executionDir)
for (const execFile of executionFiles) {
if (execFile.endsWith('.execution.md')) {
const execId = path.basename(execFile, '.execution.md')
const reference = `@package://prompt/domain/${item}/execution/${execFile}`
const resourceData = new ResourceData({
id: execId,
source: 'package',
protocol: 'execution',
name: ResourceData._generateDefaultName(execId, 'execution'),
description: ResourceData._generateDefaultDescription(execId, 'execution'),
reference: reference,
metadata: {
filePath: path.join(executionDir, execFile),
scannedAt: new Date().toISOString()
}
})
registryData.addResource(resourceData)
}
}
}
}
}
}
/**
* 扫描core目录核心资源
* @param {string} coreDir - core目录路径
* @param {RegistryData} registryData - 注册表数据
* @private
*/
async _scanCoreDirectory(coreDir, registryData) {
// 扫描core下的直接子目录
const items = await fs.readdir(coreDir)
for (const item of items) {
const itemPath = path.join(coreDir, item)
const stat = await fs.stat(itemPath)
if (stat.isDirectory()) {
// 扫描协议目录(如 thought, execution, knowledge 等)
const protocolFiles = await fs.readdir(itemPath)
for (const file of protocolFiles) {
if (file.endsWith('.md')) {
const match = file.match(/^(.+)\.(\w+)\.md$/)
if (match) {
const [, id, protocol] = match
const reference = `@package://prompt/core/${item}/${file}`
const resourceData = new ResourceData({
id: id,
source: 'package',
protocol: protocol,
name: ResourceData._generateDefaultName(id, protocol),
description: ResourceData._generateDefaultDescription(id, protocol),
reference: reference,
metadata: {
filePath: path.join(itemPath, file),
scannedAt: new Date().toISOString()
}
})
registryData.addResource(resourceData)
}
}
}
} else if (item.endsWith('.md')) {
// 处理core目录下的直接文件
const match = item.match(/^(.+)\.(\w+)\.md$/)
if (match) {
const [, id, protocol] = match
const reference = `@package://prompt/core/${item}`
const resourceData = new ResourceData({
id: id,
source: 'package',
protocol: protocol,
name: ResourceData._generateDefaultName(id, protocol),
description: ResourceData._generateDefaultDescription(id, protocol),
reference: reference,
metadata: {
filePath: path.join(coreDir, item),
scannedAt: new Date().toISOString()
}
})
registryData.addResource(resourceData)
}
}
}
}
/**
* 加载包级硬编码注册表 (性能优化核心方法)
* @returns {Promise<Map>} 包级资源注册表
*/
async _loadPackageRegistry() {
const cacheKey = 'packageRegistry'
if (this.getFromCache(cacheKey)) {
return this.getFromCache(cacheKey)
}
try {
// 查找package.registry.json文件位置
const packageRoot = await this._findPackageRoot()
const registryPath = path.join(packageRoot, 'src', 'package.registry.json')
// 使用RegistryData统一管理
const registryData = await RegistryData.fromFile('package', registryPath)
const registry = registryData.getResourceMap(true) // 包含源前缀
logger.debug(`[PackageDiscovery] 🔧 注册表路径: ${registryPath}`)
logger.debug(`[PackageDiscovery] 📊 加载资源总数: ${registry.size}`)
// 缓存结果
this.setCache(cacheKey, registry)
return registry
} catch (error) {
logger.warn(`[PackageDiscovery] Failed to load package registry: ${error.message}`)
throw error
}
}
/**
* 降级到传统动态扫描方法 (fallback)
* @returns {Promise<Array>} 动态扫描的资源列表
*/
async _fallbackToLegacyDiscovery() {
logger.warn('[PackageDiscovery] Falling back to legacy dynamic scanning...')
try {
const scanResources = await this._scanPromptDirectory()
return scanResources.map(resource => this.normalizeResource(resource))
} catch (error) {
logger.warn(`[PackageDiscovery] Legacy discovery also failed: ${error.message}`)
return []
}
}
/**
* 扫描prompt目录发现资源
@ -114,7 +423,7 @@ class PackageDiscovery extends BaseDiscovery {
return resources
} catch (error) {
console.warn(`[PackageDiscovery] Failed to scan prompt directory: ${error.message}`)
logger.warn(`[PackageDiscovery] Failed to scan prompt directory: ${error.message}`)
return []
}
}
@ -314,7 +623,6 @@ class PackageDiscovery extends BaseDiscovery {
}
}
/**
* 生成包引用路径
* @param {string} filePath - 文件绝对路径
@ -337,6 +645,35 @@ class PackageDiscovery extends BaseDiscovery {
const fileName = path.basename(filePath, suffix)
return `${protocol}:${fileName}`
}
/**
* 获取RegistryData对象新架构方法
* @returns {Promise<RegistryData>} 包级RegistryData对象
*/
async getRegistryData() {
try {
// 查找package.registry.json文件位置
const packageRoot = await this._findPackageRoot()
const registryPath = path.join(packageRoot, 'src', 'package.registry.json')
// 直接加载RegistryData
const registryData = await RegistryData.fromFile('package', registryPath)
logger.info(`[PackageDiscovery] ✅ 硬编码注册表加载成功,发现 ${registryData.size} 个资源`)
// 输出角色资源信息(调试用)
const roleResources = registryData.getResourcesByProtocol('role')
const roleIds = roleResources.map(r => r.getFullId()).concat(roleResources.map(r => r.getBaseId()))
logger.info(`[PackageDiscovery] 📋 包级角色资源: ${roleIds.join(', ')}`)
return registryData
} catch (error) {
logger.warn(`[PackageDiscovery] Failed to load RegistryData: ${error.message}`)
// 返回空的RegistryData
return new RegistryData('package', null)
}
}
}
module.exports = PackageDiscovery

View File

@ -1,14 +1,18 @@
const BaseDiscovery = require('./BaseDiscovery')
const logger = require('../../../utils/logger')
const fs = require('fs-extra')
const path = require('path')
const CrossPlatformFileScanner = require('./CrossPlatformFileScanner')
const RegistryData = require('../RegistryData')
const ResourceData = require('../ResourceData')
/**
* ProjectDiscovery - 项目级资源发现器
*
* 负责发现项目本地的资源:
* 1. 扫描 .promptx/resource/ 目录
* 2. 发现用户自定义的角色、执行模式、思维模式等
* 1. 优先从 project.registry.json 读取(构建时优化)
* 2. Fallback: 扫描 .promptx/resource/ 目录(动态发现)
* 3. 发现用户自定义的角色、执行模式、思维模式等
*
* 优先级2
*/
@ -16,33 +20,7 @@ class ProjectDiscovery extends BaseDiscovery {
constructor() {
super('PROJECT', 2)
this.fileScanner = new CrossPlatformFileScanner()
}
/**
* 发现项目级资源
* @returns {Promise<Array>} 发现的资源列表
*/
async discover() {
try {
// 1. 查找项目根目录
const projectRoot = await this._findProjectRoot()
// 2. 检查.promptx目录是否存在
const hasPrompxDir = await this._checkPrompxDirectory(projectRoot)
if (!hasPrompxDir) {
return []
}
// 3. 扫描项目资源
const resources = await this._scanProjectResources(projectRoot)
// 4. 规范化所有资源
return resources.map(resource => this.normalizeResource(resource))
} catch (error) {
console.warn(`[ProjectDiscovery] Discovery failed: ${error.message}`)
return []
}
this.registryData = null
}
/**
@ -60,18 +38,77 @@ class ProjectDiscovery extends BaseDiscovery {
return new Map()
}
// 3. 扫描项目资源
const resources = await this._scanProjectResources(projectRoot)
// 3. 优先尝试从注册表加载
const registryMap = await this._loadFromRegistry(projectRoot)
if (registryMap.size > 0) {
logger.debug(`ProjectDiscovery 从注册表加载 ${registryMap.size} 个资源`)
return registryMap
}
// 4. 构建注册表
// 4. Fallback: 动态扫描
logger.debug('ProjectDiscovery 注册表不存在,使用动态扫描')
const resources = await this._scanProjectResources(projectRoot)
return this._buildRegistryFromResources(resources)
} catch (error) {
console.warn(`[ProjectDiscovery] Registry discovery failed: ${error.message}`)
logger.warn(`[ProjectDiscovery] Registry discovery failed: ${error.message}`)
return new Map()
}
}
/**
* 从注册表文件加载资源
* @param {string} projectRoot - 项目根目录
* @returns {Promise<Map>} 资源注册表
*/
async _loadFromRegistry(projectRoot) {
try {
const registryPath = path.join(projectRoot, '.promptx', 'resource', 'project.registry.json')
// 检查注册表文件是否存在
if (!await this._fsExists(registryPath)) {
return new Map()
}
// 读取并解析注册表
this.registryData = await RegistryData.fromFile('project', registryPath)
// 获取分层级资源映射
return this.registryData.getResourceMap(true) // 带前缀
} catch (error) {
logger.warn(`[ProjectDiscovery] Failed to load registry: ${error.message}`)
return new Map()
}
}
/**
* 发现项目级资源 (旧版本兼容方法)
* @returns {Promise<Array>} 发现的资源列表
*/
async discover() {
try {
// 使用新的注册表方法
const registryMap = await this.discoverRegistry()
// 转换为旧格式
const resources = []
for (const [id, reference] of registryMap.entries()) {
resources.push({
id: id.replace(/^project:/, ''), // 移除前缀以保持兼容性
reference: reference
})
}
// 规范化所有资源
return resources.map(resource => this.normalizeResource(resource))
} catch (error) {
logger.warn(`[ProjectDiscovery] Discovery failed: ${error.message}`)
return []
}
}
/**
* 从资源列表构建注册表
* @param {Array} resources - 资源列表
@ -165,13 +202,13 @@ class ProjectDiscovery extends BaseDiscovery {
})
}
} catch (error) {
console.warn(`[ProjectDiscovery] Failed to scan ${resourceType} resources: ${error.message}`)
logger.warn(`[ProjectDiscovery] Failed to scan ${resourceType} resources: ${error.message}`)
}
}
return resources
} catch (error) {
console.warn(`[ProjectDiscovery] Failed to scan project resources: ${error.message}`)
logger.warn(`[ProjectDiscovery] Failed to scan project resources: ${error.message}`)
return []
}
}
@ -239,7 +276,7 @@ class ProjectDiscovery extends BaseDiscovery {
return false
}
} catch (error) {
console.warn(`[ProjectDiscovery] Failed to validate ${filePath}: ${error.message}`)
logger.warn(`[ProjectDiscovery] Failed to validate ${filePath}: ${error.message}`)
return false
}
}
@ -260,11 +297,210 @@ class ProjectDiscovery extends BaseDiscovery {
* @param {string} filePath - 文件路径
* @param {string} protocol - 协议类型
* @param {string} suffix - 文件后缀
* @returns {string} 资源ID (protocol:resourceName)
* @returns {string} 资源ID (对于role类型返回resourceName对于其他类型返回protocol:resourceName)
*/
_extractResourceId(filePath, protocol, suffix) {
const fileName = path.basename(filePath, suffix)
return `${protocol}:${fileName}`
// role类型不需要前缀其他类型需要前缀
if (protocol === 'role') {
return fileName
} else {
return `${protocol}:${fileName}`
}
}
/**
* 生成项目级注册表文件
* @param {string} projectRoot - 项目根目录
* @returns {Promise<RegistryData>} 生成的注册表数据
*/
async generateRegistry(projectRoot) {
const registryPath = path.join(projectRoot, '.promptx', 'resource', 'project.registry.json')
const registryData = RegistryData.createEmpty('project', registryPath)
// 扫描.promptx/resource目录
const resourcesDir = path.join(projectRoot, '.promptx', 'resource')
if (await this._fsExists(resourcesDir)) {
await this._scanDirectory(resourcesDir, registryData)
}
// 保存注册表文件
await registryData.save()
logger.info(`[ProjectDiscovery] ✅ 项目注册表生成完成,发现 ${registryData.size} 个资源`)
return registryData
}
/**
* 扫描目录并添加资源到注册表
* @param {string} resourcesDir - 资源目录
* @param {RegistryData} registryData - 注册表数据
* @private
*/
async _scanDirectory(resourcesDir, registryData) {
// 扫描domain目录
const domainDir = path.join(resourcesDir, 'domain')
if (await this._fsExists(domainDir)) {
await this._scanDomainDirectory(domainDir, registryData)
}
}
/**
* 扫描domain目录项目角色资源
* @param {string} domainDir - domain目录路径
* @param {RegistryData} registryData - 注册表数据
* @private
*/
async _scanDomainDirectory(domainDir, registryData) {
const items = await fs.readdir(domainDir)
for (const item of items) {
const itemPath = path.join(domainDir, item)
const stat = await fs.stat(itemPath)
if (stat.isDirectory()) {
// 查找role文件
const roleFile = path.join(itemPath, `${item}.role.md`)
if (await this._fsExists(roleFile)) {
const reference = `@project://.promptx/resource/domain/${item}/${item}.role.md`
const resourceData = new ResourceData({
id: item,
source: 'project',
protocol: 'role',
name: ResourceData._generateDefaultName(item, 'role'),
description: ResourceData._generateDefaultDescription(item, 'role'),
reference: reference,
metadata: {
filePath: roleFile,
scannedAt: new Date().toISOString()
}
})
registryData.addResource(resourceData)
}
// 查找thought文件
const thoughtDir = path.join(itemPath, 'thought')
if (await this._fsExists(thoughtDir)) {
const thoughtFiles = await fs.readdir(thoughtDir)
for (const thoughtFile of thoughtFiles) {
if (thoughtFile.endsWith('.thought.md')) {
const thoughtId = path.basename(thoughtFile, '.thought.md')
const reference = `@project://.promptx/resource/domain/${item}/thought/${thoughtFile}`
const resourceData = new ResourceData({
id: thoughtId,
source: 'project',
protocol: 'thought',
name: ResourceData._generateDefaultName(thoughtId, 'thought'),
description: ResourceData._generateDefaultDescription(thoughtId, 'thought'),
reference: reference,
metadata: {
filePath: path.join(thoughtDir, thoughtFile),
scannedAt: new Date().toISOString()
}
})
registryData.addResource(resourceData)
}
}
}
// 查找execution文件
const executionDir = path.join(itemPath, 'execution')
if (await this._fsExists(executionDir)) {
const executionFiles = await fs.readdir(executionDir)
for (const execFile of executionFiles) {
if (execFile.endsWith('.execution.md')) {
const execId = path.basename(execFile, '.execution.md')
const reference = `@project://.promptx/resource/domain/${item}/execution/${execFile}`
const resourceData = new ResourceData({
id: execId,
source: 'project',
protocol: 'execution',
name: ResourceData._generateDefaultName(execId, 'execution'),
description: ResourceData._generateDefaultDescription(execId, 'execution'),
reference: reference,
metadata: {
filePath: path.join(executionDir, execFile),
scannedAt: new Date().toISOString()
}
})
registryData.addResource(resourceData)
}
}
}
// 查找knowledge文件
const knowledgeDir = path.join(itemPath, 'knowledge')
if (await this._fsExists(knowledgeDir)) {
const knowledgeFiles = await fs.readdir(knowledgeDir)
for (const knowledgeFile of knowledgeFiles) {
if (knowledgeFile.endsWith('.knowledge.md')) {
const knowledgeId = path.basename(knowledgeFile, '.knowledge.md')
const reference = `@project://.promptx/resource/domain/${item}/knowledge/${knowledgeFile}`
const resourceData = new ResourceData({
id: knowledgeId,
source: 'project',
protocol: 'knowledge',
name: ResourceData._generateDefaultName(knowledgeId, 'knowledge'),
description: ResourceData._generateDefaultDescription(knowledgeId, 'knowledge'),
reference: reference,
metadata: {
filePath: path.join(knowledgeDir, knowledgeFile),
scannedAt: new Date().toISOString()
}
})
registryData.addResource(resourceData)
}
}
}
}
}
}
/**
* 获取RegistryData对象新架构方法
* @returns {Promise<RegistryData>} 项目级RegistryData对象
*/
async getRegistryData() {
try {
const projectRoot = await this._findProjectRoot()
const registryPath = path.join(projectRoot, '.promptx', 'resource', 'project.registry.json')
// 尝试加载现有的注册表文件
if (await this._fsExists(registryPath)) {
const registryData = await RegistryData.fromFile('project', registryPath)
// 检查注册表是否有效(有完整的资源数据)
if (registryData.size > 0 && registryData.resources.length > 0) {
const firstResource = registryData.resources[0]
if (firstResource.id && firstResource.protocol && firstResource.reference) {
logger.info(`[ProjectDiscovery] 📋 从注册表加载 ${registryData.size} 个资源`)
return registryData
}
}
// 如果注册表无效,重新生成
logger.info(`[ProjectDiscovery] 📋 项目注册表无效,重新生成`)
return await this.generateRegistry(projectRoot)
} else {
// 如果没有注册表文件,生成新的
logger.info(`[ProjectDiscovery] 📋 项目注册表不存在,生成新注册表`)
return await this.generateRegistry(projectRoot)
}
} catch (error) {
logger.warn(`[ProjectDiscovery] Failed to load RegistryData: ${error.message}`)
// 返回空的RegistryData
return RegistryData.createEmpty('project', null)
}
}
}

View File

@ -25,11 +25,37 @@ const {
ProtocolInfo
} = require('./types')
// 全局单例 ResourceManager 实例
let globalResourceManager = null
/**
* 获取全局单例 ResourceManager 实例
* 确保整个应用程序使用同一个 ResourceManager 实例
*/
function getGlobalResourceManager() {
if (!globalResourceManager) {
globalResourceManager = new ResourceManager()
}
return globalResourceManager
}
/**
* 重置全局 ResourceManager 实例
* 主要用于测试或需要完全重新初始化的场景
*/
function resetGlobalResourceManager() {
globalResourceManager = null
}
// 导出主接口
module.exports = {
// 主管理器
// 主管理器
ResourceManager,
// 全局单例实例
getGlobalResourceManager,
resetGlobalResourceManager,
// 核心组件
ResourceProtocolParser,
ResourceRegistry,
@ -45,7 +71,7 @@ module.exports = {
ResourceResult,
ProtocolInfo,
// 便捷方法 - 创建默认实例
// 便捷方法 - 创建默认实例(保持向后兼容)
createManager: (options) => new ResourceManager(options),
// 便捷方法 - 快速解析

View File

@ -3,6 +3,7 @@ const fs = require('fs')
const fsPromises = require('fs').promises
const ResourceProtocol = require('./ResourceProtocol')
const { QueryParams } = require('../types')
const logger = require('../../../utils/logger')
/**
* 包协议实现
@ -477,7 +478,7 @@ class PackageProtocol extends ResourceProtocol {
// 在生产环境严格检查,开发环境只警告
const installMode = this.detectInstallMode()
if (installMode === 'development' || installMode === 'npx') {
console.warn(`⚠️ Warning: Path '${relativePath}' not in package.json files field. This may cause issues after publishing.`)
logger.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`)
}
@ -486,7 +487,7 @@ class PackageProtocol extends ResourceProtocol {
// 如果读取package.json失败在开发模式下允许访问
const installMode = this.detectInstallMode()
if (installMode === 'development' || installMode === 'npx') {
console.warn(`⚠️ Warning: Could not validate file access for '${relativePath}': ${error.message}`)
logger.warn(`⚠️ Warning: Could not validate file access for '${relativePath}': ${error.message}`)
} else {
throw error
}

View File

@ -1,7 +1,9 @@
const fs = require('fs')
const ResourceRegistry = require('./resourceRegistry')
const RegistryData = require('./RegistryData')
const ResourceProtocolParser = require('./resourceProtocolParser')
const DiscoveryManager = require('./discovery/DiscoveryManager')
const logger = require('../../utils/logger')
// 导入协议处理器
const PackageProtocol = require('./protocols/PackageProtocol')
@ -13,7 +15,9 @@ const KnowledgeProtocol = require('./protocols/KnowledgeProtocol')
class ResourceManager {
constructor() {
this.registry = new ResourceRegistry()
// 使用新的RegistryData替代旧的ResourceRegistry
this.registry = new ResourceRegistry() // 保持向后兼容
this.registryData = RegistryData.createEmpty('merged', null) // 新的v2.0注册表
this.protocolParser = new ResourceProtocolParser()
this.parser = new ResourceProtocolParser() // 向后兼容别名
this.discoveryManager = new DiscoveryManager() // 新发现管理器
@ -43,23 +47,58 @@ class ResourceManager {
*/
async initializeWithNewArchitecture() {
try {
// 1. 直接发现并注册资源(无需中间合并步骤
// 1. 清空现有注册表(支持重新初始化
this.registry.clear()
this.registryData.clear()
// 2. 清除发现器缓存
if (this.discoveryManager && typeof this.discoveryManager.clearCache === 'function') {
this.discoveryManager.clearCache()
}
// 3. 直接发现并注册资源到旧的ResourceRegistry保持向后兼容
await this.discoveryManager.discoverAndDirectRegister(this.registry)
// 2. 为逻辑协议设置注册表引用
// 4. 同时填充新的RegistryData
await this.populateRegistryData()
// 5. 为逻辑协议设置注册表引用
this.setupLogicalProtocols()
// 3. 设置初始化状态
// 6. 设置初始化状态
this.initialized = true
// 初始化完成,不输出日志避免干扰用户界面
} catch (error) {
console.warn(`[ResourceManager] New architecture initialization failed: ${error.message}`)
console.warn('[ResourceManager] Continuing with empty registry')
logger.warn(`ResourceManager new architecture initialization failed: ${error.message}`)
logger.warn('ResourceManager continuing with empty registry')
this.initialized = true // 即使失败也标记为已初始化,避免重复尝试
}
}
/**
* 填充新的RegistryData
*/
async populateRegistryData() {
// 清空现有数据
this.registryData.clear()
// 从各个发现器获取RegistryData并合并
for (const discovery of this.discoveryManager.discoveries) {
try {
if (typeof discovery.getRegistryData === 'function') {
const registryData = await discovery.getRegistryData()
if (registryData && registryData.resources) {
// 合并资源到主注册表
this.registryData.merge(registryData, true) // 允许覆盖
}
}
} catch (error) {
logger.warn(`Failed to get RegistryData from ${discovery.source}: ${error.message}`)
}
}
}
/**
* 为逻辑协议设置注册表引用
*/
@ -116,33 +155,53 @@ class ResourceManager {
async loadResource(resourceId) {
try {
// 每次刷新资源(无状态设计)
await this.refreshResources()
// 不再每次刷新资源,依赖初始化时的资源发现
// 处理@!开头的DPML格式如 @!role://java-developer
if (resourceId.startsWith('@!')) {
const parsed = this.protocolParser.parse(resourceId)
const logicalResourceId = `${parsed.protocol}:${parsed.path}`
// 从注册表查找对应的@package://引用
const reference = this.registry.get(logicalResourceId)
if (!reference) {
throw new Error(`Resource not found: ${logicalResourceId}`)
// 从新的RegistryData查找资源
const resourceData = this.registryData.findResourceById(parsed.path, parsed.protocol)
if (!resourceData) {
throw new Error(`Resource not found: ${parsed.protocol}:${parsed.path}`)
}
// 通过协议解析加载内容
const content = await this.loadResourceByProtocol(reference)
const content = await this.loadResourceByProtocol(resourceData.reference)
return {
success: true,
content,
resourceId,
reference
reference: resourceData.reference
}
}
// 处理传统格式(如 role:java-developer
const reference = this.registry.get(resourceId)
// 先尝试从新的RegistryData查找
let reference = null
// 如果包含协议前缀(如 thought:remember
if (resourceId.includes(':')) {
const [protocol, id] = resourceId.split(':', 2)
const resourceData = this.registryData.findResourceById(id, protocol)
if (resourceData) {
reference = resourceData.reference
}
} else {
// 如果没有协议前缀,尝试查找任意协议的资源
const resourceData = this.registryData.findResourceById(resourceId)
if (resourceData) {
reference = resourceData.reference
}
}
// 如果新的RegistryData中没找到回退到旧的registry
if (!reference) {
reference = this.registry.get(resourceId)
}
if (!reference) {
throw new Error(`Resource not found: ${resourceId}`)
}
@ -216,8 +275,7 @@ class ResourceManager {
// 向后兼容方法
async resolve(resourceUrl) {
try {
// 每次刷新资源(无状态设计)
await this.refreshResources()
// 不再每次刷新资源,依赖初始化时的资源发现
// Handle old format: role:java-backend-developer or @package://...
if (resourceUrl.startsWith('@')) {
@ -275,7 +333,7 @@ class ResourceManager {
// 无状态设计不设置initialized标志
} catch (error) {
console.warn(`[ResourceManager] Resource refresh failed: ${error.message}`)
logger.warn(`ResourceManager resource refresh failed: ${error.message}`)
// 失败时保持注册表为空状态,下次调用时重试
}
}

View File

@ -1,3 +1,5 @@
const logger = require('../../utils/logger')
/**
* 资源注册表
* 新架构中用于存储动态发现的资源映射关系
@ -70,27 +72,27 @@ class ResourceRegistry {
* @param {string} title - 可选标题
*/
printAll(title = '注册表资源清单') {
console.log(`\n📋 ${title}`)
console.log('='.repeat(50))
logger.info(`\n📋 ${title}`)
logger.info('='.repeat(50))
if (this.size === 0) {
console.log('🔍 注册表为空')
logger.info('🔍 注册表为空')
return
}
console.log(`📊 总计: ${this.size} 个资源\n`)
logger.info(`📊 总计: ${this.size} 个资源\n`)
// 按协议分组显示
const groupedResources = this.groupByProtocol()
for (const [protocol, resources] of Object.entries(groupedResources)) {
console.log(`🔖 ${protocol.toUpperCase()} 协议 (${resources.length}个):`)
logger.info(`🔖 ${protocol.toUpperCase()} 协议 (${resources.length}个):`)
resources.forEach(({ id, reference }) => {
const resourceName = id.split(':')[1] || id
console.log(`${resourceName}`)
console.log(` └─ ${reference}`)
logger.info(`${resourceName}`)
logger.info(` └─ ${reference}`)
})
console.log('')
logger.info('')
}
}

View File

@ -1,5 +1,6 @@
const fs = require('fs');
const path = require('path');
const logger = require('./logger');
/**
* 执行上下文检测工具
@ -37,7 +38,7 @@ function getMCPWorkingDirectory() {
// 取第一个工作区路径(多工作区情况)
const firstPath = workspacePaths.split(path.delimiter)[0];
if (firstPath && isValidDirectory(firstPath)) {
console.error(`[执行上下文] 使用WORKSPACE_FOLDER_PATHS: ${firstPath}`);
logger.info(`[执行上下文] 使用WORKSPACE_FOLDER_PATHS: ${firstPath}`);
return firstPath;
}
}
@ -45,27 +46,27 @@ function getMCPWorkingDirectory() {
// 策略2PROMPTX_WORKSPACEPromptX专用环境变量
const promptxWorkspace = process.env.PROMPTX_WORKSPACE;
if (promptxWorkspace && isValidDirectory(promptxWorkspace)) {
console.error(`[执行上下文] 使用PROMPTX_WORKSPACE: ${promptxWorkspace}`);
logger.info(`[执行上下文] 使用PROMPTX_WORKSPACE: ${promptxWorkspace}`);
return promptxWorkspace;
}
// 策略3PWD环境变量某些情况下可用
const pwd = process.env.PWD;
if (pwd && isValidDirectory(pwd) && pwd !== process.cwd()) {
console.error(`[执行上下文] 使用PWD环境变量: ${pwd}`);
logger.info(`[执行上下文] 使用PWD环境变量: ${pwd}`);
return pwd;
}
// 策略4项目根目录智能推测向上查找项目标识
const projectRoot = findProjectRoot(process.cwd());
if (projectRoot && projectRoot !== process.cwd()) {
console.error(`[执行上下文] 智能推测项目根目录: ${projectRoot}`);
logger.info(`[执行上下文] 智能推测项目根目录: ${projectRoot}`);
return projectRoot;
}
// 策略5回退到process.cwd()
console.error(`[执行上下文] 回退到process.cwd(): ${process.cwd()}`);
console.error(`[执行上下文] 提示建议在MCP配置中添加 "env": {"PROMPTX_WORKSPACE": "你的项目目录"}`);
logger.warn(`[执行上下文] 回退到process.cwd(): ${process.cwd()}`);
logger.warn(`[执行上下文] 提示建议在MCP配置中添加 "env": {"PROMPTX_WORKSPACE": "你的项目目录"}`)
return process.cwd();
}