mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 21:07:15 +01:00
Python 3.14 stdlib package wiring git to the todo MCP server: - closer (post-commit): runs the done_check of each Todo: trailer task and close_task's it only on exit 0. It FAILS OPEN: the commit already happened, so a down server, bad token or no gh prints one warning and exits 0 instead of failing the hook. - sweep: nightly drift report (systemd units shipped, not enabled). - import-pr: a merged PR's '## Todo:' items become tasks, deduped. - A done_check runs only if its sha256 is in ~/.config/todo-hooks/trusted-checks; 'Check: manual' notes are never auto-closed. - Hooks exposed in .pre-commit-hooks.yaml (post-commit, post-merge, post-rewrite). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
2.7 KiB
Python
77 lines
2.7 KiB
Python
"""Post-commit closer: close ``Todo:`` trailer tasks whose check passes."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from todo_hooks.checkrun import CHECK_TIMEOUT, run_check
|
|
from todo_hooks.client import McpError, McpToolError
|
|
from todo_hooks.context import describe
|
|
from todo_hooks.gitrepo import expected_repo, head_sha, head_todo_ids, toplevel
|
|
from todo_hooks.notes import Task, parse_detail
|
|
from todo_hooks.trust import is_trusted
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
from todo_hooks.client import TaskClient
|
|
from todo_hooks.context import Context
|
|
|
|
|
|
def _refusal(ctx: Context, root: Path, task: Task) -> str | None:
|
|
"""Why this task must not be auto-closed, or None when it may run."""
|
|
expected = expected_repo(root)
|
|
id8 = task.id[:8]
|
|
if task.status == "done":
|
|
return f"{id8} already done"
|
|
if task.repo != expected:
|
|
return f"{id8} belongs to {task.repo or 'no repo'}, not {expected}; left open"
|
|
if task.is_manual:
|
|
return f"{id8} close by hand (Check: manual)"
|
|
if not task.done_check:
|
|
return f"{id8} has no done_check; close by hand"
|
|
if not is_trusted(ctx.paths.trust_file, task.done_check):
|
|
return f"{id8} untrusted done_check; run: python3 -m todo_hooks trust {id8}"
|
|
return None
|
|
|
|
|
|
def _verdict(ctx: Context, root: Path, task_id: str, client: TaskClient) -> str:
|
|
"""One line saying what happened to the task; closes it on a trusted pass."""
|
|
try:
|
|
task = parse_detail(client.call("get_task", {"id": task_id}))
|
|
except McpToolError as err:
|
|
return f"{task_id[:8]} not found: {err}"
|
|
refusal = _refusal(ctx, root, task)
|
|
if refusal:
|
|
return refusal
|
|
result = run_check(task.done_check or "", root, timeout=CHECK_TIMEOUT)
|
|
if not result.passed:
|
|
return f"{task.id[:8]} left open: done_check {result.describe()}"
|
|
evidence = f"{head_sha(root)} done_check exit 0"
|
|
try:
|
|
client.call("close_task", {"id": task.id, "evidence": evidence})
|
|
except McpToolError as err:
|
|
return f"{task.id[:8]} close failed: {err}"
|
|
return f"closed {task.id[:8]}"
|
|
|
|
|
|
def run(ctx: Context) -> int:
|
|
"""Always 0: the commit already happened, so a failure here must stay quiet."""
|
|
root = toplevel(ctx.cwd)
|
|
ids = head_todo_ids(root) if root else []
|
|
if root is None or not ids:
|
|
return 0
|
|
try:
|
|
client = ctx.make_client()
|
|
except McpError as err:
|
|
ctx.say(f"warning: {describe(err)}; nothing closed")
|
|
return 0
|
|
try:
|
|
for task_id in ids:
|
|
ctx.say(_verdict(ctx, root, task_id, client))
|
|
except McpError as err:
|
|
ctx.say(f"warning: {describe(err)}; nothing closed")
|
|
finally:
|
|
client.close()
|
|
return 0
|