Skip to main content
Copy-paste examples for real-world WarpGrep use cases. All examples use the TypeScript SDK with Anthropic, OpenAI, or Vercel AI SDK.

Codebase Q&A Agent

Answer natural language questions about any codebase. WarpGrep finds the relevant code, Claude explains it.
import Anthropic from '@anthropic-ai/sdk';
import { MorphClient } from '@morphllm/morphsdk';

const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });
const anthropic = new Anthropic();

const grepTool = morph.anthropic.createWarpGrepTool({ repoRoot: '.' });

async function askCodebase(question: string) {
  const messages: Anthropic.MessageParam[] = [
    { role: 'user', content: question }
  ];
  let maxTurns = 5;

  while (maxTurns-- > 0) {
    const response = await anthropic.messages.create({
      model: 'claude-sonnet-4-5-20250929',
      max_tokens: 8192,
      tools: [grepTool],
      system: 'You are a codebase expert. Use the search tool to find relevant code before answering. Always cite file paths and line numbers.',
      messages
    });

    if (response.stop_reason === 'end_turn') {
      return response.content.find(c => c.type === 'text')?.text;
    }

    messages.push({ role: 'assistant', content: response.content });

    const toolResults = [];
    for (const block of response.content) {
      if (block.type === 'tool_use') {
        const result = await grepTool.execute(block.input);
        toolResults.push({
          type: 'tool_result' as const,
          tool_use_id: block.id,
          content: grepTool.formatResult(result)
        });
      }
    }

    messages.push({ role: 'user', content: toolResults });
  }
}

// Usage
await askCodebase('How does authentication work in this project?');
await askCodebase('What database queries are not using transactions?');
await askCodebase('Where are environment variables validated?');
What it does: Agent receives a question, searches the codebase for relevant code, then synthesizes an answer with file references. Multiple search rounds if the first pass doesn’t find enough context.

PR Review with Full Context

Review pull requests by searching the surrounding codebase for context the diff alone doesn’t show.
import Anthropic from '@anthropic-ai/sdk';
import { MorphClient } from '@morphllm/morphsdk';

const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });
const anthropic = new Anthropic();

const grepTool = morph.anthropic.createWarpGrepTool({ repoRoot: '.' });

async function reviewPR(diff: string, changedFiles: string[]) {
  const messages: Anthropic.MessageParam[] = [{
    role: 'user',
    content: `Review this pull request. Search the codebase to understand how the changed code is used elsewhere before reviewing.

Changed files: ${changedFiles.join(', ')}

\`\`\`diff
${diff}
\`\`\`

For each changed file:
1. Search for callers and consumers of the modified functions
2. Check for similar patterns elsewhere that should be updated
3. Flag breaking changes, missing error handling, or inconsistencies

Output a structured review with severity levels (critical, warning, suggestion).`
  }];
  let maxTurns = 8;

  while (maxTurns-- > 0) {
    const response = await anthropic.messages.create({
      model: 'claude-sonnet-4-5-20250929',
      max_tokens: 8192,
      tools: [grepTool],
      messages
    });

    if (response.stop_reason === 'end_turn') {
      return response.content.find(c => c.type === 'text')?.text;
    }

    messages.push({ role: 'assistant', content: response.content });

    const toolResults = [];
    for (const block of response.content) {
      if (block.type === 'tool_use') {
        const result = await grepTool.execute(block.input);
        toolResults.push({
          type: 'tool_result' as const,
          tool_use_id: block.id,
          content: grepTool.formatResult(result)
        });
      }
    }
    messages.push({ role: 'user', content: toolResults });
  }
}

// GitHub Actions integration
const diff = process.env.PR_DIFF!;
const files = process.env.PR_FILES?.split(',') || [];
const review = await reviewPR(diff, files);

// Post as PR comment
const { Octokit } = require('@octokit/rest');
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });

await octokit.issues.createComment({
  owner: process.env.GITHUB_REPOSITORY_OWNER!,
  repo: process.env.GITHUB_REPOSITORY?.split('/')[1],
  issue_number: parseInt(process.env.PR_NUMBER!),
  body: review
});
Why this is better than reviewing the diff alone: The agent searches for callers of modified functions, checks for similar patterns that might need the same change, and finds tests that should be updated. A diff-only review misses these.
name: AI Code Review
on: pull_request

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm install @morphllm/morphsdk @anthropic-ai/sdk @octokit/rest
      - run: |
          sudo apt-get install -y ripgrep
          node review.js
        env:
          MORPH_API_KEY: ${{ secrets.MORPH_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_DIFF: ${{ github.event.pull_request.diff_url }}
          PR_FILES: ${{ join(github.event.pull_request.changed_files, ',') }}
          PR_NUMBER: ${{ github.event.pull_request.number }}

Search across multiple public GitHub repos without cloning. Find how different projects implement the same pattern.
import Anthropic from '@anthropic-ai/sdk';
import { MorphClient } from '@morphllm/morphsdk';

const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });
const anthropic = new Anthropic();

