Staging (#71)
* Develop (#66) * 重构ActionCommand和LearnCommand,更新DPMLContentParser和SemanticRenderer的导入路径,确保模块结构一致性。删除不再使用的DPMLContentParser和SemanticRenderer文件,优化代码结构,提升可维护性。 * 重构PromptX资源协议系统,采用极简两层协议架构,删除不必要的语义层,优化路径解析和资源加载流程。引入AI协作优化,支持直接生成完整协议路径,提升系统性能和用户体验。整体架构简化60%,实现零配置启动,显著降低内存占用和启动时间。 * optimize:优化女娲提示词 * Optimize:更新记忆策略文档,增加角色专业记忆的独特价值和工作流程,强调角色记忆与客户端记忆的差异,优化记忆引导话术和决策规则,以提升用户对专业记忆系统的理解和应用。 * feature:增加 Sean 角色 * optimize:优化记忆格式化逻辑,确保完整记忆内容不被截断,同时更新工具定义中的描述,增强用户对记忆回想器的理解和使用指导。 * feat: 添加DACP服务支持,允许通过命令行调用DACP专业服务,增强AI角色的执行能力,同时更新相关依赖和工具定义。 * feat: 在MCPServerCommand和MCPStreamableHttpCommand中添加'promptx_dacp'参数映射,同时在DACPCommand中优化参数处理逻辑,以支持数组参数的正确解析。 * feat: 更新DACP演示服务,重命名服务和描述,简化功能,删除不必要的日历和文档操作,增强演示效果。同时,优化了API接口和README文档,确保用户更易于理解和使用。 * feat: 添加DACP邮件发送功能,支持真实发送与Demo模式,增强邮件发送的配置管理和错误提示,优化用户体验。 * feat: 更新女娲和Sean角色文档,增强角色身份、核心特质和决策框架的描述,优化内容结构,提升用户理解和使用体验。同时,更新产品哲学知识体系,明确矛盾驱动和简洁性原则的应用。 * Add product management submodule * fix: 修复 recall 和 learn 的 bug * refactor: 把 hello 改成 welcome * feat: 添加DACP服务启动脚本和测试命令,更新相关依赖,优化配置文件路径处理 * fix: 更新pnpm-lock.yaml以匹配DACP依赖,解决CI中--frozen-lockfile的错误 * 更新DACP白皮书的更新日期至2025-01-19;在DACPConfigManager中优化配置管理,支持项目级和用户级配置的优先级处理,增强错误提示信息,更新相关方法以支持异步操作。 * Develop (#70) * 重构ActionCommand和LearnCommand,更新DPMLContentParser和SemanticRenderer的导入路径,确保模块结构一致性。删除不再使用的DPMLContentParser和SemanticRenderer文件,优化代码结构,提升可维护性。 * 重构PromptX资源协议系统,采用极简两层协议架构,删除不必要的语义层,优化路径解析和资源加载流程。引入AI协作优化,支持直接生成完整协议路径,提升系统性能和用户体验。整体架构简化60%,实现零配置启动,显著降低内存占用和启动时间。 * optimize:优化女娲提示词 * Optimize:更新记忆策略文档,增加角色专业记忆的独特价值和工作流程,强调角色记忆与客户端记忆的差异,优化记忆引导话术和决策规则,以提升用户对专业记忆系统的理解和应用。 * feature:增加 Sean 角色 * optimize:优化记忆格式化逻辑,确保完整记忆内容不被截断,同时更新工具定义中的描述,增强用户对记忆回想器的理解和使用指导。 * feat: 添加DACP服务支持,允许通过命令行调用DACP专业服务,增强AI角色的执行能力,同时更新相关依赖和工具定义。 * feat: 在MCPServerCommand和MCPStreamableHttpCommand中添加'promptx_dacp'参数映射,同时在DACPCommand中优化参数处理逻辑,以支持数组参数的正确解析。 * feat: 更新DACP演示服务,重命名服务和描述,简化功能,删除不必要的日历和文档操作,增强演示效果。同时,优化了API接口和README文档,确保用户更易于理解和使用。 * feat: 添加DACP邮件发送功能,支持真实发送与Demo模式,增强邮件发送的配置管理和错误提示,优化用户体验。 * feat: 更新女娲和Sean角色文档,增强角色身份、核心特质和决策框架的描述,优化内容结构,提升用户理解和使用体验。同时,更新产品哲学知识体系,明确矛盾驱动和简洁性原则的应用。 * Add product management submodule * fix: 修复 recall 和 learn 的 bug * refactor: 把 hello 改成 welcome * feat: 添加DACP服务启动脚本和测试命令,更新相关依赖,优化配置文件路径处理 * fix: 更新pnpm-lock.yaml以匹配DACP依赖,解决CI中--frozen-lockfile的错误 * 更新DACP白皮书的更新日期至2025-01-19;在DACPConfigManager中优化配置管理,支持项目级和用户级配置的优先级处理,增强错误提示信息,更新相关方法以支持异步操作。 * fix: 统一Pouch命令路径获取机制,解决Issue #69记忆持久化问题 修复多实例MCP环境下的路径不一致问题: - RememberCommand: 使用ResourceManager替代DirectoryService直接调用 - RecallCommand: 使用ResourceManager替代DirectoryService直接调用 - RegisterCommand: 使用ResourceManager+DirectoryService统一路径获取 核心改进: 1. 所有命令现在使用相同的getGlobalResourceManager()初始化 2. 通过resourceManager.initializeWithNewArchitecture()确保路径一致性 3. 实现"要对一起对,要错一起错"的一致性原则 测试验证: - 记忆写入和读取使用相同项目路径 - 多实例环境下路径解析行为完全一致 - 向后兼容,无破坏性变更 Fixes #69 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@ -1,179 +0,0 @@
|
||||
/**
|
||||
* DPML内容解析器
|
||||
* 统一处理DPML标签内的混合内容(@引用 + 直接内容)
|
||||
* 确保标签语义完整性
|
||||
*/
|
||||
class DPMLContentParser {
|
||||
/**
|
||||
* 解析DPML标签的完整语义内容
|
||||
* @param {string} content - 标签内的原始内容
|
||||
* @param {string} tagName - 标签名称
|
||||
* @returns {Object} 完整的语义结构
|
||||
*/
|
||||
parseTagContent(content, tagName) {
|
||||
if (!content || !content.trim()) {
|
||||
return {
|
||||
fullSemantics: '',
|
||||
references: [],
|
||||
directContent: '',
|
||||
metadata: {
|
||||
tagName,
|
||||
hasReferences: false,
|
||||
hasDirectContent: false,
|
||||
contentType: 'empty'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cleanContent = content.trim()
|
||||
const references = this.extractReferencesWithPosition(cleanContent)
|
||||
const directContent = this.extractDirectContent(cleanContent)
|
||||
|
||||
return {
|
||||
// 完整语义内容(用户看到的最终效果)
|
||||
fullSemantics: cleanContent,
|
||||
|
||||
// 引用部分(需要解析和加载的资源)
|
||||
references,
|
||||
|
||||
// 直接部分(用户原创内容)
|
||||
directContent,
|
||||
|
||||
// 元数据
|
||||
metadata: {
|
||||
tagName,
|
||||
hasReferences: references.length > 0,
|
||||
hasDirectContent: directContent.length > 0,
|
||||
contentType: this.determineContentType(cleanContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取所有@引用
|
||||
* @param {string} content - 内容
|
||||
* @returns {Array} 引用数组
|
||||
*/
|
||||
extractReferences(content) {
|
||||
// 使用新的位置信息方法,但保持向下兼容
|
||||
return this.extractReferencesWithPosition(content).map(ref => ({
|
||||
fullMatch: ref.fullMatch,
|
||||
priority: ref.priority,
|
||||
protocol: ref.protocol,
|
||||
resource: ref.resource,
|
||||
isRequired: ref.isRequired,
|
||||
isOptional: ref.isOptional
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增:获取引用的位置信息
|
||||
* @param {string} content - 内容
|
||||
* @returns {Array} 包含位置信息的引用数组
|
||||
*/
|
||||
extractReferencesWithPosition(content) {
|
||||
if (!content) {
|
||||
return []
|
||||
}
|
||||
|
||||
const resourceRegex = /@([!?]?)([a-zA-Z][a-zA-Z0-9_-]*):\/\/([a-zA-Z0-9_\/.,-]+?)(?=[\s\)\],]|$)/g
|
||||
const matches = []
|
||||
let match
|
||||
|
||||
while ((match = resourceRegex.exec(content)) !== null) {
|
||||
matches.push({
|
||||
fullMatch: match[0],
|
||||
priority: match[1],
|
||||
protocol: match[2],
|
||||
resource: match[3],
|
||||
position: match.index, // 位置信息
|
||||
isRequired: match[1] === '!',
|
||||
isOptional: match[1] === '?'
|
||||
})
|
||||
}
|
||||
|
||||
return matches.sort((a, b) => a.position - b.position) // 按位置排序
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取直接内容(移除@引用后的剩余内容)
|
||||
* @param {string} content - 内容
|
||||
* @returns {string} 直接内容
|
||||
*/
|
||||
extractDirectContent(content) {
|
||||
// 移除所有@引用行,保留其他内容
|
||||
const withoutReferences = content.replace(/^.*@[!?]?[a-zA-Z][a-zA-Z0-9_-]*:\/\/.*$/gm, '')
|
||||
|
||||
// 清理多余的空行
|
||||
const cleaned = withoutReferences.replace(/\n{3,}/g, '\n\n').trim()
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含引用
|
||||
* @param {string} content - 内容
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasReferences(content) {
|
||||
return /@[!?]?[a-zA-Z][a-zA-Z0-9_-]*:\/\//.test(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含直接内容
|
||||
* @param {string} content - 内容
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasDirectContent(content) {
|
||||
const withoutReferences = this.extractDirectContent(content)
|
||||
return withoutReferences.length > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定内容类型
|
||||
* @param {string} content - 内容
|
||||
* @returns {string} 内容类型
|
||||
*/
|
||||
determineContentType(content) {
|
||||
const hasRefs = this.hasReferences(content)
|
||||
const hasDirect = this.hasDirectContent(content)
|
||||
|
||||
if (hasRefs && hasDirect) return 'mixed'
|
||||
if (hasRefs) return 'references-only'
|
||||
if (hasDirect) return 'direct-only'
|
||||
return 'empty'
|
||||
}
|
||||
|
||||
/**
|
||||
* 从DPML文档中提取指定标签的内容
|
||||
* @param {string} dpmlContent - 完整的DPML文档内容
|
||||
* @param {string} tagName - 标签名称
|
||||
* @returns {string} 标签内容
|
||||
*/
|
||||
extractTagContent(dpmlContent, tagName) {
|
||||
const regex = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, 'i')
|
||||
const match = dpmlContent.match(regex)
|
||||
return match ? match[1] : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析完整的DPML角色文档
|
||||
* @param {string} roleContent - 角色文档内容
|
||||
* @returns {Object} 解析后的角色语义结构
|
||||
*/
|
||||
parseRoleDocument(roleContent) {
|
||||
const dpmlTags = ['personality', 'principle', 'knowledge']
|
||||
const roleSemantics = {}
|
||||
|
||||
dpmlTags.forEach(tagName => {
|
||||
const tagContent = this.extractTagContent(roleContent, tagName)
|
||||
if (tagContent) {
|
||||
roleSemantics[tagName] = this.parseTagContent(tagContent, tagName)
|
||||
}
|
||||
})
|
||||
|
||||
return roleSemantics
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DPMLContentParser
|
||||
114
src/lib/core/resource/ResourceFileNaming.js
Normal file
114
src/lib/core/resource/ResourceFileNaming.js
Normal file
@ -0,0 +1,114 @@
|
||||
/**
|
||||
* PromptX 资源文件命名管理器
|
||||
* 统一管理所有资源文件的命名规范:[id].[tag].md
|
||||
*/
|
||||
class ResourceFileNaming {
|
||||
|
||||
/**
|
||||
* 资源文件命名模式
|
||||
* 格式:[id].[tag].md
|
||||
* 示例:sean-product-philosophy.thought.md
|
||||
*/
|
||||
static NAMING_PATTERN = /^(.+)\.(\w+)\.md$/;
|
||||
|
||||
/**
|
||||
* 解析资源文件名
|
||||
* @param {string} fileName - 文件名
|
||||
* @returns {Object|null} 解析结果 {id, tag} 或 null
|
||||
*/
|
||||
static parseFileName(fileName) {
|
||||
const match = fileName.match(this.NAMING_PATTERN);
|
||||
if (match) {
|
||||
const [, id, tag] = match;
|
||||
return { id, tag };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成资源文件名
|
||||
* @param {string} id - 资源ID
|
||||
* @param {string} tag - 资源标签
|
||||
* @returns {string} 生成的文件名
|
||||
*/
|
||||
static generateFileName(id, tag) {
|
||||
return `${id}.${tag}.md`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证文件名是否符合规范
|
||||
* @param {string} fileName - 文件名
|
||||
* @returns {boolean} 是否符合规范
|
||||
*/
|
||||
static isValidFileName(fileName) {
|
||||
return this.NAMING_PATTERN.test(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否为指定标签类型
|
||||
* @param {string} fileName - 文件名
|
||||
* @param {string} expectedTag - 期望的标签
|
||||
* @returns {boolean} 是否匹配
|
||||
*/
|
||||
static hasTag(fileName, expectedTag) {
|
||||
const parsed = this.parseFileName(fileName);
|
||||
return parsed && parsed.tag === expectedTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件路径提取资源ID
|
||||
* @param {string} filePath - 文件路径
|
||||
* @param {string} expectedTag - 期望的标签
|
||||
* @returns {string|null} 资源ID或null
|
||||
*/
|
||||
static extractResourceId(filePath, expectedTag) {
|
||||
const path = require('path');
|
||||
const fileName = path.basename(filePath);
|
||||
const parsed = this.parseFileName(fileName);
|
||||
|
||||
if (parsed && parsed.tag === expectedTag) {
|
||||
return parsed.id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描目录中指定标签的所有文件
|
||||
* @param {string} directory - 目录路径
|
||||
* @param {string} tag - 标签类型
|
||||
* @returns {Promise<Array>} 文件路径数组
|
||||
*/
|
||||
static async scanTagFiles(directory, tag) {
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
try {
|
||||
if (!await fs.pathExists(directory)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const files = await fs.readdir(directory);
|
||||
const tagFiles = [];
|
||||
|
||||
for (const file of files) {
|
||||
if (this.hasTag(file, tag)) {
|
||||
tagFiles.push(path.join(directory, file));
|
||||
}
|
||||
}
|
||||
|
||||
return tagFiles;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支持的资源标签类型
|
||||
* @returns {Array<string>} 支持的标签类型
|
||||
*/
|
||||
static getSupportedTags() {
|
||||
return ['role', 'thought', 'execution', 'knowledge'];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ResourceFileNaming;
|
||||
@ -1,83 +0,0 @@
|
||||
/**
|
||||
* SemanticRenderer - DPML语义渲染器
|
||||
*
|
||||
* 核心理念:@引用 = 语义占位符
|
||||
* 在标签的原始位置插入引用内容,保持完整的语义流程
|
||||
*/
|
||||
class SemanticRenderer {
|
||||
/**
|
||||
* 语义占位符渲染:将@引用替换为实际内容
|
||||
* @param {Object} tagSemantics - 标签语义结构
|
||||
* @param {string} tagSemantics.fullSemantics - 完整的语义内容
|
||||
* @param {Array} tagSemantics.references - 引用列表
|
||||
* @param {ResourceManager} resourceManager - 资源管理器
|
||||
* @returns {string} 完整融合的语义内容
|
||||
*/
|
||||
async renderSemanticContent(tagSemantics, resourceManager) {
|
||||
if (!tagSemantics || !tagSemantics.fullSemantics) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let content = tagSemantics.fullSemantics
|
||||
|
||||
if (!tagSemantics.references || tagSemantics.references.length === 0) {
|
||||
return content.trim()
|
||||
}
|
||||
|
||||
// 按出现顺序处理每个@引用(保持位置语义)
|
||||
// 需要按位置排序确保正确的替换顺序
|
||||
const sortedReferences = [...tagSemantics.references].sort((a, b) => a.position - b.position)
|
||||
|
||||
for (const ref of sortedReferences) {
|
||||
try {
|
||||
// 解析引用内容
|
||||
const result = await resourceManager.resolve(ref.fullMatch)
|
||||
|
||||
// 检查解析是否成功
|
||||
if (result.success) {
|
||||
// 提取标签内容(去掉外层DPML标签)
|
||||
const cleanContent = this.extractTagInnerContent(result.content, ref.protocol)
|
||||
// 用<reference>标签包装引用内容,标明这是占位符渲染
|
||||
const wrappedContent = `<reference protocol="${ref.protocol}" resource="${ref.resource}">\n${cleanContent}\n</reference>`
|
||||
// 在原始位置替换@引用为实际内容
|
||||
const refIndex = content.indexOf(ref.fullMatch)
|
||||
if (refIndex !== -1) {
|
||||
content = content.substring(0, refIndex) + wrappedContent + content.substring(refIndex + ref.fullMatch.length)
|
||||
} else {
|
||||
content = content.replace(ref.fullMatch, wrappedContent)
|
||||
}
|
||||
} else {
|
||||
// 解析失败时的优雅降级
|
||||
content = content.replace(ref.fullMatch, `<!-- 引用解析失败: ${ref.fullMatch} - ${result.error?.message || 'Unknown error'} -->`)
|
||||
}
|
||||
} catch (error) {
|
||||
// 引用解析失败时的优雅降级
|
||||
content = content.replace(ref.fullMatch, `<!-- 引用解析失败: ${ref.fullMatch} - ${error.message} -->`)
|
||||
}
|
||||
}
|
||||
|
||||
return content.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取DPML标签内的内容
|
||||
* @param {string} content - 包含DPML标签的完整内容
|
||||
* @param {string} protocol - 协议名称(thought, execution等)
|
||||
* @returns {string} 标签内的纯内容
|
||||
*/
|
||||
extractTagInnerContent(content, protocol) {
|
||||
// 根据协议类型确定标签名
|
||||
const tagName = protocol
|
||||
const regex = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, 'i')
|
||||
const match = content.match(regex)
|
||||
|
||||
if (match && match[1]) {
|
||||
return match[1].trim()
|
||||
}
|
||||
|
||||
// 如果没有匹配到标签,返回原内容(可能已经是纯内容)
|
||||
return content.trim()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SemanticRenderer
|
||||
@ -1,6 +1,7 @@
|
||||
const BaseDiscovery = require('./BaseDiscovery')
|
||||
const RegistryData = require('../RegistryData')
|
||||
const ResourceData = require('../ResourceData')
|
||||
const ResourceFileNaming = require('../ResourceFileNaming')
|
||||
const logger = require('../../../utils/logger')
|
||||
const path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
@ -243,26 +244,31 @@ class PackageDiscovery extends BaseDiscovery {
|
||||
registryData.addResource(resourceData)
|
||||
}
|
||||
|
||||
// 查找thought文件
|
||||
// 查找thought文件 - 使用统一命名管理器
|
||||
const thoughtDir = path.join(itemPath, 'thought')
|
||||
if (await fs.pathExists(thoughtDir)) {
|
||||
const thoughtFile = path.join(thoughtDir, `${item}.thought.md`)
|
||||
if (await fs.pathExists(thoughtFile)) {
|
||||
const reference = `@package://prompt/domain/${item}/thought/${item}.thought.md`
|
||||
|
||||
const resourceData = new ResourceData({
|
||||
id: item,
|
||||
source: 'package',
|
||||
protocol: 'thought',
|
||||
name: ResourceData._generateDefaultName(item, 'thought'),
|
||||
description: ResourceData._generateDefaultDescription(item, 'thought'),
|
||||
reference: reference,
|
||||
metadata: {
|
||||
scannedAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
|
||||
registryData.addResource(resourceData)
|
||||
const thoughtFiles = await ResourceFileNaming.scanTagFiles(thoughtDir, 'thought')
|
||||
|
||||
for (const thoughtFile of thoughtFiles) {
|
||||
const thoughtId = ResourceFileNaming.extractResourceId(thoughtFile, 'thought')
|
||||
if (thoughtId) {
|
||||
const fileName = path.basename(thoughtFile)
|
||||
const reference = `@package://prompt/domain/${item}/thought/${fileName}`
|
||||
|
||||
const resourceData = new ResourceData({
|
||||
id: thoughtId,
|
||||
source: 'package',
|
||||
protocol: 'thought',
|
||||
name: ResourceData._generateDefaultName(thoughtId, 'thought'),
|
||||
description: ResourceData._generateDefaultDescription(thoughtId, 'thought'),
|
||||
reference: reference,
|
||||
metadata: {
|
||||
scannedAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
|
||||
registryData.addResource(resourceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -179,6 +179,26 @@ class ResourceManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理URL格式(如 thought://systematic-testing)
|
||||
const urlMatch = resourceId.match(/^([a-zA-Z][a-zA-Z0-9_-]*):\/\/(.+)$/)
|
||||
if (urlMatch) {
|
||||
const [, protocol, id] = urlMatch
|
||||
const resourceData = this.registryData.findResourceById(id, protocol)
|
||||
if (!resourceData) {
|
||||
throw new Error(`Resource not found: ${resourceId}`)
|
||||
}
|
||||
|
||||
// 通过协议解析加载内容
|
||||
const content = await this.loadResourceByProtocol(resourceData.reference)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content,
|
||||
resourceId,
|
||||
reference: resourceData.reference
|
||||
}
|
||||
}
|
||||
|
||||
// 处理传统格式(如 role:java-developer)
|
||||
let reference = null
|
||||
|
||||
|
||||
Reference in New Issue
Block a user