packfs-core
Version:
Semantic filesystem operations for LLM agent frameworks with natural language understanding. See LLM_AGENT_GUIDE.md for copy-paste examples.
398 lines • 15.9 kB
JavaScript
"use strict";
/**
* Error recovery suggestion system for semantic filesystem operations
* Provides intelligent suggestions when operations fail
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ErrorRecoveryEngine = void 0;
const path_1 = require("path");
const fs_1 = require("fs");
class ErrorRecoveryEngine {
constructor(basePath) {
this.maxSuggestions = 5;
this.basePath = basePath;
}
/**
* Generate suggestions for file not found errors
*/
async suggestForFileNotFound(requestedPath) {
const suggestions = [];
// 1. Check if parent directory exists and list contents
const parentDir = (0, path_1.dirname)(requestedPath);
const fileName = (0, path_1.basename)(requestedPath);
try {
const parentExists = await this.pathExists(parentDir);
if (parentExists) {
const files = await this.listDirectory(parentDir);
suggestions.push({
type: 'directory_listing',
description: `Directory listing of '${parentDir}'`,
data: {
directory: parentDir,
files: files.slice(0, 20), // Limit to 20 files
totalFiles: files.length,
hasMore: files.length > 20
},
confidence: 0.9
});
// 2. Find similar filenames in the parent directory
const fileNames = files.map(f => f.name);
const similarFiles = this.findSimilarFilenames(fileName, fileNames);
if (similarFiles.length > 0) {
suggestions.push({
type: 'similar_files',
description: `Similar files in '${parentDir}'`,
data: {
requestedFile: fileName,
similarFiles: similarFiles.slice(0, 5),
directory: parentDir
},
confidence: 0.8
});
}
}
}
catch (error) {
// Parent directory doesn't exist or can't be read
}
// 3. Search for the filename in other locations
const searchResults = await this.searchForFilename(fileName);
if (searchResults.length > 0) {
suggestions.push({
type: 'search_results',
description: `Found '${fileName}' in other locations`,
data: {
requestedFile: fileName,
foundLocations: searchResults.slice(0, 5)
},
confidence: 0.7
});
}
// 4. Suggest checking parent directories
const parentPaths = this.getParentPaths(requestedPath);
const existingParents = [];
for (const parent of parentPaths) {
if (await this.pathExists(parent)) {
existingParents.push(parent);
}
}
if (existingParents.length > 0) {
suggestions.push({
type: 'parent_directory',
description: 'Existing parent directories',
data: {
requestedPath,
existingParents: existingParents.slice(0, 3)
},
confidence: 0.6
});
}
// 5. Common path corrections
const alternativePaths = this.generateAlternativePaths(requestedPath);
const validAlternatives = [];
for (const altPath of alternativePaths) {
if (await this.pathExists(altPath)) {
validAlternatives.push(altPath);
}
}
if (validAlternatives.length > 0) {
suggestions.push({
type: 'alternative_path',
description: 'Alternative paths that exist',
data: {
requestedPath,
alternatives: validAlternatives
},
confidence: 0.85
});
}
return suggestions.sort((a, b) => b.confidence - a.confidence).slice(0, this.maxSuggestions);
}
/**
* Generate suggestions for directory not found errors
*/
async suggestForDirectoryNotFound(requestedPath) {
const suggestions = [];
// Similar to file not found, but focus on directories
const parentDir = (0, path_1.dirname)(requestedPath);
const dirName = (0, path_1.basename)(requestedPath);
try {
const parentExists = await this.pathExists(parentDir);
if (parentExists) {
const entries = await this.listDirectory(parentDir);
const directories = entries.filter(entry => entry.isDirectory);
suggestions.push({
type: 'directory_listing',
description: `Subdirectories in '${parentDir}'`,
data: {
directory: parentDir,
subdirectories: directories.map(d => d.name),
totalDirectories: directories.length
},
confidence: 0.9
});
// Find similar directory names
const similarDirs = this.findSimilarFilenames(dirName, directories.map(d => d.name));
if (similarDirs.length > 0) {
suggestions.push({
type: 'similar_files',
description: `Similar directories in '${parentDir}'`,
data: {
requestedDirectory: dirName,
similarDirectories: similarDirs,
parentDirectory: parentDir
},
confidence: 0.8
});
}
}
}
catch (error) {
// Parent directory doesn't exist
}
return suggestions.sort((a, b) => b.confidence - a.confidence).slice(0, this.maxSuggestions);
}
/**
* Generate suggestions for search operations that found no results
*/
async suggestForEmptySearchResults(query, searchType) {
const suggestions = [];
// 1. Suggest alternative search terms
const alternativeTerms = this.generateAlternativeSearchTerms(query);
if (alternativeTerms.length > 0) {
suggestions.push({
type: 'search_results',
description: 'Alternative search terms',
data: {
originalQuery: query,
suggestions: alternativeTerms,
searchType
},
confidence: 0.7
});
}
// 2. Suggest broader search
if (query.includes(' ')) {
const broadTerms = query.split(' ').filter(term => term.length > 2);
suggestions.push({
type: 'search_results',
description: 'Try searching for individual terms',
data: {
originalQuery: query,
broadTerms,
searchType
},
confidence: 0.6
});
}
return suggestions;
}
/**
* Format error recovery context into a helpful message
*/
formatSuggestions(context) {
if (context.suggestions.length === 0) {
return context.error;
}
let message = `${context.error}\n\nSuggestions:\n`;
for (const suggestion of context.suggestions) {
message += `\n• ${suggestion.description}`;
switch (suggestion.type) {
case 'directory_listing':
if (suggestion.data.files && suggestion.data.files.length > 0) {
message += `\n Files: ${suggestion.data.files.slice(0, 5).map((f) => f.name || f).join(', ')}`;
if (suggestion.data.hasMore) {
message += ` ... and ${suggestion.data.totalFiles - 5} more`;
}
}
break;
case 'similar_files':
if (suggestion.data.similarFiles && suggestion.data.similarFiles.length > 0) {
message += `\n Did you mean: ${suggestion.data.similarFiles.join(', ')}?`;
}
break;
case 'search_results':
if (suggestion.data.foundLocations && suggestion.data.foundLocations.length > 0) {
message += `\n Found in: ${suggestion.data.foundLocations.join(', ')}`;
}
break;
case 'alternative_path':
if (suggestion.data.alternatives && suggestion.data.alternatives.length > 0) {
message += `\n Try: ${suggestion.data.alternatives[0]}`;
}
break;
}
}
return message;
}
// Private helper methods
async pathExists(path) {
try {
await fs_1.promises.access((0, path_1.join)(this.basePath, path));
return true;
}
catch {
return false;
}
}
async listDirectory(path) {
try {
const fullPath = (0, path_1.join)(this.basePath, path);
const entries = await fs_1.promises.readdir(fullPath, { withFileTypes: true });
return entries.map(entry => ({
name: entry.name,
isDirectory: entry.isDirectory()
}));
}
catch {
return [];
}
}
findSimilarFilenames(target, candidates) {
const targetLower = target.toLowerCase();
const targetBase = (0, path_1.basename)(target, (0, path_1.extname)(target)).toLowerCase();
const scored = candidates.map(candidate => {
const candidateLower = candidate.toLowerCase();
const candidateBase = (0, path_1.basename)(candidate, (0, path_1.extname)(candidate)).toLowerCase();
let score = 0;
// Exact match (case insensitive)
if (candidateLower === targetLower) {
score = 100;
}
// Starts with target
else if (candidateLower.startsWith(targetLower) || candidateBase.startsWith(targetBase)) {
score = 80;
}
// Contains target
else if (candidateLower.includes(targetLower) || candidateBase.includes(targetBase)) {
score = 60;
}
// Levenshtein distance for fuzzy matching
else {
const distance = this.levenshteinDistance(targetBase, candidateBase);
const maxLen = Math.max(targetBase.length, candidateBase.length);
score = Math.max(0, 40 - (distance / maxLen) * 40);
}
return { name: candidate, score };
});
return scored
.filter(item => item.score > 30)
.sort((a, b) => b.score - a.score)
.map(item => item.name);
}
async searchForFilename(filename, maxDepth = 3) {
const results = [];
const visited = new Set();
const search = async (dir, depth) => {
if (depth > maxDepth || visited.has(dir))
return;
visited.add(dir);
try {
const entries = await fs_1.promises.readdir((0, path_1.join)(this.basePath, dir), { withFileTypes: true });
for (const entry of entries) {
const entryPath = (0, path_1.join)(dir, entry.name);
if (entry.name.toLowerCase() === filename.toLowerCase()) {
results.push(entryPath);
}
if (entry.isDirectory() && !this.isExcludedDirectory(entry.name)) {
await search(entryPath, depth + 1);
}
}
}
catch {
// Skip directories we can't read
}
};
await search('.', 0);
return results;
}
isExcludedDirectory(name) {
const excluded = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage'];
return excluded.includes(name);
}
getParentPaths(path) {
const parts = path.split('/').filter(p => p);
const parents = [];
for (let i = parts.length - 1; i >= 0; i--) {
parents.push(parts.slice(0, i).join('/') || '.');
}
return parents;
}
generateAlternativePaths(path) {
const alternatives = [];
const parts = path.split('/').filter(p => p);
const filename = parts[parts.length - 1];
const dir = parts.slice(0, -1).join('/');
if (!filename)
return alternatives;
// Try different extensions
const extensions = ['.md', '.ts', '.js', '.txt', '.json', '.yaml', '.yml'];
const baseWithoutExt = (0, path_1.basename)(filename, (0, path_1.extname)(filename));
for (const ext of extensions) {
if (!filename.endsWith(ext)) {
alternatives.push((0, path_1.join)(dir, baseWithoutExt + ext));
}
}
// Try index files
if (filename !== 'index.md' && filename !== 'README.md') {
alternatives.push((0, path_1.join)(path, 'index.md'));
alternatives.push((0, path_1.join)(path, 'README.md'));
}
// Try without extension
if ((0, path_1.extname)(filename)) {
alternatives.push((0, path_1.join)(dir, baseWithoutExt));
}
return alternatives;
}
generateAlternativeSearchTerms(query) {
const terms = [];
// Split camelCase and snake_case
const words = query
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/_/g, ' ')
.split(' ')
.filter(w => w.length > 2);
// Try individual words
if (words.length > 1) {
terms.push(...words);
}
// Try without common prefixes/suffixes
const prefixes = ['get', 'set', 'is', 'has', 'can', 'should'];
const suffixes = ['er', 'or', 'ing', 'ed', 's'];
for (const word of words) {
for (const prefix of prefixes) {
if (word.toLowerCase().startsWith(prefix)) {
terms.push(word.substring(prefix.length));
}
}
for (const suffix of suffixes) {
if (word.toLowerCase().endsWith(suffix)) {
terms.push(word.substring(0, word.length - suffix.length));
}
}
}
return [...new Set(terms)].filter(t => t.length > 2);
}
levenshteinDistance(str1, str2) {
const matrix = [];
for (let i = 0; i <= str2.length; i++) {
matrix[i] = [i];
}
for (let j = 0; j <= str1.length; j++) {
matrix[0][j] = j;
}
for (let i = 1; i <= str2.length; i++) {
for (let j = 1; j <= str1.length; j++) {
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
}
else {
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
}
}
}
return matrix[str2.length][str1.length];
}
}
exports.ErrorRecoveryEngine = ErrorRecoveryEngine;
//# sourceMappingURL=error-recovery.js.map