Files
PromptX/docs/issues/resource-architecture-improvement.md
sean 54b77e7096 refactor: 重构/prompt/目录为/resource/ - 更符合资源引用协议语义
- 重命名核心目录: /prompt/ → /resource/
- 更新PackageDiscovery中所有硬编码路径引用
- 重新生成package.registry.json,61个资源全部更新为@package://resource/路径
- 批量更新文档中的路径引用,保持一致性
- 目录结构保持不变:domain/, core/, protocol/, tool/子目录结构完全一致

重构原因: 随着tool协议的加入,prompt目录名称不再准确描述系统本质
重构价值: 为未来资源生态扩展奠定清晰的命名基础

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-28 15:02:34 +08:00

549 lines
14 KiB
Markdown
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.

# 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/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://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个核心方法
- 移除所有不必要的复杂性
这个方案解决了当前的架构问题为未来扩展奠定了简洁清晰的基础