Files
ClawdDotNet/deploy/roadmap/roadmap_block.py
Richard 1e9cb789c8 Weboberflaeche hinzugefuegt und Fachlogik nach App herausgeloest (#493, #494)
- ClawdDotNet.Web: Kestrel im Prozess der Anwendung, Token-Absicherung (TokenGate mit FixedTimeEquals), Endpunkte unter /api, Ereignisstrom unter /events (Server-Sent Events) und fortlaufendes Log-Streaming.

- Weboberflaeche (wwwroot/): Vollstaendiges Single-Page-Frontend mit nativem HTML5, CSS3 und ES-Modulen ohne Buildschritt (strikt textContent, #-Routing, sofortiger Login-Sprung bei fehlendem Token, Light/Dark-Theme, lokale Barlow-Schriften).

- ClawdDotNet.App: Fachlogik aus den Avalonia-ViewModels in Dienste herausgeloest (AgentAdministration, JobAdministration, ServiceAdministration, RunCoordinator) und ueber InstanceChange synchronisiert.

- Tests: ClawdDotNet.Web.Tests und ClawdDotNet.App.Tests hinzugefuegt, alle 684 Tests erfolgreich.
2026-09-07 21:56:02 +02:00

96 lines
3.2 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Den erzeugten Zustandsblock in docs/Roadmap.md schreiben und die Prosa
abgleichen.
Das Gegenstueck `php cli/roadmap.php --write` laeuft auf der Maschine des
Deploymentcenters und kennt unsere Datei nicht. Der Weg ueber die API ist in
ROADMAP_MIGRATION.md, Schritt 5 und 6, beschrieben.
python roadmap_block.py --write Block holen und zwischen die Marker setzen
python roadmap_block.py --check Prosa gegen die Items abgleichen
Rueckgabewert von --check: 0 in Ordnung (Warnungen erlaubt), 1 bei Fehlern.
Damit laesst sich der Aufruf in einen Pre-Commit-Hook haengen.
"""
import argparse
import json
import os
import sys
import urllib.parse
import urllib.request
BASIS = os.environ.get('DC_BASE_URL', 'https://dc.mhdf.de')
SLUG = 'clawddotnet'
DATEI = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(
os.path.abspath(__file__)))), 'docs', 'Roadmap.md')
ANFANG = '<!-- dc:roadmap'
ENDE = '<!-- /dc:roadmap -->'
def ruf(token, methode, query, koerper=None):
url = BASIS + '/api/bugtracker/v1/roadmap.php?' + urllib.parse.urlencode(query)
daten = json.dumps(koerper).encode('utf-8') if koerper is not None else None
anfrage = urllib.request.Request(url, data=daten, method=methode)
anfrage.add_header('Authorization', 'Bearer ' + token)
anfrage.add_header('Content-Type', 'application/json')
with urllib.request.urlopen(anfrage, timeout=30) as antwort:
return json.loads(antwort.read().decode('utf-8'))
def grenzen(text):
"""Die Marker finden. Fehlen sie, ist das ein Fehler und kein Anhaengen -
ein Block am Dateiende waere beim naechsten Lauf ein zweiter."""
a = text.find(ANFANG)
e = text.find(ENDE)
if a < 0 or e < 0 or e < a:
raise SystemExit('Marker %s ... %s nicht gefunden in %s' % (ANFANG, ENDE, DATEI))
return a, e + len(ENDE)
def main():
p = argparse.ArgumentParser()
p.add_argument('--write', action='store_true')
p.add_argument('--check', action='store_true')
a = p.parse_args()
token = os.environ.get('DC_TOKEN', '')
if not token:
raise SystemExit('DC_TOKEN fehlt')
with open(DATEI, encoding='utf-8') as f:
inhalt = f.read()
if a.write:
block = ruf(token, 'GET', {'action': 'render', 'project_slug': SLUG})['markdown']
anfang, ende = grenzen(inhalt)
neu = inhalt[:anfang] + block.rstrip() + inhalt[ende:]
with open(DATEI, 'w', encoding='utf-8', newline='\n') as f:
f.write(neu)
print('Block geschrieben: %d Zeichen.' % len(block))
return
if a.check:
ergebnis = ruf(token, 'POST', {'action': 'check', 'project_slug': SLUG},
{'content': inhalt})
befunde = ergebnis.get('findings') or ergebnis.get('befunde') or []
if not befunde:
print('Abgleich ohne Befund.')
return
fehler = 0
for b in befunde:
grad = b.get('severity') or b.get('level') or '?'
print('%-8s %-24s %s' % (grad, b.get('code', '?'), b.get('message', b)))
if grad in ('error', 'fehler'):
fehler += 1
sys.exit(1 if fehler else 0)
raise SystemExit('--write oder --check angeben')
if __name__ == '__main__':
main()