Files
skills/hub/kvb_hub/tree.py
T
Kevin Veen-BirkenbachandClaude Opus 5.5 8ba45efd04 feat(hub): kvb-hub review hub with passkey-signed approvals
The kvb-hub is a local review hub in a container bound to 127.0.0.1: one page, JSON APIs and an MCP endpoint for the operator's inbox, asks, decisions, staged-commit reviews, the radio stream, the agent graph, the worktree tree, push-to-talk and read-aloud radio (Piper, whisper.cpp) and an optional LLM command line. The hub is only a relay. The host executors in hub/client (the runner user service, the PermissionRequest hook and the approval CLI the commit skills call) verify a WebAuthn passkey signature against keys pinned outside every sandbox, refuse replays and fail closed. scripts/hub.sh with make hub-install, hub-check, hub-pin, hub-runner and hub-hook installs and checks the hub; hub-hook writes ~/.claude/settings.json after a backup. make watch-check shows a stuck watch hook. The new kvb-git-diff skill and the commit skills route reviews through the hub; the test_hub_* suites replace test_hub.py.

Security review (kvb-think-criticize, three skeptics) fixed four holes: signed rules auto-approved chained shell commands; the client and LLM openers followed redirects, the LLM one with its bearer key; the worktree scan ran git fsmonitor and filter commands from agent-writable repos; compose set no memory, pids or health limits. Documented until core requirement 093, on the operator's word: the hub page is trusted while signing and pinning, local processes can forge the unsigned operator actions, and the container reads the host /tmp. Committed on the operator's word despite the gate: the passkey path is not yet verified live.

Accepted MEDUSA false positives (137 findings on 115 lines, rows in security-accepted.jsonl): EXA002 x17, MEDUSA-MM-SCAN-058 x12, B603 x8, MEDUSA-FTSC-SCAN-089 x7, MEDUSA-PRIV-SCAN-762 x6, AIC-EXFIL-005 x5, EXA006 x5, MEDUSA-FTSCAN-231 x5, WEB-SSRF x5, MEDUSA-INF-SCAN-2057 x3, RH005 x3, TC001 x3, LO002 x2, MEDUSA-AEXP-SCAN-096 x2, MEDUSA-CGS-SCAN-028 x2, MEDUSA-INF-SCAN-1350 x2, MEDUSA-INF-SCAN-2369 x2, MEDUSA-INF-SCAN-2440 x2, MEDUSA-INF-SCAN-2638 x2, MLSC-SERIAL-002 x2, PL002 x2, PQC005 x2, SAST-PATH-001 x2, TC004 x2, TC005 x2, TC006 x2, TC007 x2, AA-146, EXA007, FINE-050, GB-ENCODE-001, GB-ENCODE-003, MEDUSA-CGS-SCAN-029, MEDUSA-CGS-SCAN-103, MEDUSA-DP-SCAN-1034, MEDUSA-DP-SCAN-1916, MEDUSA-DP-SCAN-954, MEDUSA-EXF26-TOOL-004, MEDUSA-FT-TRIGGER-005, MEDUSA-INF-SCAN-2206, MEDUSA-INF-SCAN-2457, MEDUSA-INF-WEB-003, MEDUSA-INFRA-SCAN-030, MEDUSA-MP-SCAN-1135, MEDUSA-RLHF-POISON-002, MEDUSA-RT-SCAN-006, MM-ENCODE-001, MP-124, PQC001, PQC002, PQC006, RP001, TC009, TC010, WEB-FLASK-004.

🎯 Fix: 80 %
💥 Break: 4 %
💸 Cost: 129 %
⏱️ Time: –
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 60
🚦 Gate: ⛔ fix 80 % < 90; cost 129 % > 100: ask the operator before the commit

```
/home/kevinveenbirkenbach/Repositories/github.com/kevinveenbirkenbach/skills/.worktrees/feature/kvb-hub
```

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 18:46:40 +00:00

168 lines
6.8 KiB
Python

