repo.ts

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