feat: consolidate file write, move, grep, and delete operations into unified tools and update context compaction heuristics
This commit is contained in:
@@ -56,54 +56,6 @@ internal static class FileTools
|
||||
}
|
||||
}
|
||||
|
||||
[Description("Search a file for a regex pattern. Returns matches with line:hash| anchors.")]
|
||||
public static string GrepFile(
|
||||
[Description("Path to the file to search.")] string path,
|
||||
[Description("Regex pattern.")] string pattern)
|
||||
{
|
||||
path = ResolvePath(path);
|
||||
Log($"Searching file: {path}");
|
||||
|
||||
if (!File.Exists(path))
|
||||
return $"ERROR: File not found: {path}";
|
||||
|
||||
Regex regex;
|
||||
try
|
||||
{
|
||||
regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"ERROR: Invalid regex pattern '{pattern}': {ex.Message}";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string[] lines = File.ReadAllLines(path);
|
||||
var sb = new System.Text.StringBuilder();
|
||||
int matchCount = 0;
|
||||
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
if (regex.IsMatch(lines[i]))
|
||||
{
|
||||
int lineNumber = i + 1;
|
||||
string hash = HashlineEncoder.ComputeHash(lines[i].AsSpan(), lineNumber);
|
||||
sb.Append(lineNumber).Append(':').Append(hash).Append('|').AppendLine(lines[i]);
|
||||
matchCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchCount == 0)
|
||||
return $"(no matches for '{pattern}' in {path})";
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"ERROR searching '{path}': {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
[Description("List files and subdirectories.")]
|
||||
public static string ListDir(
|
||||
@@ -174,75 +126,130 @@ internal static class FileTools
|
||||
}
|
||||
}
|
||||
|
||||
[Description("Recursive regex search across all files. Returns matches with file:line:hash| format.")]
|
||||
public static string GrepRecursive(
|
||||
[Description("Directory to search.")] string path,
|
||||
|
||||
[Description("Consolidated grep operation for single file or recursive directory search.")]
|
||||
public static string Grep(
|
||||
[Description("Directory to search (for recursive mode) or file path (for file mode).")] string path,
|
||||
[Description("Regex pattern.")] string pattern,
|
||||
[Description("Optional glob to filter files (e.g. '*.cs').")] string? filePattern = null)
|
||||
[Description("Mode: 'file' for single file, 'recursive' for directory search.")] string mode = "recursive",
|
||||
[Description("Optional glob to filter files in recursive mode (e.g. '*.cs').")] string? filePattern = null)
|
||||
{
|
||||
path = ResolvePath(path);
|
||||
Log($"Recursive grep: {pattern} in {path}" + (filePattern != null ? $" (files: {filePattern})" : ""));
|
||||
mode = mode.ToLowerInvariant();
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
return $"ERROR: Directory not found: {path}";
|
||||
|
||||
Regex regex;
|
||||
try
|
||||
if (mode == "file")
|
||||
{
|
||||
regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"ERROR: Invalid regex pattern '{pattern}': {ex.Message}";
|
||||
}
|
||||
Log($"Searching file: {path}");
|
||||
|
||||
try
|
||||
{
|
||||
string globPattern = filePattern?.Replace("**/", "") ?? "*";
|
||||
var sb = new System.Text.StringBuilder();
|
||||
int totalMatches = 0;
|
||||
if (!File.Exists(path))
|
||||
return $"ERROR: File not found: {path}";
|
||||
|
||||
foreach (var file in EnumerateFilesRecursive(path, globPattern))
|
||||
Regex regex;
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
// Skip binary files: check first 512 bytes for null chars
|
||||
using var probe = new StreamReader(file);
|
||||
var buf = new char[512];
|
||||
int read = probe.Read(buf, 0, buf.Length);
|
||||
if (new ReadOnlySpan<char>(buf, 0, read).Contains('\0'))
|
||||
continue;
|
||||
}
|
||||
catch { continue; }
|
||||
|
||||
try
|
||||
{
|
||||
string[] lines = File.ReadAllLines(file);
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
if (regex.IsMatch(lines[i]))
|
||||
{
|
||||
int lineNumber = i + 1;
|
||||
string hash = HashlineEncoder.ComputeHash(lines[i].AsSpan(), lineNumber);
|
||||
sb.Append(file).Append(':').Append(lineNumber).Append(':').Append(hash).Append('|').AppendLine(lines[i]);
|
||||
totalMatches++;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Skip files that can't be read
|
||||
}
|
||||
regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"ERROR: Invalid regex pattern '{pattern}': {ex.Message}";
|
||||
}
|
||||
|
||||
if (totalMatches == 0)
|
||||
return $"(no matches for '{pattern}' in {path})";
|
||||
try
|
||||
{
|
||||
string[] lines = File.ReadAllLines(path);
|
||||
var sb = new System.Text.StringBuilder();
|
||||
int matchCount = 0;
|
||||
|
||||
return $"Found {totalMatches} match(es):\n" + sb.ToString();
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
if (regex.IsMatch(lines[i]))
|
||||
{
|
||||
int lineNumber = i + 1;
|
||||
string hash = HashlineEncoder.ComputeHash(lines[i].AsSpan(), lineNumber);
|
||||
sb.Append(lineNumber).Append(':').Append(hash).Append('|').AppendLine(lines[i]);
|
||||
matchCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchCount == 0)
|
||||
return $"(no matches for '{pattern}' in {path})";
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"ERROR searching '{path}': {ex.Message}";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
else if (mode == "recursive")
|
||||
{
|
||||
return $"ERROR in recursive grep: {ex.Message}";
|
||||
Log($"Recursive grep: {pattern} in {path}" + (filePattern != null ? $" (files: {filePattern})" : ""));
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
return $"ERROR: Directory not found: {path}";
|
||||
|
||||
Regex regex;
|
||||
try
|
||||
{
|
||||
regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"ERROR: Invalid regex pattern '{pattern}': {ex.Message}";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string globPattern = filePattern?.Replace("**/", "") ?? "*";
|
||||
var sb = new System.Text.StringBuilder();
|
||||
int totalMatches = 0;
|
||||
|
||||
foreach (var file in EnumerateFilesRecursive(path, globPattern))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Skip binary files: check first 512 bytes for null chars
|
||||
using var probe = new StreamReader(file);
|
||||
var buf = new char[512];
|
||||
int read = probe.Read(buf, 0, buf.Length);
|
||||
if (new ReadOnlySpan<char>(buf, 0, read).Contains('\0'))
|
||||
continue;
|
||||
}
|
||||
catch { continue; }
|
||||
|
||||
try
|
||||
{
|
||||
string[] lines = File.ReadAllLines(file);
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
if (regex.IsMatch(lines[i]))
|
||||
{
|
||||
int lineNumber = i + 1;
|
||||
string hash = HashlineEncoder.ComputeHash(lines[i].AsSpan(), lineNumber);
|
||||
sb.Append(file).Append(':').Append(lineNumber).Append(':').Append(hash).Append('|').AppendLine(lines[i]);
|
||||
totalMatches++;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Skip files that can't be read
|
||||
}
|
||||
}
|
||||
|
||||
if (totalMatches == 0)
|
||||
return $"(no matches for '{pattern}' in {path})";
|
||||
|
||||
return $"Found {totalMatches} match(es):\n" + sb.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"ERROR in recursive grep: {ex.Message}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"ERROR: Invalid mode '{mode}'. Use 'file' or 'recursive'.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user