testsAndMisc/python_pkg/token_dashboard/commits.py
Krzysztof kuhy Rudnicki 1ba202c567
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
token_dashboard: ingester core + detail tables (calls, marks, turn_detail, ab_rows) for the control-panel dashboard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JThjboXTeZ3yJPBzxMx5wS
2026-10-18 14:45:06 +02:00

106 lines
3.6 KiB
Python

"""Find the commits each session made, from git rather than from tool output.
Measured 2026-10-17: of 601 recent commit commands only 35 printed a parseable
``[branch sha]`` line -- most run through ``finish_auto.sh`` with a tailed or
backgrounded output. So every commit command is stored with its time window
and repository, and the hashes come from ``git log`` instead:
* a commit whose hash a command printed belongs to that command's session;
* otherwise a commit carrying a ``Co-Authored-By: Claude`` trailer belongs to
the commit command in the same repository whose window covers its commit
time (from :data:`LEAD_S` before the call to :data:`TAIL_S` after its
result -- a backgrounded finish_auto.sh commits after the call returns),
the nearest one when several do.
Rows are only ever added, so a repository deleted later keeps its history.
"""
from __future__ import annotations
from collections import defaultdict
import re
from typing import TYPE_CHECKING
from python_pkg.token_dashboard import gitcmd
if TYPE_CHECKING:
import sqlite3
LEAD_S = 60.0
TAIL_S = 1800.0
TRAILER = re.compile(r"^Co-Authored-By:\s*Claude", re.IGNORECASE | re.MULTILINE)
_FIELD, _RECORD = "\x1f", "\x1e"
_LOG_FIELDS = 3
def _git(repo: str, *args: str) -> str | None:
"""Run git in ``repo``; ``None`` on any failure."""
return gitcmd.run(repo, *args)
def _toplevel(hint: str, cache: dict[str, str | None]) -> str | None:
"""The work tree containing ``hint``, cached per hint."""
if hint not in cache:
out = _git(hint, "rev-parse", "--show-toplevel") if hint else None
cache[hint] = out.strip() if out else None
return cache[hint]
def _log(repo: str, since: float) -> list[tuple[str, float, bool]]:
"""``(sha, commit time, has Claude trailer)`` on HEAD since ``since``."""
out = _git(
repo,
"log",
"HEAD",
f"--since=@{int(since)}",
f"--format=%H{_FIELD}%ct{_FIELD}%B{_RECORD}",
)
rows = []
for record in (out or "").split(_RECORD):
parts = record.strip("\n").split(_FIELD, 2)
if len(parts) == _LOG_FIELDS:
rows.append((parts[0], float(parts[1]), bool(TRAILER.search(parts[2]))))
return rows
Command = tuple[str, int, float, float, str] # key, prompt, start, end, sha
def _match(
sha: str, ts: float, cmds: list[Command], *, trailer: bool
) -> Command | None:
"""The command that made this commit, if any."""
for cmd in cmds:
if cmd[4] and sha.startswith(cmd[4]):
return cmd
if not trailer:
return None
covering = [c for c in cmds if c[2] - LEAD_S <= ts <= max(c[2], c[3]) + TAIL_S]
if not covering:
return None
return min(covering, key=lambda c: abs(ts - c[2]))
def discover(conn: sqlite3.Connection) -> int:
"""Attribute git commits to sessions; returns how many rows were added."""
cache: dict[str, str | None] = {}
by_repo: dict[str, list[Command]] = defaultdict(list)
for key, prompt, start, end, sha, hint in conn.execute(
"SELECT key, prompt_idx, ts, end_ts, sha, repo FROM commits"
):
top = _toplevel(hint, cache)
if top is not None:
by_repo[top].append((key, prompt, start, end, sha))
added = 0
for repo, cmds in by_repo.items():
since = min(c[2] for c in cmds) - LEAD_S
for sha, ts, trailer in _log(repo, since):
cmd = _match(sha, ts, cmds, trailer=trailer)
if cmd is None:
continue
added += conn.execute(
"INSERT OR IGNORE INTO session_commits VALUES (?,?,?,?,?)",
(sha, repo, ts, cmd[0], cmd[1]),
).rowcount
return added