init
This commit is contained in:
119
backend/cli.py
Normal file
119
backend/cli.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""CLI des Planers (Phase 1, kein Server):
|
||||
|
||||
python -m backend scan <pfad> Erst-Scan eines Projekt-Repos
|
||||
python -m backend rescan <pfad> Re-Scan über git-Diff gegen die Anker-Stände
|
||||
python -m backend status Karten-Stände aller bekannten Läufe
|
||||
"""
|
||||
import argparse
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent)) # `python -m backend` von der Wurzel
|
||||
|
||||
import agents # noqa: E402
|
||||
import database as db
|
||||
import scan_dienst
|
||||
|
||||
log = logging.getLogger("planer")
|
||||
|
||||
|
||||
def _abbruch(meldung: str) -> "NoReturn": # noqa: F821
|
||||
print(f"ABBRUCH: {meldung}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _vorflug(wurzel: Path) -> str:
|
||||
try:
|
||||
return scan_dienst.vorflug(wurzel)
|
||||
except scan_dienst.VorflugFehler as e:
|
||||
_abbruch(str(e))
|
||||
|
||||
|
||||
def _fortschritt(text: str) -> None:
|
||||
print(f" {text}", flush=True)
|
||||
|
||||
|
||||
def _bericht(ergebnis: dict) -> None:
|
||||
print(f"Status: {ergebnis['status']}")
|
||||
if ergebnis.get("infra_error"):
|
||||
print(f" Infra: {ergebnis['infra_error']}")
|
||||
for pfad in ergebnis.get("geschrieben", []):
|
||||
print(f" geschrieben: .planer/{pfad}")
|
||||
for card in ergebnis.get("dead", []):
|
||||
print(f" DEAD: {card} (requeue über erneuten Lauf oder DB)")
|
||||
if ergebnis["status"] == "ok":
|
||||
print("Artefakte aktualisiert — jetzt committen (Code + .planer/ atomar).")
|
||||
|
||||
|
||||
async def _scan(wurzel: Path, fortsetzen: bool = False) -> int:
|
||||
stand = _vorflug(wurzel)
|
||||
await db.init_db()
|
||||
agents.on_event = db.add_event
|
||||
import scan_board
|
||||
ergebnis = await scan_board.run_scan(wurzel, wurzel.name, stand, set_p=_fortschritt,
|
||||
fortsetzen=fortsetzen)
|
||||
_bericht(ergebnis)
|
||||
return 0 if ergebnis["status"] == "ok" else 1
|
||||
|
||||
|
||||
async def _rescan(wurzel: Path) -> int:
|
||||
stand_neu = _vorflug(wurzel)
|
||||
await db.init_db()
|
||||
agents.on_event = db.add_event
|
||||
try:
|
||||
ergebnis = await scan_dienst.rescan_lauf(wurzel, wurzel.name, stand_neu,
|
||||
set_p=_fortschritt)
|
||||
except scan_dienst.VorflugFehler as e:
|
||||
_abbruch(str(e))
|
||||
if ergebnis["status"] == "unveraendert":
|
||||
print("Keine Änderungen seit dem letzten Scan — nichts zu tun.")
|
||||
return 0
|
||||
_bericht(ergebnis)
|
||||
return 0 if ergebnis["status"] == "ok" else 1
|
||||
|
||||
|
||||
async def _status() -> None:
|
||||
await db.init_db()
|
||||
verbindung = await db.get_db()
|
||||
cursor = await verbindung.execute("SELECT DISTINCT topic FROM kanban_cards ORDER BY topic")
|
||||
topics = [r[0] for r in await cursor.fetchall()]
|
||||
if not topics:
|
||||
print("Keine Läufe in der Planer-DB.")
|
||||
return
|
||||
for topic in topics:
|
||||
counts = await db.kanban_stage_counts(topic)
|
||||
dead = await db.kanban_dead(topic)
|
||||
print(f"{topic}: " + ", ".join(f"{s}={n}" for b in counts.values() for s, n in b.items())
|
||||
+ (f" — DEAD: {[c['card_id'] for c in dead]}" if dead else ""))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
|
||||
# Hänger-Diagnose (offener Befund: Prozess lebt nach Board-Ende weiter):
|
||||
# `kill -USR1 <pid>` schreibt alle Python-Stacks nach stderr.
|
||||
import faulthandler
|
||||
import signal as _signal
|
||||
faulthandler.register(_signal.SIGUSR1)
|
||||
parser = argparse.ArgumentParser(prog="planer")
|
||||
sub = parser.add_subparsers(dest="kommando", required=True)
|
||||
for name in ("scan", "rescan"):
|
||||
p = sub.add_parser(name)
|
||||
p.add_argument("pfad", type=Path)
|
||||
if name == "scan":
|
||||
p.add_argument("--fortsetzen", action="store_true",
|
||||
help="abgebrochenen Lauf fortsetzen (nur tote Chunks neu)")
|
||||
sub.add_parser("status")
|
||||
args = parser.parse_args()
|
||||
if args.kommando == "scan":
|
||||
sys.exit(asyncio.run(_scan(args.pfad.resolve(), fortsetzen=args.fortsetzen)))
|
||||
elif args.kommando == "rescan":
|
||||
sys.exit(asyncio.run(_rescan(args.pfad.resolve())))
|
||||
else:
|
||||
asyncio.run(_status())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user