Files
PromptX/src/lib/core/resource/discovery/PackageDiscovery.js
Sean 5ae13c566a Develop (#91)
* 重构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>
2025-06-28 12:00:31 +08:00

752 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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