const githubTool = morph.anthropic.createGitHubSearchTool();

async function compareImplementations(pattern: string, repos: string[]) {
  const response = await anthropic.messages.create({
    model: 'claude-sonnet-4-5-20250929',
    max_tokens: 8192,
    tools: [githubTool],
    messages: [{
      role: 'user',
      content: `Search these repositories and compare how they implement "${pattern}":

${repos.map(r => `- ${r}`).join('\n')}

For each repo, find the relevant code, then write a comparison covering:
- API surface / function signatures
- Error handling approach
- Performance considerations
- Trade-offs between implementations`
    }]
  });

  return response;
}

// Compare auth middleware across frameworks
await compareImplementations('authentication middleware', [
  'vercel/next.js',
  'expressjs/express',
  'honojs/hono'
]);

// Compare rate limiting implementations
await compareImplementations('rate limiting', [
  'express-rate-limit/express-rate-limit',
  'upstash/ratelimit'
]);
What it does: Searches multiple GitHub repos server-side (no local clone needed), then compares how each project solves the same problem.

Security Audit Agent

Scan a codebase for common security vulnerabilities. WarpGrep finds the code, Claude evaluates the risk.
import Anthropic from '@anthropic-ai/sdk';
import { MorphClient } from '@morphllm/morphsdk';

const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });
const anthropic = new Anthropic();

const grepTool = morph.anthropic.createWarpGrepTool({ repoRoot: '.' });

const AUDIT_CHECKS = [
  'Find all SQL queries and check for SQL injection vulnerabilities',
  'Find user input handling and check for missing sanitization or validation',
  'Find authentication and session management code, check for token expiry and secure storage',
  'Find file upload handling and check for path traversal or unrestricted file types',
  'Find API endpoints that lack authorization checks',
  'Find secrets, API keys, or credentials hardcoded in source files',
  'Find uses of eval, exec, or dynamic code execution',
];

async function securityAudit() {
  const findings = [];

  for (const check of AUDIT_CHECKS) {
    const messages: Anthropic.MessageParam[] = [{
      role: 'user',
      content: `${check}

Search the codebase thoroughly. For each finding, report:
- File and line number
- Severity (critical / high / medium / low)
- Description of the vulnerability
- Suggested fix

If nothing is found, say "No issues found for this check."`
    }];

    let maxTurns = 5;
    while (maxTurns-- > 0) {
      const response = await anthropic.messages.create({
        model: 'claude-sonnet-4-5-20250929',
        max_tokens: 4096,
        tools: [grepTool],
        messages
      });

      if (response.stop_reason === 'end_turn') {
        findings.push({
          check,
          result: response.content.find(c => c.type === 'text')?.text
        });
        break;
      }

      messages.push({ role: 'assistant', content: response.content });
      const toolResults = [];
      for (const block of response.content) {
        if (block.type === 'tool_use') {
          const result = await grepTool.execute(block.input);
          toolResults.push({
            type: 'tool_result' as const,
            tool_use_id: block.id,
            content: grepTool.formatResult(result)
          });
        }
      }
      messages.push({ role: 'user', content: toolResults });
    }
  }

  return findings;
}
How it works: Runs each security check as an independent search-and-analyze pass. Each check gets a fresh context window, so WarpGrep can focus its search budget on that specific vulnerability class.

Streaming Search UI

Show real-time search progress in a terminal or web UI.
import { MorphClient } from '@morphllm/morphsdk';

const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });

async function searchWithProgress(query: string) {
  const stream = morph.warpGrep.execute({
    query,
    repoRoot: '.',
    streamSteps: true
  });

  const toolIcons: Record<string, string> = {
    grep: 'grep',
    read: 'read',
    list_directory: 'ls',
    finish: 'done'
  };

  for await (const step of stream) {
    console.log(`\n--- Turn ${step.turn} ---`);
    for (const call of step.toolCalls) {
      const icon = toolIcons[call.name] || call.name;
      const args = call.arguments;

      if (call.name === 'grep') {
        console.log(`  [${icon}] "${args.pattern}" in ${args.path || '.'}`);
      } else if (call.name === 'read') {
        console.log(`  [${icon}] ${args.path} lines ${args.start}-${args.end}`);
      } else if (call.name === 'list_directory') {
        console.log(`  [${icon}] ${args.path}`);
      } else if (call.name === 'finish') {
        console.log(`  [${icon}] Found ${(args as any).files?.split('\n').length || 0} files`);
      }
    }
  }

  // The generator's return value is the final WarpGrepResult
  const result = await stream.return(undefined as any);
  if (result.value?.success) {
    console.log('\nResults:');
    for (const ctx of result.value.contexts!) {
      console.log(`  ${ctx.file}`);
    }
  }
}

