import * as z from "zod/v4"; import type { CommitAgentState, GitOverviewSnapshot } from "../../../commit/agentic/state"; import { extractScopeCandidates } from "../../../commit/analysis/scope"; import type { CustomTool } from "../../../extensibility/custom-tools/types"; import * as git from "../../../utils/git"; const EXCLUDED_LOCK_FILES = new Set([ "Cargo.lock", "package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lock", "bun.lockb", "go.sum", "poetry.lock", "Pipfile.lock", "uv.lock", "composer.lock", "Gemfile.lock", "flake.lock", "pubspec.lock", "Podfile.lock", "mix.lock", "gradle.lockfile", ]); function isExcludedFile(path: string): boolean { const basename = path.split("/").pop() ?? path; return EXCLUDED_LOCK_FILES.has(basename); } function filterExcludedFiles(files: string[]): { filtered: string[]; excluded: string[] } { const filtered: string[] = []; const excluded: string[] = []; for (const file of files) { if (isExcludedFile(file)) { excluded.push(file); } else { filtered.push(file); } } return { filtered, excluded }; } const gitOverviewSchema = z.object({ staged: z.boolean().describe("use staged changes (default true)").optional(), include_untracked: z.boolean().describe("include untracked when unstaged").optional(), }); export function createGitOverviewTool(cwd: string, state: CommitAgentState): CustomTool { return { name: "git_overview", label: "Git Overview", description: "Return staged files, diff stat summary, and numstat entries.", parameters: gitOverviewSchema, async execute(_toolCallId, params) { const staged = params.staged ?? true; const allFiles = await git.diff.changedFiles(cwd, { cached: staged }); const { filtered: files, excluded } = filterExcludedFiles(allFiles); const stat = await git.diff(cwd, { stat: true, cached: staged }); const allNumstat = await git.diff.numstat(cwd, { cached: staged }); const numstat = allNumstat.filter(entry => !isExcludedFile(entry.path)); const scopeResult = extractScopeCandidates(numstat); const untrackedFiles = !staged && params.include_untracked ? await git.ls.untracked(cwd) : undefined; const snapshot: GitOverviewSnapshot = { files, stat, numstat, scopeCandidates: scopeResult.scopeCandidates, isWideScope: scopeResult.isWide, untrackedFiles, excludedFiles: excluded.length > 0 ? excluded : undefined, }; state.overview = snapshot; return { content: [{ type: "text", text: JSON.stringify(snapshot, null, 2) }], details: snapshot, }; }, }; }