dpml-prompt
Version:
DPML-powered AI prompt framework - Revolutionary AI-First CLI system based on Deepractice Prompt Markup Language. Build sophisticated AI agents with structured prompts, memory systems, and execution frameworks.
687 lines (593 loc) • 18.4 kB
JavaScript
const fs = require('fs-extra')
const path = require('path')
const os = require('os')
const ProjectManager = require('./ProjectManager')
/**
* 目录定位器基础抽象类
* 统一管理所有路径解析逻辑,支持跨平台差异化实现
*/
class DirectoryLocator {
constructor(options = {}) {
this.options = options
this.cache = new Map()
this.platform = process.platform
}
/**
* 抽象方法:定位目录
* @param {Object} context - 定位上下文
* @returns {Promise<string>} 定位到的目录路径
*/
async locate(context = {}) {
throw new Error('子类必须实现 locate 方法')
}
/**
* 获取缓存
*/
getCached(key) {
return this.cache.get(key)
}
/**
* 设置缓存
*/
setCached(key, value) {
this.cache.set(key, value)
return value
}
/**
* 清除缓存
*/
clearCache() {
this.cache.clear()
}
/**
* 检查路径是否存在且是目录
*/
async isValidDirectory(dirPath) {
try {
const stat = await fs.stat(dirPath)
return stat.isDirectory()
} catch {
return false
}
}
/**
* 规范化路径
*/
normalizePath(inputPath) {
if (!inputPath || typeof inputPath !== 'string') {
return null
}
return path.resolve(inputPath)
}
/**
* 展开家目录路径
*/
expandHome(filepath) {
if (!filepath || typeof filepath !== 'string') {
return ''
}
if (filepath.startsWith('~/') || filepath === '~') {
return path.join(os.homedir(), filepath.slice(2))
}
return filepath
}
}
/**
* 项目根目录定位器
* 负责查找项目的根目录
*/
class ProjectRootLocator extends DirectoryLocator {
constructor(options = {}) {
super(options)
// 初始化AI驱动的项目管理器
this.projectManager = new ProjectManager()
// 可配置的查找策略优先级(按可靠性和准确性排序)
this.strategies = options.strategies || [
'aiProvidedProjectPath', // 1. AI提供的项目路径(最可靠,由AI告知)
'existingPromptxDirectory', // 2. 现有.promptx目录(最可靠的项目标识)
'packageJsonDirectory', // 3. 向上查找项目标识文件(最准确的项目边界)
'gitRootDirectory', // 4. Git根目录(通用可靠)
'currentWorkingDirectoryIfHasMarkers', // 5. 当前目录项目标识(降级策略)
'currentWorkingDirectory' // 6. 纯当前目录(最后回退)
]
// 项目标识文件
this.projectMarkers = options.projectMarkers || [
'package.json',
'.git',
'pyproject.toml',
'Cargo.toml',
'pom.xml',
'build.gradle',
'composer.json'
]
}
/**
* 定位项目根目录
*/
async locate(context = {}) {
const { startDir = process.cwd() } = context
const cacheKey = `projectRoot:${startDir}`
// 检查缓存
const cached = this.getCached(cacheKey)
if (cached) {
return cached
}
// 使用上下文中的策略或默认策略
const strategies = context.strategies || this.strategies
// 按策略优先级查找
for (const strategy of strategies) {
const result = await this._executeStrategy(strategy, startDir, context)
if (result && await this._validateProjectRoot(result, context)) {
return this.setCached(cacheKey, result)
}
}
// 如果所有策略都失败,返回起始目录
return this.setCached(cacheKey, startDir)
}
/**
* 执行特定的查找策略
*/
async _executeStrategy(strategy, startDir, context) {
switch (strategy) {
case 'aiProvidedProjectPath':
return await this._findByAIProvidedPath()
case 'existingPromptxDirectory':
return await this._findByExistingPromptx(startDir)
case 'currentWorkingDirectoryIfHasMarkers':
return await this._checkCurrentDirForMarkers(startDir)
case 'packageJsonDirectory':
return await this._findByProjectMarkers(startDir)
case 'gitRootDirectory':
return await this._findByGitRoot(startDir)
case 'currentWorkingDirectory':
return startDir
default:
return null
}
}
/**
* 通过AI提供的项目路径查找(最高优先级)
*/
async _findByAIProvidedPath() {
try {
// 注意:多项目环境下需要传入mcpId,这里使用临时ID
const tempMcpId = process.env.PROMPTX_MCP_ID || `temp-${process.pid}`
const projects = await this.projectManager.getProjectsByMcpId(tempMcpId)
const aiProvidedPath = projects.length > 0 ? projects[0].projectPath : null
if (aiProvidedPath && await this.isValidDirectory(aiProvidedPath)) {
return aiProvidedPath
}
} catch (error) {
// AI提供的路径获取失败,继续使用其他策略
}
return null
}
/**
* 检查当前目录是否包含项目标识文件
*/
async _checkCurrentDirForMarkers(startDir) {
const currentDir = path.resolve(startDir)
// 检查当前目录是否包含项目标识文件
for (const marker of this.projectMarkers) {
const markerPath = path.join(currentDir, marker)
if (await fs.pathExists(markerPath)) {
return currentDir
}
}
return null
}
/**
* 通过现有.promptx目录查找
*/
async _findByExistingPromptx(startDir) {
let currentDir = path.resolve(startDir)
const root = path.parse(currentDir).root
while (currentDir !== root) {
const promptxPath = path.join(currentDir, '.promptx')
if (await this.isValidDirectory(promptxPath)) {
return currentDir
}
const parentDir = path.dirname(currentDir)
if (parentDir === currentDir) break
currentDir = parentDir
}
return null
}
/**
* 通过项目标识文件查找
*/
async _findByProjectMarkers(startDir) {
let currentDir = path.resolve(startDir)
const root = path.parse(currentDir).root
while (currentDir !== root) {
for (const marker of this.projectMarkers) {
const markerPath = path.join(currentDir, marker)
if (await fs.pathExists(markerPath)) {
return currentDir
}
}
const parentDir = path.dirname(currentDir)
if (parentDir === currentDir) break
currentDir = parentDir
}
return null
}
/**
* 通过Git根目录查找
*/
async _findByGitRoot(startDir) {
let currentDir = path.resolve(startDir)
const root = path.parse(currentDir).root
while (currentDir !== root) {
const gitPath = path.join(currentDir, '.git')
if (await fs.pathExists(gitPath)) {
return currentDir
}
const parentDir = path.dirname(currentDir)
if (parentDir === currentDir) break
currentDir = parentDir
}
return null
}
/**
* 验证项目根目录
*/
async _validateProjectRoot(projectRoot, context = {}) {
// Windows平台:避免用户家目录
if (this.platform === 'win32' && context.avoidUserHome !== false) {
const homeDir = os.homedir()
if (path.resolve(projectRoot) === path.resolve(homeDir)) {
return false
}
}
return await this.isValidDirectory(projectRoot)
}
}
/**
* PromptX工作空间定位器
* 负责确定.promptx目录的位置
*/
class PromptXWorkspaceLocator extends DirectoryLocator {
constructor(options = {}) {
super(options)
this.projectRootLocator = options.projectRootLocator || new ProjectRootLocator(options)
this.projectManager = new ProjectManager()
}
/**
* 定位PromptX工作空间
*/
async locate(context = {}) {
const cacheKey = `promptxWorkspace:${JSON.stringify(context)}`
// 检查缓存
const cached = this.getCached(cacheKey)
if (cached) {
return cached
}
// 策略1:AI提供的项目路径(最高优先级 - AI驱动的路径管理)
const workspaceFromAI = await this._fromAIProvidedPath()
if (workspaceFromAI) {
return this.setCached(cacheKey, workspaceFromAI)
}
// 策略2:IDE环境变量(用户/IDE明确指定)
const workspaceFromIDE = await this._fromIDEEnvironment()
if (workspaceFromIDE) {
return this.setCached(cacheKey, workspaceFromIDE)
}
// 策略3:PromptX专用环境变量(用户手动配置)
const workspaceFromEnv = await this._fromPromptXEnvironment()
if (workspaceFromEnv) {
return this.setCached(cacheKey, workspaceFromEnv)
}
// 策略4:特定上下文策略(如init命令的强制指定)
if (context.strategies) {
const workspaceFromProject = await this._fromProjectRoot(context)
if (workspaceFromProject) {
return this.setCached(cacheKey, workspaceFromProject)
}
}
// 策略5:现有.promptx目录(已初始化的项目)
const workspaceFromExisting = await this._fromExistingDirectory(context.startDir)
if (workspaceFromExisting) {
return this.setCached(cacheKey, workspaceFromExisting)
}
// 策略6:项目根目录(基于项目结构推断)
const workspaceFromProject = await this._fromProjectRoot(context)
if (workspaceFromProject) {
return this.setCached(cacheKey, workspaceFromProject)
}
// 策略7:智能回退策略(兜底方案)
return this.setCached(cacheKey, await this._getSmartFallback(context))
}
/**
* 从AI提供的项目路径获取(最高优先级)
*/
async _fromAIProvidedPath() {
try {
// 注意:多项目环境下需要传入mcpId,这里使用临时ID
const tempMcpId = process.env.PROMPTX_MCP_ID || `temp-${process.pid}`
const projects = await this.projectManager.getProjectsByMcpId(tempMcpId)
const aiProvidedPath = projects.length > 0 ? projects[0].projectPath : null
if (aiProvidedPath && await this.isValidDirectory(aiProvidedPath)) {
return aiProvidedPath
}
} catch (error) {
// AI提供的路径获取失败,继续使用其他策略
}
return null
}
/**
* 从IDE环境变量获取(支持多种IDE)
*/
async _fromIDEEnvironment() {
// IDE环境变量检测策略(按优先级排序)
const ideStrategies = [
// Claude IDE (现有格式)
{
name: 'Claude IDE',
vars: ['WORKSPACE_FOLDER_PATHS'],
parse: (value, varName) => {
try {
const folders = JSON.parse(value)
return Array.isArray(folders) && folders.length > 0 ? folders[0] : null
} catch {
return null
}
}
},
// VSCode
{
name: 'VSCode',
vars: ['VSCODE_WORKSPACE_FOLDER', 'VSCODE_CWD'],
parse: (value, varName) => value
},
// IntelliJ IDEA / WebStorm / PhpStorm
{
name: 'JetBrains IDEs',
vars: ['PROJECT_ROOT', 'IDEA_INITIAL_DIRECTORY', 'WEBSTORM_PROJECT_PATH'],
parse: (value, varName) => value
},
// Sublime Text
{
name: 'Sublime Text',
vars: ['SUBLIME_PROJECT_PATH', 'SUBL_PROJECT_DIR'],
parse: (value, varName) => value
},
// Atom
{
name: 'Atom',
vars: ['ATOM_PROJECT_PATH', 'ATOM_HOME_PROJECT'],
parse: (value, varName) => value
},
// Vim/Neovim
{
name: 'Vim/Neovim',
vars: ['VIM_PROJECT_ROOT', 'NVIM_PROJECT_ROOT'],
parse: (value, varName) => value
},
// 字节跳动 Trae 和其他基于PWD的IDE
{
name: 'ByteDance Trae & PWD-based IDEs',
vars: ['PWD', 'TRAE_WORKSPACE', 'BYTEDANCE_WORKSPACE'],
parse: (value, varName) => {
// 对于专用环境变量,直接使用
if (varName === 'TRAE_WORKSPACE' || varName === 'BYTEDANCE_WORKSPACE') {
return value
}
// 对于PWD,只有当它与process.cwd()不同时,才认为是IDE设置的项目路径
if (varName === 'PWD') {
const currentCwd = process.cwd()
if (value && value !== currentCwd) {
return value
}
}
return null
}
},
// 通用工作目录
{
name: 'Generic',
vars: ['WORKSPACE_ROOT', 'PROJECT_DIR', 'WORKING_DIRECTORY'],
parse: (value, varName) => value
}
]
// 按策略逐一检测
for (const strategy of ideStrategies) {
for (const varName of strategy.vars) {
const envValue = process.env[varName]
if (envValue && envValue.trim() !== '') {
// 传递varName给parse函数,支持变量名相关的解析逻辑
const parsedPath = strategy.parse(envValue.trim(), varName)
if (parsedPath) {
const normalizedPath = this.normalizePath(this.expandHome(parsedPath))
if (normalizedPath && await this.isValidDirectory(normalizedPath)) {
// 记录检测到的IDE类型(用于调试)
this._detectedIDE = strategy.name
return normalizedPath
}
}
}
}
}
return null
}
/**
* 从PromptX环境变量获取
*/
async _fromPromptXEnvironment() {
const promptxWorkspaceEnv = process.env.PROMPTX_WORKSPACE
if (promptxWorkspaceEnv && promptxWorkspaceEnv.trim() !== '') {
const workspacePath = this.normalizePath(this.expandHome(promptxWorkspaceEnv))
if (workspacePath && await this.isValidDirectory(workspacePath)) {
return workspacePath
}
}
return null
}
/**
* 从现有.promptx目录获取
*/
async _fromExistingDirectory(startDir) {
const projectRoot = await this.projectRootLocator._findByExistingPromptx(startDir || process.cwd())
return projectRoot
}
/**
* 从项目根目录获取
*/
async _fromProjectRoot(context) {
const projectRoot = await this.projectRootLocator.locate(context)
return projectRoot
}
/**
* 智能回退策略
*/
async _getSmartFallback(context) {
// 1. 尝试从命令行参数推断
const argPath = await this._fromProcessArguments()
if (argPath && await this.isValidDirectory(argPath)) {
return argPath
}
// 2. 尝试从进程的工作目录
const processCwd = process.cwd()
if (await this.isValidDirectory(processCwd)) {
return processCwd
}
// 3. 最后回退到用户主目录
return os.homedir()
}
/**
* 从进程参数推断项目路径
*/
async _fromProcessArguments() {
const args = process.argv
// 查找可能的路径参数
for (let i = 0; i < args.length; i++) {
const arg = args[i]
// 查找 --project-path 或类似参数
if (arg.startsWith('--project-path=')) {
return arg.split('=')[1]
}
if (arg === '--project-path' && i + 1 < args.length) {
return args[i + 1]
}
// 查找 --cwd 参数
if (arg.startsWith('--cwd=')) {
return arg.split('=')[1]
}
if (arg === '--cwd' && i + 1 < args.length) {
return args[i + 1]
}
}
return null
}
/**
* 获取检测调试信息
*/
getDetectionInfo() {
return {
detectedIDE: this._detectedIDE || 'Unknown',
availableEnvVars: this._getAvailableEnvVars(),
platform: process.platform,
cwd: process.cwd(),
args: process.argv
}
}
/**
* 获取可用的环境变量
*/
_getAvailableEnvVars() {
const relevantVars = [
'WORKSPACE_FOLDER_PATHS', 'VSCODE_WORKSPACE_FOLDER', 'VSCODE_CWD',
'PROJECT_ROOT', 'IDEA_INITIAL_DIRECTORY', 'WEBSTORM_PROJECT_PATH',
'SUBLIME_PROJECT_PATH', 'SUBL_PROJECT_DIR',
'ATOM_PROJECT_PATH', 'ATOM_HOME_PROJECT',
'VIM_PROJECT_ROOT', 'NVIM_PROJECT_ROOT',
'PWD', 'TRAE_WORKSPACE', 'BYTEDANCE_WORKSPACE',
'WORKSPACE_ROOT', 'PROJECT_DIR', 'WORKING_DIRECTORY',
'PROMPTX_WORKSPACE'
]
const available = {}
for (const varName of relevantVars) {
if (process.env[varName]) {
available[varName] = process.env[varName]
}
}
return available
}
}
/**
* 目录定位器工厂
*/
class DirectoryLocatorFactory {
/**
* 创建项目根目录定位器
*/
static createProjectRootLocator(options = {}) {
const platform = process.platform
// 根据平台创建特定实现
if (platform === 'win32') {
return new WindowsProjectRootLocator(options)
} else {
return new ProjectRootLocator(options)
}
}
/**
* 创建PromptX工作空间定位器
*/
static createPromptXWorkspaceLocator(options = {}) {
const projectRootLocator = this.createProjectRootLocator(options)
return new PromptXWorkspaceLocator({
...options,
projectRootLocator
})
}
/**
* 获取平台信息
*/
static getPlatform() {
return process.platform
}
}
/**
* Windows平台的项目根目录定位器
* 特殊处理Windows环境下的路径问题
*/
class WindowsProjectRootLocator extends ProjectRootLocator {
constructor(options = {}) {
super({
...options,
// Windows默认避免用户家目录
avoidUserHome: options.avoidUserHome !== false
})
}
/**
* Windows特有的项目根目录验证
*/
async _validateProjectRoot(projectRoot, context = {}) {
// 调用基类验证
const baseValid = await super._validateProjectRoot(projectRoot, context)
if (!baseValid) {
return false
}
// Windows特有:避免系统关键目录
const systemPaths = [
'C:\\Windows',
'C:\\Program Files',
'C:\\Program Files (x86)',
'C:\\System Volume Information'
]
const resolvedPath = path.resolve(projectRoot).toUpperCase()
for (const systemPath of systemPaths) {
if (resolvedPath.startsWith(systemPath.toUpperCase())) {
return false
}
}
return true
}
}
module.exports = {
DirectoryLocator,
ProjectRootLocator,
PromptXWorkspaceLocator,
DirectoryLocatorFactory,
WindowsProjectRootLocator
}