Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 67 additions & 1 deletion index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { CompactClient } from "@morphllm/morphsdk";
import { CompactClient, WarpGrepClient } from "@morphllm/morphsdk";

// These are internal to the plugin but duplicated here for testing.
const EXISTING_CODE_MARKER = "// ... existing code ...";
Expand Down Expand Up @@ -991,3 +991,69 @@ describe("ToolContext path resolution", () => {
}
});
});

describe("formatWarpGrepResult edge cases", () => {
async function executeSearch(fakeResult: unknown): Promise<string> {
const original = WarpGrepClient.prototype.execute;
WarpGrepClient.prototype.execute = function* () {
return fakeResult;
} as any;

try {
const { default: MorphPlugin } = await importPluginWithEnv({
MORPH_API_KEY: "sk-test-key",
});
const hooks = await MorphPlugin(makePluginInput("/tmp/morph-warpgrep-test"));
return (await hooks.tool.warpgrep_codebase_search.execute(
{ search_term: "auth flow" },
makeToolContext("/tmp/morph-warpgrep-test"),
)) as string;
} finally {
WarpGrepClient.prototype.execute = original;
}
}

test("contexts with implausible file paths are rejected; valid paths from every OS render normally", async () => {
// Implausible: bare letters, empty strings, whitespace, no separators or extensions
for (const file of ["C", "", " ", "noextension"]) {
const result = await executeSearch({
success: true,
contexts: [{ file, content: "", lines: "*" }],
});
expect(result).toContain("malformed");
expect(result).not.toContain("<file path=");
}

// Valid paths across all major OS conventions
const validPaths = [
{ file: "src/auth.ts", content: "code" }, // unix relative
{ file: "/usr/local/bin/server.js", content: "code" }, // unix absolute
{ file: "C:\\Users\\dev\\project\\main.rs", content: "code" }, // windows absolute
{ file: "packages\\core\\index.ts", content: "code" }, // windows relative
{ file: "../sibling/lib.py", content: "code" }, // relative with ..
{ file: "./config.yaml", content: "code" }, // relative with ./
{ file: "Makefile.toml", content: "code" }, // dot-extension only
];

for (const ctx of validPaths) {
const result = await executeSearch({
success: true,
contexts: [{ ...ctx, lines: [[1, 5]] as Array<[number, number]> }],
});
expect(result).toContain("Relevant context found:");
expect(result).toContain(`<file path="${ctx.file}"`);
}
});

test("falsy error fields never surface literally; explicit errors pass through", async () => {
for (const error of [undefined, null, ""]) {
const result = await executeSearch({ success: false, error });
expect(result).toMatch(/^Search failed:/);
expect(result).not.toContain("undefined");
expect(result).not.toContain("null");
}

const result = await executeSearch({ success: false, error: "timeout after 60s" });
expect(result).toBe("Search failed: timeout after 60s");
});
});
25 changes: 22 additions & 3 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,22 +327,41 @@ function buildMorphSystemRoutingHint(): string | null {
return lines.length > 1 ? lines.join("\n") : null;
}

/**
* Minimal check for a plausible file path on any OS:
* - at least one path separator (/ or \) OR a dot-extension
* - not empty / whitespace-only
*/
const PLAUSIBLE_PATH_RE = /[/\\]|\.[\w]+$/;

function isValidContext(ctx: { file: string; content: string }): boolean {
return Boolean(ctx.file) && PLAUSIBLE_PATH_RE.test(ctx.file) && ctx.content.length > 0;
}

/**
* Format WarpGrep results for tool output
*/
function formatWarpGrepResult(result: WarpGrepResult): string {
if (!result.success) {
return `Search failed: ${result.error}`;
return `Search failed: ${result.error || "search returned no error details."}`;
}

if (!result.contexts || result.contexts.length === 0) {
return "No relevant code found. Try rephrasing your search term.";
}

const valid = result.contexts.filter(isValidContext);

if (valid.length === 0) {
const sample = result.contexts.slice(0, 3).map((c) => c.file);
return `Search returned malformed file contexts (file values: ${JSON.stringify(sample)}).
Fallback: use \`grep\` + \`read\` for local code search.`;
}

const parts: string[] = [];
parts.push("Relevant context found:");

for (const ctx of result.contexts) {
for (const ctx of valid) {
const rangeStr =
!ctx.lines || ctx.lines === "*"
? "*"
Expand All @@ -352,7 +371,7 @@ function formatWarpGrepResult(result: WarpGrepResult): string {

parts.push("\nFile contents:\n");

for (const ctx of result.contexts) {
for (const ctx of valid) {
const rangeStr =
!ctx.lines || ctx.lines === "*"
? ""
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"ci": "bun test && bun run build && bun run typecheck"
},
"dependencies": {
"@morphllm/morphsdk": "^0.2.148",
"@morphllm/morphsdk": "0.2.160",
"@opencode-ai/plugin": "latest",
"@opencode-ai/sdk": "^1.2.22"
},
Expand Down
Loading