feat(live-conversation): add git-history tools to repo intelligence

Adds repo_recent_commits (history/authorship, optional path or author) and
repo_find_commits (by commit message or pickaxe code search) so the voice
agent can answer who-changed-what and which-commit-introduced-X over the
local karti-ai/podman checkout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kartikeya
2026-06-28 02:17:32 -07:00
parent 87c271995c
commit 604bf9d5ac
+88
View File
@@ -54,6 +54,8 @@ Use PodMan tools before making claims about current work, git state, collisions,
team memory, or recent decisions. Keep spoken answers short. Prefer one useful next step. team memory, or recent decisions. Keep spoken answers short. Prefer one useful next step.
If a critical collision event arrives, stop the current turn and state the alert immediately. If a critical collision event arrives, stop the current turn and state the alert immediately.
To find code, files, symbols, or how something is implemented in the repository, call search_repo. To find code, files, symbols, or how something is implemented in the repository, call search_repo.
For git commit history, authorship, recent changes, or which commit introduced something, call
repo_recent_commits or repo_find_commits.
For complex repository, terminal, GitHub, MongoDB, build, install, deploy, or multi-step tasks, For complex repository, terminal, GitHub, MongoDB, build, install, deploy, or multi-step tasks,
call delegate_to_hermes. Do not run those actions directly. If the user says stop, wait, cancel, call delegate_to_hermes. Do not run those actions directly. If the user says stop, wait, cancel,
or change of plans while Hermes is running, call abort_active_hermes_job immediately. or change of plans while Hermes is running, call abort_active_hermes_job immediately.
@@ -90,6 +92,25 @@ def request_json(path: str, *, method: str = "GET", body: dict[str, Any] | None
raise RuntimeError(f"PodMan backend returned {exc.code}: {detail}") from exc raise RuntimeError(f"PodMan backend returned {exc.code}: {detail}") from exc
async def _run_git(args: list[str], timeout: float = 15.0) -> tuple[int, str, str]:
"""Run a read-only git command inside the repo checkout and capture its output."""
try:
proc = await asyncio.create_subprocess_exec(
"git",
"-C",
REPO_ROOT,
*args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
out, err = await asyncio.wait_for(proc.communicate(), timeout=timeout)
except asyncio.TimeoutError:
return 124, "", "git command timed out"
except FileNotFoundError:
return 127, "", "git is not available on this host"
return proc.returncode, out.decode("utf-8", "replace"), err.decode("utf-8", "replace")
class PodManLiveAgent(Agent): class PodManLiveAgent(Agent):
def __init__(self, pod_id: str, identity: str, session_id: str, conversation_room: str) -> None: def __init__(self, pod_id: str, identity: str, session_id: str, conversation_room: str) -> None:
super().__init__(instructions=INSTRUCTIONS) super().__init__(instructions=INSTRUCTIONS)
@@ -212,6 +233,73 @@ class PodManLiveAgent(Agent):
body = "\n".join(lines) body = "\n".join(lines)
return f'Matches for "{cleaned}" in {REPO_SLUG} (path:line):\n{body}'[:7000] return f'Matches for "{cleaned}" in {REPO_SLUG} (path:line):\n{body}'[:7000]
@function_tool()
async def repo_recent_commits(
self, context: RunContext, path: str = "", author: str = "", limit: int = 15
) -> str:
"""Show recent git commit history for github.com/karti-ai/podman: who committed what and when.
Optionally scope to a file or folder (path) or filter by author name/email (author).
Use this for questions about recent changes, authorship, or a specific file's history.
"""
n = max(1, min(int(limit or 15), 50))
args = [
"log",
f"--max-count={n}",
"--no-color",
"--date=short",
"--pretty=format:%h | %an | %ad | %s",
]
if author.strip():
args.append(f"--author={author.strip()}")
if path.strip():
args += ["--", path.strip()]
code, out, err = await _run_git(args)
if code != 0:
return f"Git history lookup failed: {err.strip()[:300] or 'unknown error'}"
out = out.strip()
if not out:
scope = f" for {path.strip()}" if path.strip() else ""
who = f" by {author.strip()}" if author.strip() else ""
return f"No commits found{scope}{who}."
return f"Recent commits in {REPO_SLUG} (hash | author | date | subject):\n{out}"[:7000]
@function_tool()
async def repo_find_commits(
self, context: RunContext, query: str, by: str = "message", limit: int = 15
) -> str:
"""Find commits in github.com/karti-ai/podman. by='message' searches commit messages;
by='code' finds commits that added or removed the query text in the code (pickaxe).
Use by='code' for "which commit introduced X"; use by='message' for "commits about X".
"""
cleaned = " ".join(query.split()).strip()
if not cleaned:
return "Provide a non-empty query."
n = max(1, min(int(limit or 15), 50))
args = [
"log",
f"--max-count={n}",
"--no-color",
"--date=short",
"--pretty=format:%h | %an | %ad | %s",
]
mode = by.strip().lower()
if mode == "code":
args.append(f"-S{cleaned}")
else:
mode = "message"
args += ["-i", f"--grep={cleaned}"]
code, out, err = await _run_git(args)
if code != 0:
return f"Commit search failed: {err.strip()[:300] or 'unknown error'}"
out = out.strip()
if not out:
return f'No commits found matching "{cleaned}" (by {mode}).'
return (
f'Commits in {REPO_SLUG} matching "{cleaned}" (by {mode}) — hash | author | date | subject:\n{out}'[
:7000
]
)
@function_tool() @function_tool()
async def delegate_to_hermes( async def delegate_to_hermes(
self, self,