openai-cli-unofficial
Version:
A powerful OpenAI CLI Coding Agent built with TypeScript
804 lines • 30 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileSearchManager = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
class FileSearchManager {
constructor(baseDirectory) {
this.cachedFiles = [];
this.lastCacheTime = 0;
this.cacheExpiration = 30000; // 30秒缓存
this.gitignorePatterns = [];
this.gitignoreLastModified = 0;
// 新增:查询缓存机制
this.queryCache = new Map();
this.queryCacheExpiration = 10000; // 10秒查询缓存
// 新增:热门文件缓存
this.hotFiles = new Set();
this.fileAccessCount = new Map();
this.currentDirectory = baseDirectory || process.cwd();
}
/**
* 搜索文件,支持模糊匹配 - 优化版本,支持早期终止和智能缓存
*/
async searchFiles(query, maxResults = 10) {
// 如果查询为空,返回最近的一些文件
if (!query.trim()) {
await this.ensureCacheUpdated();
return this.cachedFiles.slice(0, maxResults);
}
const normalizedQuery = query.toLowerCase();
// 检查查询缓存
const cacheKey = `${normalizedQuery}_${maxResults}`;
const cachedQuery = this.queryCache.get(cacheKey);
if (cachedQuery && (Date.now() - cachedQuery.timestamp) < this.queryCacheExpiration) {
return cachedQuery.results;
}
await this.ensureCacheUpdated();
const results = [];
// 使用评分系统和早期终止优化搜索性能
for (const file of this.cachedFiles) {
const fileName = file.name.toLowerCase();
const relativePath = file.relativePath.toLowerCase();
let score = 0;
let matched = false;
// 1. 文件名精确匹配(最高分)
if (fileName === normalizedQuery) {
score = 1000;
matched = true;
}
// 2. 文件名开头匹配(优先级最高)
else if (fileName.startsWith(normalizedQuery)) {
score = 900 - normalizedQuery.length; // 查询越短分数越高
matched = true;
}
// 3. 路径开头匹配
else if (relativePath.startsWith(normalizedQuery)) {
score = 800 - normalizedQuery.length;
matched = true;
}
// 4. 文件名包含匹配
else if (fileName.includes(normalizedQuery)) {
score = 700 - fileName.indexOf(normalizedQuery) * 10; // 位置越靠前分数越高
matched = true;
}
// 5. 路径包含匹配
else if (relativePath.includes(normalizedQuery)) {
score = 600 - relativePath.indexOf(normalizedQuery) * 5;
matched = true;
}
// 6. 模糊匹配(字符序列匹配)
else if (this.fuzzyMatch(fileName, normalizedQuery)) {
score = 500;
matched = true;
}
else if (this.fuzzyMatch(relativePath, normalizedQuery)) {
score = 400;
matched = true;
}
if (matched) {
// 文件类型加分:文件 > 目录
if (file.type === 'file') {
score += 50;
}
// 热门文件加分
const accessCount = this.fileAccessCount.get(file.relativePath) || 0;
score += Math.min(accessCount * 10, 100); // 最多加100分
results.push({ file, score });
// 早期终止:如果找到足够多的高分结果,可以提前结束
if (results.length >= maxResults * 3 && score < 600) {
break;
}
}
}
// 按分数排序并返回顶部结果
const finalResults = results
.sort((a, b) => b.score - a.score)
.slice(0, maxResults)
.map(result => result.file);
// 缓存查询结果
this.queryCache.set(cacheKey, {
results: finalResults,
timestamp: Date.now()
});
// 更新文件访问统计
finalResults.forEach(file => {
const count = this.fileAccessCount.get(file.relativePath) || 0;
this.fileAccessCount.set(file.relativePath, count + 1);
if (count > 5) {
this.hotFiles.add(file.relativePath);
}
});
return finalResults;
}
/**
* 同步搜索文件,支持模糊匹配(避免异步导致的建议显示问题)
*/
searchFilesSync(query, maxResults = 10) {
// 如果查询为空,返回最近的一些文件
if (!query.trim()) {
this.ensureCacheUpdatedSync();
return this.cachedFiles.slice(0, maxResults);
}
this.ensureCacheUpdatedSync();
const normalizedQuery = query.toLowerCase();
// 模糊匹配算法
const filtered = this.cachedFiles.filter(file => {
const fileName = file.name.toLowerCase();
const relativePath = file.relativePath.toLowerCase();
// 1. 文件名开头匹配(优先级最高)
if (fileName.startsWith(normalizedQuery)) {
return true;
}
// 2. 路径开头匹配
if (relativePath.startsWith(normalizedQuery)) {
return true;
}
// 3. 文件名包含匹配
if (fileName.includes(normalizedQuery)) {
return true;
}
// 4. 路径包含匹配
if (relativePath.includes(normalizedQuery)) {
return true;
}
// 5. 模糊匹配(字符序列匹配)
return this.fuzzyMatch(fileName, normalizedQuery) ||
this.fuzzyMatch(relativePath, normalizedQuery);
});
// 按匹配优先级排序
const sorted = filtered.sort((a, b) => {
const aName = a.name.toLowerCase();
const bName = b.name.toLowerCase();
const aPath = a.relativePath.toLowerCase();
const bPath = b.relativePath.toLowerCase();
// 文件名开头匹配优先
const aNameStarts = aName.startsWith(normalizedQuery);
const bNameStarts = bName.startsWith(normalizedQuery);
if (aNameStarts && !bNameStarts)
return -1;
if (!aNameStarts && bNameStarts)
return 1;
// 路径开头匹配次优先
const aPathStarts = aPath.startsWith(normalizedQuery);
const bPathStarts = bPath.startsWith(normalizedQuery);
if (aPathStarts && !bPathStarts)
return -1;
if (!aPathStarts && bPathStarts)
return 1;
// 文件类型优先级:文件 > 目录
if (a.type === 'file' && b.type === 'directory')
return -1;
if (a.type === 'directory' && b.type === 'file')
return 1;
// 按文件名长度排序(短的优先)
return aName.length - bName.length;
});
return sorted.slice(0, maxResults);
}
/**
* 模糊匹配算法
*/
fuzzyMatch(text, pattern) {
if (pattern.length === 0)
return true;
if (text.length === 0)
return false;
let patternIndex = 0;
let textIndex = 0;
while (textIndex < text.length && patternIndex < pattern.length) {
if (text[textIndex] === pattern[patternIndex]) {
patternIndex++;
}
textIndex++;
}
return patternIndex === pattern.length;
}
/**
* 高级 gitignore 解析器 - 支持完整的 gitignore 规范
*/
async loadGitignorePatterns() {
const gitignorePaths = await this.discoverGitignoreFiles();
try {
const allPatterns = [];
let latestModTime = 0;
// 按优先级顺序处理 gitignore 文件
for (const gitignorePath of gitignorePaths) {
try {
const stats = await fs.promises.stat(gitignorePath);
latestModTime = Math.max(latestModTime, stats.mtimeMs);
if (latestModTime <= this.gitignoreLastModified) {
continue;
}
const content = await fs.promises.readFile(gitignorePath, 'utf-8');
const patterns = this.parseGitignoreContent(content, gitignorePath);
allPatterns.push(...patterns);
}
catch (error) {
// 忽略无法读取的 gitignore 文件
continue;
}
}
// 检查是否需要更新缓存
if (latestModTime > this.gitignoreLastModified) {
this.gitignorePatterns = allPatterns;
this.gitignoreLastModified = latestModTime;
}
}
catch (error) {
// 降级到行业标准默认规则
this.gitignorePatterns = this.getIndustryStandardIgnorePatterns();
this.gitignoreLastModified = 0;
}
}
/**
* 发现所有相关的 gitignore 文件
*/
async discoverGitignoreFiles() {
const gitignoreFiles = [];
// 1. 项目根目录的 .gitignore
const rootGitignore = path.join(this.currentDirectory, '.gitignore');
gitignoreFiles.push(rootGitignore);
// 2. 全局 gitignore(如果存在)
const globalGitignore = await this.findGlobalGitignore();
if (globalGitignore) {
gitignoreFiles.push(globalGitignore);
}
// 3. Git 仓库的 exclude 文件
const gitExclude = path.join(this.currentDirectory, '.git', 'info', 'exclude');
gitignoreFiles.push(gitExclude);
return gitignoreFiles;
}
/**
* 查找全局 gitignore 配置
*/
async findGlobalGitignore() {
const possiblePaths = [
path.join(process.env.HOME || process.env.USERPROFILE || '', '.gitignore_global'),
path.join(process.env.HOME || process.env.USERPROFILE || '', '.config', 'git', 'ignore'),
path.join(process.env.XDG_CONFIG_HOME || '', 'git', 'ignore')
];
for (const gitignorePath of possiblePaths) {
try {
await fs.promises.access(gitignorePath);
return gitignorePath;
}
catch {
continue;
}
}
return null;
}
/**
* 解析 gitignore 文件内容 - 完全兼容 Git 规范
*/
parseGitignoreContent(content, filePath) {
const patterns = [];
const lines = content.split(/\r?\n/);
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
// 处理行尾的反斜杠续行
while (line.endsWith('\\') && i + 1 < lines.length) {
line = line.slice(0, -1) + lines[++i];
}
// 移除行首尾空白
line = line.trim();
// 跳过空行和注释
if (!line || line.startsWith('#')) {
continue;
}
// 处理转义字符
line = this.unescapeGitignorePattern(line);
patterns.push(line);
}
return patterns;
}
/**
* 处理 gitignore 模式中的转义字符
*/
unescapeGitignorePattern(pattern) {
return pattern
.replace(/\\#/g, '#')
.replace(/\\ /g, ' ')
.replace(/\\\\/g, '\\')
.replace(/\\\!/g, '!');
}
/**
* 获取行业标准的忽略模式
*/
getIndustryStandardIgnorePatterns() {
return [
// Node.js 生态
'node_modules/',
'npm-debug.log*',
'yarn-debug.log*',
'yarn-error.log*',
'.pnpm-debug.log*',
// 构建输出
'dist/',
'build/',
'out/',
'.next/',
'.nuxt/',
// 依赖管理
'package-lock.json',
'yarn.lock',
'pnpm-lock.yaml',
// IDE 和编辑器
'.vscode/',
'.idea/',
'*.swp',
'*.swo',
'*~',
// 操作系统
'.DS_Store',
'.DS_Store?',
'._*',
'.Spotlight-V100',
'.Trashes',
'ehthumbs.db',
'Thumbs.db',
// 版本控制
'.git/',
'.gitignore',
'.gitattributes',
// 日志和临时文件
'*.log',
'*.tmp',
'*.temp',
'.cache/',
// 环境配置
'.env',
'.env.local',
'.env.*.local',
// 测试覆盖率
'coverage/',
'.nyc_output/',
// TypeScript
'*.tsbuildinfo',
// Webpack
'.webpack/',
];
}
/**
* 企业级路径忽略检查器 - 支持复杂的 gitignore 语义
*/
shouldIgnore(relativePath, isDirectory) {
// 标准化路径分隔符
const normalizedPath = relativePath.replace(/\\/g, '/');
// 应用配置文件允许列表(重要的点文件不被忽略)
if (this.isAllowedDotFile(normalizedPath)) {
return false;
}
// 执行高级模式匹配
for (const pattern of this.gitignorePatterns) {
const matchResult = this.executeAdvancedPatternMatching(normalizedPath, pattern, isDirectory);
if (matchResult.matches) {
// 处理否定模式(! 开头)
if (pattern.startsWith('!')) {
return false; // 否定模式匹配,不忽略
}
return true; // 正常模式匹配,忽略
}
}
return false;
}
/**
* 检查是否为允许的点文件
*/
isAllowedDotFile(filePath) {
const basename = path.basename(filePath);
const allowedDotFiles = new Set([
'.env', '.env.local', '.env.production', '.env.development',
'.gitignore', '.gitattributes', '.gitmodules',
'.nvmrc', '.node-version',
'.prettierrc', '.prettierrc.js', '.prettierrc.json', '.prettierrc.yaml',
'.eslintrc', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yaml',
'.editorconfig',
'.dockerignore',
'.browserslistrc',
'.babelrc', '.babelrc.js', '.babelrc.json',
'.tsconfig.json'
]);
return allowedDotFiles.has(basename) || basename.startsWith('.env.');
}
/**
* 高级模式匹配引擎 - 完全兼容 Git 规范
*/
executeAdvancedPatternMatching(filePath, pattern, isDirectory) {
let originalPattern = pattern;
// 处理否定模式
const isNegation = pattern.startsWith('!');
if (isNegation) {
pattern = pattern.slice(1);
}
// 处理目录专用模式
const isDirectoryOnly = pattern.endsWith('/');
if (isDirectoryOnly) {
if (!isDirectory) {
return { matches: false, reason: 'directory-only pattern on file' };
}
pattern = pattern.slice(0, -1);
}
// 处理绝对路径模式
const isAbsolute = pattern.startsWith('/');
if (isAbsolute) {
pattern = pattern.slice(1);
return {
matches: this.executeGlobMatching(filePath, pattern),
reason: 'absolute path match'
};
}
// 处理相对路径模式
return this.executeRelativePatternMatching(filePath, pattern);
}
/**
* 相对路径模式匹配
*/
executeRelativePatternMatching(filePath, pattern) {
const pathSegments = filePath.split('/').filter(Boolean);
// 完整路径匹配
if (this.executeGlobMatching(filePath, pattern)) {
return { matches: true, reason: 'full path match' };
}
// 文件名匹配
const fileName = pathSegments[pathSegments.length - 1];
if (fileName && this.executeGlobMatching(fileName, pattern)) {
return { matches: true, reason: 'filename match' };
}
// 目录路径匹配(检查路径中的任意连续段)
for (let i = 0; i < pathSegments.length; i++) {
for (let j = i + 1; j <= pathSegments.length; j++) {
const subPath = pathSegments.slice(i, j).join('/');
if (this.executeGlobMatching(subPath, pattern)) {
return { matches: true, reason: 'subpath match' };
}
}
}
return { matches: false, reason: 'no match found' };
}
/**
* 专业级 Glob 模式匹配引擎
*/
executeGlobMatching(text, pattern) {
// 处理特殊情况
if (pattern === text)
return true;
if (pattern === '')
return text === '';
if (text === '')
return pattern.match(/^\*+$/) !== null;
// 使用动态规划优化的 glob 匹配算法
return this.advancedGlobMatch(text, pattern);
}
/**
* 高性能动态规划 Glob 匹配算法
*/
advancedGlobMatch(text, pattern) {
const textLen = text.length;
const patternLen = pattern.length;
// 动态规划状态表
const dp = Array(textLen + 1)
.fill(null)
.map(() => Array(patternLen + 1).fill(false));
// 初始状态
dp[0][0] = true;
// 处理模式开头的 * 字符
for (let j = 1; j <= patternLen; j++) {
if (pattern[j - 1] === '*') {
dp[0][j] = dp[0][j - 1];
}
}
// 填充动态规划表
for (let i = 1; i <= textLen; i++) {
for (let j = 1; j <= patternLen; j++) {
const textChar = text[i - 1];
const patternChar = pattern[j - 1];
if (patternChar === '*') {
// * 可以匹配空字符串、单个字符或多个字符
dp[i][j] = dp[i][j - 1] || dp[i - 1][j] || dp[i - 1][j - 1];
}
else if (patternChar === '?') {
// ? 匹配任意单个字符
dp[i][j] = dp[i - 1][j - 1];
}
else if (patternChar === textChar) {
// 字符精确匹配
dp[i][j] = dp[i - 1][j - 1];
}
else if (this.isCharacterClassMatch(textChar, patternChar, pattern, j - 1)) {
// 字符类匹配 [abc] 或 [a-z]
dp[i][j] = dp[i - 1][j - 1];
}
// 否则保持 false(默认值)
}
}
return dp[textLen][patternLen];
}
/**
* 字符类匹配支持(如 [abc], [a-z], [!abc])
*/
isCharacterClassMatch(char, patternChar, fullPattern, index) {
// 简化实现:支持基本的字符类
if (patternChar === '[') {
const closingBracket = fullPattern.indexOf(']', index);
if (closingBracket === -1)
return false;
const charClass = fullPattern.slice(index + 1, closingBracket);
const isNegated = charClass.startsWith('!') || charClass.startsWith('^');
const actualClass = isNegated ? charClass.slice(1) : charClass;
const matches = this.matchesCharacterClass(char, actualClass);
return isNegated ? !matches : matches;
}
return false;
}
/**
* 字符类内部匹配逻辑
*/
matchesCharacterClass(char, charClass) {
// 处理范围表达式 a-z
const rangeMatch = charClass.match(/([a-zA-Z0-9])-([a-zA-Z0-9])/);
if (rangeMatch) {
const [, start, end] = rangeMatch;
return char >= start && char <= end;
}
// 直接字符匹配
return charClass.includes(char);
}
/**
* 确保缓存是最新的 - 优化版本,支持后台更新
*/
async ensureCacheUpdated() {
const now = Date.now();
if (now - this.lastCacheTime < this.cacheExpiration && this.cachedFiles.length > 0) {
return;
}
// 并发加载gitignore模式和构建文件缓存
const [, newFiles] = await Promise.all([
this.loadGitignorePatterns(),
this.buildFileCache()
]);
this.cachedFiles = newFiles;
this.lastCacheTime = now;
}
/**
* 同步确保缓存是最新的
*/
ensureCacheUpdatedSync() {
const now = Date.now();
if (now - this.lastCacheTime < this.cacheExpiration && this.cachedFiles.length > 0) {
return;
}
// 同步加载gitignore模式
this.loadGitignorePatternsSync();
this.cachedFiles = this.buildFileCacheSync();
this.lastCacheTime = now;
}
/**
* 同步加载gitignore模式
*/
loadGitignorePatternsSync() {
try {
const gitignorePath = path.join(this.currentDirectory, '.gitignore');
if (fs.existsSync(gitignorePath)) {
const stats = fs.statSync(gitignorePath);
if (stats.mtime.getTime() === this.gitignoreLastModified) {
return; // 文件没有变化
}
const content = fs.readFileSync(gitignorePath, 'utf8');
this.gitignorePatterns = this.parseGitignoreContent(content, gitignorePath);
this.gitignoreLastModified = stats.mtime.getTime();
}
else {
this.gitignorePatterns = [];
this.gitignoreLastModified = 0;
}
}
catch (error) {
console.warn('Failed to load .gitignore:', error);
this.gitignorePatterns = [];
}
}
/**
* 构建文件缓存 - 优化版本,支持增量更新和并发处理
*/
async buildFileCache() {
const files = [];
try {
// 使用异步并发遍历,提高大项目的扫描速度
await this.walkDirectory(this.currentDirectory, files, 0, 3); // 最多3层深度
// 按文件类型和名称排序,优化搜索体验
files.sort((a, b) => {
// 文件优先于目录
if (a.type !== b.type) {
return a.type === 'file' ? -1 : 1;
}
// 同类型按名称排序
return a.name.localeCompare(b.name);
});
}
catch (error) {
console.warn('Error building file cache:', error);
}
return files;
}
/**
* 同步构建文件缓存
*/
buildFileCacheSync() {
const files = [];
try {
this.walkDirectorySync(this.currentDirectory, files, 0, 3); // 最多3层深度
}
catch (error) {
console.warn('Error building file cache:', error);
}
return files;
}
/**
* 递归遍历目录 - 优化版本,支持并发处理
*/
async walkDirectory(dirPath, results, currentDepth, maxDepth) {
if (currentDepth > maxDepth)
return;
try {
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
const directories = [];
const processedResults = [];
// 第一步:并发处理所有文件和目录的基本信息
await Promise.all(entries.map(async (entry) => {
try {
const fullPath = path.join(dirPath, entry.name);
const relativePath = path.relative(this.currentDirectory, fullPath);
const isDirectory = entry.isDirectory();
// 使用动态忽略规则
if (this.shouldIgnore(relativePath, isDirectory)) {
return;
}
const fileResult = {
path: fullPath,
name: entry.name,
relativePath: relativePath,
type: isDirectory ? 'directory' : 'file'
};
processedResults.push(fileResult);
// 收集需要进一步遍历的目录
if (isDirectory && currentDepth < maxDepth) {
directories.push(fullPath);
}
}
catch (error) {
// 忽略处理失败的文件
}
}));
// 将处理的结果添加到主结果数组
results.push(...processedResults);
// 第二步:并发递归处理子目录,但限制并发数量避免过载
const concurrencyLimit = Math.min(directories.length, 5); // 限制最多5个并发目录
const chunks = this.chunkArray(directories, concurrencyLimit);
for (const chunk of chunks) {
await Promise.all(chunk.map(dir => this.walkDirectory(dir, results, currentDepth + 1, maxDepth)));
}
}
catch (error) {
// 忽略无法访问的目录
}
}
/**
* 将数组分割成指定大小的块
*/
chunkArray(array, chunkSize) {
const chunks = [];
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize));
}
return chunks;
}
/**
* 同步递归遍历目录
*/
walkDirectorySync(dirPath, results, currentDepth, maxDepth) {
if (currentDepth > maxDepth)
return;
try {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
const relativePath = path.relative(this.currentDirectory, fullPath);
const isDirectory = entry.isDirectory();
// 使用动态忽略规则
if (this.shouldIgnore(relativePath, isDirectory)) {
continue;
}
const fileResult = {
path: fullPath,
name: entry.name,
relativePath: relativePath,
type: isDirectory ? 'directory' : 'file'
};
results.push(fileResult);
// 如果是目录且没有达到最大深度,继续递归
if (isDirectory && currentDepth < maxDepth) {
this.walkDirectorySync(fullPath, results, currentDepth + 1, maxDepth);
}
}
}
catch (error) {
// 忽略无法访问的目录
}
}
/**
* 更新工作目录
*/
setWorkingDirectory(directory) {
this.currentDirectory = directory;
this.cachedFiles = [];
this.lastCacheTime = 0;
this.gitignorePatterns = [];
this.gitignoreLastModified = 0;
}
/**
* 清除缓存
*/
clearCache() {
this.cachedFiles = [];
this.lastCacheTime = 0;
this.gitignorePatterns = [];
this.gitignoreLastModified = 0;
this.queryCache.clear();
this.hotFiles.clear();
this.fileAccessCount.clear();
}
/**
* 获取热门文件列表(用于优化搜索排序)
*/
getHotFiles() {
return Array.from(this.hotFiles);
}
/**
* 清理过期的查询缓存
*/
cleanupQueryCache() {
const now = Date.now();
for (const [key, value] of this.queryCache.entries()) {
if (now - value.timestamp > this.queryCacheExpiration) {
this.queryCache.delete(key);
}
}
}
}
exports.FileSearchManager = FileSearchManager;
//# sourceMappingURL=files.js.map