更新资源管理器和命令逻辑:新增角色创建和生成相关功能,优化资源加载流程,支持用户自定义资源的发现与合并,同时增强错误处理和描述提取逻辑,提升系统的灵活性和用户体验。
This commit is contained in:
465
docs/user-role-creation-system.md
Normal file
465
docs/user-role-creation-system.md
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
# 用户资源发现系统设计
|
||||||
|
|
||||||
|
## 📋 概述
|
||||||
|
|
||||||
|
基于ResourceManager的用户资源发现机制,支持用户创建自定义角色、执行流程、思维模式等资源,实现即创即用的体验。
|
||||||
|
|
||||||
|
## 🎯 核心问题
|
||||||
|
|
||||||
|
**现状分析**:
|
||||||
|
- 系统资源已在 `src/resource.registry.json` 静态注册
|
||||||
|
- 当前 `HelloCommand.discoverLocalRoles()` 错误扫描系统路径,造成重复处理
|
||||||
|
- 用户需要在项目级别创建和管理自定义资源
|
||||||
|
|
||||||
|
**解决目标**:
|
||||||
|
- 仅发现用户资源,不重复处理系统资源
|
||||||
|
- 支持多种资源类型:角色、执行流程、思维模式
|
||||||
|
- 实现用户资源覆盖系统资源的能力
|
||||||
|
|
||||||
|
## 🏗️ 架构设计
|
||||||
|
|
||||||
|
### 资源分层
|
||||||
|
|
||||||
|
```
|
||||||
|
系统资源 (静态注册)
|
||||||
|
├── src/resource.registry.json # 系统资源注册表
|
||||||
|
└── prompt/domain/{role}/ # 系统资源文件
|
||||||
|
|
||||||
|
用户资源 (动态发现)
|
||||||
|
└── .promptx/resource/domain/{role}/ # 用户资源文件
|
||||||
|
```
|
||||||
|
|
||||||
|
### 目录结构规范
|
||||||
|
|
||||||
|
#### 用户资源目录
|
||||||
|
```
|
||||||
|
.promptx/
|
||||||
|
├── resource/
|
||||||
|
│ └── domain/
|
||||||
|
│ └── {role-id}/
|
||||||
|
│ ├── {role-id}.role.md
|
||||||
|
│ ├── thought/
|
||||||
|
│ │ └── {name}.thought.md
|
||||||
|
│ └── execution/
|
||||||
|
│ └── {name}.execution.md
|
||||||
|
└── memory/ # 现有目录
|
||||||
|
```
|
||||||
|
|
||||||
|
**设计原则**:
|
||||||
|
- **镜像结构**:用户目录结构镜像系统结构,降低认知负载
|
||||||
|
- **资源聚合**:角色相关资源统一管理在角色目录下
|
||||||
|
- **类型支持**:支持 role、thought、execution 等多种资源类型
|
||||||
|
|
||||||
|
### 发现机制重构
|
||||||
|
|
||||||
|
#### ResourceManager 扩展
|
||||||
|
```javascript
|
||||||
|
// src/lib/core/resource/resourceManager.js
|
||||||
|
class ResourceManager {
|
||||||
|
async discoverUserResources() {
|
||||||
|
const userResourcePath = path.join(packageRoot, '.promptx', 'resource', 'domain')
|
||||||
|
return await this.scanResourceDirectory(userResourcePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
async scanResourceDirectory(basePath) {
|
||||||
|
// 使用 Node.js 原生 API,移除 glob 依赖
|
||||||
|
// 支持 role、thought、execution 等多种资源类型
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadUnifiedRegistry() {
|
||||||
|
const systemResources = await this.loadSystemRegistry()
|
||||||
|
const userResources = await this.discoverUserResources()
|
||||||
|
|
||||||
|
// 用户资源覆盖系统资源
|
||||||
|
return { ...systemResources, ...userResources }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### HelloCommand 简化
|
||||||
|
```javascript
|
||||||
|
// src/lib/core/pouch/commands/HelloCommand.js
|
||||||
|
class HelloCommand {
|
||||||
|
async loadRoleRegistry() {
|
||||||
|
// 移除错误的本地发现逻辑
|
||||||
|
// 直接从 ResourceManager 获取统一注册表
|
||||||
|
return await this.resourceManager.loadUnifiedRegistry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🤖 nuwa 角色设计
|
||||||
|
|
||||||
|
### 核心职责
|
||||||
|
- **需求理解**:通过自然对话收集用户场景需求
|
||||||
|
- **资源生成**:基于 DPML 协议生成角色文件
|
||||||
|
- **文件管理**:将生成内容保存到正确的用户资源路径
|
||||||
|
|
||||||
|
### 对话策略
|
||||||
|
```
|
||||||
|
收集目标信息:
|
||||||
|
├── scenario: 用户工作场景
|
||||||
|
├── painPoint: 主要痛点
|
||||||
|
└── expectation: 期望结果
|
||||||
|
|
||||||
|
生成时机:三项信息齐全即可生成
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生成模板
|
||||||
|
```xml
|
||||||
|
<role>
|
||||||
|
<personality>
|
||||||
|
[基于场景的思维模式]
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
[基于痛点的行为原则]
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
[基于期望的知识体系]
|
||||||
|
</knowledge>
|
||||||
|
</role>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 技术实现
|
||||||
|
|
||||||
|
### 实现优先级
|
||||||
|
|
||||||
|
#### Phase 1: 核心功能
|
||||||
|
1. **ResourceManager 扩展**
|
||||||
|
- 实现 `discoverUserResources()` 方法
|
||||||
|
- 使用 Node.js 原生 API 替代 glob
|
||||||
|
- 支持多种资源类型扫描
|
||||||
|
|
||||||
|
2. **HelloCommand 重构**
|
||||||
|
- 移除错误的系统路径扫描
|
||||||
|
- 集成 ResourceManager 统一注册表
|
||||||
|
|
||||||
|
3. **nuwa 角色实现**
|
||||||
|
- DPML 协议掌握和文件生成
|
||||||
|
- 用户资源路径文件保存
|
||||||
|
|
||||||
|
#### Phase 2: 完善功能
|
||||||
|
1. **错误处理**:跨平台兼容性和容错机制
|
||||||
|
2. **性能优化**:资源发现缓存机制
|
||||||
|
3. **用户体验**:更智能的对话策略
|
||||||
|
|
||||||
|
### 关键技术要点
|
||||||
|
|
||||||
|
#### 1. 跨平台路径处理
|
||||||
|
```javascript
|
||||||
|
// 使用 Node.js 原生 API,避免 glob 兼容性问题
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
async function discoverUserResources() {
|
||||||
|
const userResourcePath = path.join(
|
||||||
|
await packageProtocol.getPackageRoot(),
|
||||||
|
'.promptx', 'resource', 'domain'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!await fs.pathExists(userResourcePath)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用原生 readdir 和 stat 扫描
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 资源覆盖机制
|
||||||
|
```javascript
|
||||||
|
// 用户资源优先级高于系统资源
|
||||||
|
const unifiedRegistry = {
|
||||||
|
...systemResources, // 系统资源作为基础
|
||||||
|
...userResources // 用户资源覆盖同名项
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. DPML 元数据提取
|
||||||
|
```javascript
|
||||||
|
function extractRoleMetadata(content, roleId) {
|
||||||
|
// 从 DPML 标签中提取角色元信息
|
||||||
|
// 用于角色发现和展示
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 测试策略与设计
|
||||||
|
|
||||||
|
### 测试架构分层
|
||||||
|
|
||||||
|
```
|
||||||
|
单元测试层 (Unit Tests)
|
||||||
|
├── ResourceManager.unit.test.js # 资源管理器核心逻辑测试
|
||||||
|
├── HelloCommand.unit.test.js # 命令行接口测试
|
||||||
|
├── UserResourceDiscovery.unit.test.js # 用户资源发现测试
|
||||||
|
└── DPMLParser.unit.test.js # DPML格式解析测试
|
||||||
|
|
||||||
|
集成测试层 (Integration Tests)
|
||||||
|
├── ResourceDiscovery.integration.test.js # 资源发现完整流程测试
|
||||||
|
├── NuwaRoleGeneration.integration.test.js # nuwa角色生成端到端测试
|
||||||
|
└── CrossPlatform.integration.test.js # 跨平台兼容性测试
|
||||||
|
|
||||||
|
端到端测试层 (E2E Tests)
|
||||||
|
└── UserWorkflow.e2e.test.js # 完整用户工作流程测试
|
||||||
|
```
|
||||||
|
|
||||||
|
### 核心测试组件
|
||||||
|
|
||||||
|
#### 1. ResourceManager 单元测试
|
||||||
|
```javascript
|
||||||
|
// src/tests/core/resource/ResourceManager.unit.test.js
|
||||||
|
describe('ResourceManager', () => {
|
||||||
|
describe('discoverUserResources', () => {
|
||||||
|
it('应该正确扫描用户资源目录', async () => {
|
||||||
|
// 模拟用户资源文件结构
|
||||||
|
// 验证发现结果的正确性
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该支持多种资源类型', async () => {
|
||||||
|
// 测试 role、thought、execution 类型
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该处理不存在的目录', async () => {
|
||||||
|
// 测试容错机制
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('loadUnifiedRegistry', () => {
|
||||||
|
it('应该正确合并系统和用户资源', async () => {
|
||||||
|
// 验证用户资源覆盖系统资源
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. HelloCommand 重构测试
|
||||||
|
```javascript
|
||||||
|
// src/tests/commands/HelloCommand.unit.test.js
|
||||||
|
describe('HelloCommand - 重构后', () => {
|
||||||
|
it('应该移除错误的系统路径扫描', async () => {
|
||||||
|
// 验证不再扫描 prompt/domain/ 路径
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该集成ResourceManager统一注册表', async () => {
|
||||||
|
// 验证使用ResourceManager.loadUnifiedRegistry()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该正确显示用户自定义角色', async () => {
|
||||||
|
// 验证用户角色在hello命令中的展示
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 用户资源发现集成测试
|
||||||
|
```javascript
|
||||||
|
// src/tests/integration/UserResourceDiscovery.integration.test.js
|
||||||
|
describe('用户资源发现机制', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
// 创建测试用的用户资源结构
|
||||||
|
await createTestUserResourceStructure()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该发现用户创建的角色', async () => {
|
||||||
|
// 创建测试角色文件
|
||||||
|
// 验证ResourceManager能正确发现
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该支持资源类型扩展', async () => {
|
||||||
|
// 测试thought、execution文件的发现
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该处理DPML格式验证', async () => {
|
||||||
|
// 测试格式错误的文件处理
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. nuwa 角色生成端到端测试
|
||||||
|
```javascript
|
||||||
|
// src/tests/integration/NuwaRoleGeneration.integration.test.js
|
||||||
|
describe('nuwa 角色生成完整流程', () => {
|
||||||
|
it('应该根据用户需求生成角色文件', async () => {
|
||||||
|
// 模拟用户输入
|
||||||
|
// 验证生成的文件内容和位置
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该生成符合DPML规范的角色', async () => {
|
||||||
|
// 验证生成文件的DPML格式正确性
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该创建正确的目录结构', async () => {
|
||||||
|
// 验证镜像系统结构的目录创建
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. 跨平台兼容性测试
|
||||||
|
```javascript
|
||||||
|
// src/tests/integration/CrossPlatform.integration.test.js
|
||||||
|
describe('跨平台兼容性', () => {
|
||||||
|
it('应该在Windows上正确处理路径', () => {
|
||||||
|
// 模拟Windows路径分隔符
|
||||||
|
// 验证路径处理的正确性
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该在macOS/Linux上正确处理路径', () => {
|
||||||
|
// 验证Unix风格路径处理
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该使用Node.js原生API替代glob', () => {
|
||||||
|
// 验证不使用glob库的实现
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试数据和环境
|
||||||
|
|
||||||
|
#### 测试数据结构
|
||||||
|
```
|
||||||
|
src/tests/fixtures/
|
||||||
|
├── user-resources/
|
||||||
|
│ └── domain/
|
||||||
|
│ ├── test-role/
|
||||||
|
│ │ ├── test-role.role.md # 标准DPML格式
|
||||||
|
│ │ ├── thought/
|
||||||
|
│ │ │ └── test.thought.md
|
||||||
|
│ │ └── execution/
|
||||||
|
│ │ └── test.execution.md
|
||||||
|
│ ├── invalid-role/
|
||||||
|
│ │ └── invalid.role.md # 格式错误的文件
|
||||||
|
│ └── sales-analyst/
|
||||||
|
│ └── sales-analyst.role.md # nuwa生成测试样例
|
||||||
|
├── system-resources/
|
||||||
|
│ └── mock-registry.json # 模拟系统注册表
|
||||||
|
└── dpml-samples/
|
||||||
|
├── valid-role.md # 有效DPML样例
|
||||||
|
└── invalid-role.md # 无效DPML样例
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 测试环境配置
|
||||||
|
```javascript
|
||||||
|
// src/tests/setup/testEnvironment.js
|
||||||
|
export class TestEnvironment {
|
||||||
|
async setup() {
|
||||||
|
// 创建临时测试目录
|
||||||
|
this.testDir = await createTempTestDirectory()
|
||||||
|
|
||||||
|
// 模拟 .promptx 结构
|
||||||
|
await this.createMockUserResourceStructure()
|
||||||
|
|
||||||
|
// 设置环境变量
|
||||||
|
process.env.PROMPTX_TEST_MODE = 'true'
|
||||||
|
}
|
||||||
|
|
||||||
|
async teardown() {
|
||||||
|
// 清理测试文件
|
||||||
|
await fs.remove(this.testDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试覆盖率要求
|
||||||
|
|
||||||
|
#### 覆盖率目标
|
||||||
|
- **整体代码覆盖率**: ≥ 85%
|
||||||
|
- **ResourceManager核心逻辑**: ≥ 95%
|
||||||
|
- **HelloCommand重构部分**: ≥ 90%
|
||||||
|
- **DPML解析逻辑**: ≥ 95%
|
||||||
|
- **跨平台路径处理**: 100%
|
||||||
|
|
||||||
|
#### 关键测试场景
|
||||||
|
```
|
||||||
|
✅ 用户资源发现功能
|
||||||
|
✅ 系统资源静态加载
|
||||||
|
✅ 资源覆盖机制
|
||||||
|
✅ DPML格式验证
|
||||||
|
✅ 跨平台路径处理
|
||||||
|
✅ 错误处理和容错
|
||||||
|
✅ nuwa角色生成流程
|
||||||
|
✅ 文件系统操作安全性
|
||||||
|
✅ 缓存机制有效性
|
||||||
|
✅ CLI集成正确性
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试执行策略
|
||||||
|
|
||||||
|
#### 测试运行配置
|
||||||
|
```json
|
||||||
|
// package.json scripts
|
||||||
|
{
|
||||||
|
"test": "jest",
|
||||||
|
"test:unit": "jest --testPathPattern=unit",
|
||||||
|
"test:integration": "jest --testPathPattern=integration",
|
||||||
|
"test:e2e": "jest --testPathPattern=e2e",
|
||||||
|
"test:coverage": "jest --coverage",
|
||||||
|
"test:watch": "jest --watch"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CI/CD 集成
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/test.yml
|
||||||
|
name: Test Suite
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
node: [16, 18, 20]
|
||||||
|
steps:
|
||||||
|
- name: Run Unit Tests
|
||||||
|
run: npm run test:unit
|
||||||
|
- name: Run Integration Tests
|
||||||
|
run: npm run test:integration
|
||||||
|
- name: Check Coverage
|
||||||
|
run: npm run test:coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 用户体验流程
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 创建角色
|
||||||
|
npx promptx action nuwa
|
||||||
|
# 对话生成: .promptx/resource/domain/sales-analyst/sales-analyst.role.md
|
||||||
|
|
||||||
|
# 2. 立即可用(自动发现)
|
||||||
|
npx promptx hello
|
||||||
|
# 显示新角色: sales-analyst
|
||||||
|
|
||||||
|
# 3. 直接使用
|
||||||
|
npx promptx action sales-analyst
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 设计决策
|
||||||
|
|
||||||
|
### 为什么选择 .promptx/resource/domain 结构?
|
||||||
|
- **镜像一致性**:与系统 `prompt/domain` 结构保持一致
|
||||||
|
- **类型扩展性**:未来可支持 thought、execution 等资源类型
|
||||||
|
- **认知简单性**:用户理解成本最低
|
||||||
|
|
||||||
|
### 为什么移除 HelloCommand 的发现逻辑?
|
||||||
|
- **职责单一**:ResourceManager 专门负责资源管理
|
||||||
|
- **避免重复**:系统资源已静态注册,无需重复发现
|
||||||
|
- **架构清晰**:分层明确,便于维护
|
||||||
|
|
||||||
|
### 为什么使用 Node.js 原生 API?
|
||||||
|
- **兼容性**:完全跨平台,无第三方库依赖问题
|
||||||
|
- **性能**:原生 API 性能更优
|
||||||
|
- **维护性**:减少依赖复杂度
|
||||||
|
|
||||||
|
## 📚 相关文档
|
||||||
|
|
||||||
|
- [DPML协议](../prompt/protocol/dpml.protocol.md)
|
||||||
|
- [ResourceManager 架构](../src/lib/core/resource/)
|
||||||
|
- [角色标签规范](../prompt/protocol/tag/role.tag.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**实现要点**:
|
||||||
|
1. ResourceManager 统一资源发现
|
||||||
|
2. 用户资源镜像系统结构
|
||||||
|
3. nuwa 基于 DPML 生成角色
|
||||||
|
4. 即创即用的无缝体验
|
||||||
|
5. 完整测试覆盖和质量保证
|
||||||
148
prompt/core/execution/execution-authoring.execution.md
Normal file
148
prompt/core/execution/execution-authoring.execution.md
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
<execution>
|
||||||
|
<constraint>
|
||||||
|
## 客观技术限制
|
||||||
|
- **DPML语法约束**:必须遵循EBNF定义的execution语法结构
|
||||||
|
- **XML格式要求**:标签必须正确闭合,属性值必须用双引号包围
|
||||||
|
- **Markdown兼容性**:内容部分必须是有效的Markdown格式
|
||||||
|
- **文件编码**:必须使用UTF-8编码
|
||||||
|
- **优先级固化**:五个子标签的优先级关系不可改变
|
||||||
|
</constraint>
|
||||||
|
|
||||||
|
<rule>
|
||||||
|
## 强制性编写规则
|
||||||
|
- **纯XML结构**:execution文件必须从`<execution>`标签开始,不得包含任何XML结构外的内容(如Markdown标题、注释等)
|
||||||
|
- **根标签强制**:文件必须使用`<execution>`作为根标签包装全部内容
|
||||||
|
- **子标签命名**:只能使用规范定义的五个子标签:constraint, rule, guideline, process, criteria
|
||||||
|
- **优先级顺序**:子标签必须按constraint → rule → guideline → process → criteria顺序排列
|
||||||
|
- **内容完整性**:每个子标签都必须包含实质性内容,不得为空
|
||||||
|
- **语义一致性**:子标签内容必须与其语义定义保持一致
|
||||||
|
- **文件纯净性**:除了`<execution>`标签结构外,不得包含任何其他内容
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<guideline>
|
||||||
|
## 编写指导原则
|
||||||
|
- **语义明确性**:每个子标签的内容应清晰表达其特定语义
|
||||||
|
- **内容层次化**:使用Markdown的标题、列表等结构组织内容
|
||||||
|
- **实用性导向**:内容应具有实际操作指导价值
|
||||||
|
- **简洁性原则**:避免冗长表述,保持核心要点突出
|
||||||
|
- **一致性维护**:在整个文件中保持术语和表达方式的一致性
|
||||||
|
</guideline>
|
||||||
|
|
||||||
|
<process>
|
||||||
|
## 编写执行流程
|
||||||
|
|
||||||
|
### Phase 1: 分析需求和上下文
|
||||||
|
1. **明确execution目的**:确定这个execution要解决什么问题
|
||||||
|
2. **识别客观限制**:分析技术、环境、资源等客观约束条件
|
||||||
|
3. **定义强制要求**:确定必须遵守的规则和底线要求
|
||||||
|
4. **收集最佳实践**:整理相关领域的指导原则和建议
|
||||||
|
|
||||||
|
### Phase 2: 内容规划和结构设计
|
||||||
|
1. **约束条件梳理**:
|
||||||
|
- 列出所有客观存在的限制条件
|
||||||
|
- 按重要性和影响程度排序
|
||||||
|
- 确保约束条件的客观性和不可变性
|
||||||
|
|
||||||
|
2. **规则定义设计**:
|
||||||
|
- 识别必须严格遵守的行为准则
|
||||||
|
- 明确违反规则的后果和风险
|
||||||
|
- 确保规则与约束条件不冲突
|
||||||
|
|
||||||
|
3. **指导原则制定**:
|
||||||
|
- 提供灵活性建议和最佳实践
|
||||||
|
- 允许根据具体情况调整
|
||||||
|
- 确保不违反已定义的规则和约束
|
||||||
|
|
||||||
|
4. **流程步骤设计**:
|
||||||
|
- 在约束和规则框架内设计执行路径
|
||||||
|
- 包含正常流程和异常处理
|
||||||
|
- 确保步骤的可操作性和逻辑性
|
||||||
|
|
||||||
|
5. **评价标准确立**:
|
||||||
|
- 定义成功完成的判断依据
|
||||||
|
- 考虑所有优先级更高元素的要求
|
||||||
|
- 提供可量化的评估方法
|
||||||
|
|
||||||
|
### Phase 3: DPML结构实现
|
||||||
|
|
||||||
|
**关键要求:文件必须从`<execution>`标签直接开始**
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<execution>
|
||||||
|
<constraint>
|
||||||
|
[客观限制条件内容]
|
||||||
|
</constraint>
|
||||||
|
|
||||||
|
<rule>
|
||||||
|
[强制性规则内容]
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<guideline>
|
||||||
|
[指导原则内容]
|
||||||
|
</guideline>
|
||||||
|
|
||||||
|
<process>
|
||||||
|
[具体执行步骤]
|
||||||
|
</process>
|
||||||
|
|
||||||
|
<criteria>
|
||||||
|
[评价标准内容]
|
||||||
|
</criteria>
|
||||||
|
</execution>
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误示例(禁止):**
|
||||||
|
```markdown
|
||||||
|
# 标题
|
||||||
|
这是描述内容...
|
||||||
|
|
||||||
|
<execution>
|
||||||
|
...
|
||||||
|
</execution>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: 质量检查和优化
|
||||||
|
1. **语法验证**:确保DPML语法正确性
|
||||||
|
2. **语义一致性检查**:验证各部分逻辑关系
|
||||||
|
3. **优先级关系验证**:确认无冲突和矛盾
|
||||||
|
4. **实用性测试**:验证内容的可操作性
|
||||||
|
5. **完整性审核**:确保覆盖所有必要方面
|
||||||
|
6. **纯净性检查**:确认文件从`<execution>`标签开始,无多余内容
|
||||||
|
</process>
|
||||||
|
|
||||||
|
<criteria>
|
||||||
|
## 质量评价标准
|
||||||
|
|
||||||
|
### 格式合规性
|
||||||
|
- ✅ 文件从`<execution>`标签直接开始,无额外内容
|
||||||
|
- ✅ 使用正确的DPML execution标签结构
|
||||||
|
- ✅ 五个子标签按规定顺序排列
|
||||||
|
- ✅ XML语法正确,标签正确闭合
|
||||||
|
- ✅ Markdown格式规范,层次清晰
|
||||||
|
|
||||||
|
### 内容完整性
|
||||||
|
- ✅ 每个子标签都包含实质性内容
|
||||||
|
- ✅ 约束条件体现客观性和不可变性
|
||||||
|
- ✅ 规则体现强制性和明确性
|
||||||
|
- ✅ 指导原则体现建议性和灵活性
|
||||||
|
- ✅ 流程步骤具有可操作性和逻辑性
|
||||||
|
- ✅ 评价标准具有可验证性
|
||||||
|
|
||||||
|
### 语义一致性
|
||||||
|
- ✅ 各子标签内容与其语义定义匹配
|
||||||
|
- ✅ 优先级关系得到正确体现
|
||||||
|
- ✅ 不存在逻辑冲突和矛盾
|
||||||
|
- ✅ 术语使用保持一致性
|
||||||
|
|
||||||
|
### 实用价值
|
||||||
|
- ✅ 内容具有实际指导意义
|
||||||
|
- ✅ 步骤和标准可以实际执行
|
||||||
|
- ✅ 能够解决实际问题
|
||||||
|
- ✅ 适用于目标场景和用户
|
||||||
|
|
||||||
|
### 文件纯净性
|
||||||
|
- ✅ 文件结构完全符合DPML execution规范
|
||||||
|
- ✅ 无任何XML结构外的多余内容
|
||||||
|
- ✅ 体现execution文件的标准格式
|
||||||
|
</criteria>
|
||||||
|
</execution>
|
||||||
223
prompt/core/execution/resource-authoring.execution.md
Normal file
223
prompt/core/execution/resource-authoring.execution.md
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
<execution>
|
||||||
|
<constraint>
|
||||||
|
## 客观技术限制
|
||||||
|
- **DPML语法约束**:必须遵循EBNF定义的resource语法结构
|
||||||
|
- **XML格式要求**:标签必须正确闭合,属性值必须用双引号包围
|
||||||
|
- **protocol属性强制**:resource标签必须包含protocol属性指定协议名
|
||||||
|
- **文件编码**:必须使用UTF-8编码
|
||||||
|
- **代码实现约束**:必须与ResourceManager、ResourceProtocol基类兼容
|
||||||
|
- **注册表集成**:必须与resource.registry.json统一注册表集成
|
||||||
|
- **查询参数限制**:查询参数必须符合URL标准格式
|
||||||
|
</constraint>
|
||||||
|
|
||||||
|
<rule>
|
||||||
|
## 强制性编写规则
|
||||||
|
- **纯XML结构**:resource文件必须从`<resource>`标签开始,不得包含任何XML结构外的内容
|
||||||
|
- **根标签强制**:文件必须使用`<resource protocol="协议名">`作为根标签
|
||||||
|
- **三组件完整**:必须包含location、params、registry三个子标签
|
||||||
|
- **组件顺序固定**:子标签必须按location → params → registry顺序排列
|
||||||
|
- **protocol属性必需**:根标签必须包含protocol属性且值唯一
|
||||||
|
- **文件纯净性**:除了`<resource>`标签结构外,不得包含任何其他内容
|
||||||
|
- **EBNF规范性**:location标签内容必须使用EBNF语法定义路径格式
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<guideline>
|
||||||
|
## 编写指导原则
|
||||||
|
- **协议名称清晰**:protocol属性值应简洁明了,符合kebab-case命名规范
|
||||||
|
- **路径格式标准化**:使用EBNF语法精确定义资源路径结构
|
||||||
|
- **参数文档完整**:详细说明所有支持的查询参数及其类型和用途
|
||||||
|
- **注册表合理性**:注册表映射应体现抽象性和实用性的平衡
|
||||||
|
- **兼容性考虑**:确保与PromptX资源管理系统的无缝集成
|
||||||
|
- **示例丰富性**:提供足够的使用示例帮助理解协议用法
|
||||||
|
</guideline>
|
||||||
|
|
||||||
|
<process>
|
||||||
|
## 编写执行流程
|
||||||
|
|
||||||
|
### Phase 1: 协议概念设计
|
||||||
|
1. **确定协议用途**:明确这个资源协议要解决什么资源访问问题
|
||||||
|
2. **分析资源特征**:识别目标资源的组织方式、访问模式和参数需求
|
||||||
|
3. **设计协议名称**:选择简洁清晰的协议标识符
|
||||||
|
4. **评估系统集成**:确认与PromptX现有协议的兼容性和差异性
|
||||||
|
|
||||||
|
### Phase 2: 路径格式设计(location组件)
|
||||||
|
1. **路径结构分析**:
|
||||||
|
- 确定资源的层次结构和定位方式
|
||||||
|
- 分析是否需要支持参数化路径
|
||||||
|
- 设计路径的语义表达
|
||||||
|
|
||||||
|
2. **EBNF语法定义**:
|
||||||
|
```ebnf
|
||||||
|
location ::= protocol_name '://' path_structure
|
||||||
|
path_structure ::= segment {'/' segment}
|
||||||
|
segment ::= literal | parameter
|
||||||
|
parameter ::= '{' parameter_name '}'
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **路径规范示例**:
|
||||||
|
- 简单路径:`protocol://resource_id`
|
||||||
|
- 参数化路径:`protocol://{category}/{id}`
|
||||||
|
- 复杂路径:`protocol://{domain}/{namespace}/{resource}`
|
||||||
|
|
||||||
|
### Phase 3: 查询参数设计(params组件)
|
||||||
|
1. **参数分类规划**:
|
||||||
|
- **格式控制参数**:如format、encoding等
|
||||||
|
- **行为控制参数**:如cache、timeout等
|
||||||
|
- **过滤参数**:如line、type等
|
||||||
|
- **特定功能参数**:协议专有的参数
|
||||||
|
|
||||||
|
2. **参数文档格式**:
|
||||||
|
```markdown
|
||||||
|
| 参数名 | 类型 | 描述 | 默认值 | 示例 |
|
||||||
|
|-------|------|------|--------|------|
|
||||||
|
| format | string | 输出格式 | text | json, xml |
|
||||||
|
| cache | boolean | 是否缓存 | true | true, false |
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **参数验证考虑**:
|
||||||
|
- 参数类型验证
|
||||||
|
- 参数值范围限制
|
||||||
|
- 参数组合逻辑
|
||||||
|
|
||||||
|
### Phase 4: 注册表设计(registry组件)
|
||||||
|
1. **注册表策略选择**:
|
||||||
|
- **有注册表协议**:需要ID到路径的映射(如thought, execution)
|
||||||
|
- **无注册表协议**:直接使用路径(如file, http)
|
||||||
|
|
||||||
|
2. **映射关系设计**(适用于有注册表协议):
|
||||||
|
```markdown
|
||||||
|
| 资源ID | 实际路径 | 描述 |
|
||||||
|
|--------|----------|------|
|
||||||
|
| resource-id | @package://path/to/file.md | 资源描述 |
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **路径引用规范**:
|
||||||
|
- 支持@package://前缀引用包资源
|
||||||
|
- 支持@project://前缀引用项目资源
|
||||||
|
- 支持@file://前缀引用文件系统资源
|
||||||
|
- 支持嵌套协议引用
|
||||||
|
|
||||||
|
### Phase 5: DPML结构实现
|
||||||
|
|
||||||
|
**关键要求:文件必须从`<resource>`标签直接开始**
|
||||||
|
|
||||||
|
**有注册表协议示例:**
|
||||||
|
```xml
|
||||||
|
<resource protocol="custom-protocol">
|
||||||
|
<location>
|
||||||
|
```ebnf
|
||||||
|
location ::= custom-protocol://{resource_id}
|
||||||
|
resource_id ::= [a-zA-Z][a-zA-Z0-9_-]*
|
||||||
|
```
|
||||||
|
</location>
|
||||||
|
|
||||||
|
<params>
|
||||||
|
| 参数名 | 类型 | 描述 | 默认值 |
|
||||||
|
|-------|------|------|--------|
|
||||||
|
| format | string | 输出格式(text\|json\|xml) | text |
|
||||||
|
| cache | boolean | 是否缓存结果 | true |
|
||||||
|
| encoding | string | 文件编码 | utf8 |
|
||||||
|
</params>
|
||||||
|
|
||||||
|
<registry>
|
||||||
|
| 资源ID | 文件路径 |
|
||||||
|
|--------|----------|
|
||||||
|
| example-resource | @package://path/to/example.md |
|
||||||
|
| another-resource | @project://config/another.md |
|
||||||
|
</registry>
|
||||||
|
</resource>
|
||||||
|
```
|
||||||
|
|
||||||
|
**无注册表协议示例:**
|
||||||
|
```xml
|
||||||
|
<resource protocol="direct-access">
|
||||||
|
<location>
|
||||||
|
```ebnf
|
||||||
|
location ::= direct-access://{path}
|
||||||
|
path ::= absolute_path | relative_path
|
||||||
|
absolute_path ::= '/' path_segment {'/' path_segment}
|
||||||
|
relative_path ::= path_segment {'/' path_segment}
|
||||||
|
path_segment ::= [^/]+
|
||||||
|
```
|
||||||
|
</location>
|
||||||
|
|
||||||
|
<params>
|
||||||
|
| 参数名 | 类型 | 描述 | 默认值 |
|
||||||
|
|-------|------|------|--------|
|
||||||
|
| encoding | string | 文件编码 | utf8 |
|
||||||
|
| line | string | 行范围(如"1-10") | - |
|
||||||
|
</params>
|
||||||
|
|
||||||
|
<registry>
|
||||||
|
<!-- 此协议不使用注册表,直接通过路径访问资源 -->
|
||||||
|
</registry>
|
||||||
|
</resource>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 6: 系统集成验证
|
||||||
|
1. **注册表集成**:确保协议定义与resource.registry.json格式兼容
|
||||||
|
2. **代码实现检查**:验证是否需要创建对应的Protocol类文件
|
||||||
|
3. **ResourceManager集成**:确认协议能被ResourceManager正确加载
|
||||||
|
4. **加载语义支持**:验证@、@!、@?前缀的正确处理
|
||||||
|
5. **查询参数解析**:确保参数能被正确解析和应用
|
||||||
|
|
||||||
|
### Phase 7: 质量检查和测试
|
||||||
|
1. **语法验证**:确保DPML resource语法正确性
|
||||||
|
2. **EBNF验证**:验证location部分的EBNF语法正确性
|
||||||
|
3. **参数完整性**:确认所有参数都有清晰的类型和描述
|
||||||
|
4. **注册表一致性**:验证注册表映射的逻辑正确性
|
||||||
|
5. **纯净性检查**:确认文件从`<resource>`标签开始,无多余内容
|
||||||
|
</process>
|
||||||
|
|
||||||
|
<criteria>
|
||||||
|
## 质量评价标准
|
||||||
|
|
||||||
|
### 格式合规性
|
||||||
|
- ✅ 文件从`<resource protocol="协议名">`标签直接开始
|
||||||
|
- ✅ 使用正确的DPML resource标签结构
|
||||||
|
- ✅ 三个子标签按location → params → registry顺序排列
|
||||||
|
- ✅ XML语法正确,标签正确闭合
|
||||||
|
- ✅ protocol属性值符合命名规范
|
||||||
|
|
||||||
|
### 路径格式规范性
|
||||||
|
- ✅ location部分使用正确的EBNF语法
|
||||||
|
- ✅ 路径格式清晰明确,无歧义
|
||||||
|
- ✅ 参数化路径使用`{parameter}`格式
|
||||||
|
- ✅ 路径结构与协议用途匹配
|
||||||
|
- ✅ 支持协议的典型使用场景
|
||||||
|
|
||||||
|
### 参数文档完整性
|
||||||
|
- ✅ 所有参数都有清晰的类型定义
|
||||||
|
- ✅ 参数描述详细且准确
|
||||||
|
- ✅ 提供了合理的默认值
|
||||||
|
- ✅ 参数示例有助于理解
|
||||||
|
- ✅ 参数组合逻辑合理
|
||||||
|
|
||||||
|
### 注册表设计合理性
|
||||||
|
- ✅ 注册表策略与协议特性匹配
|
||||||
|
- ✅ 映射关系清晰且实用
|
||||||
|
- ✅ 路径引用符合PromptX规范
|
||||||
|
- ✅ 抽象性和具体性平衡适当
|
||||||
|
- ✅ 支持嵌套协议引用
|
||||||
|
|
||||||
|
### 系统集成性
|
||||||
|
- ✅ 与ResourceManager兼容
|
||||||
|
- ✅ 与resource.registry.json格式一致
|
||||||
|
- ✅ 支持标准加载语义(@、@!、@?)
|
||||||
|
- ✅ 查询参数能被正确解析
|
||||||
|
- ✅ 与现有协议生态协调
|
||||||
|
|
||||||
|
### 实用价值
|
||||||
|
- ✅ 解决了实际的资源访问需求
|
||||||
|
- ✅ 路径格式简洁易用
|
||||||
|
- ✅ 参数设计灵活且必要
|
||||||
|
- ✅ 注册表提供了实际价值
|
||||||
|
- ✅ 整体设计具有可扩展性
|
||||||
|
|
||||||
|
### 文件纯净性
|
||||||
|
- ✅ 文件结构完全符合DPML resource规范
|
||||||
|
- ✅ 无任何XML结构外的多余内容
|
||||||
|
- ✅ 体现resource协议定义的标准格式
|
||||||
|
- ✅ 三组件内容充实且相互配合
|
||||||
|
</criteria>
|
||||||
|
</execution>
|
||||||
241
prompt/core/execution/role-authoring.execution.md
Normal file
241
prompt/core/execution/role-authoring.execution.md
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
<execution>
|
||||||
|
<constraint>
|
||||||
|
## 客观技术限制
|
||||||
|
- **DPML语法约束**:必须遵循EBNF定义的role语法结构
|
||||||
|
- **XML格式要求**:标签必须正确闭合,属性值必须用双引号包围
|
||||||
|
- **三组件架构固化**:personality、principle、knowledge三组件的语义边界固定
|
||||||
|
- **文件编码**:必须使用UTF-8编码
|
||||||
|
- **引用协议约束**:@!引用必须指向实际存在的资源
|
||||||
|
- **PromptX系统集成**:必须与promptx命令行工具和ResourceManager兼容
|
||||||
|
</constraint>
|
||||||
|
|
||||||
|
<rule>
|
||||||
|
## 强制性编写规则
|
||||||
|
- **纯XML结构**:role文件必须从`<role>`标签开始,不得包含任何XML结构外的内容
|
||||||
|
- **根标签强制**:文件必须使用`<role>`作为根标签包装全部内容
|
||||||
|
- **三组件完整**:必须包含personality、principle、knowledge三个子标签
|
||||||
|
- **组件顺序固定**:子标签必须按personality → principle → knowledge顺序排列
|
||||||
|
- **文件纯净性**:除了`<role>`标签结构外,不得包含任何其他内容
|
||||||
|
- **引用规范性**:使用@!引用时必须遵循resource协议语法
|
||||||
|
- **镜像结构约束**:用户资源必须遵循`.promptx/resource/domain/`结构,镜像系统`prompt/domain/`
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<guideline>
|
||||||
|
## 编写指导原则
|
||||||
|
- **编排优先**:role文件主要职责是编排组合,推荐使用@!引用机制而非直接内容
|
||||||
|
- **简洁性原则**:保持role文件的简洁和清晰,避免冗长的直接内容
|
||||||
|
- **模块化思维**:将具体内容抽离到独立的thought、execution、knowledge文件中
|
||||||
|
- **引用一致性**:在同一role文件中保持引用风格的一致性
|
||||||
|
- **可维护性**:通过引用机制实现内容的独立维护和复用
|
||||||
|
- **灵活性保留**:允许在引用和直接内容之间选择,但推荐引用
|
||||||
|
- **镜像一致性**:用户资源结构与系统资源保持一致,降低认知负载
|
||||||
|
</guideline>
|
||||||
|
|
||||||
|
<process>
|
||||||
|
## 编写执行流程
|
||||||
|
|
||||||
|
### Phase 1: 角色概念设计
|
||||||
|
1. **明确角色定位**:确定AI角色的核心身份和专业领域
|
||||||
|
2. **分析能力需求**:识别角色需要的思维特征、行为原则和专业知识
|
||||||
|
3. **规划组件结构**:决定三个组件的具体内容来源和组织方式
|
||||||
|
4. **选择编排策略**:决定使用引用机制还是直接内容
|
||||||
|
|
||||||
|
### Phase 2: 资源组织规划
|
||||||
|
|
||||||
|
#### 用户资源目录结构(镜像系统结构):
|
||||||
|
```
|
||||||
|
.promptx/resource/domain/{roleId}/
|
||||||
|
├── {roleId}.role.md # 主角色文件
|
||||||
|
├── thought/ # 思维模式目录
|
||||||
|
│ └── {name}.thought.md # 专业思维模式
|
||||||
|
└── execution/ # 执行流程目录
|
||||||
|
└── {name}.execution.md # 专业执行流程
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 内容来源规划:
|
||||||
|
1. **思维模式来源**(personality组件):
|
||||||
|
- 核心引用:`@!thought://remember`(记忆能力)
|
||||||
|
- 核心引用:`@!thought://recall`(回忆能力)
|
||||||
|
- 专业引用:`@!thought://[role-specific]`(角色特定思维)
|
||||||
|
- 或直接定义角色的思维特征和认知偏好
|
||||||
|
|
||||||
|
2. **行为原则来源**(principle组件):
|
||||||
|
- 专业引用:`@!execution://[role-specific]`(角色特定执行原则)
|
||||||
|
- 或直接定义角色的行为准则和工作流程
|
||||||
|
|
||||||
|
3. **专业知识来源**(knowledge组件):
|
||||||
|
- 领域引用:`@!knowledge://[domain-specific]`(领域专业知识)
|
||||||
|
- 或直接定义角色的知识体系和技能框架
|
||||||
|
|
||||||
|
### Phase 3: DPML结构实现
|
||||||
|
|
||||||
|
**关键要求:文件必须从`<role>`标签直接开始**
|
||||||
|
|
||||||
|
**推荐编排风格(引用优先):**
|
||||||
|
```xml
|
||||||
|
<role>
|
||||||
|
<personality>
|
||||||
|
@!thought://remember
|
||||||
|
@!thought://recall
|
||||||
|
@!thought://[role-specific-thought]
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
@!execution://[role-specific-execution]
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
@!knowledge://[domain-specific-knowledge]
|
||||||
|
</knowledge>
|
||||||
|
</role>
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例:助手角色(参考assistant.role.md)**
|
||||||
|
```xml
|
||||||
|
<role>
|
||||||
|
<personality>
|
||||||
|
@!thought://remember
|
||||||
|
@!thought://recall
|
||||||
|
@!thought://assistant
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
@!execution://assistant
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
@!knowledge://general-assistant
|
||||||
|
</knowledge>
|
||||||
|
</role>
|
||||||
|
```
|
||||||
|
|
||||||
|
**用户资源示例(自定义销售分析师):**
|
||||||
|
```xml
|
||||||
|
<role>
|
||||||
|
<personality>
|
||||||
|
@!thought://remember
|
||||||
|
@!thought://recall
|
||||||
|
@!thought://sales-analyst
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
@!execution://sales-data-analysis
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
@!knowledge://business-intelligence
|
||||||
|
</knowledge>
|
||||||
|
</role>
|
||||||
|
```
|
||||||
|
|
||||||
|
**混合风格(引用+直接内容):**
|
||||||
|
```xml
|
||||||
|
<role>
|
||||||
|
<personality>
|
||||||
|
@!thought://remember
|
||||||
|
@!thought://recall
|
||||||
|
|
||||||
|
## 角色特定思维特征
|
||||||
|
- **用户导向思维**:始终以用户需求为中心
|
||||||
|
- **解决方案思维**:专注于提供实用的解决方案
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
@!execution://assistant
|
||||||
|
|
||||||
|
## 补充行为原则
|
||||||
|
- 保持耐心和友善的交互风格
|
||||||
|
- 承认不确定性,不臆测答案
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
@!knowledge://general-assistant
|
||||||
|
</knowledge>
|
||||||
|
</role>
|
||||||
|
```
|
||||||
|
|
||||||
|
**纯直接内容风格(不推荐但允许):**
|
||||||
|
```xml
|
||||||
|
<role>
|
||||||
|
<personality>
|
||||||
|
# 角色思维模式
|
||||||
|
## 核心思维特征
|
||||||
|
- **特征1**:描述
|
||||||
|
- **特征2**:描述
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
# 角色行为原则
|
||||||
|
## 核心原则
|
||||||
|
- **原则1**:描述
|
||||||
|
- **原则2**:描述
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
# 角色专业知识
|
||||||
|
## 知识领域
|
||||||
|
- **领域1**:描述
|
||||||
|
- **领域2**:描述
|
||||||
|
</knowledge>
|
||||||
|
</role>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: 质量检查和集成验证
|
||||||
|
1. **结构验证**:确保DPML role语法正确性
|
||||||
|
2. **引用检查**:验证所有@!引用的资源实际存在
|
||||||
|
3. **三组件完整性**:确认personality、principle、knowledge都有实质内容
|
||||||
|
4. **系统集成测试**:验证与promptx命令和ResourceManager的兼容性
|
||||||
|
5. **纯净性检查**:确认文件从`<role>`标签开始,无多余内容
|
||||||
|
6. **镜像结构验证**:确认用户资源目录结构符合镜像规范
|
||||||
|
</process>
|
||||||
|
|
||||||
|
<criteria>
|
||||||
|
## 质量评价标准
|
||||||
|
|
||||||
|
### 格式合规性
|
||||||
|
- ✅ 文件从`<role>`标签直接开始,无额外内容
|
||||||
|
- ✅ 使用正确的DPML role标签结构
|
||||||
|
- ✅ 三个子标签按personality → principle → knowledge顺序排列
|
||||||
|
- ✅ XML语法正确,标签正确闭合
|
||||||
|
- ✅ Markdown格式规范(如有直接内容)
|
||||||
|
|
||||||
|
### 编排质量
|
||||||
|
- ✅ 体现role文件的编排组合职责
|
||||||
|
- ✅ 合理使用@!引用机制实现模块化
|
||||||
|
- ✅ 保持文件的简洁性和可读性
|
||||||
|
- ✅ 引用风格在文件内保持一致
|
||||||
|
- ✅ 避免不必要的冗长直接内容
|
||||||
|
|
||||||
|
### 三组件完整性
|
||||||
|
- ✅ personality组件包含思维特征定义或引用
|
||||||
|
- ✅ principle组件包含行为原则定义或引用
|
||||||
|
- ✅ knowledge组件包含专业知识定义或引用
|
||||||
|
- ✅ 三组件逻辑一致,共同构建完整角色
|
||||||
|
- ✅ 组件内容与角色定位匹配
|
||||||
|
|
||||||
|
### 引用有效性
|
||||||
|
- ✅ 所有@!引用遵循resource协议语法
|
||||||
|
- ✅ 引用的资源路径正确且存在
|
||||||
|
- ✅ 引用内容与组件语义匹配
|
||||||
|
- ✅ 引用关系清晰,无循环依赖
|
||||||
|
|
||||||
|
### 系统集成性
|
||||||
|
- ✅ 与PromptX锦囊串联系统兼容
|
||||||
|
- ✅ 支持promptx action命令激活
|
||||||
|
- ✅ 角色定义可被AI系统正确解析
|
||||||
|
- ✅ 实现角色的即时专家化能力
|
||||||
|
- ✅ ResourceManager可正确发现和加载
|
||||||
|
|
||||||
|
### 文件纯净性
|
||||||
|
- ✅ 文件结构完全符合DPML role规范
|
||||||
|
- ✅ 无任何XML结构外的多余内容
|
||||||
|
- ✅ 体现role文件的标准编排格式
|
||||||
|
- ✅ 维持role文件的简洁优雅特性
|
||||||
|
|
||||||
|
### 架构合规性
|
||||||
|
- ✅ 用户资源目录结构镜像系统结构
|
||||||
|
- ✅ 文件组织符合`.promptx/resource/domain/`规范
|
||||||
|
- ✅ 与系统资源结构保持一致性
|
||||||
|
- ✅ 降低用户认知负载和学习成本
|
||||||
|
</criteria>
|
||||||
|
</execution>
|
||||||
138
prompt/core/execution/role-generation.execution.md
Normal file
138
prompt/core/execution/role-generation.execution.md
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<execution>
|
||||||
|
<constraint>
|
||||||
|
## 客观技术限制
|
||||||
|
- **DPML协议约束**:生成的角色必须严格遵循DPML `<role>`标签框架和三组件架构
|
||||||
|
- **文件格式要求**:生成的角色文件必须是有效的Markdown格式并符合XML语法
|
||||||
|
- **系统集成约束**:生成的角色必须与PromptX系统兼容,支持ResourceManager发现机制
|
||||||
|
- **快速生成要求**:整个创建过程应在1-2分钟内完成
|
||||||
|
- **目录结构约束**:用户资源必须创建在`.promptx/resource/domain/{roleId}/`目录,镜像系统结构
|
||||||
|
- **文件组织约束**:角色相关的所有文件(execution、thought等)必须统一存放在角色目录下
|
||||||
|
</constraint>
|
||||||
|
|
||||||
|
<rule>
|
||||||
|
## 强制性执行规则
|
||||||
|
- **三组件完整性**:每个生成的角色必须包含personality、principle、knowledge三个完整组件
|
||||||
|
- **DPML语法严格性**:生成内容必须使用正确的XML标签语法,标签必须正确闭合
|
||||||
|
- **领域识别准确性**:必须准确识别用户需求的专业领域
|
||||||
|
- **模板化生成**:基于标准模板快速生成,避免复杂的定制化过程
|
||||||
|
- **一次性交付**:生成后直接交付,避免反复确认和修改
|
||||||
|
- **镜像结构强制**:用户资源必须创建在`.promptx/resource/domain/{roleId}/`,镜像系统`prompt/domain/`结构
|
||||||
|
- **文件统一管理**:角色的execution、thought等扩展文件必须放在同一角色目录下,便于统一管理
|
||||||
|
- **引用路径准确**:使用@!引用时必须指向正确的文件路径,确保引用关系有效
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<guideline>
|
||||||
|
## 执行指导原则
|
||||||
|
- **简洁高效**:优先速度和效率,避免冗长对话
|
||||||
|
- **标准化优先**:使用领域标准能力,而非深度定制
|
||||||
|
- **即用原则**:生成的角色应立即可用,无需额外配置
|
||||||
|
- **用户友好**:保持简单明了的交互体验
|
||||||
|
- **镜像一致**:与系统结构保持一致,降低认知负载
|
||||||
|
</guideline>
|
||||||
|
|
||||||
|
<process>
|
||||||
|
## 极简3步生成流程
|
||||||
|
|
||||||
|
### Step 1: 领域快速识别 (30秒内)
|
||||||
|
```
|
||||||
|
目标:从用户描述中快速提取领域关键词
|
||||||
|
|
||||||
|
识别策略:
|
||||||
|
- 提取技术栈关键词(如:微信小程序、React、Python等)
|
||||||
|
- 识别职业角色关键词(如:产品经理、设计师、运营等)
|
||||||
|
- 理解功能需求关键词(如:开发、分析、营销等)
|
||||||
|
|
||||||
|
快速确认:
|
||||||
|
"明白了!您需要一个【X领域】的专业AI助手,对吗?"
|
||||||
|
|
||||||
|
处理原则:
|
||||||
|
- 最多1次确认,用户确认后立即进入生成
|
||||||
|
- 如果领域明确,跳过确认直接生成
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: 模板化角色生成 (60秒内)
|
||||||
|
```
|
||||||
|
基于识别的领域,调用标准模板:
|
||||||
|
|
||||||
|
模板选择逻辑:
|
||||||
|
- 微信小程序 → 小程序开发专家模板
|
||||||
|
- 前端开发 → 前端工程师模板
|
||||||
|
- 产品管理 → 产品经理模板
|
||||||
|
- 数据分析 → 数据分析师模板
|
||||||
|
- 更多领域... → 对应专业模板
|
||||||
|
|
||||||
|
文件组织结构(镜像系统结构):
|
||||||
|
.promptx/resource/domain/{roleId}/
|
||||||
|
├── {roleId}.role.md # 主角色文件
|
||||||
|
├── thought/ # 思维模式目录(需要时创建)
|
||||||
|
│ └── {specific}.thought.md # 专业思维模式
|
||||||
|
└── execution/ # 执行模式目录(需要时创建)
|
||||||
|
└── {specific}.execution.md # 专业执行流程
|
||||||
|
|
||||||
|
三组件自动填充:
|
||||||
|
personality: 引用该领域的标准思维模式(remember + recall + 专业思维)
|
||||||
|
principle: 引用该领域的标准执行流程(可独立创建execution文件)
|
||||||
|
knowledge: 引用该领域的专业知识体系(或直接定义)
|
||||||
|
|
||||||
|
质量检查:
|
||||||
|
☐ DPML格式正确
|
||||||
|
☐ 三组件完整
|
||||||
|
☐ 引用资源有效
|
||||||
|
☐ 目录结构规范(镜像系统结构)
|
||||||
|
☐ 文件路径正确
|
||||||
|
☐ ResourceManager可发现
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: 结果直接交付 (30秒内)
|
||||||
|
```
|
||||||
|
呈现格式:
|
||||||
|
1. 角色价值简述
|
||||||
|
2. 文件创建确认(正确目录结构)
|
||||||
|
3. 激活命令说明
|
||||||
|
4. 使用建议(可选)
|
||||||
|
|
||||||
|
目录结构展示(镜像系统结构):
|
||||||
|
.promptx/resource/domain/{roleId}/
|
||||||
|
├── {roleId}.role.md # ✅ 已创建
|
||||||
|
└── [其他扩展文件] # ✅ 按需创建
|
||||||
|
|
||||||
|
交付策略:
|
||||||
|
- 确认角色已正确创建在用户资源目录
|
||||||
|
- 提供立即可用的激活命令
|
||||||
|
- 说明文件组织规范(与系统结构一致)
|
||||||
|
- 说明ResourceManager自动发现机制
|
||||||
|
|
||||||
|
后续支持:
|
||||||
|
- 如果用户满意,直接结束
|
||||||
|
- 如果需要扩展功能,指导创建execution/thought文件
|
||||||
|
```
|
||||||
|
</process>
|
||||||
|
|
||||||
|
<criteria>
|
||||||
|
## 质量评价标准
|
||||||
|
|
||||||
|
### 效率指标
|
||||||
|
- ✅ 总用时 ≤ 2分钟
|
||||||
|
- ✅ 对话轮次 ≤ 3轮
|
||||||
|
- ✅ 一次性生成成功率 ≥ 90%
|
||||||
|
- ✅ 用户满意度 ≥ 85%
|
||||||
|
|
||||||
|
### 角色质量
|
||||||
|
- ✅ DPML协议完全合规
|
||||||
|
- ✅ 三组件内容实用
|
||||||
|
- ✅ 角色定位准确
|
||||||
|
- ✅ 立即可激活使用
|
||||||
|
|
||||||
|
### 架构合规
|
||||||
|
- ✅ 目录结构镜像系统结构
|
||||||
|
- ✅ ResourceManager可发现
|
||||||
|
- ✅ 用户资源路径正确
|
||||||
|
- ✅ 引用关系有效
|
||||||
|
|
||||||
|
### 用户体验
|
||||||
|
- ✅ 交互流程简洁
|
||||||
|
- ✅ 生成结果清晰
|
||||||
|
- ✅ 激活方法明确
|
||||||
|
- ✅ 学习成本极低
|
||||||
|
</criteria>
|
||||||
|
</execution>
|
||||||
183
prompt/core/execution/thought-authoring.execution.md
Normal file
183
prompt/core/execution/thought-authoring.execution.md
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<execution>
|
||||||
|
<constraint>
|
||||||
|
## 客观技术限制
|
||||||
|
- **DPML语法约束**:必须遵循EBNF定义的thought语法结构
|
||||||
|
- **XML格式要求**:标签必须正确闭合,属性值必须用双引号包围
|
||||||
|
- **Markdown兼容性**:内容部分必须是有效的Markdown格式,支持Mermaid图表
|
||||||
|
- **文件编码**:必须使用UTF-8编码
|
||||||
|
- **思维模式固化**:四种思维模式的语义特征不可改变
|
||||||
|
- **可视化限制**:Mermaid图表必须符合语法规范
|
||||||
|
</constraint>
|
||||||
|
|
||||||
|
<rule>
|
||||||
|
## 强制性编写规则
|
||||||
|
- **纯XML结构**:thought文件必须从`<thought>`标签开始,不得包含任何XML结构外的内容
|
||||||
|
- **根标签强制**:文件必须使用`<thought>`作为根标签包装全部内容
|
||||||
|
- **子标签命名**:只能使用规范定义的四个思维模式子标签:exploration, reasoning, plan, challenge
|
||||||
|
- **语义一致性**:每个子标签内容必须与其思维模式语义定义保持一致
|
||||||
|
- **文件纯净性**:除了`<thought>`标签结构外,不得包含任何其他内容
|
||||||
|
- **内容实质性**:包含的子标签都必须有实质性思考内容,不得为空
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<guideline>
|
||||||
|
## 编写指导原则
|
||||||
|
- **思维模式清晰性**:每个子标签的内容应明确体现对应的思维特征
|
||||||
|
- **推荐思考顺序**:建议按exploration → challenge → reasoning → plan顺序组织思考
|
||||||
|
- **可视化优先**:优先使用Mermaid图表表达复杂的思维关系和逻辑结构
|
||||||
|
- **内容层次化**:使用Markdown的标题、列表等结构组织思考内容
|
||||||
|
- **思维完整性**:确保思考覆盖问题的关键维度,避免思维盲区
|
||||||
|
- **灵活组合**:根据实际需求选择合适的思维模式组合,无需全部使用
|
||||||
|
</guideline>
|
||||||
|
|
||||||
|
<process>
|
||||||
|
## 编写执行流程
|
||||||
|
|
||||||
|
### Phase 1: 思考需求分析
|
||||||
|
1. **明确思考目标**:确定这个thought要解决什么问题或分析什么议题
|
||||||
|
2. **识别思考复杂度**:判断问题是否需要多维度思考
|
||||||
|
3. **选择思维模式**:根据问题特点选择合适的思维模式组合
|
||||||
|
4. **确定思考深度**:决定每个思维模式需要的分析深度
|
||||||
|
|
||||||
|
### Phase 2: 思维模式规划
|
||||||
|
1. **探索思维规划**:
|
||||||
|
- 确定需要发散思考的维度
|
||||||
|
- 列出要探索的可能性和创新点
|
||||||
|
- 规划关联性分析的范围
|
||||||
|
|
||||||
|
2. **挑战思维规划**:
|
||||||
|
- 识别需要质疑的假设和观点
|
||||||
|
- 列出潜在风险和问题点
|
||||||
|
- 规划批判性分析的角度
|
||||||
|
|
||||||
|
3. **推理思维规划**:
|
||||||
|
- 确定需要验证的逻辑链条
|
||||||
|
- 规划因果关系分析路径
|
||||||
|
- 设计系统性推理结构
|
||||||
|
|
||||||
|
4. **计划思维规划**:
|
||||||
|
- 明确需要设计的行动方案
|
||||||
|
- 规划决策路径和组织结构
|
||||||
|
- 确定系统架构的层次
|
||||||
|
|
||||||
|
### Phase 3: DPML结构实现
|
||||||
|
|
||||||
|
**关键要求:文件必须从`<thought>`标签直接开始**
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<thought>
|
||||||
|
<exploration>
|
||||||
|
# 探索思维:发散性思考
|
||||||
|
[跳跃思考、生成可能性、寻找创新点和关联性]
|
||||||
|
</exploration>
|
||||||
|
|
||||||
|
<challenge>
|
||||||
|
# 挑战思维:批判性思考
|
||||||
|
[逆向思考、质疑假设、识别风险和问题点]
|
||||||
|
</challenge>
|
||||||
|
|
||||||
|
<reasoning>
|
||||||
|
# 推理思维:系统性思考
|
||||||
|
[连续推理、验证逻辑、分析因果关系]
|
||||||
|
</reasoning>
|
||||||
|
|
||||||
|
<plan>
|
||||||
|
# 计划思维:结构性思考
|
||||||
|
[设计方案、决策路径、组织架构]
|
||||||
|
</plan>
|
||||||
|
</thought>
|
||||||
|
```
|
||||||
|
|
||||||
|
**推荐思考顺序示例:**
|
||||||
|
```xml
|
||||||
|
<thought>
|
||||||
|
<!-- 1. 先发散探索 -->
|
||||||
|
<exploration>
|
||||||
|
## 可能性探索
|
||||||
|
- 方向A:...
|
||||||
|
- 方向B:...
|
||||||
|
|
||||||
|
## 关联性分析
|
||||||
|
```mermaid
|
||||||
|
mindmap
|
||||||
|
root)问题核心(
|
||||||
|
分支1
|
||||||
|
分支2
|
||||||
|
分支3
|
||||||
|
```
|
||||||
|
</exploration>
|
||||||
|
|
||||||
|
<!-- 2. 再批判质疑 -->
|
||||||
|
<challenge>
|
||||||
|
## 假设质疑
|
||||||
|
- 对方向A的质疑:...
|
||||||
|
- 对方向B的质疑:...
|
||||||
|
|
||||||
|
## 风险识别
|
||||||
|
- 潜在风险1:...
|
||||||
|
- 潜在风险2:...
|
||||||
|
</challenge>
|
||||||
|
|
||||||
|
<!-- 3. 然后系统推理 -->
|
||||||
|
<reasoning>
|
||||||
|
## 逻辑验证
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[前提] --> B[推理]
|
||||||
|
B --> C[结论]
|
||||||
|
```
|
||||||
|
</reasoning>
|
||||||
|
|
||||||
|
<!-- 4. 最后制定计划 -->
|
||||||
|
<plan>
|
||||||
|
## 行动方案
|
||||||
|
1. 步骤一:...
|
||||||
|
2. 步骤二:...
|
||||||
|
</plan>
|
||||||
|
</thought>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: 思维质量检查
|
||||||
|
1. **思维模式验证**:确保每个子标签体现正确的思维特征
|
||||||
|
2. **逻辑一致性检查**:验证不同思维模式间的逻辑关系
|
||||||
|
3. **思考完整性审核**:确认思考覆盖问题的关键维度
|
||||||
|
4. **可视化检查**:验证Mermaid图表语法正确性和表达清晰性
|
||||||
|
5. **纯净性检查**:确认文件从`<thought>`标签开始,无多余内容
|
||||||
|
</process>
|
||||||
|
|
||||||
|
<criteria>
|
||||||
|
## 质量评价标准
|
||||||
|
|
||||||
|
### 格式合规性
|
||||||
|
- ✅ 文件从`<thought>`标签直接开始,无额外内容
|
||||||
|
- ✅ 使用正确的DPML thought标签结构
|
||||||
|
- ✅ 子标签命名符合四种思维模式规范
|
||||||
|
- ✅ XML语法正确,标签正确闭合
|
||||||
|
- ✅ Markdown格式规范,Mermaid图表有效
|
||||||
|
|
||||||
|
### 思维模式准确性
|
||||||
|
- ✅ exploration体现发散性、跳跃性思考特征
|
||||||
|
- ✅ challenge体现批判性、逆向思考特征
|
||||||
|
- ✅ reasoning体现系统性、连续性推理特征
|
||||||
|
- ✅ plan体现结构性、秩序性思考特征
|
||||||
|
- ✅ 各思维模式语义边界清晰,不混淆
|
||||||
|
|
||||||
|
### 思考质量
|
||||||
|
- ✅ 思考内容具有实质性和深度
|
||||||
|
- ✅ 逻辑关系清晰,推理链条完整
|
||||||
|
- ✅ 覆盖问题的关键维度,无明显盲区
|
||||||
|
- ✅ 思维过程系统化,层次分明
|
||||||
|
- ✅ 结论或方案具有可操作性
|
||||||
|
|
||||||
|
### 可视化效果
|
||||||
|
- ✅ 恰当使用Mermaid图表表达复杂关系
|
||||||
|
- ✅ 图表类型选择合适(mindmap, flowchart, graph等)
|
||||||
|
- ✅ 可视化内容与文字描述相互补充
|
||||||
|
- ✅ 图表简洁清晰,易于理解
|
||||||
|
|
||||||
|
### 文件纯净性
|
||||||
|
- ✅ 文件结构完全符合DPML thought规范
|
||||||
|
- ✅ 无任何XML结构外的多余内容
|
||||||
|
- ✅ 体现thought文件的标准格式
|
||||||
|
- ✅ 思维模式组合合理,符合实际需求
|
||||||
|
</criteria>
|
||||||
|
</execution>
|
||||||
19
prompt/core/nuwa/nuwa.role.md
Normal file
19
prompt/core/nuwa/nuwa.role.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<role>
|
||||||
|
<personality>
|
||||||
|
@!thought://remember
|
||||||
|
@!thought://recall
|
||||||
|
@!thought://role-creation
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
@!execution://role-generation
|
||||||
|
@!execution://role-authoring
|
||||||
|
@!execution://thought-authoring
|
||||||
|
@!execution://execution-authoring
|
||||||
|
@!execution://resource-authoring
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
<!-- 未来可以在这里添加其他协议资源引用 -->
|
||||||
|
</knowledge>
|
||||||
|
</role>
|
||||||
63
prompt/core/thought/role-creation.thought.md
Normal file
63
prompt/core/thought/role-creation.thought.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<thought>
|
||||||
|
<exploration>
|
||||||
|
## 领域快速识别
|
||||||
|
|
||||||
|
### 从用户描述中提取核心信息
|
||||||
|
- **领域关键词**:用户提到的技术栈、职业、业务领域
|
||||||
|
- **功能期望**:用户希望AI助手具备的核心能力
|
||||||
|
- **应用场景**:主要的使用场景和工作环境
|
||||||
|
|
||||||
|
### 领域标准化映射
|
||||||
|
- **技术领域**:前端开发、后端开发、移动开发、数据分析等
|
||||||
|
- **业务领域**:产品管理、市场营销、设计创意、运营管理等
|
||||||
|
- **综合领域**:项目管理、技术架构、创业咨询、教育培训等
|
||||||
|
|
||||||
|
### 快速能力框架识别
|
||||||
|
- 该领域的核心技能需求
|
||||||
|
- 该领域的典型工作流程
|
||||||
|
- 该领域的专业知识体系
|
||||||
|
</exploration>
|
||||||
|
|
||||||
|
<reasoning>
|
||||||
|
## 基于ResourceManager的资源生成逻辑
|
||||||
|
|
||||||
|
### 架构驱动的生成策略
|
||||||
|
```
|
||||||
|
用户描述 → 领域识别 → 资源规划 → 文件生成 → ResourceManager发现
|
||||||
|
```
|
||||||
|
|
||||||
|
### 镜像结构思维模式
|
||||||
|
- **结构一致性**:用户资源目录镜像系统`prompt/domain/`结构
|
||||||
|
- **认知负载最小化**:与系统结构保持一致,降低学习成本
|
||||||
|
- **资源聚合原则**:角色相关的所有文件统一管理在角色目录下
|
||||||
|
|
||||||
|
### 三组件标准化填充策略
|
||||||
|
- **Personality设计**:
|
||||||
|
- 基于领域的通用思维特征
|
||||||
|
- 该领域专业人士的认知偏好
|
||||||
|
- 高效协作的交互风格
|
||||||
|
|
||||||
|
- **Principle设计**:
|
||||||
|
- 该领域的标准工作流程
|
||||||
|
- 通用的质量标准和最佳实践
|
||||||
|
- 常见问题的处理原则
|
||||||
|
|
||||||
|
- **Knowledge设计**:
|
||||||
|
- 该领域的核心技能栈
|
||||||
|
- 必备的专业知识体系
|
||||||
|
- 常用工具和方法论
|
||||||
|
|
||||||
|
### 文件组织优化思维
|
||||||
|
- **目录结构规划**:`.promptx/resource/domain/{roleId}/`
|
||||||
|
- **扩展文件支持**:thought/、execution/子目录按需创建
|
||||||
|
- **引用关系设计**:优先使用@!引用机制,实现模块化
|
||||||
|
- **发现机制适配**:确保ResourceManager能正确发现和加载
|
||||||
|
|
||||||
|
### 质量保证机制
|
||||||
|
- 确保三组件逻辑一致
|
||||||
|
- 验证角色定位清晰准确
|
||||||
|
- 保证实用性和可操作性
|
||||||
|
- 符合DPML协议规范
|
||||||
|
- 满足ResourceManager发现要求
|
||||||
|
</reasoning>
|
||||||
|
</thought>
|
||||||
@ -0,0 +1,244 @@
|
|||||||
|
<execution>
|
||||||
|
<constraint>
|
||||||
|
## 微信平台技术约束
|
||||||
|
- **包大小限制**:主包不超过2MB,总包不超过20MB
|
||||||
|
- **代码包限制**:分包不超过2MB,最多使用20个分包
|
||||||
|
- **API调用限制**:网络请求并发限制,部分API有调用频次限制
|
||||||
|
- **性能要求**:页面渲染时间不超过2秒,交互响应时间不超过300ms
|
||||||
|
- **平台兼容性**:需兼容微信不同版本和iOS/Android双平台
|
||||||
|
- **审核规范**:必须遵守微信小程序平台规则和内容规范
|
||||||
|
- **开发工具依赖**:必须使用微信开发者工具进行开发和调试
|
||||||
|
</constraint>
|
||||||
|
|
||||||
|
<rule>
|
||||||
|
## 强制性开发规则
|
||||||
|
- **代码规范强制**:必须遵循微信小程序开发规范和ESLint配置
|
||||||
|
- **文件结构固定**:页面必须包含.js/.wxml/.wxss/.json四个文件
|
||||||
|
- **生命周期规范**:严格按照小程序生命周期进行开发,避免内存泄漏
|
||||||
|
- **API使用规范**:必须进行用户授权检查,合规使用敏感API
|
||||||
|
- **安全要求**:敏感数据必须加密传输,用户隐私信息严格保护
|
||||||
|
- **性能底线**:setData调用频次控制,避免频繁的数据更新
|
||||||
|
- **审核合规**:代码和内容必须符合微信审核要求,避免违规操作
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<guideline>
|
||||||
|
## 开发最佳实践指导
|
||||||
|
- **组件化优先**:优先封装可复用组件,提高开发效率和代码质量
|
||||||
|
- **性能优化导向**:关注首屏加载时间、内存使用、用户体验流畅度
|
||||||
|
- **用户体验优先**:遵循微信设计规范,保持界面一致性和易用性
|
||||||
|
- **渐进增强**:优雅降级处理,确保在低版本微信中基本功能可用
|
||||||
|
- **错误处理友好**:提供清晰的错误提示和用户引导
|
||||||
|
- **测试覆盖全面**:真机测试、兼容性测试、性能测试并重
|
||||||
|
- **版本管理规范**:使用语义化版本控制,合理规划版本发布
|
||||||
|
</guideline>
|
||||||
|
|
||||||
|
<process>
|
||||||
|
## 微信小程序开发执行流程
|
||||||
|
|
||||||
|
### Phase 1: 项目规划与准备
|
||||||
|
```
|
||||||
|
需求分析:
|
||||||
|
1. 明确功能需求和用户场景
|
||||||
|
2. 分析技术可行性和平台限制
|
||||||
|
3. 确定MVP功能范围和迭代计划
|
||||||
|
4. 评估开发周期和资源需求
|
||||||
|
|
||||||
|
技术选型:
|
||||||
|
1. 框架选择:原生/uni-app/Taro/WePY
|
||||||
|
2. UI组件库:WeUI/Vant Weapp/ColorUI
|
||||||
|
3. 状态管理:原生/MobX/Redux
|
||||||
|
4. 后端服务:传统后端/云开发
|
||||||
|
|
||||||
|
项目初始化:
|
||||||
|
1. 创建小程序项目,配置基础信息
|
||||||
|
2. 搭建项目目录结构
|
||||||
|
3. 配置开发环境和构建工具
|
||||||
|
4. 设置代码规范和Git工作流
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: 架构设计与环境配置
|
||||||
|
```
|
||||||
|
目录结构设计:
|
||||||
|
├── pages/ # 页面文件
|
||||||
|
│ ├── index/ # 首页
|
||||||
|
│ └── detail/ # 详情页
|
||||||
|
├── components/ # 公共组件
|
||||||
|
├── utils/ # 工具函数
|
||||||
|
├── api/ # 接口封装
|
||||||
|
├── store/ # 状态管理
|
||||||
|
├── styles/ # 公共样式
|
||||||
|
├── static/ # 静态资源
|
||||||
|
└── app.js/wxss/json # 全局配置
|
||||||
|
|
||||||
|
架构设计原则:
|
||||||
|
1. 模块化:功能模块独立,便于维护
|
||||||
|
2. 组件化:UI组件可复用,提高效率
|
||||||
|
3. 服务化:API接口统一封装管理
|
||||||
|
4. 配置化:可配置参数集中管理
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: UI开发与组件封装
|
||||||
|
```
|
||||||
|
页面开发流程:
|
||||||
|
1. 分析设计稿,拆解页面结构
|
||||||
|
2. 使用WXML构建页面骨架
|
||||||
|
3. 使用WXSS实现样式效果
|
||||||
|
4. 处理响应式布局和适配
|
||||||
|
|
||||||
|
组件开发策略:
|
||||||
|
1. 识别可复用的UI模块
|
||||||
|
2. 封装自定义组件
|
||||||
|
3. 定义组件属性和事件
|
||||||
|
4. 编写组件文档和使用示例
|
||||||
|
|
||||||
|
样式开发规范:
|
||||||
|
1. 使用rpx单位适配不同屏幕
|
||||||
|
2. 遵循BEM命名规范
|
||||||
|
3. 使用CSS变量管理主题色彩
|
||||||
|
4. 优化CSS性能,避免复杂选择器
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: 功能开发与API集成
|
||||||
|
```
|
||||||
|
业务逻辑开发:
|
||||||
|
1. 实现页面交互逻辑
|
||||||
|
2. 处理用户输入和表单验证
|
||||||
|
3. 实现路由跳转和参数传递
|
||||||
|
4. 处理页面状态管理
|
||||||
|
|
||||||
|
API集成开发:
|
||||||
|
1. 封装网络请求模块
|
||||||
|
2. 实现接口调用和数据处理
|
||||||
|
3. 添加请求拦截器和错误处理
|
||||||
|
4. 实现数据缓存和同步策略
|
||||||
|
|
||||||
|
数据管理:
|
||||||
|
1. 设计数据流向和状态管理
|
||||||
|
2. 实现本地存储和缓存策略
|
||||||
|
3. 处理数据同步和更新
|
||||||
|
4. 优化setData性能
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 5: 性能优化与调试
|
||||||
|
```
|
||||||
|
性能优化策略:
|
||||||
|
1. 代码分包:合理拆分主包和分包
|
||||||
|
2. 懒加载:按需加载页面和组件
|
||||||
|
3. 图片优化:压缩图片,使用WebP格式
|
||||||
|
4. 缓存策略:合理使用缓存减少请求
|
||||||
|
|
||||||
|
调试与测试:
|
||||||
|
1. 开发者工具调试
|
||||||
|
2. 真机预览和调试
|
||||||
|
3. 性能分析和优化
|
||||||
|
4. 兼容性测试
|
||||||
|
|
||||||
|
代码质量保证:
|
||||||
|
1. ESLint代码检查
|
||||||
|
2. 单元测试编写
|
||||||
|
3. 代码审查
|
||||||
|
4. 性能监控
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 6: 测试发布与上线
|
||||||
|
```
|
||||||
|
测试阶段:
|
||||||
|
1. 功能测试:验证所有功能正常
|
||||||
|
2. 兼容性测试:测试不同设备和版本
|
||||||
|
3. 性能测试:检查加载速度和流畅度
|
||||||
|
4. 安全测试:验证数据安全和权限控制
|
||||||
|
|
||||||
|
发布准备:
|
||||||
|
1. 准备小程序基础信息和资料
|
||||||
|
2. 配置服务器域名和业务域名
|
||||||
|
3. 设置用户隐私保护指引
|
||||||
|
4. 准备审核说明和测试账号
|
||||||
|
|
||||||
|
审核发布:
|
||||||
|
1. 提交代码审核
|
||||||
|
2. 响应审核反馈
|
||||||
|
3. 发布正式版本
|
||||||
|
4. 监控线上表现
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 7: 运营维护与迭代
|
||||||
|
```
|
||||||
|
上线监控:
|
||||||
|
1. 监控小程序性能指标
|
||||||
|
2. 收集用户反馈和错误日志
|
||||||
|
3. 分析用户行为数据
|
||||||
|
4. 优化用户体验
|
||||||
|
|
||||||
|
迭代优化:
|
||||||
|
1. 根据数据分析优化功能
|
||||||
|
2. 修复发现的Bug
|
||||||
|
3. 新功能开发和上线
|
||||||
|
4. 持续性能优化
|
||||||
|
|
||||||
|
版本管理:
|
||||||
|
1. 制定版本发布计划
|
||||||
|
2. 管理版本兼容性
|
||||||
|
3. 处理版本回滚
|
||||||
|
4. 维护发布文档
|
||||||
|
```
|
||||||
|
</process>
|
||||||
|
|
||||||
|
<criteria>
|
||||||
|
## 质量评价标准
|
||||||
|
|
||||||
|
### 代码质量指标
|
||||||
|
- ✅ **规范性检查**:通过ESLint检查,无语法错误
|
||||||
|
- ✅ **结构清晰**:目录结构合理,文件命名规范
|
||||||
|
- ✅ **组件化程度**:公共组件复用率≥60%
|
||||||
|
- ✅ **代码注释**:关键业务逻辑注释覆盖率≥80%
|
||||||
|
- ✅ **函数复杂度**:单个函数行数不超过50行
|
||||||
|
|
||||||
|
### 性能质量指标
|
||||||
|
- ✅ **首屏加载**:首屏渲染时间≤2秒
|
||||||
|
- ✅ **包大小控制**:主包大小≤1.5MB,分包合理使用
|
||||||
|
- ✅ **内存使用**:页面内存占用≤50MB
|
||||||
|
- ✅ **网络请求**:接口响应时间≤1秒
|
||||||
|
- ✅ **用户体验**:页面切换流畅,无明显卡顿
|
||||||
|
|
||||||
|
### 功能质量指标
|
||||||
|
- ✅ **功能完整性**:核心功能100%实现
|
||||||
|
- ✅ **交互友好性**:操作响应及时,反馈明确
|
||||||
|
- ✅ **兼容性**:支持微信6.6.3以上版本
|
||||||
|
- ✅ **错误处理**:异常情况有友好提示
|
||||||
|
- ✅ **权限管理**:合规申请和使用用户权限
|
||||||
|
|
||||||
|
### 安全合规指标
|
||||||
|
- ✅ **数据安全**:敏感数据加密传输和存储
|
||||||
|
- ✅ **隐私保护**:用户隐私信息保护到位
|
||||||
|
- ✅ **内容合规**:内容符合平台规范
|
||||||
|
- ✅ **API合规**:API使用符合官方要求
|
||||||
|
- ✅ **审核通过**:能够顺利通过微信审核
|
||||||
|
|
||||||
|
### 用户体验指标
|
||||||
|
- ✅ **界面美观**:UI设计符合微信规范
|
||||||
|
- ✅ **操作便捷**:用户操作路径简洁明了
|
||||||
|
- ✅ **信息架构**:信息层次清晰,导航明确
|
||||||
|
- ✅ **反馈及时**:操作反馈及时准确
|
||||||
|
- ✅ **错误容错**:用户误操作有友好处理
|
||||||
|
|
||||||
|
## 持续改进标准
|
||||||
|
|
||||||
|
### 技术债务管理
|
||||||
|
- 定期代码重构,消除技术债务
|
||||||
|
- 升级依赖库版本,保持技术栈新鲜度
|
||||||
|
- 优化陈旧代码,提高维护效率
|
||||||
|
- 完善文档,降低维护成本
|
||||||
|
|
||||||
|
### 性能持续优化
|
||||||
|
- 建立性能监控体系
|
||||||
|
- 定期性能分析和优化
|
||||||
|
- 关注新技术和最佳实践
|
||||||
|
- 优化用户体验细节
|
||||||
|
|
||||||
|
### 团队协作效率
|
||||||
|
- 完善开发规范和流程
|
||||||
|
- 建立代码审查机制
|
||||||
|
- 提升自动化程度
|
||||||
|
- 知识分享和技能提升
|
||||||
|
</criteria>
|
||||||
|
</execution>
|
||||||
1
scripts/start-mcp.sh
Normal file
1
scripts/start-mcp.sh
Normal file
@ -0,0 +1 @@
|
|||||||
|
cd /Users/sean/WorkSpaces/DeepracticeProjects/PromptX && pnpm start mcp-server
|
||||||
@ -85,10 +85,16 @@ ${COMMANDS.HELLO}
|
|||||||
*/
|
*/
|
||||||
async analyzeRoleDependencies (roleInfo) {
|
async analyzeRoleDependencies (roleInfo) {
|
||||||
try {
|
try {
|
||||||
// 处理文件路径,将@package://前缀替换为实际路径
|
// 处理文件路径,将@package://和@project://前缀替换为实际路径
|
||||||
let filePath = roleInfo.file
|
let filePath = roleInfo.file
|
||||||
if (filePath.startsWith('@package://')) {
|
if (filePath.startsWith('@package://')) {
|
||||||
filePath = filePath.replace('@package://', '')
|
filePath = filePath.replace('@package://', '')
|
||||||
|
} else if (filePath.startsWith('@project://')) {
|
||||||
|
// 对于@project://路径,使用当前工作目录作为基础路径
|
||||||
|
const ProjectProtocol = require('../../resource/protocols/ProjectProtocol')
|
||||||
|
const projectProtocol = new ProjectProtocol()
|
||||||
|
const relativePath = filePath.replace('@project://', '')
|
||||||
|
filePath = path.join(process.cwd(), relativePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取角色文件内容
|
// 读取角色文件内容
|
||||||
|
|||||||
@ -26,23 +26,25 @@ class HelloCommand extends BasePouchCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 从ResourceManager获取统一注册表
|
// 使用新的ResourceManager架构
|
||||||
const ResourceManager = require('../../resource/resourceManager')
|
const ResourceManager = require('../../resource/resourceManager')
|
||||||
const resourceManager = new ResourceManager()
|
const resourceManager = new ResourceManager()
|
||||||
await resourceManager.initialize() // 确保初始化完成
|
|
||||||
|
|
||||||
let registeredRoles = {}
|
// 加载统一注册表(包含系统+用户资源)
|
||||||
if (resourceManager.registry && resourceManager.registry.protocols && resourceManager.registry.protocols.role && resourceManager.registry.protocols.role.registry) {
|
const unifiedRegistry = await resourceManager.loadUnifiedRegistry()
|
||||||
registeredRoles = resourceManager.registry.protocols.role.registry
|
|
||||||
|
// 提取角色数据
|
||||||
|
const roleData = unifiedRegistry.role || {}
|
||||||
|
|
||||||
|
// 转换为HelloCommand期望的格式
|
||||||
|
this.roleRegistry = {}
|
||||||
|
for (const [roleId, roleInfo] of Object.entries(roleData)) {
|
||||||
|
this.roleRegistry[roleId] = {
|
||||||
|
file: roleInfo.file,
|
||||||
|
name: roleInfo.name || roleId,
|
||||||
|
description: this.extractDescription(roleInfo) || `${roleInfo.name || roleId}专业角色`,
|
||||||
|
source: roleInfo.source || 'unknown'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动态发现本地角色并合并
|
|
||||||
const discoveredRoles = await this.discoverLocalRoles()
|
|
||||||
|
|
||||||
// 合并注册表中的角色和动态发现的角色
|
|
||||||
this.roleRegistry = {
|
|
||||||
...registeredRoles,
|
|
||||||
...discoveredRoles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有任何角色,使用基础角色
|
// 如果没有任何角色,使用基础角色
|
||||||
@ -51,31 +53,21 @@ class HelloCommand extends BasePouchCommand {
|
|||||||
assistant: {
|
assistant: {
|
||||||
file: '@package://prompt/domain/assistant/assistant.role.md',
|
file: '@package://prompt/domain/assistant/assistant.role.md',
|
||||||
name: '🙋 智能助手',
|
name: '🙋 智能助手',
|
||||||
description: '通用助理角色,提供基础的助理服务和记忆支持'
|
description: '通用助理角色,提供基础的助理服务和记忆支持',
|
||||||
|
source: 'fallback'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('角色注册表加载失败,尝试动态发现:', error.message)
|
console.warn('角色注册表加载失败,使用基础角色:', error.message)
|
||||||
|
|
||||||
// fallback到动态发现
|
// 使用基础角色作为fallback
|
||||||
try {
|
|
||||||
const discoveredRoles = await this.discoverLocalRoles()
|
|
||||||
this.roleRegistry = Object.keys(discoveredRoles).length > 0 ? discoveredRoles : {
|
|
||||||
assistant: {
|
|
||||||
file: '@package://prompt/domain/assistant/assistant.role.md',
|
|
||||||
name: '🙋 智能助手',
|
|
||||||
description: '通用助理角色,提供基础的助理服务和记忆支持'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (discoveryError) {
|
|
||||||
console.warn('动态角色发现也失败了:', discoveryError.message)
|
|
||||||
this.roleRegistry = {
|
this.roleRegistry = {
|
||||||
assistant: {
|
assistant: {
|
||||||
file: '@package://prompt/domain/assistant/assistant.role.md',
|
file: '@package://prompt/domain/assistant/assistant.role.md',
|
||||||
name: '🙋 智能助手',
|
name: '🙋 智能助手',
|
||||||
description: '通用助理角色,提供基础的助理服务和记忆支持'
|
description: '通用助理角色,提供基础的助理服务和记忆支持',
|
||||||
}
|
source: 'fallback'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,6 +75,21 @@ class HelloCommand extends BasePouchCommand {
|
|||||||
return this.roleRegistry
|
return this.roleRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从角色信息中提取描述
|
||||||
|
* @param {Object} roleInfo - 角色信息对象
|
||||||
|
* @returns {string} 角色描述
|
||||||
|
*/
|
||||||
|
extractDescription(roleInfo) {
|
||||||
|
// 尝试从不同字段提取描述
|
||||||
|
if (roleInfo.description) {
|
||||||
|
return roleInfo.description
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有更多元数据,可以在这里扩展提取逻辑
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有角色列表(转换为数组格式)
|
* 获取所有角色列表(转换为数组格式)
|
||||||
*/
|
*/
|
||||||
@ -92,10 +99,29 @@ class HelloCommand extends BasePouchCommand {
|
|||||||
id,
|
id,
|
||||||
name: roleInfo.name,
|
name: roleInfo.name,
|
||||||
description: roleInfo.description,
|
description: roleInfo.description,
|
||||||
file: roleInfo.file
|
file: roleInfo.file,
|
||||||
|
source: roleInfo.source
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取来源标签
|
||||||
|
* @param {string} source - 资源来源
|
||||||
|
* @returns {string} 来源标签
|
||||||
|
*/
|
||||||
|
getSourceLabel(source) {
|
||||||
|
switch (source) {
|
||||||
|
case 'user-generated':
|
||||||
|
return '(用户生成)'
|
||||||
|
case 'system':
|
||||||
|
return '(系统角色)'
|
||||||
|
case 'fallback':
|
||||||
|
return '(默认角色)'
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getContent (args) {
|
async getContent (args) {
|
||||||
await this.loadRoleRegistry()
|
await this.loadRoleRegistry()
|
||||||
const allRoles = await this.getAllRoles()
|
const allRoles = await this.getAllRoles()
|
||||||
@ -111,7 +137,8 @@ class HelloCommand extends BasePouchCommand {
|
|||||||
|
|
||||||
// 清楚显示角色ID和激活命令
|
// 清楚显示角色ID和激活命令
|
||||||
allRoles.forEach((role, index) => {
|
allRoles.forEach((role, index) => {
|
||||||
content += `### ${index + 1}. ${role.name}
|
const sourceLabel = this.getSourceLabel(role.source)
|
||||||
|
content += `### ${index + 1}. ${role.name} ${sourceLabel}
|
||||||
**角色ID**: \`${role.id}\`
|
**角色ID**: \`${role.id}\`
|
||||||
**专业能力**: ${role.description}
|
**专业能力**: ${role.description}
|
||||||
**激活命令**: \`${buildCommand.action(role.id)}\`
|
**激活命令**: \`${buildCommand.action(role.id)}\`
|
||||||
|
|||||||
@ -11,6 +11,16 @@ const ProjectProtocol = require('./protocols/ProjectProtocol')
|
|||||||
const UserProtocol = require('./protocols/UserProtocol')
|
const UserProtocol = require('./protocols/UserProtocol')
|
||||||
const PromptProtocol = require('./protocols/PromptProtocol')
|
const PromptProtocol = require('./protocols/PromptProtocol')
|
||||||
|
|
||||||
|
// 常量定义
|
||||||
|
const USER_RESOURCE_DIR = '.promptx'
|
||||||
|
const RESOURCE_DOMAIN_PATH = ['resource', 'domain']
|
||||||
|
const SUPPORTED_RESOURCE_TYPES = ['role', 'thought', 'execution']
|
||||||
|
const DPML_TAGS = {
|
||||||
|
role: { start: '<role>', end: '</role>' },
|
||||||
|
thought: { start: '<thought>', end: '</thought>' },
|
||||||
|
execution: { start: '<execution>', end: '</execution>' }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资源管理器 - 统一管理各种协议的资源加载
|
* 资源管理器 - 统一管理各种协议的资源加载
|
||||||
*/
|
*/
|
||||||
@ -41,17 +51,95 @@ class ResourceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载统一资源注册表
|
* 加载统一资源注册表(合并系统和用户资源)
|
||||||
*/
|
*/
|
||||||
async loadUnifiedRegistry () {
|
async loadUnifiedRegistry () {
|
||||||
|
try {
|
||||||
|
// 加载系统资源注册表
|
||||||
const registryPath = path.resolve(__dirname, '../../../resource.registry.json')
|
const registryPath = path.resolve(__dirname, '../../../resource.registry.json')
|
||||||
|
|
||||||
if (!await fs.pathExists(registryPath)) {
|
if (!await fs.pathExists(registryPath)) {
|
||||||
throw new Error(`统一资源注册表文件不存在: ${registryPath}`)
|
throw new Error(`统一资源注册表文件不存在: ${registryPath}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const registryContent = await fs.readJSON(registryPath)
|
const systemRegistry = await fs.readJSON(registryPath)
|
||||||
this.registry = registryContent
|
|
||||||
|
// 发现用户资源
|
||||||
|
const userResources = await this.discoverUserResources()
|
||||||
|
|
||||||
|
// 从系统注册表中提取资源数据
|
||||||
|
const extractedSystemResources = {}
|
||||||
|
for (const resourceType of SUPPORTED_RESOURCE_TYPES) {
|
||||||
|
const protocolConfig = systemRegistry.protocols[resourceType]
|
||||||
|
if (protocolConfig && protocolConfig.registry) {
|
||||||
|
extractedSystemResources[resourceType] = protocolConfig.registry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并资源,用户资源覆盖系统资源
|
||||||
|
const mergedRegistry = { ...systemRegistry }
|
||||||
|
|
||||||
|
// 合并各种资源类型
|
||||||
|
for (const resourceType of SUPPORTED_RESOURCE_TYPES) {
|
||||||
|
// 确保有基础结构
|
||||||
|
if (!mergedRegistry[resourceType]) {
|
||||||
|
mergedRegistry[resourceType] = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先添加系统资源
|
||||||
|
if (extractedSystemResources[resourceType]) {
|
||||||
|
if (!mergedRegistry[resourceType]) mergedRegistry[resourceType] = {}
|
||||||
|
for (const [id, resourceInfo] of Object.entries(extractedSystemResources[resourceType])) {
|
||||||
|
mergedRegistry[resourceType][id] = {
|
||||||
|
...resourceInfo,
|
||||||
|
source: 'system'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 再添加用户资源(覆盖同名的系统资源)
|
||||||
|
if (userResources[resourceType]) {
|
||||||
|
for (const [id, resourceInfo] of Object.entries(userResources[resourceType])) {
|
||||||
|
let filePath = resourceInfo.file || resourceInfo
|
||||||
|
|
||||||
|
// 将绝对路径转换为@project://相对路径格式
|
||||||
|
if (path.isAbsolute(filePath)) {
|
||||||
|
// 简单的路径转换:去掉项目根目录前缀
|
||||||
|
const projectRoot = process.cwd()
|
||||||
|
if (filePath.startsWith(projectRoot)) {
|
||||||
|
const relativePath = path.relative(projectRoot, filePath)
|
||||||
|
filePath = `@project://${relativePath}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于role资源类型,需要保持对象格式以包含name和description
|
||||||
|
if (resourceType === 'role') {
|
||||||
|
mergedRegistry[resourceType][id] = {
|
||||||
|
file: filePath,
|
||||||
|
name: resourceInfo.name || id,
|
||||||
|
description: resourceInfo.description || `${resourceInfo.name || id}专业角色`,
|
||||||
|
source: 'user-generated',
|
||||||
|
format: resourceInfo.format,
|
||||||
|
type: resourceInfo.type
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 对于thought和execution,协议处理器期望的是文件路径字符串
|
||||||
|
if (!mergedRegistry[resourceType]) mergedRegistry[resourceType] = {}
|
||||||
|
mergedRegistry[resourceType][id] = filePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.registry = mergedRegistry
|
||||||
|
return mergedRegistry
|
||||||
|
} catch (error) {
|
||||||
|
// 如果加载失败,至少返回一个基本结构
|
||||||
|
logger.warn(`加载统一注册表失败: ${error.message}`)
|
||||||
|
const fallbackRegistry = { role: {} }
|
||||||
|
this.registry = fallbackRegistry
|
||||||
|
return fallbackRegistry
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -204,6 +292,178 @@ class ResourceManager {
|
|||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发现用户资源
|
||||||
|
* @returns {Promise<Object>} 用户资源注册表
|
||||||
|
*/
|
||||||
|
async discoverUserResources() {
|
||||||
|
try {
|
||||||
|
const PackageProtocol = require('./protocols/PackageProtocol')
|
||||||
|
const packageProtocol = new PackageProtocol()
|
||||||
|
const packageRoot = await packageProtocol.getPackageRoot()
|
||||||
|
|
||||||
|
const userResourcePath = path.join(packageRoot, USER_RESOURCE_DIR, ...RESOURCE_DOMAIN_PATH)
|
||||||
|
|
||||||
|
// 检查用户资源目录是否存在
|
||||||
|
if (!await fs.pathExists(userResourcePath)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.scanResourceDirectory(userResourcePath)
|
||||||
|
} catch (error) {
|
||||||
|
// 出错时返回空对象,不抛出异常
|
||||||
|
logger.warn(`用户资源发现失败: ${error.message}`)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫描资源目录
|
||||||
|
* @param {string} basePath - 基础路径
|
||||||
|
* @returns {Promise<Object>} 发现的资源
|
||||||
|
*/
|
||||||
|
async scanResourceDirectory(basePath) {
|
||||||
|
const resources = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const directories = await fs.readdir(basePath)
|
||||||
|
|
||||||
|
for (const roleDir of directories) {
|
||||||
|
const rolePath = path.join(basePath, roleDir)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stat = await fs.stat(rolePath)
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
// 扫描角色文件
|
||||||
|
await this.scanRoleResources(rolePath, roleDir, resources)
|
||||||
|
|
||||||
|
// 扫描其他资源类型(thought, execution)
|
||||||
|
await this.scanOtherResources(rolePath, roleDir, resources)
|
||||||
|
}
|
||||||
|
} catch (dirError) {
|
||||||
|
// 跳过无法访问的目录
|
||||||
|
logger.debug(`跳过目录 ${roleDir}: ${dirError.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`扫描资源目录失败 ${basePath}: ${error.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫描角色资源
|
||||||
|
* @param {string} rolePath - 角色目录路径
|
||||||
|
* @param {string} roleId - 角色ID
|
||||||
|
* @param {Object} resources - 资源容器
|
||||||
|
*/
|
||||||
|
async scanRoleResources(rolePath, roleId, resources) {
|
||||||
|
const roleFile = path.join(rolePath, `${roleId}.role.md`)
|
||||||
|
|
||||||
|
if (await fs.pathExists(roleFile)) {
|
||||||
|
try {
|
||||||
|
const content = await fs.readFile(roleFile, 'utf8')
|
||||||
|
|
||||||
|
// 验证DPML格式
|
||||||
|
if (this.validateDPMLFormat(content, 'role')) {
|
||||||
|
const name = this.extractRoleName(content)
|
||||||
|
|
||||||
|
if (!resources.role) resources.role = {}
|
||||||
|
resources.role[roleId] = {
|
||||||
|
file: roleFile,
|
||||||
|
name: name || roleId,
|
||||||
|
source: 'user-generated',
|
||||||
|
format: 'dpml',
|
||||||
|
type: 'role'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略单个文件的错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫描其他资源类型
|
||||||
|
* @param {string} rolePath - 角色目录路径
|
||||||
|
* @param {string} roleId - 角色ID
|
||||||
|
* @param {Object} resources - 资源容器
|
||||||
|
*/
|
||||||
|
async scanOtherResources(rolePath, roleId, resources) {
|
||||||
|
for (const resourceType of SUPPORTED_RESOURCE_TYPES.filter(type => type !== 'role')) {
|
||||||
|
const resourceDir = path.join(rolePath, resourceType)
|
||||||
|
|
||||||
|
if (await fs.pathExists(resourceDir)) {
|
||||||
|
try {
|
||||||
|
const files = await fs.readdir(resourceDir)
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.endsWith(`.${resourceType}.md`)) {
|
||||||
|
const resourceName = file.replace(`.${resourceType}.md`, '')
|
||||||
|
const filePath = path.join(resourceDir, file)
|
||||||
|
const content = await fs.readFile(filePath, 'utf8')
|
||||||
|
|
||||||
|
if (this.validateDPMLFormat(content, resourceType)) {
|
||||||
|
if (!resources[resourceType]) resources[resourceType] = {}
|
||||||
|
resources[resourceType][resourceName] = {
|
||||||
|
file: filePath,
|
||||||
|
name: resourceName,
|
||||||
|
source: 'user-generated',
|
||||||
|
format: 'dpml',
|
||||||
|
type: resourceType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug(`扫描${resourceType}资源失败: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证DPML格式
|
||||||
|
* @param {string} content - 文件内容
|
||||||
|
* @param {string} type - 资源类型
|
||||||
|
* @returns {boolean} 是否为有效格式
|
||||||
|
*/
|
||||||
|
validateDPMLFormat(content, type) {
|
||||||
|
const tags = DPML_TAGS[type]
|
||||||
|
if (!tags) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return content.includes(tags.start) && content.includes(tags.end)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从角色内容中提取名称
|
||||||
|
* @param {string} content - 角色文件内容
|
||||||
|
* @returns {string} 角色名称
|
||||||
|
*/
|
||||||
|
extractRoleName(content) {
|
||||||
|
// 简单的名称提取逻辑
|
||||||
|
const match = content.match(/#\s*([^\n]+)/)
|
||||||
|
return match ? match[1].trim() : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载系统资源注册表(兼容现有方法)
|
||||||
|
* @returns {Promise<Object>} 系统资源注册表
|
||||||
|
*/
|
||||||
|
async loadSystemRegistry() {
|
||||||
|
const registryPath = path.resolve(__dirname, '../../../resource.registry.json')
|
||||||
|
|
||||||
|
if (!await fs.pathExists(registryPath)) {
|
||||||
|
throw new Error(`统一资源注册表文件不存在: ${registryPath}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await fs.readJSON(registryPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ResourceManager
|
module.exports = ResourceManager
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"assistant": "@package://prompt/domain/assistant/thought/assistant.thought.md",
|
"assistant": "@package://prompt/domain/assistant/thought/assistant.thought.md",
|
||||||
"remember": "@package://prompt/core/thought/remember.thought.md",
|
"remember": "@package://prompt/core/thought/remember.thought.md",
|
||||||
"recall": "@package://prompt/core/thought/recall.thought.md",
|
"recall": "@package://prompt/core/thought/recall.thought.md",
|
||||||
|
"role-creation": "@package://prompt/core/thought/role-creation.thought.md",
|
||||||
"product-manager": "@package://prompt/domain/product-manager/thought/product-manager.thought.md",
|
"product-manager": "@package://prompt/domain/product-manager/thought/product-manager.thought.md",
|
||||||
"java-backend-developer": "@package://prompt/domain/java-backend-developer/thought/java-backend-developer.thought.md"
|
"java-backend-developer": "@package://prompt/domain/java-backend-developer/thought/java-backend-developer.thought.md"
|
||||||
}
|
}
|
||||||
@ -33,7 +34,14 @@
|
|||||||
"system-architecture": "@package://prompt/domain/java-backend-developer/execution/system-architecture.execution.md",
|
"system-architecture": "@package://prompt/domain/java-backend-developer/execution/system-architecture.execution.md",
|
||||||
"spring-ecosystem": "@package://prompt/domain/java-backend-developer/execution/spring-ecosystem.execution.md",
|
"spring-ecosystem": "@package://prompt/domain/java-backend-developer/execution/spring-ecosystem.execution.md",
|
||||||
"code-quality": "@package://prompt/domain/java-backend-developer/execution/code-quality.execution.md",
|
"code-quality": "@package://prompt/domain/java-backend-developer/execution/code-quality.execution.md",
|
||||||
"database-design": "@package://prompt/domain/java-backend-developer/execution/database-design.execution.md"
|
"database-design": "@package://prompt/domain/java-backend-developer/execution/database-design.execution.md",
|
||||||
|
"role-generation": "@package://prompt/core/execution/role-generation.execution.md",
|
||||||
|
"execution-authoring": "@package://prompt/core/execution/execution-authoring.execution.md",
|
||||||
|
"thought-authoring": "@package://prompt/core/execution/thought-authoring.execution.md",
|
||||||
|
"role-authoring": "@package://prompt/core/execution/role-authoring.execution.md",
|
||||||
|
"resource-authoring": "@package://prompt/core/execution/resource-authoring.execution.md",
|
||||||
|
|
||||||
|
"wechat-miniprogram-development": "@package://prompt/domain/frontend-developer/execution/wechat-miniprogram-development.execution.md"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
@ -78,6 +86,11 @@
|
|||||||
"name": "☕ Java后端开发者",
|
"name": "☕ Java后端开发者",
|
||||||
"description": "专业Java后端开发专家,精通Spring生态系统、微服务架构和系统设计"
|
"description": "专业Java后端开发专家,精通Spring生态系统、微服务架构和系统设计"
|
||||||
},
|
},
|
||||||
|
"nuwa": {
|
||||||
|
"file": "@package://prompt/core/nuwa/nuwa.role.md",
|
||||||
|
"name": "🎨 女娲",
|
||||||
|
"description": "专业角色创造顾问,通过对话收集需求,为用户量身定制AI助手角色"
|
||||||
|
},
|
||||||
"test-role": {
|
"test-role": {
|
||||||
"file": "@package://prompt/domain/test-role/test-role.role.md",
|
"file": "@package://prompt/domain/test-role/test-role.role.md",
|
||||||
"name": "🧪 测试角色",
|
"name": "🧪 测试角色",
|
||||||
|
|||||||
221
src/tests/commands/CrossPlatformDiscovery.unit.test.js
Normal file
221
src/tests/commands/CrossPlatformDiscovery.unit.test.js
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
const os = require('os')
|
||||||
|
|
||||||
|
describe('跨平台角色发现兼容性测试', () => {
|
||||||
|
let tempDir
|
||||||
|
let projectDir
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'cross-platform-test-'))
|
||||||
|
projectDir = path.join(tempDir, 'test-project')
|
||||||
|
|
||||||
|
await fs.ensureDir(path.join(projectDir, 'prompt', 'domain'))
|
||||||
|
await fs.ensureDir(path.join(projectDir, '.promptx', 'user-roles'))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
if (tempDir) {
|
||||||
|
await fs.remove(tempDir)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Node.js 原生 API 替代 glob', () => {
|
||||||
|
test('应该能使用 fs.readdir 代替 glob.sync', async () => {
|
||||||
|
// 创建测试角色文件
|
||||||
|
const roleDir = path.join(projectDir, 'prompt', 'domain', 'test-role')
|
||||||
|
await fs.ensureDir(roleDir)
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(roleDir, 'test-role.role.md'),
|
||||||
|
'<role><personality>测试</personality></role>'
|
||||||
|
)
|
||||||
|
|
||||||
|
// 使用Node.js原生API实现角色发现(替代glob)
|
||||||
|
async function discoverRolesWithNativeAPI(scanPath) {
|
||||||
|
const discoveredRoles = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (await fs.pathExists(scanPath)) {
|
||||||
|
const domains = await fs.readdir(scanPath)
|
||||||
|
|
||||||
|
for (const domain of domains) {
|
||||||
|
const domainDir = path.join(scanPath, domain)
|
||||||
|
const stat = await fs.stat(domainDir)
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const roleFile = path.join(domainDir, `${domain}.role.md`)
|
||||||
|
if (await fs.pathExists(roleFile)) {
|
||||||
|
const content = await fs.readFile(roleFile, 'utf-8')
|
||||||
|
|
||||||
|
discoveredRoles[domain] = {
|
||||||
|
file: roleFile,
|
||||||
|
name: `🎭 ${domain}`,
|
||||||
|
description: '原生API发现的角色',
|
||||||
|
source: 'native-api'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return discoveredRoles
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('原生API角色发现失败:', error.message)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainPath = path.join(projectDir, 'prompt', 'domain')
|
||||||
|
const discoveredRoles = await discoverRolesWithNativeAPI(domainPath)
|
||||||
|
|
||||||
|
expect(discoveredRoles).toHaveProperty('test-role')
|
||||||
|
expect(discoveredRoles['test-role'].source).toBe('native-api')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('应该能处理不同平台的路径分隔符', () => {
|
||||||
|
const unixPath = 'prompt/domain/role/role.role.md'
|
||||||
|
const windowsPath = 'prompt\\domain\\role\\role.role.md'
|
||||||
|
|
||||||
|
// 使用path.join确保跨平台兼容性
|
||||||
|
const normalizedPath = path.join('prompt', 'domain', 'role', 'role.role.md')
|
||||||
|
|
||||||
|
// 在当前平台上验证路径处理
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
expect(normalizedPath).toContain('\\')
|
||||||
|
} else {
|
||||||
|
expect(normalizedPath).toContain('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
// path.relative应该也能正常工作
|
||||||
|
const relativePath = path.relative(projectDir, path.join(projectDir, normalizedPath))
|
||||||
|
expect(relativePath).toBe(normalizedPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('应该处理路径中的特殊字符', async () => {
|
||||||
|
// 创建包含特殊字符的角色名(但符合文件系统要求)
|
||||||
|
const specialRoleName = 'role-with_special.chars'
|
||||||
|
const roleDir = path.join(projectDir, 'prompt', 'domain', specialRoleName)
|
||||||
|
await fs.ensureDir(roleDir)
|
||||||
|
|
||||||
|
const roleFile = path.join(roleDir, `${specialRoleName}.role.md`)
|
||||||
|
await fs.writeFile(roleFile, '<role><personality>特殊角色</personality></role>')
|
||||||
|
|
||||||
|
// 验证能正确处理特殊字符的文件名
|
||||||
|
expect(await fs.pathExists(roleFile)).toBe(true)
|
||||||
|
|
||||||
|
const content = await fs.readFile(roleFile, 'utf-8')
|
||||||
|
expect(content).toContain('特殊角色')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('文件系统权限处理', () => {
|
||||||
|
test('应该优雅处理无权限访问的目录', async () => {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
// Windows权限测试较为复杂,跳过
|
||||||
|
expect(true).toBe(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const restrictedDir = path.join(projectDir, 'restricted')
|
||||||
|
await fs.ensureDir(restrictedDir)
|
||||||
|
|
||||||
|
// 移除读权限
|
||||||
|
await fs.chmod(restrictedDir, 0o000)
|
||||||
|
|
||||||
|
// 角色发现应该不会因为权限问题而崩溃
|
||||||
|
async function safeDiscoverRoles(scanPath) {
|
||||||
|
try {
|
||||||
|
if (await fs.pathExists(scanPath)) {
|
||||||
|
const domains = await fs.readdir(scanPath)
|
||||||
|
return domains
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
} catch (error) {
|
||||||
|
// 应该优雅处理权限错误
|
||||||
|
console.warn('权限不足,跳过目录:', scanPath)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await safeDiscoverRoles(restrictedDir)
|
||||||
|
expect(Array.isArray(result)).toBe(true)
|
||||||
|
|
||||||
|
// 恢复权限以便清理
|
||||||
|
await fs.chmod(restrictedDir, 0o755)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('错误恢复机制', () => {
|
||||||
|
test('应该在部分文件失败时继续处理其他文件', async () => {
|
||||||
|
// 创建多个角色,其中一个有问题
|
||||||
|
const goodRoleDir = path.join(projectDir, 'prompt', 'domain', 'good-role')
|
||||||
|
await fs.ensureDir(goodRoleDir)
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(goodRoleDir, 'good-role.role.md'),
|
||||||
|
'<role><personality>正常角色</personality></role>'
|
||||||
|
)
|
||||||
|
|
||||||
|
const badRoleDir = path.join(projectDir, 'prompt', 'domain', 'bad-role')
|
||||||
|
await fs.ensureDir(badRoleDir)
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(badRoleDir, 'bad-role.role.md'),
|
||||||
|
'无效内容'
|
||||||
|
)
|
||||||
|
|
||||||
|
// 模拟容错的角色发现实现
|
||||||
|
async function resilientDiscoverRoles(scanPath) {
|
||||||
|
const discoveredRoles = {}
|
||||||
|
const errors = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (await fs.pathExists(scanPath)) {
|
||||||
|
const domains = await fs.readdir(scanPath)
|
||||||
|
|
||||||
|
for (const domain of domains) {
|
||||||
|
try {
|
||||||
|
const domainDir = path.join(scanPath, domain)
|
||||||
|
const stat = await fs.stat(domainDir)
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const roleFile = path.join(domainDir, `${domain}.role.md`)
|
||||||
|
if (await fs.pathExists(roleFile)) {
|
||||||
|
const content = await fs.readFile(roleFile, 'utf-8')
|
||||||
|
|
||||||
|
// 简单验证内容
|
||||||
|
if (content.includes('<role>')) {
|
||||||
|
discoveredRoles[domain] = {
|
||||||
|
file: roleFile,
|
||||||
|
name: `🎭 ${domain}`,
|
||||||
|
description: '容错发现的角色',
|
||||||
|
source: 'resilient-discovery'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('无效的角色文件格式')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 记录错误但继续处理其他文件
|
||||||
|
errors.push({ domain, error: error.message })
|
||||||
|
console.warn(`跳过无效角色 ${domain}:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('角色发现过程中出错:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { discoveredRoles, errors }
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainPath = path.join(projectDir, 'prompt', 'domain')
|
||||||
|
const result = await resilientDiscoverRoles(domainPath)
|
||||||
|
|
||||||
|
// 应该发现正常角色,跳过问题角色
|
||||||
|
expect(result.discoveredRoles).toHaveProperty('good-role')
|
||||||
|
expect(result.discoveredRoles).not.toHaveProperty('bad-role')
|
||||||
|
expect(result.errors).toHaveLength(1)
|
||||||
|
expect(result.errors[0].domain).toBe('bad-role')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
231
src/tests/commands/HelloCommand.integration.test.js
Normal file
231
src/tests/commands/HelloCommand.integration.test.js
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
const HelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
const ResourceManager = require('../../lib/core/resource/resourceManager')
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
const path = require('path')
|
||||||
|
const os = require('os')
|
||||||
|
|
||||||
|
describe('HelloCommand - ResourceManager集成', () => {
|
||||||
|
let helloCommand
|
||||||
|
let tempDir
|
||||||
|
let mockPackageRoot
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// 创建临时测试目录
|
||||||
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-hello-test-'))
|
||||||
|
mockPackageRoot = tempDir
|
||||||
|
|
||||||
|
// 模拟用户资源目录结构
|
||||||
|
await fs.ensureDir(path.join(tempDir, '.promptx', 'resource', 'domain'))
|
||||||
|
|
||||||
|
helloCommand = new HelloCommand()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
// 清理临时目录
|
||||||
|
await fs.remove(tempDir)
|
||||||
|
jest.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('用户角色发现集成', () => {
|
||||||
|
it('应该显示用户创建的角色', async () => {
|
||||||
|
// 创建测试用户角色
|
||||||
|
const roleDir = path.join(tempDir, '.promptx', 'resource', 'domain', 'sales-expert')
|
||||||
|
await fs.ensureDir(roleDir)
|
||||||
|
|
||||||
|
const roleContent = `<role>
|
||||||
|
<personality>
|
||||||
|
# 销售专家思维模式
|
||||||
|
## 核心特征
|
||||||
|
- **客户导向思维**:始终以客户需求为出发点
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
# 销售专家行为原则
|
||||||
|
## 核心原则
|
||||||
|
- **诚信为本**:建立长期客户关系
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
# 销售专业知识体系
|
||||||
|
## 销售技巧
|
||||||
|
- **需求挖掘**:深度了解客户真实需求
|
||||||
|
</knowledge>
|
||||||
|
</role>`
|
||||||
|
|
||||||
|
await fs.writeFile(path.join(roleDir, 'sales-expert.role.md'), roleContent)
|
||||||
|
|
||||||
|
// Mock ResourceManager的loadUnifiedRegistry方法
|
||||||
|
jest.spyOn(ResourceManager.prototype, 'loadUnifiedRegistry')
|
||||||
|
.mockResolvedValue({
|
||||||
|
role: {
|
||||||
|
'assistant': {
|
||||||
|
file: '@package://prompt/domain/assistant/assistant.role.md',
|
||||||
|
name: '🙋 智能助手',
|
||||||
|
source: 'system',
|
||||||
|
format: 'dpml',
|
||||||
|
type: 'role'
|
||||||
|
},
|
||||||
|
'sales-expert': {
|
||||||
|
file: path.join(roleDir, 'sales-expert.role.md'),
|
||||||
|
name: '销售专家思维模式',
|
||||||
|
source: 'user-generated',
|
||||||
|
format: 'dpml',
|
||||||
|
type: 'role'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模拟执行hello命令
|
||||||
|
const result = await helloCommand.execute([])
|
||||||
|
|
||||||
|
// 验证用户角色在输出中显示
|
||||||
|
const allOutput = result.content || ''
|
||||||
|
|
||||||
|
expect(allOutput).toContain('sales-expert')
|
||||||
|
expect(allOutput).toContain('销售专家')
|
||||||
|
expect(allOutput).toContain('(用户生成)')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该允许用户角色覆盖系统角色', async () => {
|
||||||
|
// 创建与系统角色同名的用户角色
|
||||||
|
const roleDir = path.join(tempDir, '.promptx', 'resource', 'domain', 'assistant')
|
||||||
|
await fs.ensureDir(roleDir)
|
||||||
|
|
||||||
|
const customAssistantContent = `<role>
|
||||||
|
<personality>
|
||||||
|
# 定制智能助手
|
||||||
|
## 个性化特征
|
||||||
|
- **专业导向**:专注于技术问题解决
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
# 定制助手原则
|
||||||
|
## 核心原则
|
||||||
|
- **精准回答**:提供准确的技术解决方案
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
# 定制助手知识体系
|
||||||
|
## 技术领域
|
||||||
|
- **编程语言**:多种编程语言的深度理解
|
||||||
|
</knowledge>
|
||||||
|
</role>`
|
||||||
|
|
||||||
|
await fs.writeFile(path.join(roleDir, 'assistant.role.md'), customAssistantContent)
|
||||||
|
|
||||||
|
// Mock ResourceManager返回用户覆盖的角色
|
||||||
|
jest.spyOn(ResourceManager.prototype, 'loadUnifiedRegistry')
|
||||||
|
.mockResolvedValue({
|
||||||
|
role: {
|
||||||
|
'assistant': {
|
||||||
|
file: path.join(roleDir, 'assistant.role.md'),
|
||||||
|
name: '定制智能助手',
|
||||||
|
source: 'user-generated',
|
||||||
|
format: 'dpml',
|
||||||
|
type: 'role'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await helloCommand.execute([])
|
||||||
|
|
||||||
|
const allOutput = result.content || ''
|
||||||
|
|
||||||
|
// 验证显示的是用户版本
|
||||||
|
expect(allOutput).toContain('定制智能助手')
|
||||||
|
expect(allOutput).toContain('(用户生成)')
|
||||||
|
expect(allOutput).not.toContain('🙋 智能助手')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该同时显示系统角色和用户角色', async () => {
|
||||||
|
// 创建用户角色
|
||||||
|
const userRoleDir = path.join(tempDir, '.promptx', 'resource', 'domain', 'data-analyst')
|
||||||
|
await fs.ensureDir(userRoleDir)
|
||||||
|
|
||||||
|
const userRoleContent = `<role>
|
||||||
|
<personality>
|
||||||
|
# 数据分析师
|
||||||
|
## 分析思维
|
||||||
|
- **逻辑思维**:系统性分析数据模式
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
# 分析原则
|
||||||
|
## 核心原则
|
||||||
|
- **数据驱动**:基于数据做决策
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
# 分析知识
|
||||||
|
## 统计学
|
||||||
|
- **描述统计**:数据的基本特征分析
|
||||||
|
</knowledge>
|
||||||
|
</role>`
|
||||||
|
|
||||||
|
await fs.writeFile(path.join(userRoleDir, 'data-analyst.role.md'), userRoleContent)
|
||||||
|
|
||||||
|
// Mock ResourceManager返回系统和用户角色
|
||||||
|
jest.spyOn(ResourceManager.prototype, 'loadUnifiedRegistry')
|
||||||
|
.mockResolvedValue({
|
||||||
|
role: {
|
||||||
|
'assistant': {
|
||||||
|
file: '@package://prompt/domain/assistant/assistant.role.md',
|
||||||
|
name: '🙋 智能助手',
|
||||||
|
source: 'system',
|
||||||
|
format: 'dpml',
|
||||||
|
type: 'role'
|
||||||
|
},
|
||||||
|
'java-backend-developer': {
|
||||||
|
file: '@package://prompt/domain/java-backend-developer/java-backend-developer.role.md',
|
||||||
|
name: '☕ Java后端开发专家',
|
||||||
|
source: 'system',
|
||||||
|
format: 'dpml',
|
||||||
|
type: 'role'
|
||||||
|
},
|
||||||
|
'data-analyst': {
|
||||||
|
file: path.join(userRoleDir, 'data-analyst.role.md'),
|
||||||
|
name: '数据分析师',
|
||||||
|
source: 'user-generated',
|
||||||
|
format: 'dpml',
|
||||||
|
type: 'role'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await helloCommand.execute([])
|
||||||
|
|
||||||
|
const allOutput = result.content || ''
|
||||||
|
|
||||||
|
// 验证系统角色和用户角色都显示
|
||||||
|
expect(allOutput).toContain('智能助手')
|
||||||
|
expect(allOutput).toContain('Java后端开发专家')
|
||||||
|
expect(allOutput).toContain('数据分析师')
|
||||||
|
expect(allOutput).toContain('data-analyst')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('错误处理', () => {
|
||||||
|
it('应该优雅处理资源发现失败', async () => {
|
||||||
|
// 模拟ResourceManager错误
|
||||||
|
jest.spyOn(ResourceManager.prototype, 'loadUnifiedRegistry')
|
||||||
|
.mockRejectedValue(new Error('资源发现失败'))
|
||||||
|
|
||||||
|
// 应该不抛出异常
|
||||||
|
const result = await helloCommand.execute([])
|
||||||
|
|
||||||
|
// 应该显示基础角色(fallback)
|
||||||
|
expect(result.content).toContain('智能助手')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该处理空的资源注册表', async () => {
|
||||||
|
// Mock空的资源注册表
|
||||||
|
jest.spyOn(ResourceManager.prototype, 'loadUnifiedRegistry')
|
||||||
|
.mockResolvedValue({ role: {} })
|
||||||
|
|
||||||
|
const result = await helloCommand.execute([])
|
||||||
|
|
||||||
|
// 应该显示基础角色(fallback)
|
||||||
|
expect(result.content).toContain('智能助手')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
323
src/tests/commands/HelloCommand.unit.test.js
Normal file
323
src/tests/commands/HelloCommand.unit.test.js
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
const os = require('os')
|
||||||
|
const HelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
|
||||||
|
describe('HelloCommand 单元测试', () => {
|
||||||
|
let helloCommand
|
||||||
|
let tempDir
|
||||||
|
let tempProjectDir
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
helloCommand = new HelloCommand()
|
||||||
|
|
||||||
|
// 创建临时目录模拟项目结构
|
||||||
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hello-command-test-'))
|
||||||
|
tempProjectDir = path.join(tempDir, 'test-project')
|
||||||
|
|
||||||
|
// 创建基础目录结构
|
||||||
|
await fs.ensureDir(path.join(tempProjectDir, 'prompt', 'domain'))
|
||||||
|
await fs.ensureDir(path.join(tempProjectDir, '.promptx', 'user-roles'))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
if (tempDir) {
|
||||||
|
await fs.remove(tempDir)
|
||||||
|
}
|
||||||
|
// 清理缓存
|
||||||
|
if (helloCommand.roleRegistry) {
|
||||||
|
helloCommand.roleRegistry = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('基础功能测试', () => {
|
||||||
|
test('应该能实例化HelloCommand', () => {
|
||||||
|
expect(helloCommand).toBeInstanceOf(HelloCommand)
|
||||||
|
expect(typeof helloCommand.discoverLocalRoles).toBe('function')
|
||||||
|
expect(typeof helloCommand.loadRoleRegistry).toBe('function')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('getPurpose应该返回正确的目的描述', () => {
|
||||||
|
const purpose = helloCommand.getPurpose()
|
||||||
|
expect(purpose).toContain('AI')
|
||||||
|
expect(purpose).toContain('角色')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('discoverLocalRoles 功能测试', () => {
|
||||||
|
test('应该能发现系统内置角色', async () => {
|
||||||
|
// 创建模拟的系统角色文件
|
||||||
|
const assistantDir = path.join(tempProjectDir, 'prompt', 'domain', 'assistant')
|
||||||
|
await fs.ensureDir(assistantDir)
|
||||||
|
|
||||||
|
const roleFileContent = `<!--
|
||||||
|
name: 🙋 智能助手
|
||||||
|
description: 通用助理角色,提供基础的助理服务和记忆支持
|
||||||
|
-->
|
||||||
|
|
||||||
|
<role>
|
||||||
|
<personality>
|
||||||
|
@!thought://remember
|
||||||
|
@!thought://recall
|
||||||
|
@!thought://assistant
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
@!execution://assistant
|
||||||
|
</principle>
|
||||||
|
</role>`
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(assistantDir, 'assistant.role.md'),
|
||||||
|
roleFileContent
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock PackageProtocol.getPackageRoot 返回临时目录
|
||||||
|
const originalRequire = require
|
||||||
|
jest.doMock('../../lib/core/resource/protocols/PackageProtocol', () => {
|
||||||
|
return class MockPackageProtocol {
|
||||||
|
async getPackageRoot() {
|
||||||
|
return tempProjectDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 重新加载HelloCommand使用mock
|
||||||
|
delete require.cache[require.resolve('../../lib/core/pouch/commands/HelloCommand')]
|
||||||
|
const MockedHelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
const mockedCommand = new MockedHelloCommand()
|
||||||
|
|
||||||
|
const discoveredRoles = await mockedCommand.discoverLocalRoles()
|
||||||
|
|
||||||
|
expect(discoveredRoles).toHaveProperty('assistant')
|
||||||
|
expect(discoveredRoles.assistant.name).toContain('智能助手')
|
||||||
|
expect(discoveredRoles.assistant.description).toContain('通用助理角色')
|
||||||
|
expect(discoveredRoles.assistant.source).toBe('local-discovery')
|
||||||
|
|
||||||
|
// 恢复原始require
|
||||||
|
jest.unmock('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('应该处理空的角色目录', async () => {
|
||||||
|
// Mock PackageProtocol.getPackageRoot 返回空目录
|
||||||
|
jest.doMock('../../lib/core/resource/protocols/PackageProtocol', () => {
|
||||||
|
return class MockPackageProtocol {
|
||||||
|
async getPackageRoot() {
|
||||||
|
return tempProjectDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../../lib/core/pouch/commands/HelloCommand')]
|
||||||
|
const MockedHelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
const mockedCommand = new MockedHelloCommand()
|
||||||
|
|
||||||
|
const discoveredRoles = await mockedCommand.discoverLocalRoles()
|
||||||
|
expect(discoveredRoles).toEqual({})
|
||||||
|
|
||||||
|
jest.unmock('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('应该优雅处理文件读取错误', async () => {
|
||||||
|
// 创建无效的角色文件(权限问题)
|
||||||
|
const invalidRoleDir = path.join(tempProjectDir, 'prompt', 'domain', 'invalid')
|
||||||
|
await fs.ensureDir(invalidRoleDir)
|
||||||
|
|
||||||
|
const invalidRoleFile = path.join(invalidRoleDir, 'invalid.role.md')
|
||||||
|
await fs.writeFile(invalidRoleFile, 'invalid content')
|
||||||
|
|
||||||
|
// 修改文件权限使其不可读(仅在Unix系统上有效)
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
await fs.chmod(invalidRoleFile, 0o000)
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.doMock('../../lib/core/resource/protocols/PackageProtocol', () => {
|
||||||
|
return class MockPackageProtocol {
|
||||||
|
async getPackageRoot() {
|
||||||
|
return tempProjectDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../../lib/core/pouch/commands/HelloCommand')]
|
||||||
|
const MockedHelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
const mockedCommand = new MockedHelloCommand()
|
||||||
|
|
||||||
|
// 应该不抛出异常,而是记录警告并跳过无效文件
|
||||||
|
const discoveredRoles = await mockedCommand.discoverLocalRoles()
|
||||||
|
expect(typeof discoveredRoles).toBe('object')
|
||||||
|
|
||||||
|
// 恢复文件权限
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
await fs.chmod(invalidRoleFile, 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.unmock('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('元数据提取测试', () => {
|
||||||
|
test('应该正确提取角色名称和描述', async () => {
|
||||||
|
const testRoleDir = path.join(tempProjectDir, 'prompt', 'domain', 'test-role')
|
||||||
|
await fs.ensureDir(testRoleDir)
|
||||||
|
|
||||||
|
const roleContent = `<!--
|
||||||
|
name: 🧪 测试角色
|
||||||
|
description: 这是一个测试用的角色
|
||||||
|
-->
|
||||||
|
|
||||||
|
<role>
|
||||||
|
<personality>
|
||||||
|
测试思维模式
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
测试行为原则
|
||||||
|
</principle>
|
||||||
|
</role>`
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(testRoleDir, 'test-role.role.md'),
|
||||||
|
roleContent
|
||||||
|
)
|
||||||
|
|
||||||
|
jest.doMock('../../lib/core/resource/protocols/PackageProtocol', () => {
|
||||||
|
return class MockPackageProtocol {
|
||||||
|
async getPackageRoot() {
|
||||||
|
return tempProjectDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../../lib/core/pouch/commands/HelloCommand')]
|
||||||
|
const MockedHelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
const mockedCommand = new MockedHelloCommand()
|
||||||
|
|
||||||
|
const discoveredRoles = await mockedCommand.discoverLocalRoles()
|
||||||
|
|
||||||
|
expect(discoveredRoles).toHaveProperty('test-role')
|
||||||
|
expect(discoveredRoles['test-role'].name).toBe('🧪 测试角色')
|
||||||
|
expect(discoveredRoles['test-role'].description).toBe('这是一个测试用的角色')
|
||||||
|
|
||||||
|
jest.unmock('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('应该处理缺少元数据的角色文件', async () => {
|
||||||
|
const testRoleDir = path.join(tempProjectDir, 'prompt', 'domain', 'no-meta')
|
||||||
|
await fs.ensureDir(testRoleDir)
|
||||||
|
|
||||||
|
const roleContent = `<role>
|
||||||
|
<personality>
|
||||||
|
基础角色内容
|
||||||
|
</personality>
|
||||||
|
</role>`
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(testRoleDir, 'no-meta.role.md'),
|
||||||
|
roleContent
|
||||||
|
)
|
||||||
|
|
||||||
|
jest.doMock('../../lib/core/resource/protocols/PackageProtocol', () => {
|
||||||
|
return class MockPackageProtocol {
|
||||||
|
async getPackageRoot() {
|
||||||
|
return tempProjectDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../../lib/core/pouch/commands/HelloCommand')]
|
||||||
|
const MockedHelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
const mockedCommand = new MockedHelloCommand()
|
||||||
|
|
||||||
|
const discoveredRoles = await mockedCommand.discoverLocalRoles()
|
||||||
|
|
||||||
|
expect(discoveredRoles).toHaveProperty('no-meta')
|
||||||
|
expect(discoveredRoles['no-meta'].name).toBe('🎭 no-meta') // 默认格式
|
||||||
|
expect(discoveredRoles['no-meta'].description).toBe('本地发现的角色') // 默认描述
|
||||||
|
|
||||||
|
jest.unmock('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('角色注册表加载测试', () => {
|
||||||
|
test('应该能加载角色注册表', async () => {
|
||||||
|
const result = await helloCommand.loadRoleRegistry()
|
||||||
|
|
||||||
|
expect(typeof result).toBe('object')
|
||||||
|
expect(helloCommand.roleRegistry).toBe(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('应该在失败时返回默认assistant角色', async () => {
|
||||||
|
// Mock ResourceManager抛出异常
|
||||||
|
jest.doMock('../../lib/core/resource/resourceManager', () => {
|
||||||
|
return class MockResourceManager {
|
||||||
|
async initialize() {
|
||||||
|
throw new Error('Mock initialization failure')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Mock discoverLocalRoles也失败
|
||||||
|
jest.spyOn(helloCommand, 'discoverLocalRoles').mockRejectedValue(new Error('Mock discovery failure'))
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../../lib/core/pouch/commands/HelloCommand')]
|
||||||
|
const MockedHelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
const mockedCommand = new MockedHelloCommand()
|
||||||
|
|
||||||
|
const result = await mockedCommand.loadRoleRegistry()
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('assistant')
|
||||||
|
expect(result.assistant.name).toContain('智能助手')
|
||||||
|
|
||||||
|
jest.unmock('../../lib/core/resource/resourceManager')
|
||||||
|
helloCommand.discoverLocalRoles.mockRestore()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('角色信息获取测试', () => {
|
||||||
|
test('getRoleInfo应该返回正确的角色信息', async () => {
|
||||||
|
// Mock注册表
|
||||||
|
helloCommand.roleRegistry = {
|
||||||
|
'test-role': {
|
||||||
|
name: '测试角色',
|
||||||
|
description: '测试描述',
|
||||||
|
file: '@package://test/path'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleInfo = await helloCommand.getRoleInfo('test-role')
|
||||||
|
|
||||||
|
expect(roleInfo).toEqual({
|
||||||
|
id: 'test-role',
|
||||||
|
name: '测试角色',
|
||||||
|
description: '测试描述',
|
||||||
|
file: '@package://test/path'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('getRoleInfo对不存在的角色应该返回null', async () => {
|
||||||
|
helloCommand.roleRegistry = {}
|
||||||
|
|
||||||
|
const roleInfo = await helloCommand.getRoleInfo('non-existent')
|
||||||
|
expect(roleInfo).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getAllRoles测试', () => {
|
||||||
|
test('应该返回角色数组格式', async () => {
|
||||||
|
helloCommand.roleRegistry = {
|
||||||
|
'role1': { name: '角色1', description: '描述1', file: 'file1' },
|
||||||
|
'role2': { name: '角色2', description: '描述2', file: 'file2' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const allRoles = await helloCommand.getAllRoles()
|
||||||
|
|
||||||
|
expect(Array.isArray(allRoles)).toBe(true)
|
||||||
|
expect(allRoles).toHaveLength(2)
|
||||||
|
expect(allRoles[0]).toHaveProperty('id')
|
||||||
|
expect(allRoles[0]).toHaveProperty('name')
|
||||||
|
expect(allRoles[0]).toHaveProperty('description')
|
||||||
|
expect(allRoles[0]).toHaveProperty('file')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
525
src/tests/commands/UserRoleDiscovery.integration.test.js
Normal file
525
src/tests/commands/UserRoleDiscovery.integration.test.js
Normal file
@ -0,0 +1,525 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
const os = require('os')
|
||||||
|
const HelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
|
||||||
|
describe('用户角色发现机制 集成测试', () => {
|
||||||
|
let tempDir
|
||||||
|
let projectDir
|
||||||
|
let helloCommand
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// 创建临时项目目录
|
||||||
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'user-role-discovery-'))
|
||||||
|
projectDir = path.join(tempDir, 'test-project')
|
||||||
|
|
||||||
|
// 创建完整的项目结构
|
||||||
|
await fs.ensureDir(path.join(projectDir, 'prompt', 'domain'))
|
||||||
|
await fs.ensureDir(path.join(projectDir, '.promptx', 'user-roles'))
|
||||||
|
|
||||||
|
helloCommand = new HelloCommand()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
if (tempDir) {
|
||||||
|
await fs.remove(tempDir)
|
||||||
|
}
|
||||||
|
if (helloCommand.roleRegistry) {
|
||||||
|
helloCommand.roleRegistry = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('用户角色路径扫描', () => {
|
||||||
|
test('应该能扫描 .promptx/user-roles 目录', async () => {
|
||||||
|
// 创建用户自定义角色
|
||||||
|
const userRoleDir = path.join(projectDir, '.promptx', 'user-roles', 'custom-analyst')
|
||||||
|
await fs.ensureDir(userRoleDir)
|
||||||
|
|
||||||
|
const userRoleContent = `<!--
|
||||||
|
name: 📊 自定义分析师
|
||||||
|
description: 用户定制的数据分析专家
|
||||||
|
-->
|
||||||
|
|
||||||
|
<role>
|
||||||
|
<personality>
|
||||||
|
# 数据分析思维
|
||||||
|
我是一个专注于数据洞察的分析师,善于从复杂数据中发现业务价值。
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
# 分析原则
|
||||||
|
- 数据驱动决策
|
||||||
|
- 业务价值导向
|
||||||
|
- 简洁清晰表达
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
# 专业技能
|
||||||
|
- 统计分析方法
|
||||||
|
- 数据可视化技能
|
||||||
|
- 业务理解能力
|
||||||
|
</knowledge>
|
||||||
|
</role>`
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(userRoleDir, 'custom-analyst.role.md'),
|
||||||
|
userRoleContent
|
||||||
|
)
|
||||||
|
|
||||||
|
// 这个测试假设我们已经实现了用户角色发现功能
|
||||||
|
// 实际实现时,discoverLocalRoles会被扩展以支持用户角色路径
|
||||||
|
|
||||||
|
// 验证文件创建成功
|
||||||
|
expect(await fs.pathExists(path.join(userRoleDir, 'custom-analyst.role.md'))).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('应该同时支持系统角色和用户角色', async () => {
|
||||||
|
// 创建系统角色
|
||||||
|
const systemRoleDir = path.join(projectDir, 'prompt', 'domain', 'assistant')
|
||||||
|
await fs.ensureDir(systemRoleDir)
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(systemRoleDir, 'assistant.role.md'),
|
||||||
|
`<!--
|
||||||
|
name: 🤖 系统助手
|
||||||
|
description: 系统内置助手
|
||||||
|
-->
|
||||||
|
|
||||||
|
<role>
|
||||||
|
<personality>系统助手思维</personality>
|
||||||
|
</role>`
|
||||||
|
)
|
||||||
|
|
||||||
|
// 创建用户角色
|
||||||
|
const userRoleDir = path.join(projectDir, '.promptx', 'user-roles', 'my-role')
|
||||||
|
await fs.ensureDir(userRoleDir)
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(userRoleDir, 'my-role.role.md'),
|
||||||
|
`<!--
|
||||||
|
name: 👤 我的角色
|
||||||
|
description: 用户自定义角色
|
||||||
|
-->
|
||||||
|
|
||||||
|
<role>
|
||||||
|
<personality>用户自定义思维</personality>
|
||||||
|
</role>`
|
||||||
|
)
|
||||||
|
|
||||||
|
jest.doMock('../../lib/core/resource/protocols/PackageProtocol', () => {
|
||||||
|
return class MockPackageProtocol {
|
||||||
|
async getPackageRoot() {
|
||||||
|
return projectDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../../lib/core/pouch/commands/HelloCommand')]
|
||||||
|
const MockedHelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
const mockedCommand = new MockedHelloCommand()
|
||||||
|
|
||||||
|
// 模拟双路径扫描实现
|
||||||
|
mockedCommand.discoverLocalRoles = async function() {
|
||||||
|
const PackageProtocol = require('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
const packageProtocol = new PackageProtocol()
|
||||||
|
const glob = require('glob')
|
||||||
|
const discoveredRoles = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const packageRoot = await packageProtocol.getPackageRoot()
|
||||||
|
|
||||||
|
// 扫描路径:系统角色 + 用户角色
|
||||||
|
const scanPaths = [
|
||||||
|
path.join(packageRoot, 'prompt', 'domain'), // 系统角色
|
||||||
|
path.join(packageRoot, '.promptx', 'user-roles') // 用户角色
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const scanPath of scanPaths) {
|
||||||
|
if (await fs.pathExists(scanPath)) {
|
||||||
|
const domains = await fs.readdir(scanPath)
|
||||||
|
|
||||||
|
for (const domain of domains) {
|
||||||
|
const domainDir = path.join(scanPath, domain)
|
||||||
|
const stat = await fs.stat(domainDir)
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const roleFile = path.join(domainDir, `${domain}.role.md`)
|
||||||
|
if (await fs.pathExists(roleFile)) {
|
||||||
|
const content = await fs.readFile(roleFile, 'utf-8')
|
||||||
|
const relativePath = path.relative(packageRoot, roleFile)
|
||||||
|
|
||||||
|
let name = `🎭 ${domain}`
|
||||||
|
let description = '本地发现的角色'
|
||||||
|
let source = 'local-discovery'
|
||||||
|
|
||||||
|
// 区分系统角色和用户角色
|
||||||
|
if (scanPath.includes('.promptx')) {
|
||||||
|
source = 'user-generated'
|
||||||
|
description = '用户自定义角色'
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameMatch = content.match(/name:\s*(.+?)(?:\n|$)/i)
|
||||||
|
if (nameMatch) {
|
||||||
|
name = nameMatch[1].trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
const descMatch = content.match(/description:\s*(.+?)(?:\n|$)/i)
|
||||||
|
if (descMatch) {
|
||||||
|
description = descMatch[1].trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
discoveredRoles[domain] = {
|
||||||
|
file: scanPath.includes('.promptx')
|
||||||
|
? `@project://${relativePath}`
|
||||||
|
: `@package://${relativePath}`,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return discoveredRoles
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('角色发现失败:', error.message)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const discoveredRoles = await mockedCommand.discoverLocalRoles()
|
||||||
|
|
||||||
|
// 验证同时发现了系统角色和用户角色
|
||||||
|
expect(discoveredRoles).toHaveProperty('assistant')
|
||||||
|
expect(discoveredRoles).toHaveProperty('my-role')
|
||||||
|
|
||||||
|
expect(discoveredRoles.assistant.source).toBe('local-discovery')
|
||||||
|
expect(discoveredRoles.assistant.file).toContain('@package://')
|
||||||
|
|
||||||
|
expect(discoveredRoles['my-role'].source).toBe('user-generated')
|
||||||
|
expect(discoveredRoles['my-role'].file).toContain('@project://')
|
||||||
|
|
||||||
|
jest.unmock('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('DPML格式元数据提取', () => {
|
||||||
|
test('应该能从DPML格式中提取元数据', async () => {
|
||||||
|
const userRoleDir = path.join(projectDir, '.promptx', 'user-roles', 'dpml-role')
|
||||||
|
await fs.ensureDir(userRoleDir)
|
||||||
|
|
||||||
|
// DPML格式的角色文件(根据文档设计的格式)
|
||||||
|
const dpmlRoleContent = `<role>
|
||||||
|
<personality>
|
||||||
|
# 数据分析师思维模式
|
||||||
|
|
||||||
|
## 核心思维特征
|
||||||
|
- **数据敏感性思维**:善于从数字中发现故事和趋势模式
|
||||||
|
- **逻辑分析思维**:系统性地分解复杂数据问题,追求因果关系
|
||||||
|
- **结果导向思维**:专注于为业务决策提供可行洞察和建议
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
# 数据分析师行为原则
|
||||||
|
|
||||||
|
## 核心工作原则
|
||||||
|
- **数据驱动决策**:所有分析建议必须有可靠数据支撑
|
||||||
|
- **简洁清晰表达**:复杂分析结果要用简单易懂的方式呈现
|
||||||
|
- **业务价值优先**:分析要紧密围绕业务目标和价值创造
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
# 数据分析专业知识体系
|
||||||
|
|
||||||
|
## 数据处理技能
|
||||||
|
- **数据清洗方法**:缺失值处理、异常值识别、数据标准化
|
||||||
|
- **数据整合技巧**:多源数据合并、关联分析、数据建模
|
||||||
|
- **质量控制流程**:数据校验、一致性检查、完整性验证
|
||||||
|
|
||||||
|
## 分析方法论
|
||||||
|
- **描述性分析**:趋势分析、对比分析、分布分析
|
||||||
|
- **诊断性分析**:钻取分析、根因分析、相关性分析
|
||||||
|
</knowledge>
|
||||||
|
</role>`
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(userRoleDir, 'dpml-role.role.md'),
|
||||||
|
dpmlRoleContent
|
||||||
|
)
|
||||||
|
|
||||||
|
jest.doMock('../../lib/core/resource/protocols/PackageProtocol', () => {
|
||||||
|
return class MockPackageProtocol {
|
||||||
|
async getPackageRoot() {
|
||||||
|
return projectDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../../lib/core/pouch/commands/HelloCommand')]
|
||||||
|
const MockedHelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
const mockedCommand = new MockedHelloCommand()
|
||||||
|
|
||||||
|
// 实现DPML元数据提取逻辑(这是我们要实现的功能)
|
||||||
|
function extractDPMLMetadata(content, roleId) {
|
||||||
|
// 从<personality>标签中提取角色名称
|
||||||
|
const personalityMatch = content.match(/<personality[^>]*>([\s\S]*?)<\/personality>/i)
|
||||||
|
const roleNameFromPersonality = personalityMatch
|
||||||
|
? personalityMatch[1].split('\n')[0].replace(/^#\s*/, '').trim()
|
||||||
|
: null
|
||||||
|
|
||||||
|
// 从<knowledge>标签中提取专业能力描述
|
||||||
|
const knowledgeMatch = content.match(/<knowledge[^>]*>([\s\S]*?)<\/knowledge>/i)
|
||||||
|
const roleDescription = knowledgeMatch
|
||||||
|
? knowledgeMatch[1].split('\n').slice(0, 3).join(' ').replace(/[#\-\*]/g, '').trim()
|
||||||
|
: null
|
||||||
|
|
||||||
|
return {
|
||||||
|
file: `@project://.promptx/user-roles/${roleId}/${roleId}.role.md`,
|
||||||
|
name: roleNameFromPersonality || `🎭 ${roleId}`,
|
||||||
|
description: roleDescription || '用户自定义DPML角色',
|
||||||
|
source: 'user-generated',
|
||||||
|
format: 'dpml'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mockedCommand.discoverLocalRoles = async function() {
|
||||||
|
const PackageProtocol = require('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
const packageProtocol = new PackageProtocol()
|
||||||
|
const discoveredRoles = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const packageRoot = await packageProtocol.getPackageRoot()
|
||||||
|
const userRolesPath = path.join(packageRoot, '.promptx', 'user-roles')
|
||||||
|
|
||||||
|
if (await fs.pathExists(userRolesPath)) {
|
||||||
|
const userRoleDirs = await fs.readdir(userRolesPath)
|
||||||
|
|
||||||
|
for (const roleId of userRoleDirs) {
|
||||||
|
const roleDir = path.join(userRolesPath, roleId)
|
||||||
|
const stat = await fs.stat(roleDir)
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const roleFile = path.join(roleDir, `${roleId}.role.md`)
|
||||||
|
if (await fs.pathExists(roleFile)) {
|
||||||
|
const content = await fs.readFile(roleFile, 'utf-8')
|
||||||
|
|
||||||
|
// 使用DPML元数据提取
|
||||||
|
const roleInfo = extractDPMLMetadata(content, roleId)
|
||||||
|
discoveredRoles[roleId] = roleInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return discoveredRoles
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('DPML角色发现失败:', error.message)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const discoveredRoles = await mockedCommand.discoverLocalRoles()
|
||||||
|
|
||||||
|
// 验证DPML元数据提取
|
||||||
|
expect(discoveredRoles).toHaveProperty('dpml-role')
|
||||||
|
expect(discoveredRoles['dpml-role'].name).toBe('数据分析师思维模式')
|
||||||
|
expect(discoveredRoles['dpml-role'].description).toContain('数据分析专业知识体系')
|
||||||
|
expect(discoveredRoles['dpml-role'].format).toBe('dpml')
|
||||||
|
expect(discoveredRoles['dpml-role'].source).toBe('user-generated')
|
||||||
|
|
||||||
|
jest.unmock('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('错误处理和边界情况', () => {
|
||||||
|
test('应该处理不存在的用户角色目录', async () => {
|
||||||
|
// 只创建系统角色目录,不创建用户角色目录
|
||||||
|
const systemRoleDir = path.join(projectDir, 'prompt', 'domain', 'assistant')
|
||||||
|
await fs.ensureDir(systemRoleDir)
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(systemRoleDir, 'assistant.role.md'),
|
||||||
|
`<role><personality>助手</personality></role>`
|
||||||
|
)
|
||||||
|
|
||||||
|
jest.doMock('../../lib/core/resource/protocols/PackageProtocol', () => {
|
||||||
|
return class MockPackageProtocol {
|
||||||
|
async getPackageRoot() {
|
||||||
|
return projectDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../../lib/core/pouch/commands/HelloCommand')]
|
||||||
|
const MockedHelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
const mockedCommand = new MockedHelloCommand()
|
||||||
|
|
||||||
|
// 模拟处理不存在目录的逻辑
|
||||||
|
mockedCommand.discoverLocalRoles = async function() {
|
||||||
|
const PackageProtocol = require('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
const packageProtocol = new PackageProtocol()
|
||||||
|
const discoveredRoles = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const packageRoot = await packageProtocol.getPackageRoot()
|
||||||
|
|
||||||
|
const scanPaths = [
|
||||||
|
{ path: path.join(packageRoot, 'prompt', 'domain'), prefix: '@package://' },
|
||||||
|
{ path: path.join(packageRoot, '.promptx', 'user-roles'), prefix: '@project://' }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const { path: scanPath, prefix } of scanPaths) {
|
||||||
|
if (await fs.pathExists(scanPath)) {
|
||||||
|
const domains = await fs.readdir(scanPath)
|
||||||
|
|
||||||
|
for (const domain of domains) {
|
||||||
|
const domainDir = path.join(scanPath, domain)
|
||||||
|
const stat = await fs.stat(domainDir)
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const roleFile = path.join(domainDir, `${domain}.role.md`)
|
||||||
|
if (await fs.pathExists(roleFile)) {
|
||||||
|
const content = await fs.readFile(roleFile, 'utf-8')
|
||||||
|
const relativePath = path.relative(packageRoot, roleFile)
|
||||||
|
|
||||||
|
discoveredRoles[domain] = {
|
||||||
|
file: `${prefix}${relativePath}`,
|
||||||
|
name: `🎭 ${domain}`,
|
||||||
|
description: '本地发现的角色',
|
||||||
|
source: prefix.includes('project') ? 'user-generated' : 'local-discovery'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return discoveredRoles
|
||||||
|
} catch (error) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const discoveredRoles = await mockedCommand.discoverLocalRoles()
|
||||||
|
|
||||||
|
// 应该只发现系统角色,不会因为用户角色目录不存在而出错
|
||||||
|
expect(discoveredRoles).toHaveProperty('assistant')
|
||||||
|
expect(Object.keys(discoveredRoles)).toHaveLength(1)
|
||||||
|
|
||||||
|
jest.unmock('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('应该处理用户角色ID冲突', async () => {
|
||||||
|
// 创建同名的系统角色和用户角色
|
||||||
|
const systemRoleDir = path.join(projectDir, 'prompt', 'domain', 'analyst')
|
||||||
|
await fs.ensureDir(systemRoleDir)
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(systemRoleDir, 'analyst.role.md'),
|
||||||
|
`<!--
|
||||||
|
name: 📊 系统分析师
|
||||||
|
description: 系统内置分析师
|
||||||
|
-->
|
||||||
|
<role><personality>系统分析师</personality></role>`
|
||||||
|
)
|
||||||
|
|
||||||
|
const userRoleDir = path.join(projectDir, '.promptx', 'user-roles', 'analyst')
|
||||||
|
await fs.ensureDir(userRoleDir)
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(userRoleDir, 'analyst.role.md'),
|
||||||
|
`<!--
|
||||||
|
name: 👤 用户分析师
|
||||||
|
description: 用户自定义分析师
|
||||||
|
-->
|
||||||
|
<role><personality>用户分析师</personality></role>`
|
||||||
|
)
|
||||||
|
|
||||||
|
jest.doMock('../../lib/core/resource/protocols/PackageProtocol', () => {
|
||||||
|
return class MockPackageProtocol {
|
||||||
|
async getPackageRoot() {
|
||||||
|
return projectDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../../lib/core/pouch/commands/HelloCommand')]
|
||||||
|
const MockedHelloCommand = require('../../lib/core/pouch/commands/HelloCommand')
|
||||||
|
const mockedCommand = new MockedHelloCommand()
|
||||||
|
|
||||||
|
// 模拟冲突处理逻辑(用户角色优先)
|
||||||
|
mockedCommand.discoverLocalRoles = async function() {
|
||||||
|
const PackageProtocol = require('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
const packageProtocol = new PackageProtocol()
|
||||||
|
const discoveredRoles = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const packageRoot = await packageProtocol.getPackageRoot()
|
||||||
|
|
||||||
|
// 先扫描系统角色,再扫描用户角色(用户角色会覆盖同名系统角色)
|
||||||
|
const scanPaths = [
|
||||||
|
{ path: path.join(packageRoot, 'prompt', 'domain'), prefix: '@package://', source: 'local-discovery' },
|
||||||
|
{ path: path.join(packageRoot, '.promptx', 'user-roles'), prefix: '@project://', source: 'user-generated' }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const { path: scanPath, prefix, source } of scanPaths) {
|
||||||
|
if (await fs.pathExists(scanPath)) {
|
||||||
|
const domains = await fs.readdir(scanPath)
|
||||||
|
|
||||||
|
for (const domain of domains) {
|
||||||
|
const domainDir = path.join(scanPath, domain)
|
||||||
|
const stat = await fs.stat(domainDir)
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const roleFile = path.join(domainDir, `${domain}.role.md`)
|
||||||
|
if (await fs.pathExists(roleFile)) {
|
||||||
|
const content = await fs.readFile(roleFile, 'utf-8')
|
||||||
|
const relativePath = path.relative(packageRoot, roleFile)
|
||||||
|
|
||||||
|
let name = `🎭 ${domain}`
|
||||||
|
let description = '本地发现的角色'
|
||||||
|
|
||||||
|
const nameMatch = content.match(/name:\s*(.+?)(?:\n|$)/i)
|
||||||
|
if (nameMatch) {
|
||||||
|
name = nameMatch[1].trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
const descMatch = content.match(/description:\s*(.+?)(?:\n|$)/i)
|
||||||
|
if (descMatch) {
|
||||||
|
description = descMatch[1].trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户角色会覆盖系统角色
|
||||||
|
discoveredRoles[domain] = {
|
||||||
|
file: `${prefix}${relativePath}`,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return discoveredRoles
|
||||||
|
} catch (error) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const discoveredRoles = await mockedCommand.discoverLocalRoles()
|
||||||
|
|
||||||
|
// 验证用户角色优先级更高
|
||||||
|
expect(discoveredRoles).toHaveProperty('analyst')
|
||||||
|
expect(discoveredRoles.analyst.name).toBe('👤 用户分析师')
|
||||||
|
expect(discoveredRoles.analyst.description).toBe('用户自定义分析师')
|
||||||
|
expect(discoveredRoles.analyst.source).toBe('user-generated')
|
||||||
|
expect(discoveredRoles.analyst.file).toContain('@project://')
|
||||||
|
|
||||||
|
jest.unmock('../../lib/core/resource/protocols/PackageProtocol')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
220
src/tests/core/resource/ResourceManager.unit.test.js
Normal file
220
src/tests/core/resource/ResourceManager.unit.test.js
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
const ResourceManager = require('../../../lib/core/resource/resourceManager')
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
const path = require('path')
|
||||||
|
const os = require('os')
|
||||||
|
|
||||||
|
describe('ResourceManager - 用户资源发现', () => {
|
||||||
|
let resourceManager
|
||||||
|
let tempDir
|
||||||
|
let mockPackageRoot
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// 创建临时测试目录
|
||||||
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-test-'))
|
||||||
|
mockPackageRoot = tempDir
|
||||||
|
|
||||||
|
// 模拟用户资源目录结构
|
||||||
|
await fs.ensureDir(path.join(tempDir, '.promptx', 'resource', 'domain'))
|
||||||
|
|
||||||
|
resourceManager = new ResourceManager()
|
||||||
|
|
||||||
|
// Mock packageProtocol module
|
||||||
|
jest.doMock('../../../lib/core/resource/protocols/PackageProtocol', () => {
|
||||||
|
return class MockPackageProtocol {
|
||||||
|
async getPackageRoot() {
|
||||||
|
return mockPackageRoot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
// 清理临时目录
|
||||||
|
await fs.remove(tempDir)
|
||||||
|
jest.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('discoverUserResources', () => {
|
||||||
|
it('应该返回空对象当用户资源目录不存在时', async () => {
|
||||||
|
// 删除用户资源目录
|
||||||
|
await fs.remove(path.join(tempDir, '.promptx'))
|
||||||
|
|
||||||
|
const result = await resourceManager.discoverUserResources()
|
||||||
|
|
||||||
|
expect(result).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该发现用户创建的角色文件', async () => {
|
||||||
|
// 创建测试角色文件
|
||||||
|
const roleDir = path.join(tempDir, '.promptx', 'resource', 'domain', 'test-sales-analyst')
|
||||||
|
await fs.ensureDir(roleDir)
|
||||||
|
|
||||||
|
const roleContent = `<role>
|
||||||
|
<personality>
|
||||||
|
# 销售数据分析师思维模式
|
||||||
|
## 核心思维特征
|
||||||
|
- **数据敏感性思维**:善于从数字中发现故事和趋势模式
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
# 销售数据分析师行为原则
|
||||||
|
## 核心工作原则
|
||||||
|
- **数据驱动决策**:所有分析建议必须有可靠数据支撑
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
# 销售数据分析专业知识体系
|
||||||
|
## 数据处理技能
|
||||||
|
- **数据清洗方法**:缺失值处理、异常值识别
|
||||||
|
</knowledge>
|
||||||
|
</role>`
|
||||||
|
|
||||||
|
await fs.writeFile(path.join(roleDir, 'test-sales-analyst.role.md'), roleContent)
|
||||||
|
|
||||||
|
const result = await resourceManager.discoverUserResources()
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('role')
|
||||||
|
expect(result.role).toHaveProperty('test-sales-analyst')
|
||||||
|
expect(result.role['test-sales-analyst']).toMatchObject({
|
||||||
|
file: expect.stringContaining('test-sales-analyst.role.md'),
|
||||||
|
name: expect.stringContaining('销售数据分析师'),
|
||||||
|
source: 'user-generated',
|
||||||
|
format: 'dpml',
|
||||||
|
type: 'role'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该支持多种资源类型发现', async () => {
|
||||||
|
// 创建角色和相关资源
|
||||||
|
const roleDir = path.join(tempDir, '.promptx', 'resource', 'domain', 'test-role')
|
||||||
|
await fs.ensureDir(roleDir)
|
||||||
|
await fs.ensureDir(path.join(roleDir, 'thought'))
|
||||||
|
await fs.ensureDir(path.join(roleDir, 'execution'))
|
||||||
|
|
||||||
|
// 创建角色文件
|
||||||
|
await fs.writeFile(path.join(roleDir, 'test-role.role.md'), '<role><personality>Test</personality><principle>Test</principle><knowledge>Test</knowledge></role>')
|
||||||
|
|
||||||
|
// 创建思维文件
|
||||||
|
await fs.writeFile(path.join(roleDir, 'thought', 'test.thought.md'), '<thought><exploration>Test exploration</exploration><reasoning>Test reasoning</reasoning></thought>')
|
||||||
|
|
||||||
|
// 创建执行文件
|
||||||
|
await fs.writeFile(path.join(roleDir, 'execution', 'test.execution.md'), '<execution><constraint>Test constraint</constraint></execution>')
|
||||||
|
|
||||||
|
const result = await resourceManager.discoverUserResources()
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('role')
|
||||||
|
expect(result).toHaveProperty('thought')
|
||||||
|
expect(result).toHaveProperty('execution')
|
||||||
|
expect(result.role).toHaveProperty('test-role')
|
||||||
|
expect(result.thought).toHaveProperty('test')
|
||||||
|
expect(result.execution).toHaveProperty('test')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该处理DPML格式错误的文件', async () => {
|
||||||
|
// 创建格式错误的角色文件
|
||||||
|
const roleDir = path.join(tempDir, '.promptx', 'resource', 'domain', 'invalid-role')
|
||||||
|
await fs.ensureDir(roleDir)
|
||||||
|
|
||||||
|
const invalidContent = `这不是有效的DPML格式`
|
||||||
|
await fs.writeFile(path.join(roleDir, 'invalid-role.role.md'), invalidContent)
|
||||||
|
|
||||||
|
const result = await resourceManager.discoverUserResources()
|
||||||
|
|
||||||
|
// 应该跳过格式错误的文件,但不应该抛出错误
|
||||||
|
expect(result.role || {}).not.toHaveProperty('invalid-role')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该跨平台正确处理路径', async () => {
|
||||||
|
// 在不同平台上创建角色文件
|
||||||
|
const roleDir = path.join(tempDir, '.promptx', 'resource', 'domain', 'cross-platform-role')
|
||||||
|
await fs.ensureDir(roleDir)
|
||||||
|
|
||||||
|
const roleContent = '<role><personality>Test</personality><principle>Test</principle><knowledge>Test</knowledge></role>'
|
||||||
|
await fs.writeFile(path.join(roleDir, 'cross-platform-role.role.md'), roleContent)
|
||||||
|
|
||||||
|
const result = await resourceManager.discoverUserResources()
|
||||||
|
|
||||||
|
expect(result.role).toHaveProperty('cross-platform-role')
|
||||||
|
|
||||||
|
// 验证文件路径使用正确的分隔符
|
||||||
|
const roleInfo = result.role['cross-platform-role']
|
||||||
|
expect(roleInfo.file).toBe(path.normalize(roleInfo.file))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('loadUnifiedRegistry', () => {
|
||||||
|
it('应该合并系统资源和用户资源', async () => {
|
||||||
|
// 模拟系统资源
|
||||||
|
const mockSystemResources = {
|
||||||
|
role: {
|
||||||
|
'assistant': {
|
||||||
|
file: '@package://prompt/domain/assistant/assistant.role.md',
|
||||||
|
name: '🙋 智能助手',
|
||||||
|
source: 'system',
|
||||||
|
format: 'dpml',
|
||||||
|
type: 'role'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock fs.readJSON for system registry
|
||||||
|
jest.spyOn(fs, 'readJSON')
|
||||||
|
.mockImplementation((filePath) => {
|
||||||
|
if (filePath.includes('resource.registry.json')) {
|
||||||
|
return Promise.resolve(mockSystemResources)
|
||||||
|
}
|
||||||
|
return Promise.resolve({})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建用户资源
|
||||||
|
const roleDir = path.join(tempDir, '.promptx', 'resource', 'domain', 'user-role')
|
||||||
|
await fs.ensureDir(roleDir)
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(roleDir, 'user-role.role.md'),
|
||||||
|
'<role><personality>User</personality><principle>User</principle><knowledge>User</knowledge></role>'
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await resourceManager.loadUnifiedRegistry()
|
||||||
|
|
||||||
|
expect(result.role).toHaveProperty('assistant') // 系统资源
|
||||||
|
expect(result.role).toHaveProperty('user-role') // 用户资源
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该让用户资源覆盖同名系统资源', async () => {
|
||||||
|
// 模拟系统资源
|
||||||
|
const mockSystemResources = {
|
||||||
|
role: {
|
||||||
|
'assistant': {
|
||||||
|
file: '@package://prompt/domain/assistant/assistant.role.md',
|
||||||
|
name: '🙋 智能助手',
|
||||||
|
source: 'system',
|
||||||
|
format: 'dpml',
|
||||||
|
type: 'role'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock fs.readJSON for system registry
|
||||||
|
jest.spyOn(fs, 'readJSON')
|
||||||
|
.mockImplementation((filePath) => {
|
||||||
|
if (filePath.includes('resource.registry.json')) {
|
||||||
|
return Promise.resolve(mockSystemResources)
|
||||||
|
}
|
||||||
|
return Promise.resolve({})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建同名的用户资源
|
||||||
|
const roleDir = path.join(tempDir, '.promptx', 'resource', 'domain', 'assistant')
|
||||||
|
await fs.ensureDir(roleDir)
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(roleDir, 'assistant.role.md'),
|
||||||
|
'<role><personality># 自定义助手\n用户定制的助手</personality><principle>Custom</principle><knowledge>Custom</knowledge></role>'
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await resourceManager.loadUnifiedRegistry()
|
||||||
|
|
||||||
|
expect(result.role.assistant.source).toBe('user-generated')
|
||||||
|
expect(result.role.assistant.name).toContain('自定义助手')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user