Files
istosandClaude Fable 5 29cbb11eb5 Bench 1: the task board extracted as its own distribution
Everything core from cicero-pas's .task-manager, with instance data
reduced to skeleton: empty stage directories, the task template, empty
local/ scaffolding, and a README covering install (clone into
.task-manager/, vendored on purpose) and update (update.sh replaces core
wholesale; local/ and tasks/ survive).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 17:57:07 +02:00

108 lines
3.9 KiB
Python

"""Local commands: project-owned executables run against a task's worktree.
Core knows nothing about what any command does — the project does
(local/commands/<name>, executables the project owns and evolves). The
contract:
- env in: CMD_WORKTREE, CMD_BRANCH, CMD_TASK, CMD_REPO (+ BOARD_*)
- stdout/stderr → a log under local/state/commands/
- exit 0 = done; anything else = failed — either way the ticker narrates
the ending with the log's last line
One run per (task, command) at a time.
"""
from __future__ import annotations
import os
import subprocess
import threading
import time
from pathlib import Path
import config
import state
RUNNING: dict[str, dict] = {} # "task:name" -> record
def public() -> list[dict]:
with state.LOCK:
return [{"task": r["task"], "name": r["name"], "started": r["started"]}
for r in RUNNING.values()]
def run(name: str, filename: str) -> dict:
if not any(c["name"] == name for c in config.commands()):
raise ValueError(f"no such command: {name}")
if Path(filename).name != filename or not filename.endswith(".md"):
raise ValueError("bad filename")
key = f"{filename}:{name}"
with state.LOCK:
if key in RUNNING:
raise ValueError(f"{name} is already running on {filename}")
stem = filename[:-3]
branch = f"task/{stem}"
worktree = config.WORKTREES / stem
if not worktree.exists():
if subprocess.run(["git", "-C", str(config.REPO), "rev-parse", "--verify",
"--quiet", branch], capture_output=True).returncode != 0:
raise ValueError(f"no worktree and no branch {branch} — nothing to run against")
config.WORKTREES.mkdir(exist_ok=True)
result = subprocess.run(
["git", "-C", str(config.REPO), "worktree", "add", str(worktree), branch],
capture_output=True, text=True)
if result.returncode != 0:
raise ValueError(f"could not recreate the worktree: {result.stderr.strip()[:200]}")
log_dir = config.STATE / "commands"
log_dir.mkdir(parents=True, exist_ok=True)
log_path = log_dir / f"{stem}-{name}-{time.strftime('%H%M%S')}.log"
env = config.child_env()
env.update({
"CMD_WORKTREE": str(worktree),
"CMD_BRANCH": branch,
"CMD_TASK": filename,
"CMD_REPO": str(config.REPO),
"BOARD_PORT": str(state.serve_port),
})
log_file = log_path.open("wb")
try:
proc = subprocess.Popen(
[str(config.LOCAL / "commands" / name)], cwd=str(worktree), env=env,
stdout=log_file, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL)
except OSError as exc:
log_file.close()
raise ValueError(f"could not launch {name}: {exc}")
record = {"task": filename, "name": name, "started": time.time(),
"proc": proc, "log": str(log_path)}
with state.LOCK:
RUNNING[key] = record
state.record_board_event({
"kind": "agent", "actor": "board", "file": filename,
"summary": f"running {name} on {filename}'s worktree"})
state.broadcast({"type": "board"})
threading.Thread(target=_reap, args=(key, record, log_file), daemon=True).start()
return {"name": name, "task": filename}
def _reap(key: str, record: dict, log_file) -> None:
rc = record["proc"].wait()
log_file.close()
with state.LOCK:
RUNNING.pop(key, None)
try:
lines = [l.strip() for l in Path(record["log"]).read_text(
encoding="utf-8", errors="replace").splitlines() if l.strip()]
last = lines[-1][:150] if lines else ""
except OSError:
last = ""
verdict = "done" if rc == 0 else f"failed (rc={rc})"
state.record_board_event({
"kind": "agent", "actor": "board", "file": record["task"],
"summary": f"{record['name']} on {record['task']} {verdict}"
+ (f" — {last}" if last else "")})
state.broadcast({"type": "board"})