mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 19:05:11 +00:00
feat(coding-agent): add read-only exploration tools (grep, find, ls) and --tools flag
Add grep, find, and ls tools for safe code exploration without modification risk. These tools are available via the new --tools CLI flag. - grep: Uses ripgrep (auto-downloaded) for fast regex searching. Respects .gitignore, supports glob filtering, context lines, and hidden files. - find: Uses fd (auto-downloaded) for fast file finding. Respects .gitignore, supports glob patterns, and hidden files. - ls: Lists directory contents with proper sorting and directory indicators. - --tools flag: Specify available tools (e.g., --tools read,grep,find,ls for read-only mode) - Dynamic system prompt adapts to selected tools with relevant guidelines Closes #74
This commit is contained in:
parent
a61eca5dee
commit
186169a820
10 changed files with 928 additions and 25 deletions
|
|
@ -4,6 +4,9 @@ import { join } from "path";
|
|||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { bashTool } from "../src/tools/bash.js";
|
||||
import { editTool } from "../src/tools/edit.js";
|
||||
import { findTool } from "../src/tools/find.js";
|
||||
import { grepTool } from "../src/tools/grep.js";
|
||||
import { lsTool } from "../src/tools/ls.js";
|
||||
import { readTool } from "../src/tools/read.js";
|
||||
import { writeTool } from "../src/tools/write.js";
|
||||
|
||||
|
|
@ -247,4 +250,90 @@ describe("Coding Agent Tools", () => {
|
|||
expect(getTextOutput(result)).toContain("Command failed");
|
||||
}, 35000);
|
||||
});
|
||||
|
||||
describe("grep tool", () => {
|
||||
it("should include filename when searching a single file", async () => {
|
||||
const testFile = join(testDir, "example.txt");
|
||||
writeFileSync(testFile, "first line\nmatch line\nlast line");
|
||||
|
||||
const result = await grepTool.execute("test-call-11", {
|
||||
pattern: "match",
|
||||
path: testFile,
|
||||
});
|
||||
|
||||
const output = getTextOutput(result);
|
||||
expect(output).toContain("example.txt:2: match line");
|
||||
});
|
||||
|
||||
it("should respect global limit and include context lines", async () => {
|
||||
const testFile = join(testDir, "context.txt");
|
||||
const content = ["before", "match one", "after", "middle", "match two", "after two"].join("\n");
|
||||
writeFileSync(testFile, content);
|
||||
|
||||
const result = await grepTool.execute("test-call-12", {
|
||||
pattern: "match",
|
||||
path: testFile,
|
||||
limit: 1,
|
||||
context: 1,
|
||||
});
|
||||
|
||||
const output = getTextOutput(result);
|
||||
expect(output).toContain("context.txt-1- before");
|
||||
expect(output).toContain("context.txt:2: match one");
|
||||
expect(output).toContain("context.txt-3- after");
|
||||
expect(output).toContain("(truncated, limit of 1 matches reached)");
|
||||
// Ensure second match is not present
|
||||
expect(output).not.toContain("match two");
|
||||
});
|
||||
});
|
||||
|
||||
describe("find tool", () => {
|
||||
it("should include hidden files that are not gitignored", async () => {
|
||||
const hiddenDir = join(testDir, ".secret");
|
||||
mkdirSync(hiddenDir);
|
||||
writeFileSync(join(hiddenDir, "hidden.txt"), "hidden");
|
||||
writeFileSync(join(testDir, "visible.txt"), "visible");
|
||||
|
||||
const result = await findTool.execute("test-call-13", {
|
||||
pattern: "**/*.txt",
|
||||
path: testDir,
|
||||
});
|
||||
|
||||
const outputLines = getTextOutput(result)
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
expect(outputLines).toContain("visible.txt");
|
||||
expect(outputLines).toContain(".secret/hidden.txt");
|
||||
});
|
||||
|
||||
it("should respect .gitignore", async () => {
|
||||
writeFileSync(join(testDir, ".gitignore"), "ignored.txt\n");
|
||||
writeFileSync(join(testDir, "ignored.txt"), "ignored");
|
||||
writeFileSync(join(testDir, "kept.txt"), "kept");
|
||||
|
||||
const result = await findTool.execute("test-call-14", {
|
||||
pattern: "**/*.txt",
|
||||
path: testDir,
|
||||
});
|
||||
|
||||
const output = getTextOutput(result);
|
||||
expect(output).toContain("kept.txt");
|
||||
expect(output).not.toContain("ignored.txt");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ls tool", () => {
|
||||
it("should list dotfiles and directories", async () => {
|
||||
writeFileSync(join(testDir, ".hidden-file"), "secret");
|
||||
mkdirSync(join(testDir, ".hidden-dir"));
|
||||
|
||||
const result = await lsTool.execute("test-call-15", { path: testDir });
|
||||
const output = getTextOutput(result);
|
||||
|
||||
expect(output).toContain(".hidden-file");
|
||||
expect(output).toContain(".hidden-dir/");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue