fix: 重构 资源的注册,发现,解析架构,解决兼容性问题

This commit is contained in:
sean
2025-06-12 12:28:53 +08:00
parent 88874ff7ec
commit 5d6e678bd2
15 changed files with 2029 additions and 1354 deletions

View File

@ -0,0 +1,549 @@
# 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://prompt/core/role.md" // 正确
"@packages://promptx/prompt/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([
'prompt/', // 包内资源
'.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://prompt/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://prompt/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个核心方法
- 移除所有不必要的复杂性
这个方案解决了当前的架构问题为未来扩展奠定了简洁清晰的基础