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:
@@ -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.
|
||||
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.
|
||||
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,
|
||||
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.
|
||||
@@ -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
|
||||
|
||||
|
||||
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):
|
||||
def __init__(self, pod_id: str, identity: str, session_id: str, conversation_room: str) -> None:
|
||||
super().__init__(instructions=INSTRUCTIONS)
|
||||
@@ -212,6 +233,73 @@ class PodManLiveAgent(Agent):
|
||||
body = "\n".join(lines)
|
||||
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()
|
||||
async def delegate_to_hermes(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user