Files
PromptX/docs/issues/resource-architecture-improvement.md
sean 5779aa837c fix: 全面清理prompt关键词引用 - 完成prompt→resource重构
核心修复:
- 修复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>
2025-06-28 15:12:17 +08:00

14 KiB
Raw Blame History

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. 协议系统不一致

当前协议处理问题

// 问题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. 发现与注册强耦合

// 当前实现问题
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 映射,兼容现有格式

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)

职责:纯粹的发现功能,不操作注册表

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)

职责:统一协议解析入口,移除分散的解析逻辑

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)

职责:协调三个核心组件,提供统一接口

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 体系

// 之前:绕过协议,直接路径映射
registry['role:java'] = '/path/to/file'

// 现在:保持协议一致性
registry['role:java'] = '@package://resource/domain/java/java.role.md'
resolver.resolve('@package://...') // 统一解析

2. 解耦发现与注册

// 之前:发现服务直接操作注册表
discovery.scan() // 内部调用 registry.register()

// 现在:发现服务只返回结果
const resources = discovery.discoverResources()
resources.forEach(r => registry.register(r.id, r.reference))

3. 统一协议解析

// 之前:每处都自己解析
if (path.startsWith('@package://')) {
  const pkg = new PackageProtocol()
  return pkg.resolve(path)
}

// 现在:统一入口
const filePath = await resolver.resolve(reference) // 所有协议

4. 完全兼容现有格式

// 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 统一协议解析器

# 创建统一协议解析器
src/lib/core/resource/ProtocolResolver.js

# 移除分散的协议解析逻辑
# 统一所有 @reference 解析到此处

1.2 简化资源注册表

# 重构 ResourceRegistry 为纯索引
src/lib/core/resource/ResourceRegistry.js

# 移除:缓存、元数据管理、复杂逻辑
# 保留id → @reference 映射
# 兼容:现有 resource.registry.json 格式

1.3 解耦资源发现

# 创建独立的资源发现服务
src/lib/core/resource/ResourceDiscovery.js

# 移除:与注册表的直接耦合
# 保留:纯粹的发现功能,返回结果
# 实现:生成标准化 @reference

1.4 重构资源管理器

# 简化 ResourceManager 为协调器
src/lib/core/resource/ResourceManager.js

# 移除:复杂的初始化逻辑、性能监控
# 保留:协调三个核心组件
# 实现:发现 → 注册 → 解析 → 读取

阶段2集成测试 (2-3天)

2.1 兼容性验证

# 验证现有代码继续工作
src/tests/compatibility/existing-api.test.js

# 验证 resource.registry.json 加载正确
src/tests/integration/registry-loading.test.js

2.2 功能测试

# 测试资源发现功能
src/tests/integration/resource-discovery.test.js

# 测试协议解析功能
src/tests/integration/protocol-resolution.test.js

阶段3部署验证 (1-2天)

3.1 向后兼容验证

# 确保现有 ActionCommand 继续工作
# 确保角色激活流程正常
# 确保跨平台兼容性

3.2 文档更新

# 更新架构文档
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个核心方法
  • 移除所有不必要的复杂性

这个方案解决了当前的架构问题,为未来扩展奠定了简洁、清晰的基础。