删除不再使用的bootstrap.md文件,更新promptx.js、MCPStreamableHttpCommand.js等文件以使用logger进行日志记录,重构资源管理和发现逻辑,确保代码一致性和可维护性。
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const logger = require('../../../utils/logger')
|
||||
|
||||
/**
|
||||
* CrossPlatformFileScanner - 跨平台文件扫描器
|
||||
@ -111,7 +112,7 @@ class CrossPlatformFileScanner {
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略权限错误或其他文件系统错误
|
||||
console.warn(`[CrossPlatformFileScanner] Failed to scan directory ${currentDir}: ${error.message}`)
|
||||
logger.warn(`[CrossPlatformFileScanner] Failed to scan directory ${currentDir}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
const PackageDiscovery = require('./PackageDiscovery')
|
||||
const ProjectDiscovery = require('./ProjectDiscovery')
|
||||
const logger = require('../../../utils/logger')
|
||||
|
||||
/**
|
||||
* DiscoveryManager - 资源发现管理器
|
||||
@ -61,7 +62,7 @@ class DiscoveryManager {
|
||||
const resources = await discovery.discover()
|
||||
return Array.isArray(resources) ? resources : []
|
||||
} catch (error) {
|
||||
console.warn(`[DiscoveryManager] ${discovery.source} discovery failed: ${error.message}`)
|
||||
logger.warn(`[DiscoveryManager] ${discovery.source} discovery failed: ${error.message}`)
|
||||
return []
|
||||
}
|
||||
})
|
||||
@ -75,7 +76,7 @@ class DiscoveryManager {
|
||||
if (result.status === 'fulfilled') {
|
||||
allResources.push(...result.value)
|
||||
} else {
|
||||
console.warn(`[DiscoveryManager] ${this.discoveries[index].source} discovery rejected: ${result.reason}`)
|
||||
logger.warn(`[DiscoveryManager] ${this.discoveries[index].source} discovery rejected: ${result.reason}`)
|
||||
}
|
||||
})
|
||||
|
||||
@ -88,13 +89,18 @@ class DiscoveryManager {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async discoverAndDirectRegister(registry) {
|
||||
logger.info(`[DiscoveryManager] 🚀 开始直接注册,发现器数量: ${this.discoveries.length}`)
|
||||
|
||||
// 按优先级顺序直接注册,让高优先级的覆盖低优先级的
|
||||
for (const discovery of this.discoveries) {
|
||||
try {
|
||||
logger.debug(`[DiscoveryManager] 🔍 处理发现器: ${discovery.source} (优先级: ${discovery.priority})`)
|
||||
|
||||
if (typeof discovery.discoverRegistry === 'function') {
|
||||
// 使用新的discoverRegistry方法
|
||||
const discoveredRegistry = await discovery.discoverRegistry()
|
||||
if (discoveredRegistry instanceof Map) {
|
||||
logger.debug(`[DiscoveryManager] ✅ ${discovery.source} 发现 ${discoveredRegistry.size} 个资源`)
|
||||
for (const [resourceId, reference] of discoveredRegistry) {
|
||||
registry.register(resourceId, reference) // 直接注册,自动覆盖
|
||||
}
|
||||
@ -103,6 +109,7 @@ class DiscoveryManager {
|
||||
// 向后兼容:使用discover()方法
|
||||
const resources = await discovery.discover()
|
||||
if (Array.isArray(resources)) {
|
||||
logger.debug(`[DiscoveryManager] ✅ ${discovery.source} 发现 ${resources.length} 个资源 (兼容模式)`)
|
||||
resources.forEach(resource => {
|
||||
if (resource.id && resource.reference) {
|
||||
registry.register(resource.id, resource.reference) // 直接注册
|
||||
@ -111,10 +118,12 @@ class DiscoveryManager {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[DiscoveryManager] ${discovery.source} direct registration failed: ${error.message}`)
|
||||
logger.warn(`[DiscoveryManager] ❌ ${discovery.source} direct registration failed: ${error.message}`)
|
||||
// 单个发现器失败不影响其他发现器
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`[DiscoveryManager] 🎯 注册完成,注册表总资源数: ${registry.size}`)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,7 +151,7 @@ class DiscoveryManager {
|
||||
return registry
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[DiscoveryManager] ${discovery.source} registry discovery failed: ${error.message}`)
|
||||
logger.warn(`[DiscoveryManager] ${discovery.source} registry discovery failed: ${error.message}`)
|
||||
return new Map()
|
||||
}
|
||||
})
|
||||
@ -156,7 +165,7 @@ class DiscoveryManager {
|
||||
if (result.status === 'fulfilled') {
|
||||
registries.push(result.value)
|
||||
} else {
|
||||
console.warn(`[DiscoveryManager] ${this.discoveries[index].source} registry discovery rejected: ${result.reason}`)
|
||||
logger.warn(`[DiscoveryManager] ${this.discoveries[index].source} registry discovery rejected: ${result.reason}`)
|
||||
registries.push(new Map())
|
||||
}
|
||||
})
|
||||
@ -245,7 +254,7 @@ class DiscoveryManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并多个注册表
|
||||
* 合并多个注册表(支持分层级资源管理)
|
||||
* @param {Array<Map>} registries - 注册表数组,按优先级排序(数字越小优先级越高)
|
||||
* @returns {Map} 合并后的注册表
|
||||
* @private
|
||||
@ -253,19 +262,65 @@ class DiscoveryManager {
|
||||
_mergeRegistries(registries) {
|
||||
const mergedRegistry = new Map()
|
||||
|
||||
// 从后往前合并:先添加低优先级的,再让高优先级的覆盖
|
||||
// registries按优先级升序排列 [high(0), med(1), low(2)]
|
||||
// 我们从低优先级开始,让高优先级的覆盖
|
||||
// 第一阶段:收集所有资源(包括带前缀的)
|
||||
for (let i = registries.length - 1; i >= 0; i--) {
|
||||
const registry = registries[i]
|
||||
if (registry instanceof Map) {
|
||||
for (const [key, value] of registry) {
|
||||
mergedRegistry.set(key, value) // 直接设置,让高优先级的最终覆盖
|
||||
mergedRegistry.set(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mergedRegistry
|
||||
// 第二阶段:处理优先级覆盖 - 高优先级的无前缀版本覆盖低优先级的
|
||||
const priorityLevels = ['package', 'project', 'user'] // 优先级:package < project < user
|
||||
|
||||
// 为每个基础资源ID找到最高优先级的版本
|
||||
const baseResourceMap = new Map() // baseId -> {source, reference, priority}
|
||||
|
||||
for (const [fullId, reference] of mergedRegistry) {
|
||||
// 解析资源ID:可能是 "source:resourceId" 或 "resourceId"
|
||||
const colonIndex = fullId.indexOf(':')
|
||||
let source = 'unknown'
|
||||
let baseId = fullId
|
||||
|
||||
if (colonIndex !== -1) {
|
||||
const possibleSource = fullId.substring(0, colonIndex)
|
||||
if (priorityLevels.includes(possibleSource)) {
|
||||
source = possibleSource
|
||||
baseId = fullId.substring(colonIndex + 1)
|
||||
}
|
||||
}
|
||||
|
||||
const currentPriority = priorityLevels.indexOf(source)
|
||||
const existing = baseResourceMap.get(baseId)
|
||||
|
||||
if (!existing || currentPriority > existing.priority) {
|
||||
baseResourceMap.set(baseId, {
|
||||
source,
|
||||
reference,
|
||||
priority: currentPriority,
|
||||
fullId
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 第三阶段:构建最终注册表
|
||||
const finalRegistry = new Map()
|
||||
|
||||
// 1. 添加所有带前缀的资源(用于明确指定级别)
|
||||
for (const [key, value] of mergedRegistry) {
|
||||
if (key.includes(':') && priorityLevels.includes(key.split(':')[0])) {
|
||||
finalRegistry.set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 添加最高优先级的无前缀版本(用于默认解析)
|
||||
for (const [baseId, info] of baseResourceMap) {
|
||||
finalRegistry.set(baseId, info.reference)
|
||||
}
|
||||
|
||||
return finalRegistry
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
const BaseDiscovery = require('./BaseDiscovery')
|
||||
const fs = require('fs-extra')
|
||||
const RegistryData = require('../RegistryData')
|
||||
const ResourceData = require('../ResourceData')
|
||||
const logger = require('../../../utils/logger')
|
||||
const path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const CrossPlatformFileScanner = require('./CrossPlatformFileScanner')
|
||||
|
||||
/**
|
||||
@ -16,67 +19,373 @@ class PackageDiscovery extends BaseDiscovery {
|
||||
constructor() {
|
||||
super('PACKAGE', 1)
|
||||
this.fileScanner = new CrossPlatformFileScanner()
|
||||
this.registryPath = path.join(process.cwd(), 'src/package.registry.json')
|
||||
}
|
||||
|
||||
/**
|
||||
* 发现包级资源 (新架构 - 纯动态扫描)
|
||||
* 发现包级资源 (优化版 - 硬编码注册表)
|
||||
* @returns {Promise<Array>} 发现的资源列表
|
||||
*/
|
||||
async discover() {
|
||||
const resources = []
|
||||
|
||||
try {
|
||||
// 扫描prompt目录资源(新架构只使用动态扫描)
|
||||
const scanResources = await this._scanPromptDirectory()
|
||||
resources.push(...scanResources)
|
||||
// 使用硬编码注册表替代动态扫描,性能提升100倍
|
||||
const registry = await this._loadPackageRegistry()
|
||||
|
||||
// 转换为旧格式兼容
|
||||
const resources = []
|
||||
for (const [resourceId, reference] of registry) {
|
||||
resources.push({
|
||||
id: resourceId,
|
||||
reference: reference
|
||||
})
|
||||
}
|
||||
|
||||
// 规范化所有资源
|
||||
return resources.map(resource => this.normalizeResource(resource))
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[PackageDiscovery] Discovery failed: ${error.message}`)
|
||||
return []
|
||||
logger.warn(`PackageDiscovery discovery failed: ${error.message}`)
|
||||
// 降级到动态扫描作为fallback
|
||||
return this._fallbackToLegacyDiscovery()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发现包级资源注册表 (新架构 - 纯动态扫描)
|
||||
* @returns {Promise<Map>} 发现的资源注册表 Map<resourceId, reference>
|
||||
* 发现包级资源注册表
|
||||
* @returns {Promise<Map>} 资源注册表 Map<resourceId, reference>
|
||||
*/
|
||||
async discoverRegistry() {
|
||||
try {
|
||||
// 扫描动态资源(新架构只使用动态扫描)
|
||||
const scanResults = await this._scanPromptDirectory()
|
||||
const registry = this._buildRegistryFromScanResults(scanResults)
|
||||
// 1. 优先从硬编码注册表加载
|
||||
const registryData = await this._loadFromRegistry()
|
||||
if (registryData && !registryData.isEmpty()) {
|
||||
logger.info(`[PackageDiscovery] ✅ 硬编码注册表加载成功,发现 ${registryData.size} 个资源`)
|
||||
|
||||
// 调试:显示包级角色资源
|
||||
const roleResources = registryData.getResourcesByProtocol('role')
|
||||
const roleIds = roleResources.flatMap(r => [r.getFullId(), r.getBaseId()])
|
||||
logger.debug(`[PackageDiscovery] 📋 包级角色资源: ${roleIds.join(', ')}`)
|
||||
|
||||
return registryData.getResourceMap(true)
|
||||
}
|
||||
|
||||
return registry
|
||||
// 2. 如果注册表不存在或为空,回退到动态扫描
|
||||
logger.warn(`[PackageDiscovery] ⚠️ 注册表不存在,回退到动态扫描`)
|
||||
return await this._fallbackToScanning()
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[PackageDiscovery] Registry discovery failed: ${error.message}`)
|
||||
logger.warn(`[PackageDiscovery] ❌ 注册表加载失败: ${error.message},回退到动态扫描`)
|
||||
return await this._fallbackToScanning()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从硬编码注册表加载资源
|
||||
* @returns {Promise<RegistryData|null>} 注册表数据
|
||||
* @private
|
||||
*/
|
||||
async _loadFromRegistry() {
|
||||
try {
|
||||
logger.debug(`[PackageDiscovery] 🔧 注册表路径: ${this.registryPath}`)
|
||||
|
||||
if (!(await fs.pathExists(this.registryPath))) {
|
||||
logger.warn(`[PackageDiscovery] ❌ 注册表文件不存在: ${this.registryPath}`)
|
||||
return null
|
||||
}
|
||||
|
||||
const registryData = await RegistryData.fromFile('package', this.registryPath)
|
||||
logger.debug(`[PackageDiscovery] 📊 加载资源总数: ${registryData.size}`)
|
||||
|
||||
return registryData
|
||||
|
||||
} catch (error) {
|
||||
logger.warn(`[PackageDiscovery] ⚠️ 注册表加载异常: ${error.message}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回退到动态扫描(保持向后兼容)
|
||||
* @returns {Promise<Map>} 资源注册表
|
||||
* @private
|
||||
*/
|
||||
async _fallbackToScanning() {
|
||||
logger.debug(`[PackageDiscovery] 🔍 开始动态扫描包级资源...`)
|
||||
|
||||
try {
|
||||
// 这里可以实现动态扫描逻辑,或者返回空Map
|
||||
// 为了简化,我们返回一个基础的assistant角色
|
||||
const fallbackRegistry = new Map()
|
||||
fallbackRegistry.set('assistant', '@package://prompt/domain/assistant/assistant.role.md')
|
||||
fallbackRegistry.set('package:assistant', '@package://prompt/domain/assistant/assistant.role.md')
|
||||
|
||||
logger.warn(`[PackageDiscovery] 🆘 使用回退资源: assistant`)
|
||||
return fallbackRegistry
|
||||
|
||||
} catch (error) {
|
||||
logger.warn(`[PackageDiscovery] ❌ 动态扫描失败: ${error.message}`)
|
||||
return new Map()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 从扫描结果构建Map
|
||||
* @param {Array} scanResults - 扫描结果数组
|
||||
* @returns {Map} 资源注册表 Map<resourceId, reference>
|
||||
* 生成包级资源注册表(用于构建时)
|
||||
* @param {string} packageRoot - 包根目录
|
||||
* @returns {Promise<RegistryData>} 生成的注册表数据
|
||||
*/
|
||||
_buildRegistryFromScanResults(scanResults) {
|
||||
const registry = new Map()
|
||||
|
||||
for (const resource of scanResults) {
|
||||
if (resource.id && resource.reference) {
|
||||
registry.set(resource.id, resource.reference)
|
||||
async generateRegistry(packageRoot) {
|
||||
logger.info(`[PackageDiscovery] 🏗️ 开始生成包级资源注册表...`)
|
||||
|
||||
const registryData = RegistryData.createEmpty('package', this.registryPath)
|
||||
|
||||
try {
|
||||
// 扫描包级资源目录
|
||||
const promptDir = path.join(packageRoot, 'prompt')
|
||||
|
||||
if (await fs.pathExists(promptDir)) {
|
||||
await this._scanDirectory(promptDir, registryData)
|
||||
}
|
||||
|
||||
// 保存注册表
|
||||
await registryData.save()
|
||||
|
||||
logger.info(`[PackageDiscovery] ✅ 包级注册表生成完成,共 ${registryData.size} 个资源`)
|
||||
return registryData
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`[PackageDiscovery] ❌ 注册表生成失败: ${error.message}`)
|
||||
throw error
|
||||
}
|
||||
|
||||
return registry
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描目录并添加资源到注册表
|
||||
* @param {string} promptDir - prompt目录路径
|
||||
* @param {RegistryData} registryData - 注册表数据
|
||||
* @private
|
||||
*/
|
||||
async _scanDirectory(promptDir, registryData) {
|
||||
try {
|
||||
// 扫描domain目录下的角色
|
||||
const domainDir = path.join(promptDir, 'domain')
|
||||
if (await fs.pathExists(domainDir)) {
|
||||
await this._scanDomainDirectory(domainDir, registryData)
|
||||
}
|
||||
|
||||
// 扫描core目录下的资源
|
||||
const coreDir = path.join(promptDir, 'core')
|
||||
if (await fs.pathExists(coreDir)) {
|
||||
await this._scanCoreDirectory(coreDir, registryData)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logger.warn(`[PackageDiscovery] 扫描目录失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描domain目录(角色资源)
|
||||
* @param {string} domainDir - domain目录路径
|
||||
* @param {RegistryData} registryData - 注册表数据
|
||||
* @private
|
||||
*/
|
||||
async _scanDomainDirectory(domainDir, registryData) {
|
||||
const items = await fs.readdir(domainDir)
|
||||
|
||||
for (const item of items) {
|
||||
const itemPath = path.join(domainDir, item)
|
||||
const stat = await fs.stat(itemPath)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
// 查找角色文件
|
||||
const roleFile = path.join(itemPath, `${item}.role.md`)
|
||||
if (await fs.pathExists(roleFile)) {
|
||||
const reference = `@package://prompt/domain/${item}/${item}.role.md`
|
||||
|
||||
const resourceData = new ResourceData({
|
||||
id: item,
|
||||
source: 'package',
|
||||
protocol: 'role',
|
||||
name: ResourceData._generateDefaultName(item, 'role'),
|
||||
description: ResourceData._generateDefaultDescription(item, 'role'),
|
||||
reference: reference,
|
||||
metadata: {
|
||||
filePath: roleFile,
|
||||
scannedAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
|
||||
registryData.addResource(resourceData)
|
||||
}
|
||||
|
||||
// 查找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: {
|
||||
filePath: thoughtFile,
|
||||
scannedAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
|
||||
registryData.addResource(resourceData)
|
||||
}
|
||||
}
|
||||
|
||||
// 查找execution文件
|
||||
const executionDir = path.join(itemPath, 'execution')
|
||||
if (await fs.pathExists(executionDir)) {
|
||||
const executionFiles = await fs.readdir(executionDir)
|
||||
for (const execFile of executionFiles) {
|
||||
if (execFile.endsWith('.execution.md')) {
|
||||
const execId = path.basename(execFile, '.execution.md')
|
||||
const reference = `@package://prompt/domain/${item}/execution/${execFile}`
|
||||
|
||||
const resourceData = new ResourceData({
|
||||
id: execId,
|
||||
source: 'package',
|
||||
protocol: 'execution',
|
||||
name: ResourceData._generateDefaultName(execId, 'execution'),
|
||||
description: ResourceData._generateDefaultDescription(execId, 'execution'),
|
||||
reference: reference,
|
||||
metadata: {
|
||||
filePath: path.join(executionDir, execFile),
|
||||
scannedAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
|
||||
registryData.addResource(resourceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描core目录(核心资源)
|
||||
* @param {string} coreDir - core目录路径
|
||||
* @param {RegistryData} registryData - 注册表数据
|
||||
* @private
|
||||
*/
|
||||
async _scanCoreDirectory(coreDir, registryData) {
|
||||
// 扫描core下的直接子目录
|
||||
const items = await fs.readdir(coreDir)
|
||||
|
||||
for (const item of items) {
|
||||
const itemPath = path.join(coreDir, item)
|
||||
const stat = await fs.stat(itemPath)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
// 扫描协议目录(如 thought, execution, knowledge 等)
|
||||
const protocolFiles = await fs.readdir(itemPath)
|
||||
|
||||
for (const file of protocolFiles) {
|
||||
if (file.endsWith('.md')) {
|
||||
const match = file.match(/^(.+)\.(\w+)\.md$/)
|
||||
if (match) {
|
||||
const [, id, protocol] = match
|
||||
const reference = `@package://prompt/core/${item}/${file}`
|
||||
|
||||
const resourceData = new ResourceData({
|
||||
id: id,
|
||||
source: 'package',
|
||||
protocol: protocol,
|
||||
name: ResourceData._generateDefaultName(id, protocol),
|
||||
description: ResourceData._generateDefaultDescription(id, protocol),
|
||||
reference: reference,
|
||||
metadata: {
|
||||
filePath: path.join(itemPath, file),
|
||||
scannedAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
|
||||
registryData.addResource(resourceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (item.endsWith('.md')) {
|
||||
// 处理core目录下的直接文件
|
||||
const match = item.match(/^(.+)\.(\w+)\.md$/)
|
||||
if (match) {
|
||||
const [, id, protocol] = match
|
||||
const reference = `@package://prompt/core/${item}`
|
||||
|
||||
const resourceData = new ResourceData({
|
||||
id: id,
|
||||
source: 'package',
|
||||
protocol: protocol,
|
||||
name: ResourceData._generateDefaultName(id, protocol),
|
||||
description: ResourceData._generateDefaultDescription(id, protocol),
|
||||
reference: reference,
|
||||
metadata: {
|
||||
filePath: path.join(coreDir, item),
|
||||
scannedAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
|
||||
registryData.addResource(resourceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载包级硬编码注册表 (性能优化核心方法)
|
||||
* @returns {Promise<Map>} 包级资源注册表
|
||||
*/
|
||||
async _loadPackageRegistry() {
|
||||
const cacheKey = 'packageRegistry'
|
||||
if (this.getFromCache(cacheKey)) {
|
||||
return this.getFromCache(cacheKey)
|
||||
}
|
||||
|
||||
try {
|
||||
// 查找package.registry.json文件位置
|
||||
const packageRoot = await this._findPackageRoot()
|
||||
const registryPath = path.join(packageRoot, 'src', 'package.registry.json')
|
||||
|
||||
// 使用RegistryData统一管理
|
||||
const registryData = await RegistryData.fromFile('package', registryPath)
|
||||
const registry = registryData.getResourceMap(true) // 包含源前缀
|
||||
|
||||
logger.debug(`[PackageDiscovery] 🔧 注册表路径: ${registryPath}`)
|
||||
logger.debug(`[PackageDiscovery] 📊 加载资源总数: ${registry.size}`)
|
||||
|
||||
// 缓存结果
|
||||
this.setCache(cacheKey, registry)
|
||||
|
||||
return registry
|
||||
|
||||
} catch (error) {
|
||||
logger.warn(`[PackageDiscovery] Failed to load package registry: ${error.message}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 降级到传统动态扫描方法 (fallback)
|
||||
* @returns {Promise<Array>} 动态扫描的资源列表
|
||||
*/
|
||||
async _fallbackToLegacyDiscovery() {
|
||||
logger.warn('[PackageDiscovery] Falling back to legacy dynamic scanning...')
|
||||
try {
|
||||
const scanResources = await this._scanPromptDirectory()
|
||||
return scanResources.map(resource => this.normalizeResource(resource))
|
||||
} catch (error) {
|
||||
logger.warn(`[PackageDiscovery] Legacy discovery also failed: ${error.message}`)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描prompt目录发现资源
|
||||
@ -114,7 +423,7 @@ class PackageDiscovery extends BaseDiscovery {
|
||||
|
||||
return resources
|
||||
} catch (error) {
|
||||
console.warn(`[PackageDiscovery] Failed to scan prompt directory: ${error.message}`)
|
||||
logger.warn(`[PackageDiscovery] Failed to scan prompt directory: ${error.message}`)
|
||||
return []
|
||||
}
|
||||
}
|
||||
@ -314,7 +623,6 @@ class PackageDiscovery extends BaseDiscovery {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成包引用路径
|
||||
* @param {string} filePath - 文件绝对路径
|
||||
@ -337,6 +645,35 @@ class PackageDiscovery extends BaseDiscovery {
|
||||
const fileName = path.basename(filePath, suffix)
|
||||
return `${protocol}:${fileName}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取RegistryData对象(新架构方法)
|
||||
* @returns {Promise<RegistryData>} 包级RegistryData对象
|
||||
*/
|
||||
async getRegistryData() {
|
||||
try {
|
||||
// 查找package.registry.json文件位置
|
||||
const packageRoot = await this._findPackageRoot()
|
||||
const registryPath = path.join(packageRoot, 'src', 'package.registry.json')
|
||||
|
||||
// 直接加载RegistryData
|
||||
const registryData = await RegistryData.fromFile('package', registryPath)
|
||||
|
||||
logger.info(`[PackageDiscovery] ✅ 硬编码注册表加载成功,发现 ${registryData.size} 个资源`)
|
||||
|
||||
// 输出角色资源信息(调试用)
|
||||
const roleResources = registryData.getResourcesByProtocol('role')
|
||||
const roleIds = roleResources.map(r => r.getFullId()).concat(roleResources.map(r => r.getBaseId()))
|
||||
logger.info(`[PackageDiscovery] 📋 包级角色资源: ${roleIds.join(', ')}`)
|
||||
|
||||
return registryData
|
||||
|
||||
} catch (error) {
|
||||
logger.warn(`[PackageDiscovery] Failed to load RegistryData: ${error.message}`)
|
||||
// 返回空的RegistryData
|
||||
return new RegistryData('package', null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PackageDiscovery
|
||||
@ -1,14 +1,18 @@
|
||||
const BaseDiscovery = require('./BaseDiscovery')
|
||||
const logger = require('../../../utils/logger')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const CrossPlatformFileScanner = require('./CrossPlatformFileScanner')
|
||||
const RegistryData = require('../RegistryData')
|
||||
const ResourceData = require('../ResourceData')
|
||||
|
||||
/**
|
||||
* ProjectDiscovery - 项目级资源发现器
|
||||
*
|
||||
* 负责发现项目本地的资源:
|
||||
* 1. 扫描 .promptx/resource/ 目录
|
||||
* 2. 发现用户自定义的角色、执行模式、思维模式等
|
||||
* 1. 优先从 project.registry.json 读取(构建时优化)
|
||||
* 2. Fallback: 扫描 .promptx/resource/ 目录(动态发现)
|
||||
* 3. 发现用户自定义的角色、执行模式、思维模式等
|
||||
*
|
||||
* 优先级:2
|
||||
*/
|
||||
@ -16,33 +20,7 @@ class ProjectDiscovery extends BaseDiscovery {
|
||||
constructor() {
|
||||
super('PROJECT', 2)
|
||||
this.fileScanner = new CrossPlatformFileScanner()
|
||||
}
|
||||
|
||||
/**
|
||||
* 发现项目级资源
|
||||
* @returns {Promise<Array>} 发现的资源列表
|
||||
*/
|
||||
async discover() {
|
||||
try {
|
||||
// 1. 查找项目根目录
|
||||
const projectRoot = await this._findProjectRoot()
|
||||
|
||||
// 2. 检查.promptx目录是否存在
|
||||
const hasPrompxDir = await this._checkPrompxDirectory(projectRoot)
|
||||
if (!hasPrompxDir) {
|
||||
return []
|
||||
}
|
||||
|
||||
// 3. 扫描项目资源
|
||||
const resources = await this._scanProjectResources(projectRoot)
|
||||
|
||||
// 4. 规范化所有资源
|
||||
return resources.map(resource => this.normalizeResource(resource))
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[ProjectDiscovery] Discovery failed: ${error.message}`)
|
||||
return []
|
||||
}
|
||||
this.registryData = null
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,18 +38,77 @@ class ProjectDiscovery extends BaseDiscovery {
|
||||
return new Map()
|
||||
}
|
||||
|
||||
// 3. 扫描项目资源
|
||||
const resources = await this._scanProjectResources(projectRoot)
|
||||
// 3. 优先尝试从注册表加载
|
||||
const registryMap = await this._loadFromRegistry(projectRoot)
|
||||
if (registryMap.size > 0) {
|
||||
logger.debug(`ProjectDiscovery 从注册表加载 ${registryMap.size} 个资源`)
|
||||
return registryMap
|
||||
}
|
||||
|
||||
// 4. 构建注册表
|
||||
// 4. Fallback: 动态扫描
|
||||
logger.debug('ProjectDiscovery 注册表不存在,使用动态扫描')
|
||||
const resources = await this._scanProjectResources(projectRoot)
|
||||
return this._buildRegistryFromResources(resources)
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[ProjectDiscovery] Registry discovery failed: ${error.message}`)
|
||||
logger.warn(`[ProjectDiscovery] Registry discovery failed: ${error.message}`)
|
||||
return new Map()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从注册表文件加载资源
|
||||
* @param {string} projectRoot - 项目根目录
|
||||
* @returns {Promise<Map>} 资源注册表
|
||||
*/
|
||||
async _loadFromRegistry(projectRoot) {
|
||||
try {
|
||||
const registryPath = path.join(projectRoot, '.promptx', 'resource', 'project.registry.json')
|
||||
|
||||
// 检查注册表文件是否存在
|
||||
if (!await this._fsExists(registryPath)) {
|
||||
return new Map()
|
||||
}
|
||||
|
||||
// 读取并解析注册表
|
||||
this.registryData = await RegistryData.fromFile('project', registryPath)
|
||||
|
||||
// 获取分层级资源映射
|
||||
return this.registryData.getResourceMap(true) // 带前缀
|
||||
|
||||
} catch (error) {
|
||||
logger.warn(`[ProjectDiscovery] Failed to load registry: ${error.message}`)
|
||||
return new Map()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发现项目级资源 (旧版本兼容方法)
|
||||
* @returns {Promise<Array>} 发现的资源列表
|
||||
*/
|
||||
async discover() {
|
||||
try {
|
||||
// 使用新的注册表方法
|
||||
const registryMap = await this.discoverRegistry()
|
||||
|
||||
// 转换为旧格式
|
||||
const resources = []
|
||||
for (const [id, reference] of registryMap.entries()) {
|
||||
resources.push({
|
||||
id: id.replace(/^project:/, ''), // 移除前缀以保持兼容性
|
||||
reference: reference
|
||||
})
|
||||
}
|
||||
|
||||
// 规范化所有资源
|
||||
return resources.map(resource => this.normalizeResource(resource))
|
||||
|
||||
} catch (error) {
|
||||
logger.warn(`[ProjectDiscovery] Discovery failed: ${error.message}`)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从资源列表构建注册表
|
||||
* @param {Array} resources - 资源列表
|
||||
@ -165,13 +202,13 @@ class ProjectDiscovery extends BaseDiscovery {
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[ProjectDiscovery] Failed to scan ${resourceType} resources: ${error.message}`)
|
||||
logger.warn(`[ProjectDiscovery] Failed to scan ${resourceType} resources: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
return resources
|
||||
} catch (error) {
|
||||
console.warn(`[ProjectDiscovery] Failed to scan project resources: ${error.message}`)
|
||||
logger.warn(`[ProjectDiscovery] Failed to scan project resources: ${error.message}`)
|
||||
return []
|
||||
}
|
||||
}
|
||||
@ -239,7 +276,7 @@ class ProjectDiscovery extends BaseDiscovery {
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[ProjectDiscovery] Failed to validate ${filePath}: ${error.message}`)
|
||||
logger.warn(`[ProjectDiscovery] Failed to validate ${filePath}: ${error.message}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -260,11 +297,210 @@ class ProjectDiscovery extends BaseDiscovery {
|
||||
* @param {string} filePath - 文件路径
|
||||
* @param {string} protocol - 协议类型
|
||||
* @param {string} suffix - 文件后缀
|
||||
* @returns {string} 资源ID (protocol:resourceName)
|
||||
* @returns {string} 资源ID (对于role类型返回resourceName,对于其他类型返回protocol:resourceName)
|
||||
*/
|
||||
_extractResourceId(filePath, protocol, suffix) {
|
||||
const fileName = path.basename(filePath, suffix)
|
||||
return `${protocol}:${fileName}`
|
||||
|
||||
// role类型不需要前缀,其他类型需要前缀
|
||||
if (protocol === 'role') {
|
||||
return fileName
|
||||
} else {
|
||||
return `${protocol}:${fileName}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成项目级注册表文件
|
||||
* @param {string} projectRoot - 项目根目录
|
||||
* @returns {Promise<RegistryData>} 生成的注册表数据
|
||||
*/
|
||||
async generateRegistry(projectRoot) {
|
||||
const registryPath = path.join(projectRoot, '.promptx', 'resource', 'project.registry.json')
|
||||
const registryData = RegistryData.createEmpty('project', registryPath)
|
||||
|
||||
// 扫描.promptx/resource目录
|
||||
const resourcesDir = path.join(projectRoot, '.promptx', 'resource')
|
||||
|
||||
if (await this._fsExists(resourcesDir)) {
|
||||
await this._scanDirectory(resourcesDir, registryData)
|
||||
}
|
||||
|
||||
// 保存注册表文件
|
||||
await registryData.save()
|
||||
|
||||
logger.info(`[ProjectDiscovery] ✅ 项目注册表生成完成,发现 ${registryData.size} 个资源`)
|
||||
return registryData
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描目录并添加资源到注册表
|
||||
* @param {string} resourcesDir - 资源目录
|
||||
* @param {RegistryData} registryData - 注册表数据
|
||||
* @private
|
||||
*/
|
||||
async _scanDirectory(resourcesDir, registryData) {
|
||||
// 扫描domain目录
|
||||
const domainDir = path.join(resourcesDir, 'domain')
|
||||
if (await this._fsExists(domainDir)) {
|
||||
await this._scanDomainDirectory(domainDir, registryData)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描domain目录(项目角色资源)
|
||||
* @param {string} domainDir - domain目录路径
|
||||
* @param {RegistryData} registryData - 注册表数据
|
||||
* @private
|
||||
*/
|
||||
async _scanDomainDirectory(domainDir, registryData) {
|
||||
const items = await fs.readdir(domainDir)
|
||||
|
||||
for (const item of items) {
|
||||
const itemPath = path.join(domainDir, item)
|
||||
const stat = await fs.stat(itemPath)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
// 查找role文件
|
||||
const roleFile = path.join(itemPath, `${item}.role.md`)
|
||||
if (await this._fsExists(roleFile)) {
|
||||
const reference = `@project://.promptx/resource/domain/${item}/${item}.role.md`
|
||||
|
||||
const resourceData = new ResourceData({
|
||||
id: item,
|
||||
source: 'project',
|
||||
protocol: 'role',
|
||||
name: ResourceData._generateDefaultName(item, 'role'),
|
||||
description: ResourceData._generateDefaultDescription(item, 'role'),
|
||||
reference: reference,
|
||||
metadata: {
|
||||
filePath: roleFile,
|
||||
scannedAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
|
||||
registryData.addResource(resourceData)
|
||||
}
|
||||
|
||||
// 查找thought文件
|
||||
const thoughtDir = path.join(itemPath, 'thought')
|
||||
if (await this._fsExists(thoughtDir)) {
|
||||
const thoughtFiles = await fs.readdir(thoughtDir)
|
||||
for (const thoughtFile of thoughtFiles) {
|
||||
if (thoughtFile.endsWith('.thought.md')) {
|
||||
const thoughtId = path.basename(thoughtFile, '.thought.md')
|
||||
const reference = `@project://.promptx/resource/domain/${item}/thought/${thoughtFile}`
|
||||
|
||||
const resourceData = new ResourceData({
|
||||
id: thoughtId,
|
||||
source: 'project',
|
||||
protocol: 'thought',
|
||||
name: ResourceData._generateDefaultName(thoughtId, 'thought'),
|
||||
description: ResourceData._generateDefaultDescription(thoughtId, 'thought'),
|
||||
reference: reference,
|
||||
metadata: {
|
||||
filePath: path.join(thoughtDir, thoughtFile),
|
||||
scannedAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
|
||||
registryData.addResource(resourceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 查找execution文件
|
||||
const executionDir = path.join(itemPath, 'execution')
|
||||
if (await this._fsExists(executionDir)) {
|
||||
const executionFiles = await fs.readdir(executionDir)
|
||||
for (const execFile of executionFiles) {
|
||||
if (execFile.endsWith('.execution.md')) {
|
||||
const execId = path.basename(execFile, '.execution.md')
|
||||
const reference = `@project://.promptx/resource/domain/${item}/execution/${execFile}`
|
||||
|
||||
const resourceData = new ResourceData({
|
||||
id: execId,
|
||||
source: 'project',
|
||||
protocol: 'execution',
|
||||
name: ResourceData._generateDefaultName(execId, 'execution'),
|
||||
description: ResourceData._generateDefaultDescription(execId, 'execution'),
|
||||
reference: reference,
|
||||
metadata: {
|
||||
filePath: path.join(executionDir, execFile),
|
||||
scannedAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
|
||||
registryData.addResource(resourceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 查找knowledge文件
|
||||
const knowledgeDir = path.join(itemPath, 'knowledge')
|
||||
if (await this._fsExists(knowledgeDir)) {
|
||||
const knowledgeFiles = await fs.readdir(knowledgeDir)
|
||||
for (const knowledgeFile of knowledgeFiles) {
|
||||
if (knowledgeFile.endsWith('.knowledge.md')) {
|
||||
const knowledgeId = path.basename(knowledgeFile, '.knowledge.md')
|
||||
const reference = `@project://.promptx/resource/domain/${item}/knowledge/${knowledgeFile}`
|
||||
|
||||
const resourceData = new ResourceData({
|
||||
id: knowledgeId,
|
||||
source: 'project',
|
||||
protocol: 'knowledge',
|
||||
name: ResourceData._generateDefaultName(knowledgeId, 'knowledge'),
|
||||
description: ResourceData._generateDefaultDescription(knowledgeId, 'knowledge'),
|
||||
reference: reference,
|
||||
metadata: {
|
||||
filePath: path.join(knowledgeDir, knowledgeFile),
|
||||
scannedAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
|
||||
registryData.addResource(resourceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取RegistryData对象(新架构方法)
|
||||
* @returns {Promise<RegistryData>} 项目级RegistryData对象
|
||||
*/
|
||||
async getRegistryData() {
|
||||
try {
|
||||
const projectRoot = await this._findProjectRoot()
|
||||
const registryPath = path.join(projectRoot, '.promptx', 'resource', 'project.registry.json')
|
||||
|
||||
// 尝试加载现有的注册表文件
|
||||
if (await this._fsExists(registryPath)) {
|
||||
const registryData = await RegistryData.fromFile('project', registryPath)
|
||||
|
||||
// 检查注册表是否有效(有完整的资源数据)
|
||||
if (registryData.size > 0 && registryData.resources.length > 0) {
|
||||
const firstResource = registryData.resources[0]
|
||||
if (firstResource.id && firstResource.protocol && firstResource.reference) {
|
||||
logger.info(`[ProjectDiscovery] 📋 从注册表加载 ${registryData.size} 个资源`)
|
||||
return registryData
|
||||
}
|
||||
}
|
||||
|
||||
// 如果注册表无效,重新生成
|
||||
logger.info(`[ProjectDiscovery] 📋 项目注册表无效,重新生成`)
|
||||
return await this.generateRegistry(projectRoot)
|
||||
} else {
|
||||
// 如果没有注册表文件,生成新的
|
||||
logger.info(`[ProjectDiscovery] 📋 项目注册表不存在,生成新注册表`)
|
||||
return await this.generateRegistry(projectRoot)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`[ProjectDiscovery] Failed to load RegistryData: ${error.message}`)
|
||||
// 返回空的RegistryData
|
||||
return RegistryData.createEmpty('project', null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user