核心修复: - 修复PackageDiscovery._isValidDevelopmentRoot()检查resource目录而非prompt - 更新package.json files字段从prompt/改为resource/ - 修复RegisterCommand.js中的资源路径引用 - 更新WelcomeCommand.js中的@package://prompt/为@package://resource/ - 修复PromptProtocol.js中所有@package://prompt/路径引用 - 更新PackageProtocol.js示例路径 - 批量更新docs/目录下26个文档的路径引用 技术价值: - 解决PackageDiscovery无法加载系统级角色的问题 - 消除PackageProtocol的Access denied错误 - 实现prompt→resource语义重构的100%完整性 - 确保所有8个系统级角色正常加载和激活 验证结果: - ✅ 61个系统级资源正常加载 - ✅ 8个角色完全可用(assistant,frontend-developer,java-backend-developer,noface,nuwa,sean,xiaohongshu-marketer,product-manager) - ✅ welcome和action命令完全正常工作 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
549 lines
14 KiB
Markdown
549 lines
14 KiB
Markdown
# PromptX 资源管理架构改进方案
|
||
|
||
## 背景
|
||
|
||
**Issue #31** 的修复虽然解决了 Windows 路径解析兼容性问题,但暴露了 PromptX 资源管理架构的深层次问题。本文档提出了一个基于**注册表缓存 + @reference protocol 体系**的全新架构改进方案。
|
||
|
||
## 当前架构问题分析
|
||
|
||
### 1. 架构复杂度过高
|
||
|
||
当前系统存在多个重叠的组件和注册表:
|
||
|
||
```
|
||
ResourceRegistry.js (248行) + resource.registry.json (167行) + 内存协议注册表
|
||
↓
|
||
ResourceManager.js (482行)
|
||
↓
|
||
SimplifiedRoleDiscovery.js (284行)
|
||
↓
|
||
PackageProtocol.js (581行)
|
||
```
|
||
|
||
**问题**:
|
||
- 职责重叠,维护成本高
|
||
- 数据流路径不清晰
|
||
- 调试困难
|
||
|
||
### 2. 协议系统不一致
|
||
|
||
**当前协议处理问题**:
|
||
```javascript
|
||
// 问题1:协议前缀不统一
|
||
"@package://resource/core/role.md" // 正确
|
||
"@packages://promptx/resource/core/" // 错误变换
|
||
|
||
// 问题2:循环依赖
|
||
ResourceManager → PackageProtocol → ResourceManager
|
||
|
||
// 问题3:硬编码协议处理
|
||
if (filePath.startsWith('@package://')) {
|
||
// 每次都创建新实例,无复用
|
||
const PackageProtocol = require('../../resource/protocols/PackageProtocol')
|
||
const packageProtocol = new PackageProtocol()
|
||
}
|
||
```
|
||
|
||
### 3. 缓存机制缺失
|
||
|
||
**性能问题**:
|
||
- 每次资源解析都触发文件系统操作
|
||
- 资源发现结果未缓存,重复扫描
|
||
- 协议实例未复用
|
||
|
||
**具体数据**:
|
||
- 首次角色激活:~200ms(包含文件系统扫描)
|
||
- 后续角色激活:~150ms(仍需重复解析)
|
||
- 期望性能:<10ms(缓存命中)
|
||
|
||
### 4. 发现与注册强耦合
|
||
|
||
```javascript
|
||
// 当前实现问题
|
||
class SimplifiedRoleDiscovery {
|
||
async discoverRoles() {
|
||
// 直接操作文件系统
|
||
const files = await this.scanFileSystem()
|
||
// 直接写入注册表
|
||
this.registry.register(roles)
|
||
}
|
||
}
|
||
```
|
||
|
||
**问题**:
|
||
- 无法独立测试发现逻辑
|
||
- 扩展新资源类型需要修改多处代码
|
||
- 资源变更无法实时响应
|
||
|
||
## 新架构设计(奥卡姆剃刀原则)
|
||
|
||
### 核心问题聚焦
|
||
|
||
**Issue #31 暴露的根本问题**:
|
||
1. **注册表与协议不一致**:注册表存储直接路径,绕过了 @reference 体系
|
||
2. **发现与注册耦合**:SimplifiedRoleDiscovery 直接操作注册表
|
||
3. **协议解析分散**:每个地方都在解析协议,没有统一入口
|
||
|
||
### 简化架构设计
|
||
|
||
**核心原则**:
|
||
```
|
||
用户请求 → ResourceRegistry(查找@reference) → ProtocolResolver(统一解析) → 文件系统
|
||
```
|
||
|
||
**3个核心组件,解决核心问题**:
|
||
- `ResourceRegistry`:纯索引,存储 id → @reference 映射
|
||
- `ProtocolResolver`:统一协议解析入口
|
||
- `ResourceDiscovery`:独立发现服务,与注册表解耦
|
||
|
||
**完全兼容现有格式**:
|
||
- ✅ 100% 兼容现有 `resource.registry.json`
|
||
- ✅ 现有代码无需修改
|
||
- ✅ 保持 @reference 体系一致性
|
||
|
||
### 详细组件设计
|
||
|
||
#### 1. 简化的资源注册表 (ResourceRegistry)
|
||
|
||
**职责**:纯索引,存储 id → @reference 映射,兼容现有格式
|
||
|
||
```javascript
|
||
class ResourceRegistry {
|
||
constructor() {
|
||
this.index = new Map() // 纯粹的 id → @reference 映射
|
||
}
|
||
|
||
// 加载 resource.registry.json(兼容现有格式)
|
||
loadFromFile(registryPath = 'src/resource.registry.json') {
|
||
const data = JSON.parse(fs.readFileSync(registryPath, 'utf8'))
|
||
|
||
for (const [protocol, info] of Object.entries(data.protocols)) {
|
||
if (info.registry) {
|
||
for (const [id, resourceInfo] of Object.entries(info.registry)) {
|
||
const reference = typeof resourceInfo === 'string'
|
||
? resourceInfo
|
||
: resourceInfo.file
|
||
|
||
this.index.set(`${protocol}:${id}`, reference)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 注册新发现的资源
|
||
register(id, reference) {
|
||
this.index.set(id, reference)
|
||
}
|
||
|
||
// 解析资源ID到@reference
|
||
resolve(resourceId) {
|
||
// 1. 直接查找
|
||
if (this.index.has(resourceId)) {
|
||
return this.index.get(resourceId)
|
||
}
|
||
|
||
// 2. 兼容性:尝试添加协议前缀
|
||
for (const protocol of ['role', 'thought', 'execution', 'memory']) {
|
||
const fullId = `${protocol}:${resourceId}`
|
||
if (this.index.has(fullId)) {
|
||
return this.index.get(fullId)
|
||
}
|
||
}
|
||
|
||
throw new Error(`Resource '${resourceId}' not found`)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2. 解耦的资源发现服务 (ResourceDiscovery)
|
||
|
||
**职责**:纯粹的发现功能,不操作注册表
|
||
|
||
```javascript
|
||
class ResourceDiscovery {
|
||
// 纯粹的发现功能,不操作注册表
|
||
async discoverResources(scanPaths) {
|
||
const discovered = []
|
||
|
||
for (const basePath of scanPaths) {
|
||
// 发现角色文件
|
||
const roleFiles = await glob(`${basePath}/**/*.role.md`)
|
||
for (const file of roleFiles) {
|
||
discovered.push({
|
||
id: `role:${this.extractId(file, '.role.md')}`,
|
||
reference: this.generateReference(file)
|
||
})
|
||
}
|
||
|
||
// 发现执行模式文件
|
||
const execFiles = await glob(`${basePath}/**/execution/*.execution.md`)
|
||
for (const file of execFiles) {
|
||
discovered.push({
|
||
id: `execution:${this.extractId(file, '.execution.md')}`,
|
||
reference: this.generateReference(file)
|
||
})
|
||
}
|
||
|
||
// 发现思维模式文件
|
||
const thoughtFiles = await glob(`${basePath}/**/thought/*.thought.md`)
|
||
for (const file of thoughtFiles) {
|
||
discovered.push({
|
||
id: `thought:${this.extractId(file, '.thought.md')}`,
|
||
reference: this.generateReference(file)
|
||
})
|
||
}
|
||
}
|
||
|
||
return discovered
|
||
}
|
||
|
||
extractId(filePath, suffix) {
|
||
return path.basename(filePath, suffix)
|
||
}
|
||
|
||
generateReference(filePath) {
|
||
// 简单的规则:根据路径判断协议
|
||
if (filePath.includes('node_modules/promptx')) {
|
||
const relativePath = path.relative(this.findPackageRoot(), filePath)
|
||
return `@package://${relativePath}`
|
||
} else if (filePath.includes('.promptx')) {
|
||
const relativePath = path.relative(process.cwd(), filePath)
|
||
return `@project://${relativePath}`
|
||
} else {
|
||
return `@file://${filePath}`
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 3. 统一协议解析器 (ProtocolResolver)
|
||
|
||
**职责**:统一协议解析入口,移除分散的解析逻辑
|
||
|
||
```javascript
|
||
class ProtocolResolver {
|
||
constructor() {
|
||
this.packageRoot = null // 延迟加载
|
||
}
|
||
|
||
// 统一解析 @reference 到文件路径
|
||
async resolve(reference) {
|
||
const [protocol, path] = this.parseReference(reference)
|
||
|
||
switch (protocol) {
|
||
case 'package':
|
||
return this.resolvePackage(path)
|
||
case 'project':
|
||
return this.resolveProject(path)
|
||
case 'file':
|
||
return this.resolveFile(path)
|
||
default:
|
||
throw new Error(`Unsupported protocol: ${protocol}`)
|
||
}
|
||
}
|
||
|
||
parseReference(reference) {
|
||
const match = reference.match(/^@(\w+):\/\/(.+)$/)
|
||
if (!match) {
|
||
throw new Error(`Invalid reference format: ${reference}`)
|
||
}
|
||
return [match[1], match[2]]
|
||
}
|
||
|
||
async resolvePackage(relativePath) {
|
||
if (!this.packageRoot) {
|
||
this.packageRoot = await this.findPackageRoot()
|
||
}
|
||
return path.resolve(this.packageRoot, relativePath)
|
||
}
|
||
|
||
resolveProject(relativePath) {
|
||
return path.resolve(process.cwd(), relativePath)
|
||
}
|
||
|
||
resolveFile(filePath) {
|
||
return path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath)
|
||
}
|
||
|
||
// 简化的包根目录查找
|
||
async findPackageRoot() {
|
||
let dir = __dirname
|
||
while (dir !== path.parse(dir).root) {
|
||
const packageJson = path.join(dir, 'package.json')
|
||
if (fs.existsSync(packageJson)) {
|
||
const pkg = JSON.parse(fs.readFileSync(packageJson, 'utf8'))
|
||
if (pkg.name === 'promptx') {
|
||
return dir
|
||
}
|
||
}
|
||
dir = path.dirname(dir)
|
||
}
|
||
throw new Error('PromptX package root not found')
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 4. 简化的资源管理器 (ResourceManager)
|
||
|
||
**职责**:协调三个核心组件,提供统一接口
|
||
|
||
```javascript
|
||
class ResourceManager {
|
||
constructor() {
|
||
this.registry = new ResourceRegistry()
|
||
this.resolver = new ProtocolResolver()
|
||
this.discovery = new ResourceDiscovery()
|
||
}
|
||
|
||
// 初始化:加载静态注册表 + 发现动态资源
|
||
async initialize() {
|
||
// 1. 加载静态注册表
|
||
this.registry.loadFromFile('src/resource.registry.json')
|
||
|
||
// 2. 发现动态资源
|
||
const discovered = await this.discovery.discoverResources([
|
||
'resource/', // 包内资源
|
||
'.promptx/', // 项目资源
|
||
process.env.PROMPTX_USER_DIR // 用户资源
|
||
].filter(Boolean))
|
||
|
||
// 3. 注册发现的资源(不覆盖静态注册表)
|
||
for (const resource of discovered) {
|
||
if (!this.registry.index.has(resource.id)) {
|
||
this.registry.register(resource.id, resource.reference)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 核心方法:加载资源
|
||
async loadResource(resourceId) {
|
||
// 1. 从注册表获取 @reference
|
||
const reference = this.registry.resolve(resourceId)
|
||
|
||
// 2. 通过协议解析器解析到文件路径
|
||
const filePath = await this.resolver.resolve(reference)
|
||
|
||
// 3. 加载文件内容
|
||
return {
|
||
content: fs.readFileSync(filePath, 'utf8'),
|
||
path: filePath,
|
||
reference
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 解决的核心问题
|
||
|
||
### 1. 统一 @reference 体系
|
||
```javascript
|
||
// 之前:绕过协议,直接路径映射
|
||
registry['role:java'] = '/path/to/file'
|
||
|
||
// 现在:保持协议一致性
|
||
registry['role:java'] = '@package://resource/domain/java/java.role.md'
|
||
resolver.resolve('@package://...') // 统一解析
|
||
```
|
||
|
||
### 2. 解耦发现与注册
|
||
```javascript
|
||
// 之前:发现服务直接操作注册表
|
||
discovery.scan() // 内部调用 registry.register()
|
||
|
||
// 现在:发现服务只返回结果
|
||
const resources = discovery.discoverResources()
|
||
resources.forEach(r => registry.register(r.id, r.reference))
|
||
```
|
||
|
||
### 3. 统一协议解析
|
||
```javascript
|
||
// 之前:每处都自己解析
|
||
if (path.startsWith('@package://')) {
|
||
const pkg = new PackageProtocol()
|
||
return pkg.resolve(path)
|
||
}
|
||
|
||
// 现在:统一入口
|
||
const filePath = await resolver.resolve(reference) // 所有协议
|
||
```
|
||
|
||
### 4. 完全兼容现有格式
|
||
```javascript
|
||
// resource.registry.json 继续工作
|
||
"java-backend-developer": "@package://resource/domain/java/java.role.md"
|
||
|
||
// 代码继续工作
|
||
await resourceManager.loadResource('java-backend-developer')
|
||
```
|
||
|
||
## 奥卡姆剃刀原则体现
|
||
|
||
**移除的复杂性**:
|
||
- ❌ 多级缓存机制
|
||
- ❌ 复杂的性能监控
|
||
- ❌ 过度的抽象层
|
||
- ❌ 文件监视器
|
||
- ❌ 复杂的元数据管理
|
||
- ❌ 多个重叠的注册表
|
||
|
||
**保留的核心功能**:
|
||
- ✅ 统一的 @reference 体系
|
||
- ✅ 发现与注册解耦
|
||
- ✅ 协议解析统一化
|
||
- ✅ 100% 向后兼容
|
||
|
||
**简化后的架构**:
|
||
```
|
||
3个核心类,4个核心方法,解决核心问题
|
||
ResourceRegistry.resolve() → ProtocolResolver.resolve() → fs.readFileSync()
|
||
```
|
||
|
||
## 迁移计划
|
||
|
||
### 阶段1:核心重构 (1周)
|
||
|
||
**严格遵循:发现 → 注册 → 解析 → 读取,无缓存**
|
||
|
||
**1.1 统一协议解析器**
|
||
```bash
|
||
# 创建统一协议解析器
|
||
src/lib/core/resource/ProtocolResolver.js
|
||
|
||
# 移除分散的协议解析逻辑
|
||
# 统一所有 @reference 解析到此处
|
||
```
|
||
|
||
**1.2 简化资源注册表**
|
||
```bash
|
||
# 重构 ResourceRegistry 为纯索引
|
||
src/lib/core/resource/ResourceRegistry.js
|
||
|
||
# 移除:缓存、元数据管理、复杂逻辑
|
||
# 保留:id → @reference 映射
|
||
# 兼容:现有 resource.registry.json 格式
|
||
```
|
||
|
||
**1.3 解耦资源发现**
|
||
```bash
|
||
# 创建独立的资源发现服务
|
||
src/lib/core/resource/ResourceDiscovery.js
|
||
|
||
# 移除:与注册表的直接耦合
|
||
# 保留:纯粹的发现功能,返回结果
|
||
# 实现:生成标准化 @reference
|
||
```
|
||
|
||
**1.4 重构资源管理器**
|
||
```bash
|
||
# 简化 ResourceManager 为协调器
|
||
src/lib/core/resource/ResourceManager.js
|
||
|
||
# 移除:复杂的初始化逻辑、性能监控
|
||
# 保留:协调三个核心组件
|
||
# 实现:发现 → 注册 → 解析 → 读取
|
||
```
|
||
|
||
### 阶段2:集成测试 (2-3天)
|
||
|
||
**2.1 兼容性验证**
|
||
```bash
|
||
# 验证现有代码继续工作
|
||
src/tests/compatibility/existing-api.test.js
|
||
|
||
# 验证 resource.registry.json 加载正确
|
||
src/tests/integration/registry-loading.test.js
|
||
```
|
||
|
||
**2.2 功能测试**
|
||
```bash
|
||
# 测试资源发现功能
|
||
src/tests/integration/resource-discovery.test.js
|
||
|
||
# 测试协议解析功能
|
||
src/tests/integration/protocol-resolution.test.js
|
||
```
|
||
|
||
### 阶段3:部署验证 (1-2天)
|
||
|
||
**3.1 向后兼容验证**
|
||
```bash
|
||
# 确保现有 ActionCommand 继续工作
|
||
# 确保角色激活流程正常
|
||
# 确保跨平台兼容性
|
||
```
|
||
|
||
**3.2 文档更新**
|
||
```bash
|
||
# 更新架构文档
|
||
docs/architecture/resource-management.md
|
||
|
||
# 更新开发者指南
|
||
docs/development/adding-resources.md
|
||
```
|
||
|
||
## 实施原则
|
||
|
||
**严格的简化原则**:
|
||
- ❌ 不引入任何缓存机制
|
||
- ❌ 不添加性能监控
|
||
- ❌ 不实现文件监视
|
||
- ❌ 不添加复杂的元数据管理
|
||
|
||
**专注核心问题**:
|
||
- ✅ 统一 @reference 体系
|
||
- ✅ 解耦发现与注册
|
||
- ✅ 统一协议解析
|
||
- ✅ 保持向后兼容
|
||
|
||
## 风险评估
|
||
|
||
### 技术风险
|
||
|
||
**1. 向后兼容性**
|
||
- **风险**:现有代码依赖旧API
|
||
- **缓解**:严格保持 API 兼容性,现有调用方式继续有效
|
||
|
||
**2. 功能回归**
|
||
- **风险**:重构可能影响现有功能
|
||
- **缓解**:充分测试,确保功能对等
|
||
|
||
### 业务风险
|
||
|
||
**1. 开发时间**
|
||
- **风险**:重构时间超出预期
|
||
- **缓解**:专注核心问题,避免过度设计
|
||
|
||
**2. 稳定性影响**
|
||
- **风险**:重构影响现有功能
|
||
- **缓解**:分阶段实施,充分验证
|
||
|
||
## 成功指标
|
||
|
||
### 功能指标
|
||
- ✅ 现有代码 100% 兼容
|
||
- ✅ resource.registry.json 继续有效
|
||
- ✅ 角色激活功能正常
|
||
- ✅ 跨平台兼容性保持
|
||
|
||
### 架构指标
|
||
- ✅ 统一 @reference 体系
|
||
- ✅ 发现与注册解耦
|
||
- ✅ 协议解析统一化
|
||
- ✅ 代码复杂度降低
|
||
|
||
## 总结
|
||
|
||
本架构改进方案严格遵循**奥卡姆剃刀原则**,专注解决 Issue #31 暴露的核心架构问题:
|
||
|
||
**核心改进**:
|
||
1. **统一 @reference 体系**:注册表存储 @reference 而非直接路径
|
||
2. **解耦发现与注册**:独立的发现服务,纯粹返回结果
|
||
3. **统一协议解析**:单一解析入口,移除分散逻辑
|
||
4. **完全向后兼容**:现有代码和配置无需修改
|
||
|
||
**实施原则**:
|
||
- 发现 → 注册 → 解析 → 读取,每步都不缓存
|
||
- 3个核心类,4个核心方法
|
||
- 移除所有不必要的复杂性
|
||
|
||
这个方案解决了当前的架构问题,为未来扩展奠定了简洁、清晰的基础。 |