mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 20:27: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>
84 lines
2.2 KiB
Python
84 lines
2.2 KiB
Python
"""Parse the todo server's list_tasks and get_task text replies."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
import re
|
|
|
|
_FIELDS = ("id", "priority", "status", "created", "updated", "repo")
|
|
_FIELDS += ("done_check", "by", "evidence")
|
|
_META_LINE = re.compile(rf"^({'|'.join(_FIELDS)}): ?(.*)$")
|
|
_MANUAL = re.compile(r"^\s*check:\s*manual\s*$", re.IGNORECASE | re.MULTILINE)
|
|
_COLUMNS = 5
|
|
|
|
|
|
def clean_title(title: str) -> str:
|
|
"""Drop surrounding space and a leading markdown ``#``."""
|
|
return re.sub(r"^#+\s*", "", title.strip())
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TaskLine:
|
|
"""One list_tasks row."""
|
|
|
|
id8: str
|
|
status: str
|
|
priority: str
|
|
repo: str
|
|
title: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Task:
|
|
"""One get_task reply: metadata lines, ``---``, then the note text."""
|
|
|
|
id: str
|
|
status: str
|
|
repo: str | None
|
|
done_check: str | None
|
|
text: str
|
|
|
|
@property
|
|
def title(self) -> str:
|
|
return clean_title(self.text.split("\n", 1)[0])
|
|
|
|
@property
|
|
def is_manual(self) -> bool:
|
|
return _MANUAL.search(self.text) is not None
|
|
|
|
|
|
def parse_list(text: str) -> list[TaskLine]:
|
|
"""Rows of tab-separated ``id8 status priority repo title`` lines."""
|
|
rows = []
|
|
for raw in text.splitlines():
|
|
parts = raw.split("\t")
|
|
if len(parts) >= _COLUMNS:
|
|
title = clean_title("\t".join(parts[4:]))
|
|
rows.append(TaskLine(parts[0], parts[1], parts[2], parts[3], title))
|
|
return rows
|
|
|
|
|
|
def parse_detail(text: str) -> Task:
|
|
"""Split a get_task reply into metadata fields and the note text."""
|
|
meta: dict[str, str] = {}
|
|
last = ""
|
|
lines = text.split("\n")
|
|
body: list[str] = []
|
|
for index, row in enumerate(lines):
|
|
if row == "---":
|
|
body = lines[index + 1 :]
|
|
break
|
|
found = _META_LINE.match(row)
|
|
if found:
|
|
last = found.group(1)
|
|
meta[last] = found.group(2)
|
|
elif last:
|
|
meta[last] += "\n" + row
|
|
return Task(
|
|
id=meta.get("id", ""),
|
|
status=meta.get("status", ""),
|
|
repo=meta.get("repo"),
|
|
done_check=meta.get("done_check"),
|
|
text="\n".join(body),
|
|
)
|