* 重构ActionCommand和LearnCommand,更新DPMLContentParser和SemanticRenderer的导入路径,确保模块结构一致性。删除不再使用的DPMLContentParser和SemanticRenderer文件,优化代码结构,提升可维护性。 * 重构PromptX资源协议系统,采用极简两层协议架构,删除不必要的语义层,优化路径解析和资源加载流程。引入AI协作优化,支持直接生成完整协议路径,提升系统性能和用户体验。整体架构简化60%,实现零配置启动,显著降低内存占用和启动时间。 * optimize:优化女娲提示词 * Optimize:更新记忆策略文档,增加角色专业记忆的独特价值和工作流程,强调角色记忆与客户端记忆的差异,优化记忆引导话术和决策规则,以提升用户对专业记忆系统的理解和应用。 * feature:增加 Sean 角色 * optimize:优化记忆格式化逻辑,确保完整记忆内容不被截断,同时更新工具定义中的描述,增强用户对记忆回想器的理解和使用指导。 * feat: 添加DACP服务支持,允许通过命令行调用DACP专业服务,增强AI角色的执行能力,同时更新相关依赖和工具定义。 * feat: 在MCPServerCommand和MCPStreamableHttpCommand中添加'promptx_dacp'参数映射,同时在DACPCommand中优化参数处理逻辑,以支持数组参数的正确解析。 * feat: 更新DACP演示服务,重命名服务和描述,简化功能,删除不必要的日历和文档操作,增强演示效果。同时,优化了API接口和README文档,确保用户更易于理解和使用。 * feat: 添加DACP邮件发送功能,支持真实发送与Demo模式,增强邮件发送的配置管理和错误提示,优化用户体验。 * feat: 更新女娲和Sean角色文档,增强角色身份、核心特质和决策框架的描述,优化内容结构,提升用户理解和使用体验。同时,更新产品哲学知识体系,明确矛盾驱动和简洁性原则的应用。 * Add product management submodule * fix: 修复 recall 和 learn 的 bug * refactor: 把 hello 改成 welcome * feat: 添加DACP服务启动脚本和测试命令,更新相关依赖,优化配置文件路径处理 * fix: 更新pnpm-lock.yaml以匹配DACP依赖,解决CI中--frozen-lockfile的错误 * 更新DACP白皮书的更新日期至2025-01-19;在DACPConfigManager中优化配置管理,支持项目级和用户级配置的优先级处理,增强错误提示信息,更新相关方法以支持异步操作。 * Develop (#66) * 重构ActionCommand和LearnCommand,更新DPMLContentParser和SemanticRenderer的导入路径,确保模块结构一致性。删除不再使用的DPMLContentParser和SemanticRenderer文件,优化代码结构,提升可维护性。 * 重构PromptX资源协议系统,采用极简两层协议架构,删除不必要的语义层,优化路径解析和资源加载流程。引入AI协作优化,支持直接生成完整协议路径,提升系统性能和用户体验。整体架构简化60%,实现零配置启动,显著降低内存占用和启动时间。 * optimize:优化女娲提示词 * Optimize:更新记忆策略文档,增加角色专业记忆的独特价值和工作流程,强调角色记忆与客户端记忆的差异,优化记忆引导话术和决策规则,以提升用户对专业记忆系统的理解和应用。 * feature:增加 Sean 角色 * optimize:优化记忆格式化逻辑,确保完整记忆内容不被截断,同时更新工具定义中的描述,增强用户对记忆回想器的理解和使用指导。 * feat: 添加DACP服务支持,允许通过命令行调用DACP专业服务,增强AI角色的执行能力,同时更新相关依赖和工具定义。 * feat: 在MCPServerCommand和MCPStreamableHttpCommand中添加'promptx_dacp'参数映射,同时在DACPCommand中优化参数处理逻辑,以支持数组参数的正确解析。 * feat: 更新DACP演示服务,重命名服务和描述,简化功能,删除不必要的日历和文档操作,增强演示效果。同时,优化了API接口和README文档,确保用户更易于理解和使用。 * feat: 添加DACP邮件发送功能,支持真实发送与Demo模式,增强邮件发送的配置管理和错误提示,优化用户体验。 * feat: 更新女娲和Sean角色文档,增强角色身份、核心特质和决策框架的描述,优化内容结构,提升用户理解和使用体验。同时,更新产品哲学知识体系,明确矛盾驱动和简洁性原则的应用。 * Add product management submodule * fix: 修复 recall 和 learn 的 bug * refactor: 把 hello 改成 welcome * feat: 添加DACP服务启动脚本和测试命令,更新相关依赖,优化配置文件路径处理 * fix: 更新pnpm-lock.yaml以匹配DACP依赖,解决CI中--frozen-lockfile的错误 * 更新DACP白皮书的更新日期至2025-01-19;在DACPConfigManager中优化配置管理,支持项目级和用户级配置的优先级处理,增强错误提示信息,更新相关方法以支持异步操作。 * fix: 统一Pouch命令路径获取机制,解决Issue #69记忆持久化问题 修复多实例MCP环境下的路径不一致问题: - RememberCommand: 使用ResourceManager替代DirectoryService直接调用 - RecallCommand: 使用ResourceManager替代DirectoryService直接调用 - RegisterCommand: 使用ResourceManager+DirectoryService统一路径获取 核心改进: 1. 所有命令现在使用相同的getGlobalResourceManager()初始化 2. 通过resourceManager.initializeWithNewArchitecture()确保路径一致性 3. 实现"要对一起对,要错一起错"的一致性原则 测试验证: - 记忆写入和读取使用相同项目路径 - 多实例环境下路径解析行为完全一致 - 向后兼容,无破坏性变更 Fixes #69 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Develop (#70) * 重构ActionCommand和LearnCommand,更新DPMLContentParser和SemanticRenderer的导入路径,确保模块结构一致性。删除不再使用的DPMLContentParser和SemanticRenderer文件,优化代码结构,提升可维护性。 * 重构PromptX资源协议系统,采用极简两层协议架构,删除不必要的语义层,优化路径解析和资源加载流程。引入AI协作优化,支持直接生成完整协议路径,提升系统性能和用户体验。整体架构简化60%,实现零配置启动,显著降低内存占用和启动时间。 * optimize:优化女娲提示词 * Optimize:更新记忆策略文档,增加角色专业记忆的独特价值和工作流程,强调角色记忆与客户端记忆的差异,优化记忆引导话术和决策规则,以提升用户对专业记忆系统的理解和应用。 * feature:增加 Sean 角色 * optimize:优化记忆格式化逻辑,确保完整记忆内容不被截断,同时更新工具定义中的描述,增强用户对记忆回想器的理解和使用指导。 * feat: 添加DACP服务支持,允许通过命令行调用DACP专业服务,增强AI角色的执行能力,同时更新相关依赖和工具定义。 * feat: 在MCPServerCommand和MCPStreamableHttpCommand中添加'promptx_dacp'参数映射,同时在DACPCommand中优化参数处理逻辑,以支持数组参数的正确解析。 * feat: 更新DACP演示服务,重命名服务和描述,简化功能,删除不必要的日历和文档操作,增强演示效果。同时,优化了API接口和README文档,确保用户更易于理解和使用。 * feat: 添加DACP邮件发送功能,支持真实发送与Demo模式,增强邮件发送的配置管理和错误提示,优化用户体验。 * feat: 更新女娲和Sean角色文档,增强角色身份、核心特质和决策框架的描述,优化内容结构,提升用户理解和使用体验。同时,更新产品哲学知识体系,明确矛盾驱动和简洁性原则的应用。 * Add product management submodule * fix: 修复 recall 和 learn 的 bug * refactor: 把 hello 改成 welcome * feat: 添加DACP服务启动脚本和测试命令,更新相关依赖,优化配置文件路径处理 * fix: 更新pnpm-lock.yaml以匹配DACP依赖,解决CI中--frozen-lockfile的错误 * 更新DACP白皮书的更新日期至2025-01-19;在DACPConfigManager中优化配置管理,支持项目级和用户级配置的优先级处理,增强错误提示信息,更新相关方法以支持异步操作。 * fix: 统一Pouch命令路径获取机制,解决Issue #69记忆持久化问题 修复多实例MCP环境下的路径不一致问题: - RememberCommand: 使用ResourceManager替代DirectoryService直接调用 - RecallCommand: 使用ResourceManager替代DirectoryService直接调用 - RegisterCommand: 使用ResourceManager+DirectoryService统一路径获取 核心改进: 1. 所有命令现在使用相同的getGlobalResourceManager()初始化 2. 通过resourceManager.initializeWithNewArchitecture()确保路径一致性 3. 实现"要对一起对,要错一起错"的一致性原则 测试验证: - 记忆写入和读取使用相同项目路径 - 多实例环境下路径解析行为完全一致 - 向后兼容,无破坏性变更 Fixes #69 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * doc: 新增“女娲”使用心得 (#73) * feat:添加女娲的使用心得 refactor:readme最新版本同步到英文版 * docs: 添加社区教程与案例部分,包含基于PromptX架构的MCP工具开发实践经验 * Staging (#71) * Develop (#66) * 重构ActionCommand和LearnCommand,更新DPMLContentParser和SemanticRenderer的导入路径,确保模块结构一致性。删除不再使用的DPMLContentParser和SemanticRenderer文件,优化代码结构,提升可维护性。 * 重构PromptX资源协议系统,采用极简两层协议架构,删除不必要的语义层,优化路径解析和资源加载流程。引入AI协作优化,支持直接生成完整协议路径,提升系统性能和用户体验。整体架构简化60%,实现零配置启动,显著降低内存占用和启动时间。 * optimize:优化女娲提示词 * Optimize:更新记忆策略文档,增加角色专业记忆的独特价值和工作流程,强调角色记忆与客户端记忆的差异,优化记忆引导话术和决策规则,以提升用户对专业记忆系统的理解和应用。 * feature:增加 Sean 角色 * optimize:优化记忆格式化逻辑,确保完整记忆内容不被截断,同时更新工具定义中的描述,增强用户对记忆回想器的理解和使用指导。 * feat: 添加DACP服务支持,允许通过命令行调用DACP专业服务,增强AI角色的执行能力,同时更新相关依赖和工具定义。 * feat: 在MCPServerCommand和MCPStreamableHttpCommand中添加'promptx_dacp'参数映射,同时在DACPCommand中优化参数处理逻辑,以支持数组参数的正确解析。 * feat: 更新DACP演示服务,重命名服务和描述,简化功能,删除不必要的日历和文档操作,增强演示效果。同时,优化了API接口和README文档,确保用户更易于理解和使用。 * feat: 添加DACP邮件发送功能,支持真实发送与Demo模式,增强邮件发送的配置管理和错误提示,优化用户体验。 * feat: 更新女娲和Sean角色文档,增强角色身份、核心特质和决策框架的描述,优化内容结构,提升用户理解和使用体验。同时,更新产品哲学知识体系,明确矛盾驱动和简洁性原则的应用。 * Add product management submodule * fix: 修复 recall 和 learn 的 bug * refactor: 把 hello 改成 welcome * feat: 添加DACP服务启动脚本和测试命令,更新相关依赖,优化配置文件路径处理 * fix: 更新pnpm-lock.yaml以匹配DACP依赖,解决CI中--frozen-lockfile的错误 * 更新DACP白皮书的更新日期至2025-01-19;在DACPConfigManager中优化配置管理,支持项目级和用户级配置的优先级处理,增强错误提示信息,更新相关方法以支持异步操作。 * Develop (#70) * 重构ActionCommand和LearnCommand,更新DPMLContentParser和SemanticRenderer的导入路径,确保模块结构一致性。删除不再使用的DPMLContentParser和SemanticRenderer文件,优化代码结构,提升可维护性。 * 重构PromptX资源协议系统,采用极简两层协议架构,删除不必要的语义层,优化路径解析和资源加载流程。引入AI协作优化,支持直接生成完整协议路径,提升系统性能和用户体验。整体架构简化60%,实现零配置启动,显著降低内存占用和启动时间。 * optimize:优化女娲提示词 * Optimize:更新记忆策略文档,增加角色专业记忆的独特价值和工作流程,强调角色记忆与客户端记忆的差异,优化记忆引导话术和决策规则,以提升用户对专业记忆系统的理解和应用。 * feature:增加 Sean 角色 * optimize:优化记忆格式化逻辑,确保完整记忆内容不被截断,同时更新工具定义中的描述,增强用户对记忆回想器的理解和使用指导。 * feat: 添加DACP服务支持,允许通过命令行调用DACP专业服务,增强AI角色的执行能力,同时更新相关依赖和工具定义。 * feat: 在MCPServerCommand和MCPStreamableHttpCommand中添加'promptx_dacp'参数映射,同时在DACPCommand中优化参数处理逻辑,以支持数组参数的正确解析。 * feat: 更新DACP演示服务,重命名服务和描述,简化功能,删除不必要的日历和文档操作,增强演示效果。同时,优化了API接口和README文档,确保用户更易于理解和使用。 * feat: 添加DACP邮件发送功能,支持真实发送与Demo模式,增强邮件发送的配置管理和错误提示,优化用户体验。 * feat: 更新女娲和Sean角色文档,增强角色身份、核心特质和决策框架的描述,优化内容结构,提升用户理解和使用体验。同时,更新产品哲学知识体系,明确矛盾驱动和简洁性原则的应用。 * Add product management submodule * fix: 修复 recall 和 learn 的 bug * refactor: 把 hello 改成 welcome * feat: 添加DACP服务启动脚本和测试命令,更新相关依赖,优化配置文件路径处理 * fix: 更新pnpm-lock.yaml以匹配DACP依赖,解决CI中--frozen-lockfile的错误 * 更新DACP白皮书的更新日期至2025-01-19;在DACPConfigManager中优化配置管理,支持项目级和用户级配置的优先级处理,增强错误提示信息,更新相关方法以支持异步操作。 * fix: 统一Pouch命令路径获取机制,解决Issue #69记忆持久化问题 修复多实例MCP环境下的路径不一致问题: - RememberCommand: 使用ResourceManager替代DirectoryService直接调用 - RecallCommand: 使用ResourceManager替代DirectoryService直接调用 - RegisterCommand: 使用ResourceManager+DirectoryService统一路径获取 核心改进: 1. 所有命令现在使用相同的getGlobalResourceManager()初始化 2. 通过resourceManager.initializeWithNewArchitecture()确保路径一致性 3. 实现"要对一起对,要错一起错"的一致性原则 测试验证: - 记忆写入和读取使用相同项目路径 - 多实例环境下路径解析行为完全一致 - 向后兼容,无破坏性变更 Fixes #69 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * 更新README文件,替换女娲创造工坊的logo图片,添加社区教程与案例部分,展示基于PromptX架构的MCP工具开发经验,提升内容的可读性和用户体验。 * 更新README文件,优化“女娲”功能的描述,强调其无需编程知识即可使用的便利性,并介绍其创建“小红书营销”专家的能力,提升内容的清晰度和用户体验。 --------- Co-authored-by: Sean <sean@deepracticex.com> Co-authored-by: coso <wutongci@example.com> Co-authored-by: Claude <noreply@anthropic.com> * refactor: 优化DACP工具提示词,去除诱导性描述 - 将DACP工具描述从功能介绍改为使用条件 - 强调需要专业知识才能正确使用 - 避免AI在不了解服务配置时盲目尝试 - 符合"先学会使用工具再做事"的设计理念 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Optimize:优化女娲提示词,为 Sean 添加 Github Issues 矛盾分析方法论 * fix: 修复 这几个命令使用了废弃的项目路径定位方案 * Optimize: 优化 sean 提示词 * Optimize: 优化 sean 提示词 * 删除产品子项目文件 * 🚀 feat: 记忆系统架构升级 + declarative.dpml命名重构 + MCP边界条件Bug修复 ## 📊 变更概览 - declarative.dpml架构升级:memory.xml → declarative.dpml (认知科学语义精准) - MCP环境边界条件Bug修复:解决空文件导致的记忆保存失败问题 - 跨项目角色发现Bug修复:优化环境检测顺序,MCP环境角色发现从1个→9个 - XML转义处理增强:完整的存储-显示分离架构,数据安全+用户友好 ## 🎯 核心成就 ✅ declarative.dpml升级:100%测试验证通过 ✅ 边界条件修复:三重保护机制,文件状态自动检测修复 ✅ 角色发现修复:环境检测顺序优化,跨项目使用稳定 ✅ 存储分离架构:XML转义安全存储 + AI友好显示 ## 📁 主要文件变更 - RememberCommand.js/RecallCommand.js: declarative.dpml升级 + 边界条件修复 - PackageDiscovery.js: 环境检测顺序优化 - 新增思维模式文件: recall-xml.thought.md, remember-xml.thought.md - 新增测试: memory-dpml-integration.test.js - 完整文档: PR文档 + Bug报告 + 修复总结 🎉 架构升级验证:MCP重启测试100%通过,零中断平滑切换 * fix: 修复记忆时的问题处理合并的问题 * fix: 系统化优化角色输出显示,解决角色名称混淆问题 - 优化WelcomeCommand输出格式,角色列表显示为 `id` - name 格式 - 优化ActionCommand输出格式,激活成功时显示 `id` (name) 格式 - 更新package.registry.json中7个角色的name和description: * nuwa: "Nuwa 角色" → "女娲",描述更新为专业的角色创造能力 * sean: "Sean 角色" → "Sean",描述更新为CEO身份 * product-manager: 更新为"产品经理",描述强调价值平衡能力 * java-backend-developer: 更新为"Java后端开发工程师",描述强调架构能力 * frontend-developer: 更新为"前端开发工程师",描述强调用户体验 * xiaohongshu-marketer: 更新为"小红书营销专家",描述强调平台运营 * assistant: 更新为"总经理秘书",描述强调执行和协调能力 - 解决角色名称显示不一致和描述过于通用的问题 - 提升用户体验,避免角色身份混淆 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: noface角色重命名及file://协议路径转换优化 ## 主要变更 - **角色重命名**: wumian → noface,更符合英文命名规范 - **file://协议优化**: 新增FileProtocol.js支持本地文件访问 - **路径转换修复**: 智能处理Shell反斜杠转义问题 - **ResourceManager增强**: 支持基础协议直接处理 ## 技术改进 - 修复复杂路径格式兼容性(如WeChat路径、中文字符、特殊符号) - 自动清理反斜杠转义符(Application\ Support → Application Support) - 完善错误处理机制和用户提示 ## 文件变更 - 新增: noface角色完整文件结构(role + 2个execution文件) - 新增: FileProtocol.js协议处理器 - 更新: ResourceManager.js基础协议支持 - 更新: package.registry.json角色注册信息 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: 重新定位产品价值主张,强化AI上下文工程概念 核心改进: - 🎯 主标题改为「领先的AI上下文工程平台」蹭技术热点 - ✨ 引入「Chat is all you need」革命性交互理念 - 🚀 前置强力案例数据(11000行代码、40小时→30分钟) - 🎭 重新包装核心能力,突出专业价值 产品战略价值: - 结合AI上下文工程趋势,提升技术定位 - 通过真实数据建立社会证明 - 降低技术理解门槛,扩大目标用户群 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: 重构社区章节和案例展示 核心改进: - 🌟 新增「Deepractice深度实践社区」愿景展示 - 🎯 突出「AI时代Life Style」定位和「君子和而不同」价值观 - 🏗️ 重新设计社区资源架构:开源产品+开发者舞台+商业共建 - 📋 移动案例到最后,重命名为「社区优质案例分享」 - 🤝 简化「加入我们」为纯二维码展示 产品战略价值: - 从工具项目升华为AI时代生活方式社区 - 建立开放包容的商业合作模式(内容换价值) - 为社区可持续发展和商业化提供健康路径 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: 全面优化社区价值体系和README结构 ## 主要变更 - **社区定位升级**:从"AI时代Life Style"优化为"AI原生Life Style社区" - **价值观体系重构**:技术开源·内容分享·社区开放·价值交换四维度 - **社区价值机制**:构建注意力价值交换+商业价值交换双重生态 - **内容展示优化**:社区案例分享格式精简,提升阅读体验 - **精神内核明确**:突出"实践·协作·创新"的社区精神 ## 技术改进 - 移除冗余的传统企业级展示模式 - 优化信息架构,突出价值交换核心理念 - 精简社区案例展示,提升信息密度 - 强化AI原生社区的独特定位 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: 添加安装成功示意图 - 新增 assets/install-success.jpg 用于展示MCP工具安装成功效果 - 完善README中的安装成功确认章节视觉展示 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: AgustD <100462005+lsh2002@users.noreply.github.com> Co-authored-by: coso <wutongci@example.com> Co-authored-by: Cen-Yaozu <80613496+Cen-Yaozu@users.noreply.github.com>
752 lines
24 KiB
JavaScript
752 lines
24 KiB
JavaScript
const BaseDiscovery = require('./BaseDiscovery')
|
||
const RegistryData = require('../RegistryData')
|
||
const ResourceData = require('../ResourceData')
|
||
const ResourceFileNaming = require('../ResourceFileNaming')
|
||
const logger = require('../../../utils/logger')
|
||
const path = require('path')
|
||
const fs = require('fs-extra')
|
||
const CrossPlatformFileScanner = require('./CrossPlatformFileScanner')
|
||
const { getDirectoryService } = require('../../../utils/DirectoryService')
|
||
|
||
/**
|
||
* PackageDiscovery - 包级资源发现器
|
||
*
|
||
* 负责发现NPM包内的资源:
|
||
* 1. 从 src/resource.registry.json 加载静态注册表
|
||
* 2. 扫描 prompt/ 目录发现动态资源
|
||
*
|
||
* 优先级:1 (最高优先级)
|
||
*/
|
||
class PackageDiscovery extends BaseDiscovery {
|
||
constructor() {
|
||
super('PACKAGE', 1)
|
||
this.fileScanner = new CrossPlatformFileScanner()
|
||
this.directoryService = getDirectoryService()
|
||
// 将在_getRegistryPath()中动态计算
|
||
this.registryPath = null
|
||
}
|
||
|
||
/**
|
||
* 发现包级资源 (优化版 - 硬编码注册表)
|
||
* @returns {Promise<Array>} 发现的资源列表
|
||
*/
|
||
async discover() {
|
||
try {
|
||
// 使用硬编码注册表替代动态扫描,性能提升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) {
|
||
logger.warn(`PackageDiscovery discovery failed: ${error.message}`)
|
||
// 降级到动态扫描作为fallback
|
||
return this._fallbackToLegacyDiscovery()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 发现包级资源注册表
|
||
* @returns {Promise<Map>} 资源注册表 Map<resourceId, reference>
|
||
*/
|
||
async discoverRegistry() {
|
||
try {
|
||
// 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)
|
||
}
|
||
|
||
// 2. 如果注册表不存在或为空,回退到动态扫描
|
||
logger.warn(`[PackageDiscovery] ⚠️ 注册表不存在,回退到动态扫描`)
|
||
return await this._fallbackToScanning()
|
||
|
||
} catch (error) {
|
||
logger.warn(`[PackageDiscovery] ❌ 注册表加载失败: ${error.message},回退到动态扫描`)
|
||
return await this._fallbackToScanning()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取注册表路径
|
||
* @returns {Promise<string>} 注册表文件路径
|
||
* @private
|
||
*/
|
||
async _getRegistryPath() {
|
||
if (!this.registryPath) {
|
||
try {
|
||
const context = {
|
||
startDir: process.cwd(),
|
||
platform: process.platform,
|
||
avoidUserHome: true
|
||
}
|
||
const projectRoot = await this.directoryService.getProjectRoot(context)
|
||
this.registryPath = path.join(projectRoot, 'src/package.registry.json')
|
||
} catch (error) {
|
||
// 回退到默认路径
|
||
this.registryPath = path.join(process.cwd(), 'src/package.registry.json')
|
||
}
|
||
}
|
||
return this.registryPath
|
||
}
|
||
|
||
/**
|
||
* 从硬编码注册表加载资源
|
||
* @returns {Promise<RegistryData|null>} 注册表数据
|
||
* @private
|
||
*/
|
||
async _loadFromRegistry() {
|
||
try {
|
||
const registryPath = await this._getRegistryPath()
|
||
logger.debug(`[PackageDiscovery] 🔧 注册表路径: ${registryPath}`)
|
||
|
||
if (!(await fs.pathExists(registryPath))) {
|
||
logger.warn(`[PackageDiscovery] ❌ 注册表文件不存在: ${registryPath}`)
|
||
return null
|
||
}
|
||
|
||
const registryData = await RegistryData.fromFile('package', 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()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 生成包级资源注册表(用于构建时)
|
||
* @param {string} packageRoot - 包根目录
|
||
* @returns {Promise<RegistryData>} 生成的注册表数据
|
||
*/
|
||
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
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 扫描目录并添加资源到注册表
|
||
* @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: {
|
||
scannedAt: new Date().toISOString()
|
||
}
|
||
})
|
||
|
||
registryData.addResource(resourceData)
|
||
}
|
||
|
||
// 查找thought文件 - 使用统一命名管理器
|
||
const thoughtDir = path.join(itemPath, 'thought')
|
||
if (await fs.pathExists(thoughtDir)) {
|
||
const thoughtFiles = await ResourceFileNaming.scanTagFiles(thoughtDir, 'thought')
|
||
|
||
for (const thoughtFile of thoughtFiles) {
|
||
const thoughtId = ResourceFileNaming.extractResourceId(thoughtFile, 'thought')
|
||
if (thoughtId) {
|
||
const fileName = path.basename(thoughtFile)
|
||
const reference = `@package://prompt/domain/${item}/thought/${fileName}`
|
||
|
||
const resourceData = new ResourceData({
|
||
id: thoughtId,
|
||
source: 'package',
|
||
protocol: 'thought',
|
||
name: ResourceData._generateDefaultName(thoughtId, 'thought'),
|
||
description: ResourceData._generateDefaultDescription(thoughtId, 'thought'),
|
||
reference: reference,
|
||
metadata: {
|
||
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: {
|
||
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: {
|
||
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: {
|
||
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目录发现资源
|
||
* @returns {Promise<Array>} 扫描发现的资源列表
|
||
*/
|
||
async _scanPromptDirectory() {
|
||
try {
|
||
const packageRoot = await this._findPackageRoot()
|
||
const promptDir = path.join(packageRoot, 'prompt')
|
||
|
||
if (!await fs.pathExists(promptDir)) {
|
||
return []
|
||
}
|
||
|
||
const resources = []
|
||
|
||
// 定义要扫描的资源类型
|
||
const resourceTypes = ['role', 'execution', 'thought', 'knowledge']
|
||
|
||
// 并行扫描所有资源类型
|
||
for (const resourceType of resourceTypes) {
|
||
const files = await this.fileScanner.scanResourceFiles(promptDir, resourceType)
|
||
|
||
for (const filePath of files) {
|
||
const suffix = `.${resourceType}.md`
|
||
const id = this._extractResourceId(filePath, resourceType, suffix)
|
||
const reference = this._generatePackageReference(filePath, packageRoot)
|
||
|
||
resources.push({
|
||
id: id,
|
||
reference: reference
|
||
})
|
||
}
|
||
}
|
||
|
||
return resources
|
||
} catch (error) {
|
||
logger.warn(`[PackageDiscovery] Failed to scan prompt directory: ${error.message}`)
|
||
return []
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 文件扫描(可以被测试mock)
|
||
* @param {string} baseDir - 基础目录
|
||
* @param {string} resourceType - 资源类型
|
||
* @returns {Promise<Array>} 匹配的文件路径列表
|
||
*/
|
||
async _scanFiles(baseDir, resourceType) {
|
||
return await this.fileScanner.scanResourceFiles(baseDir, resourceType)
|
||
}
|
||
|
||
/**
|
||
* 检测执行环境类型
|
||
* @returns {Promise<string>} 环境类型:development, npx, local, unknown
|
||
*/
|
||
async _detectExecutionEnvironment() {
|
||
// 1. 优先检查npx执行(具体环境,避免MCP误判)
|
||
if (this._isNpxExecution()) {
|
||
return 'npx'
|
||
}
|
||
|
||
// 2. 检查本地安装(具体环境)
|
||
if (this._isLocalInstallation()) {
|
||
return 'local'
|
||
}
|
||
|
||
// 3. 最后检查开发环境(通用环境,优先级降低)
|
||
if (await this._isDevelopmentMode()) {
|
||
return 'development'
|
||
}
|
||
|
||
return 'unknown'
|
||
}
|
||
|
||
/**
|
||
* 检查是否在开发模式
|
||
* @returns {Promise<boolean>} 是否为开发模式
|
||
*/
|
||
async _isDevelopmentMode() {
|
||
try {
|
||
const context = {
|
||
startDir: process.cwd(),
|
||
platform: process.platform,
|
||
avoidUserHome: true
|
||
}
|
||
const projectRoot = await this.directoryService.getProjectRoot(context)
|
||
|
||
const hasCliScript = await fs.pathExists(path.join(projectRoot, 'src', 'bin', 'promptx.js'))
|
||
const hasPackageJson = await fs.pathExists(path.join(projectRoot, 'package.json'))
|
||
|
||
if (!hasCliScript || !hasPackageJson) {
|
||
return false
|
||
}
|
||
|
||
const packageJson = await fs.readJSON(path.join(projectRoot, 'package.json'))
|
||
return packageJson.name === 'dpml-prompt'
|
||
} catch (error) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查是否通过npx执行
|
||
* @returns {boolean} 是否为npx执行
|
||
*/
|
||
_isNpxExecution() {
|
||
// 检查环境变量
|
||
if (process.env.npm_execpath && process.env.npm_execpath.includes('npx')) {
|
||
return true
|
||
}
|
||
|
||
// 检查目录路径(npx缓存目录)
|
||
const currentDir = this._getCurrentDirectory()
|
||
if (currentDir.includes('.npm/_npx/') || currentDir.includes('_npx')) {
|
||
return true
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
/**
|
||
* 检查是否在本地安装
|
||
* @returns {boolean} 是否为本地安装
|
||
*/
|
||
_isLocalInstallation() {
|
||
const currentDir = this._getCurrentDirectory()
|
||
return currentDir.includes('node_modules/dpml-prompt')
|
||
}
|
||
|
||
/**
|
||
* 获取当前目录(可以被测试mock)
|
||
* @returns {string} 当前目录路径
|
||
*/
|
||
_getCurrentDirectory() {
|
||
return __dirname
|
||
}
|
||
|
||
/**
|
||
* 查找包根目录
|
||
* @returns {Promise<string>} 包根目录路径
|
||
*/
|
||
async _findPackageRoot() {
|
||
const cacheKey = 'packageRoot'
|
||
const cached = this.getFromCache(cacheKey)
|
||
if (cached) {
|
||
return cached
|
||
}
|
||
|
||
const environment = await this._detectExecutionEnvironment()
|
||
let packageRoot = null
|
||
|
||
switch (environment) {
|
||
case 'development':
|
||
packageRoot = await this._findDevelopmentRoot()
|
||
break
|
||
case 'npx':
|
||
case 'local':
|
||
packageRoot = await this._findInstalledRoot()
|
||
break
|
||
default:
|
||
packageRoot = await this._findFallbackRoot()
|
||
}
|
||
|
||
if (!packageRoot) {
|
||
throw new Error('Package root not found')
|
||
}
|
||
|
||
this.setCache(cacheKey, packageRoot)
|
||
return packageRoot
|
||
}
|
||
|
||
/**
|
||
* 查找开发环境的包根目录
|
||
* @returns {Promise<string|null>} 包根目录路径或null
|
||
*/
|
||
async _findDevelopmentRoot() {
|
||
// 策略1:检查当前工作目录
|
||
const cwd = process.cwd()
|
||
if (await this._isValidDevelopmentRoot(cwd)) {
|
||
return fs.realpathSync(cwd)
|
||
}
|
||
|
||
// 策略2:检查启动脚本的目录(适用于通过脚本启动的情况)
|
||
const scriptDir = path.dirname(process.argv[1])
|
||
let searchDir = scriptDir
|
||
|
||
// 向上查找最多5级目录
|
||
for (let i = 0; i < 5; i++) {
|
||
if (await this._isValidDevelopmentRoot(searchDir)) {
|
||
return fs.realpathSync(searchDir)
|
||
}
|
||
|
||
const parentDir = path.dirname(searchDir)
|
||
if (parentDir === searchDir) break // 已到根目录
|
||
searchDir = parentDir
|
||
}
|
||
|
||
return null
|
||
}
|
||
|
||
/**
|
||
* 检查目录是否为有效的开发环境根目录
|
||
* @param {string} dir - 要检查的目录
|
||
* @returns {Promise<boolean>} 是否为有效的开发根目录
|
||
* @private
|
||
*/
|
||
async _isValidDevelopmentRoot(dir) {
|
||
const hasPackageJson = await fs.pathExists(path.join(dir, 'package.json'))
|
||
const hasPromptDir = await fs.pathExists(path.join(dir, 'prompt'))
|
||
|
||
if (!hasPackageJson || !hasPromptDir) {
|
||
return false
|
||
}
|
||
|
||
try {
|
||
const packageJson = await fs.readJSON(path.join(dir, 'package.json'))
|
||
return packageJson.name === 'dpml-prompt'
|
||
} catch (error) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 查找已安装包的根目录
|
||
* @returns {Promise<string|null>} 包根目录路径或null
|
||
*/
|
||
async _findInstalledRoot() {
|
||
try {
|
||
const currentDir = this._getCurrentDirectory()
|
||
let searchDir = currentDir
|
||
|
||
// 向上查找package.json
|
||
while (searchDir !== path.parse(searchDir).root) {
|
||
const packageJsonPath = path.join(searchDir, 'package.json')
|
||
|
||
if (await fs.pathExists(packageJsonPath)) {
|
||
const packageJson = await fs.readJSON(packageJsonPath)
|
||
|
||
if (packageJson.name === 'dpml-prompt') {
|
||
return searchDir
|
||
}
|
||
}
|
||
|
||
searchDir = path.dirname(searchDir)
|
||
}
|
||
} catch (error) {
|
||
// Ignore errors
|
||
}
|
||
|
||
return null
|
||
}
|
||
|
||
/**
|
||
* 后备方案:使用模块解析查找包根目录
|
||
* @returns {Promise<string|null>} 包根目录路径或null
|
||
*/
|
||
async _findFallbackRoot() {
|
||
try {
|
||
// 优先使用__dirname计算包根目录(更可靠的路径)
|
||
const packageRoot = path.resolve(__dirname, '../../../../../')
|
||
|
||
// 验证是否为有效的dpml-prompt包
|
||
const packageJsonPath = path.join(packageRoot, 'package.json')
|
||
if (await fs.pathExists(packageJsonPath)) {
|
||
const packageJson = await fs.readJSON(packageJsonPath)
|
||
if (packageJson.name === 'dpml-prompt') {
|
||
return packageRoot
|
||
}
|
||
}
|
||
|
||
// 后备方案:使用模块解析(使用__dirname作为basedir)
|
||
const resolve = require('resolve')
|
||
const resolvedPackageJsonPath = resolve.sync('dpml-prompt/package.json', {
|
||
basedir: __dirname
|
||
})
|
||
return path.dirname(resolvedPackageJsonPath)
|
||
} catch (error) {
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 生成包引用路径
|
||
* @param {string} filePath - 文件绝对路径
|
||
* @param {string} packageRoot - 包根目录
|
||
* @returns {string} @package://相对路径
|
||
*/
|
||
_generatePackageReference(filePath, packageRoot) {
|
||
const relativePath = this.fileScanner.getRelativePath(packageRoot, filePath)
|
||
return `@package://${relativePath}`
|
||
}
|
||
|
||
/**
|
||
* 提取资源ID
|
||
* @param {string} filePath - 文件路径
|
||
* @param {string} protocol - 协议类型
|
||
* @param {string} suffix - 文件后缀
|
||
* @returns {string} 资源ID (protocol:resourceName)
|
||
*/
|
||
_extractResourceId(filePath, protocol, suffix) {
|
||
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 |