await searchWithProgress('Find authentication middleware');
Output:
--- Turn 1 ---
  [grep] "auth" in src/
  [grep] "middleware" in src/
  [ls] src/auth

--- Turn 2 ---
  [read] src/auth/middleware.ts lines 1-60

--- Turn 3 ---
  [done] Found 2 files

Results:
  src/auth/middleware.ts
  src/middleware/auth.ts
Streaming adds zero latency overhead. The steps are yields from the same search operation, not separate API calls.

Dependency Debugger

Search inside node_modules to understand how a library works or debug an issue at the source.
import Anthropic from '@anthropic-ai/sdk';
import { MorphClient } from '@morphllm/morphsdk';

const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });
const anthropic = new Anthropic();

async function debugDependency(packageName: string, question: string) {
  // Search only inside the specific package, with no default excludes
  const grepTool = morph.anthropic.createWarpGrepTool({
    repoRoot: `./node_modules/${packageName}`,
    excludes: [],
  });

  const messages: Anthropic.MessageParam[] = [{
    role: 'user',
    content: `I'm debugging the "${packageName}" package. ${question}

Search the source code and explain what you find. Include file paths and relevant code snippets.`
  }];
  let maxTurns = 5;

  while (maxTurns-- > 0) {
    const response = await anthropic.messages.create({
      model: 'claude-sonnet-4-5-20250929',
      max_tokens: 4096,
      tools: [grepTool],
      messages
    });

    if (response.stop_reason === 'end_turn') {
      return response.content.find(c => c.type === 'text')?.text;
    }

    messages.push({ role: 'assistant', content: response.content });
    const toolResults = [];
    for (const block of response.content) {
      if (block.type === 'tool_use') {
        const result = await grepTool.execute(block.input);
        toolResults.push({
          type: 'tool_result' as const,
          tool_use_id: block.id,
          content: grepTool.formatResult(result)
        });
      }
    }
    messages.push({ role: 'user', content: toolResults });
  }
}

// Debug why a library behaves unexpectedly
await debugDependency('next', 'How does the App Router handle route matching?');
await debugDependency('prisma', 'Where does connection pooling happen?');
await debugDependency('zod', 'How does .transform() chain with .refine()?');
Why excludes: []: WarpGrep excludes node_modules by default. Passing an empty excludes list disables all default excludes so you can search library source code.

Migration Scout

Before a large migration, search for every pattern that needs to change.
import Anthropic from '@anthropic-ai/sdk';
import { MorphClient } from '@morphllm/morphsdk';

const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });
const anthropic = new Anthropic();

const grepTool = morph.anthropic.createWarpGrepTool({ repoRoot: '.' });

async function migrationPlan(from: string, to: string) {
  const messages: Anthropic.MessageParam[] = [{
    role: 'user',
    content: `I'm migrating from ${from} to ${to}. Search the codebase and produce a migration checklist.

For each pattern you find:
1. Search for all usages of ${from}-specific APIs, imports, and patterns
2. List every file that needs changes
3. Show the current code and what it should become
4. Flag any patterns that don't have a direct equivalent in ${to}

Group findings by category (imports, API calls, configuration, types, tests).
Output a numbered checklist I can work through file by file.`
  }];
  let maxTurns = 10;

  while (maxTurns-- > 0) {
    const response = await anthropic.messages.create({
      model: 'claude-sonnet-4-5-20250929',
      max_tokens: 12000,
      tools: [grepTool],
      messages
    });

    if (response.stop_reason === 'end_turn') {
      return response.content.find(c => c.type === 'text')?.text;
    }

    messages.push({ role: 'assistant', content: response.content });
    const toolResults = [];
    for (const block of response.content) {
      if (block.type === 'tool_use') {
        const result = await grepTool.execute(block.input);
        toolResults.push({
          type: 'tool_result' as const,
          tool_use_id: block.id,
          content: grepTool.formatResult(result)
        });
      }
    }
    messages.push({ role: 'user', content: toolResults });
  }
}

// Generate migration plans
await migrationPlan('Express', 'Hono');
await migrationPlan('Mongoose', 'Drizzle');
await migrationPlan('Jest', 'Vitest');
await migrationPlan('Pages Router', 'App Router');
What it does: Exhaustively searches for every usage of the old framework’s patterns, then produces a file-by-file migration checklist with before/after code snippets.

More Examples