#!/app/.venv/bin/python """Narrow source reader for the code-investigator agent. It exposes no Git operation and prints no cache filesystem path. The backend owns the shared bare mirror; this helper only prepares and reads an already available SHA through backend-owned Git queries. """ from __future__ import annotations import argparse import json import re import sys from pathlib import Path # The helper is copied under /opt/agentkit while the backend package remains # under /app in the runtime image. Do not rely on the invoking agent's CWD. sys.path.insert(0, "/app") from investibook.core.code_context import ( CodeContextError, LocalCodeContextService, ) from investibook.core.code_repositories import code_repository_by_context_prefix _CONTEXT = re.compile(r"^(?P[a-z0-9-]+):(?P[0-9a-f]{40}):(source|no-tests)$") _SOURCE_PROFILES = {"source", "no-tests"} _MAX_RESULTS = 100 _MAX_LINE = 10_000 _MAX_DISCOVERY_PER_ANCHOR = 12 _MAX_INSPECT_RANGES = 8 _INSPECT_LINES_BEFORE = 16 _INSPECT_LINES_AFTER = 56 _MAX_INSPECT_LINES = 2_000 _MAX_INSPECT_OUTPUT_BYTES = 40_000 def _prepare(args: argparse.Namespace) -> dict[str, object]: if args.profile not in _SOURCE_PROFILES: raise CodeContextError("unsupported code source profile") context, navigation = LocalCodeContextService().prepare_context_with_navigation( repo=args.repo, commit_sha=args.sha, profile=args.profile ) return { "repo": context.repo, "commit_sha": context.commit_sha, "profile": context.profile, "context_id": context.context_id, "reused": context.reused, "navigation": navigation, } def _read_many(args: argparse.Namespace) -> dict[str, object]: ranges: list[tuple[str, int, int]] = [] for raw_range in args.range: try: path, start, end = raw_range.rsplit(":", 2) ranges.append((path, int(start), int(end))) except ValueError as exc: raise CodeContextError("range must be path:start:end") from exc return LocalCodeContextService().read_context_ranges( context_id=args.context, ranges=ranges ) # Bare-mirror implementation. No command materializes or traverses a per-SHA # checkout. def _service_for_context(context_id: str) -> tuple[LocalCodeContextService, str]: matched = _CONTEXT.fullmatch(context_id) if not matched: raise CodeContextError("invalid code context") service = LocalCodeContextService() repository = code_repository_by_context_prefix(matched.group("context_prefix")) if repository is None or not repository.enabled: raise CodeContextError("invalid code context") service.prepare_context(repo=repository.repo, commit_sha=matched.group("sha")) return service, matched.group("sha") def _search(args: argparse.Namespace) -> dict[str, object]: try: re.compile(args.query) except re.error as exc: raise CodeContextError("invalid search expression") from exc service, _ = _service_for_context(args.context) return { "context_id": args.context, "matches": service.search_context( context_id=args.context, expression=args.query, max_results=_MAX_RESULTS ), "truncated": False, } def _discover(args: argparse.Namespace) -> dict[str, object]: anchors = [item.strip() for item in args.anchor if item.strip()] if not anchors or len(anchors) > 12: raise CodeContextError("provide between 1 and 12 semantic anchors") hints = [ Path(item).as_posix().strip("/") for item in args.path_hint if item.strip() ] service, _ = _service_for_context(args.context) results: dict[str, list[dict[str, object]]] = {} for anchor in anchors: rows = service.search_context( context_id=args.context, expression=re.escape(anchor), max_results=_MAX_DISCOVERY_PER_ANCHOR, ignore_case=True, ) results[anchor] = sorted( rows, key=lambda row: ( not any( str(row["reference"]).split(":", 1)[0].startswith(hint + "/") or str(row["reference"]).split(":", 1)[0] == hint for hint in hints ), str(row["reference"]), ), ) return {"context_id": args.context, "anchors": results, "path_hints_used": hints} def _inspect(args: argparse.Namespace) -> dict[str, object]: anchors = [item.strip() for item in args.anchor if item.strip()] if not anchors or len(anchors) > 6: raise CodeContextError("provide between 1 and 6 semantic anchors") discovery = _discover( argparse.Namespace( context=args.context, anchor=anchors, path_hint=args.path_hint ) ) candidates = discovery["anchors"] assert isinstance(candidates, dict) ranges: list[tuple[str, int, int]] = [] selected: set[tuple[str, int]] = set() pending = [list(candidates[anchor]) for anchor in anchors] while any(pending) and len(ranges) < _MAX_INSPECT_RANGES: progressed = False for rows in pending: if not rows: continue match = rows.pop(0) reference = str(match["reference"]) path, raw_line = reference.rsplit("@", 1)[0].rsplit(":", 1) line = int(raw_line) if (path, line) in selected: continue start, end = ( max(1, line - _INSPECT_LINES_BEFORE), line + _INSPECT_LINES_AFTER, ) if ( sum( current_end - current_start + 1 for _, current_start, current_end in ranges ) + end - start + 1 > _MAX_INSPECT_LINES ): continue ranges.append((path, start, end)) selected.add((path, line)) progressed = True if len(ranges) >= _MAX_INSPECT_RANGES: break if not progressed: break if not ranges: # Refinement anchors are model-generated hypotheses. Return a # structured miss so the agent can fall back to its last useful # inspection instead of treating a search miss as a broken context. return { "context_id": args.context, "anchors": anchors, "path_hints_used": discovery["path_hints_used"], "candidate_count": sum(len(items) for items in candidates.values()), "selected_range_count": 0, "truncated": False, "refinement_available": False, "matched": False, "error": "no source matches for semantic anchors", "ranges": [], } service, _ = _service_for_context(args.context) result = service.read_context_ranges(context_id=args.context, ranges=ranges) payload: dict[str, object] = { "context_id": args.context, "anchors": anchors, "path_hints_used": discovery["path_hints_used"], "candidate_count": sum(len(items) for items in candidates.values()), "selected_range_count": len(ranges), "truncated": any(rows for rows in pending), "refinement_available": any(rows for rows in pending), "matched": True, "ranges": [ { "path": item["path"], "start_line": int(item["lines"][0]["reference"].rsplit(":", 1)[1]), "end_line": int(item["lines"][-1]["reference"].rsplit(":", 1)[1]), "content": "\n".join( f"{line['reference'].rsplit(':', 1)[1]}: {line['text']}" for line in item["lines"] ), } for item in result["ranges"] if item["lines"] ], } output_truncated = False while ( len(json.dumps(payload, ensure_ascii=False, indent=2).encode("utf-8")) > _MAX_INSPECT_OUTPUT_BYTES ): blocks = payload["ranges"] assert isinstance(blocks, list) if not blocks: raise CodeContextError("source context exceeds inspect output budget") block = blocks[-1] assert isinstance(block, dict) content = block["content"] assert isinstance(content, str) lines = content.splitlines() if len(lines) <= 1: blocks.pop() else: block["content"] = "\n".join(lines[:-1]) block["end_line"] = int(lines[-2].split(":", 1)[0]) output_truncated = True if output_truncated: payload["truncated"] = True payload["refinement_available"] = True payload["returned_range_count"] = len(payload["ranges"]) return payload def _history_log(args: argparse.Namespace) -> dict[str, object]: service, _ = _service_for_context(args.context) return {"context_id": args.context, "entries": service.history_log(context_id=args.context, path=args.path)} def _history_blame(args: argparse.Namespace) -> dict[str, object]: service, _ = _service_for_context(args.context) return {"context_id": args.context, "path": args.path, "lines": service.history_blame(context_id=args.context, path=args.path, start=args.start, end=args.end)} def _history_show(args: argparse.Namespace) -> dict[str, object]: service, _ = _service_for_context(args.context) return {"context_id": args.context, "commit_sha": args.commit, "content": service.history_show(context_id=args.context, commit_sha=args.commit)} def _history_diff(args: argparse.Namespace) -> dict[str, object]: service, _ = _service_for_context(args.context) return {"context_id": args.context, "from_commit": args.from_commit, "to_commit": args.to_commit, "content": service.history_diff(context_id=args.context, from_commit=args.from_commit, to_commit=args.to_commit)} def main() -> None: parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(dest="command", required=True) prepare = subparsers.add_parser("prepare") prepare.add_argument("--repo", required=True) prepare.add_argument("--sha", required=True) prepare.add_argument("--profile", default="source") read_many = subparsers.add_parser("read-many") read_many.add_argument("--context", required=True) read_many.add_argument( "--range", action="append", default=[], help="path:start:end; repeatable" ) search = subparsers.add_parser("search") search.add_argument("--context", required=True) search.add_argument("--query", required=True) discover = subparsers.add_parser("discover") discover.add_argument("--context", required=True) discover.add_argument("--anchor", action="append", default=[]) discover.add_argument("--path-hint", action="append", default=[]) inspect = subparsers.add_parser("inspect") inspect.add_argument("--context", required=True) inspect.add_argument("--anchor", action="append", default=[]) inspect.add_argument("--path-hint", action="append", default=[]) refine = subparsers.add_parser("inspect-refine") refine.add_argument("--context", required=True) refine.add_argument("--anchor", action="append", default=[]) refine.add_argument("--path-hint", action="append", default=[]) history_log = subparsers.add_parser("history-log") history_log.add_argument("--context", required=True) history_log.add_argument("--path") history_blame = subparsers.add_parser("history-blame") history_blame.add_argument("--context", required=True) history_blame.add_argument("--path", required=True) history_blame.add_argument("--start", required=True, type=int) history_blame.add_argument("--end", required=True, type=int) history_show = subparsers.add_parser("history-show") history_show.add_argument("--context", required=True) history_show.add_argument("--commit", required=True) history_diff = subparsers.add_parser("history-diff") history_diff.add_argument("--context", required=True) history_diff.add_argument("--from", dest="from_commit", required=True) history_diff.add_argument("--to", dest="to_commit", required=True) args = parser.parse_args() actions = { "prepare": _prepare, "read-many": _read_many, "search": _search, "discover": _discover, "inspect": _inspect, "inspect-refine": _inspect, "history-log": _history_log, "history-blame": _history_blame, "history-show": _history_show, "history-diff": _history_diff, } try: print(json.dumps(actions[args.command](args), ensure_ascii=False, indent=2)) except CodeContextError as exc: print(json.dumps({"error": str(exc)})) raise SystemExit(2) from exc if __name__ == "__main__": main()