/**
* Git Context — gather branch, status, recent commits for system prompt
*
* Extracted from agent-loop.ts for single-responsibility.
* All consumers should import from agent-loop.ts (re-export facade).
*/
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
/** CLI-only: cached git context (cleared by refreshGitContext/resetGitContext) */
let cachedGitContext: string | null = null;
let gitContextCwd: string | null = null;
export async function gatherGitContext(): Promise<string> {
const cwd = process.cwd();
// Return cached if same cwd
if (cachedGitContext !== null && gitContextCwd === cwd) return cachedGitContext;
gitContextCwd = cwd;
try {
await execAsync("git rev-parse --is-inside-work-tree", { cwd, timeout: 3000 });
} catch {
cachedGitContext = "";
return "";
}
const parts: string[] = [];
try {
const { stdout } = await execAsync("git branch --show-current", { cwd, timeout: 3000 });
const branch = stdout.trim();
if (branch) parts.push(`Branch: ${branch}`);
} catch { /* skip */ }
try {
const { stdout } = await execAsync("git status --short 2>/dev/null | head -20", { cwd, timeout: 3000 });
const status = stdout.trim();
if (status) {
const lines = status.split("\n");
parts.push(`Status: ${lines.length} changed file${lines.length !== 1 ? "s" : ""}`);
parts.push(status);
} else {
parts.push("Status: clean");
}
} catch { /* skip */ }
try {
const { stdout } = await execAsync("git log --oneline -5 2>/dev/null", { cwd, timeout: 3000 });
const log = stdout.trim();
if (log) parts.push(`Recent commits:\n${log}`);
} catch { /* skip */ }
cachedGitContext = parts.length > 0 ? parts.join("\n") : "";
return cachedGitContext;
}
/** Force refresh of git context (e.g. after a commit) */
export function refreshGitContext(): void {
cachedGitContext = null;
gitContextCwd = null;
}
/** Reset git context cache (called by resetSessionState) */
export function resetGitContext(): void {
cachedGitContext = null;
gitContextCwd = null;
}