repo.ts

  1import { readFile } from "node:fs/promises";
  2import { expandHomePath } from "../../util/path.js";
  3import { applyConfigOverrides, loadConfig } from "../../config/loader.js";
  4import { createWorkspace } from "../../workspace/manager.js";
  5import { createGrepTool } from "../../agent/tools/grep.js";
  6import { createReadTool } from "../../agent/tools/read.js";
  7import { createLsTool } from "../../agent/tools/ls.js";
  8import { createFindTool } from "../../agent/tools/find.js";
  9import { createGitLogTool } from "../../agent/tools/git/log.js";
 10import { createGitShowTool } from "../../agent/tools/git/show.js";
 11import { createGitBlameTool } from "../../agent/tools/git/blame.js";
 12import { createGitDiffTool } from "../../agent/tools/git/diff.js";
 13import { createGitRefsTool } from "../../agent/tools/git/refs.js";
 14import { createGitCheckoutTool } from "../../agent/tools/git/checkout.js";
 15import { runAgent } from "../../agent/runner.js";
 16import { buildRepoPrompt } from "../../agent/prompts/repo.js";
 17import { createEventLogger, printUsageSummary } from "../output.js";
 18import { CloneError } from "../../util/errors.js";
 19import simpleGit from "simple-git";
 20
 21export interface RepoCommandOptions {
 22  query: string;
 23  uri: string;
 24  ref?: string;
 25  full: boolean;
 26  model?: string;
 27  verbose: boolean;
 28  cleanup: boolean;
 29}
 30
 31export async function runRepoCommand(options: RepoCommandOptions): Promise<void> {
 32  const { config } = await loadConfig();
 33  const overrides = applyConfigOverrides(config, {
 34    defaults: { model: options.model ?? config.defaults.model, cleanup: options.cleanup },
 35    repo: { model: options.model ?? config.repo.model },
 36  });
 37
 38  const workspace = await createWorkspace({ cleanup: overrides.defaults.cleanup });
 39
 40  try {
 41    const logger = createEventLogger({ verbose: options.verbose });
 42
 43    let systemPrompt = buildRepoPrompt({
 44      currentTime: new Date().toISOString(),
 45      hasHistory: options.full,
 46    });
 47    const promptPath = overrides.repo.system_prompt_path;
 48    if (promptPath) {
 49      systemPrompt = await readFile(expandHomePath(promptPath), "utf8");
 50    }
 51
 52    const git = simpleGit();
 53    const cloneArgs: string[] = [];
 54    if (!options.full) {
 55      const depth = overrides.repo.default_depth ?? 1;
 56      const blobLimit = overrides.repo.blob_limit ?? "5m";
 57      cloneArgs.push("--depth", String(depth), `--filter=blob:limit=${blobLimit}`);
 58    }
 59
 60    try {
 61      await git.clone(options.uri, workspace.path, cloneArgs);
 62    } catch (error: any) {
 63      if (!overrides.defaults.cleanup) {
 64        console.error(`Workspace preserved at ${workspace.path}`);
 65      }
 66      throw new CloneError(options.uri, error?.message ?? String(error));
 67    }
 68
 69    const repoGit = simpleGit(workspace.path);
 70    if (options.ref) {
 71      await repoGit.checkout(options.ref);
 72    }
 73
 74    const tools = [
 75      createReadTool(workspace.path),
 76      createGrepTool(workspace.path),
 77      createLsTool(workspace.path),
 78      createFindTool(workspace.path),
 79      createGitLogTool(workspace.path),
 80      createGitShowTool(workspace.path),
 81      createGitBlameTool(workspace.path),
 82      createGitDiffTool(workspace.path),
 83      createGitRefsTool(workspace.path),
 84      createGitCheckoutTool(workspace.path),
 85    ];
 86
 87    const result = await runAgent(options.query, {
 88      model: overrides.repo.model ?? overrides.defaults.model,
 89      systemPrompt,
 90      tools,
 91      onEvent: logger,
 92      config: overrides,
 93    });
 94
 95    process.stdout.write(result.message + "\n");
 96    printUsageSummary(result.usage as any, result.requestCount);
 97  } finally {
 98    await workspace.cleanup();
 99  }
100}