from __future__ import annotations
import os
import re
import subprocess
import threading
import time
from dataclasses import dataclass
from pathlib import Path
from .council import agent
from .register import ENDED, status_of
from .skill_scripts import SAFE_PATH
TREE_TTL = 60.0
TREE_DEPTH = 6
GIT_TIMEOUT = 10
def git(cwd: Path, args: list[str], home: Path) -> str | None:
env = {"PATH": SAFE_PATH, "HOME": str(home), "GIT_CONFIG_NOSYSTEM": "1", "GIT_OPTIONAL_LOCKS": "0", "GIT_TERMINAL_PROMPT": "0", "LC_ALL": "C"}
try:
done = subprocess.run(
["git", "-c", "safe.directory=*", "-c", "core.fsmonitor=false", "-C", str(cwd), *args],
capture_output=True,
encoding="utf-8",
errors="replace",
timeout=GIT_TIMEOUT,
env=env,
stdin=subprocess.DEVNULL,
check=False,
)
except (OSError, subprocess.TimeoutExpired):
return None
return done.stdout if done.returncode == 0 else None
def parse_worktrees(text: str) -> list[dict]:
found = []
for block in text.strip().split("\n\n"):
lines = dict(line.partition(" ")[::2] for line in block.splitlines() if line)
if "worktree" not in lines or "bare" in lines:
continue
found.append({
"path": lines["worktree"], "head": lines.get("HEAD", ""), "branch": lines.get("branch", "").removeprefix("refs/heads/"),
"prunable": "prunable" in lines,
})
return found
def tracking(text: str) -> tuple[int, int, bool]:
counts = {name: int(number) for name, number in re.findall(r"(ahead|behind) ([0-9]+)", text)}
return counts.get("ahead", 0), counts.get("behind", 0), text.strip() == "gone"
def parse_refs(text: str) -> list[dict]:
found = []
for line in text.splitlines():
parts = line.split("\x00")
if len(parts) != 4:
continue
ahead, behind, gone = tracking(parts[3])
found.append({"name": parts[0], "head": parts[1], "upstream": parts[2], "ahead": ahead, "behind": behind, "gone": gone})
return found
def linked(root: Path) -> list[Path]:
found = []
for top, dirs, names in os.walk(root):
here = Path(top)
if ".git" in names or ".git" in dirs:
found.append(here)
dirs[:] = []
elif len(here.relative_to(root).parts) >= TREE_DEPTH:
dirs[:] = []
else:
dirs.sort()
return found
def dirty(path: Path, home: Path) -> bool | None:
# agents write these repos, and status runs every filter.*.clean command the config names
if git(path, ["config", "--get-regexp", r"^filter\."], home) is not None:
return None
status = git(path, ["status", "--porcelain=v1", "--untracked-files=normal", "--ignore-submodules=all"], home)
return None if status is None else bool(status.strip())
def repo_entry(path: Path, label: str, home: Path) -> dict:
trees = parse_worktrees(git(path, ["worktree", "list", "--porcelain"], home) or "")
refs = git(path, ["for-each-ref", "--format=%(refname:short)%00%(objectname)%00%(upstream:short)%00%(upstream:track,nobracket)", "refs/heads"], home)
branches = parse_refs(refs or "")
for tree in trees:
tree["dirty"] = None if tree["prunable"] else dirty(Path(tree["path"]), home)
known = {branch["name"] for branch in branches}
branches += [{"name": tree["branch"], "head": tree["head"], "upstream": "", "ahead": 0, "behind": 0, "gone": False} for tree in trees if tree["branch"] and tree["branch"] not in known]
for branch in branches:
branch["worktrees"] = [tree for tree in trees if tree["branch"] == branch["name"]]
return {"path": str(path), "name": label, "branches": branches, "detached": [tree for tree in trees if not tree["branch"]]}
def scan_tree(repos: Path, worktrees: Path, home: Path) -> dict:
root = repos.resolve()
try:
mains = sorted(path.resolve() for path in root.glob("*/*/*") if (path / ".git").is_dir())
except OSError:
mains = []
entries = {str(path): repo_entry(path, str(path.relative_to(root)), home) for path in mains if path.is_relative_to(root)}
seen = {tree["path"] for entry in entries.values() for branch in entry["branches"] for tree in branch["worktrees"]}
seen |= {tree["path"] for entry in entries.values() for tree in entry["detached"]}
for path in linked(worktrees.resolve()):
if str(path) in seen:
continue
common = git(path, ["rev-parse", "--path-format=absolute", "--git-common-dir"], home)
main = Path(common.strip()).parent if common else None
if main is not None and str(main) not in entries and (main / ".git").is_dir():
entries[str(main)] = repo_entry(main, str(main), home)
return {"repos": sorted(entries.values(), key=lambda entry: entry["name"]), "scanned": time.time()}
def inside(path: str, folder: str) -> bool:
return path == folder or path.startswith(f"{folder.rstrip('/')}/")
def assign(scan: dict, tasks: dict[str, dict], claims: list[dict], reviews: list[dict]) -> dict:
current = {task: row for task, row in tasks.items() if status_of(row) not in ENDED}
holders: dict[str, set[str]] = {}
for task, row in current.items():
names = {agent(row.get("worker"))} | {claim["agent"] for claim in claims if claim["office"] == task}
holders[task] = {name for name in names if name}
latest: dict[str, dict] = {}
for review in reviews:
latest[review["worktree"]] = {"status": review["status"], "gate": review.get("gate", ""), "title": review["title"]}
repos = []
for entry in scan["repos"]:
branches = []
for branch in entry["branches"]:
trees = []
for tree in branch["worktrees"]:
workers = set()
for task, row in current.items():
workdir, name = row.get("workdir"), row.get("branch")
by_dir = isinstance(workdir, str) and workdir.startswith("/") and inside(workdir, tree["path"])
by_branch = not isinstance(workdir, str) and name == branch["name"]
if by_dir or by_branch:
workers |= holders[task]
trees.append({**tree, "workers": sorted(workers, key=str.lower), "review": latest.get(tree["path"])})
branches.append({**branch, "worktrees": trees})
repos.append({**entry, "branches": branches})
return {"repos": repos, "scanned": scan["scanned"]}
@dataclass
class Tree:
repos: Path
worktrees: Path
home: Path
lock: threading.Lock
cached: dict | None
scanned: float
def get(self) -> dict:
with self.lock:
if self.cached is None or time.monotonic() - self.scanned >= TREE_TTL:
self.cached = scan_tree(self.repos, self.worktrees, self.home)
self.scanned = time.monotonic()
return self.cached