Files
ClawdDotNet/deploy/roadmap/import_items.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

430 lines
38 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
docs/Roadmap.md einmalig ins Deploymentcenter uebernehmen.
Verfahren: Deploymentcenter/docs/ROADMAP_MIGRATION.md, Schritte 2 bis 4.
Das Skript scripts/roadmap_import.py des Deploymentcenters passt nicht - es ist
auf dessen eigene Datei, den Slug "deploymentcenter" und Tabellen der Form
"| A4 | Titel | X |" fest verdrahtet. Unsere Datei hat andere Spalten und
Marker, die es nicht kennt. Die Anleitung sieht diesen Fall vor: das Skript ist
dort die Vorlage, nicht das Werkzeug.
python import_items.py --lesen nur zeigen, kein Netzverkehr
python import_items.py --uebernehmen gegen dc.mhdf.de
Wiederholbar: Jedes Item traegt ein client_ref. Ein zweiter Lauf legt nichts
doppelt an, sondern findet dieselben Items wieder und schreibt die
Roadmap-Felder neu.
"""
import argparse
import json
import os
import re
import time
import urllib.error
import urllib.parse
import urllib.request
BASIS = os.environ.get('DC_BASE_URL', 'https://dc.mhdf.de')
SLUG = 'clawddotnet'
ANKER_DATEI = 'docs/Roadmap.md'
# Sprechende Kuerzel statt A-H: die alten Herkunftskuerzel im Fliesstext heissen
# bereits A1, B8, C1, L1 - Buchstabenbereiche daneben waeren zwei Nummernkreise,
# die gleich aussehen und Verschiedenes meinen.
BEREICHE = [
('DEC', 'Offene Entscheidungen', 'Kosten kein Budget, nur eine Festlegung - blockieren aber Umsetzung.', 5),
('SEC', 'Kontrolle und Sicherheit', 'Erst Bremsen, dann PS. Steht vor den Faehigkeiten.', 10),
('CAP', 'Agenten-Faehigkeiten', 'Werkzeuge, Gedaechtnis, Reichweite in die Welt.', 20),
('COMM', 'Kommunikation zwischen Agenten', 'Fuenf Phasen aus dem Agentenkommunikation-Konzept.', 30),
('DATA', 'Daten und Speicher', 'SQLite ist die einzige Wahrheit; alles Weitere ist Spiegel.', 40),
('OPS', 'Betrieb und Auslieferung', 'Deploymentcenter, Linux-Zielbetrieb, Release-Strecke.', 50),
('FIN', 'Finanz und Analyse', 'Bewusst nachgelagert - erst der allgemeine Client, dann der Handel.', 60),
('HYG', 'Hygiene', 'Kleinbugs, in einem Aufwasch zu erledigen.', 70),
]
O, D, R = 'open', 'deferred', 'resolved'
BUG, FEAT = 'bug', 'feature_request'
# (ref, bereich, rang, status, schwere, art, titel, beschreibung, ausloeser)
ITEMS = [
# ---------------------------------------------------------------- DEC
('e10', 'DEC', 5, O, 'high', FEAT,
'E10 entscheiden: Womit wird der Hauptschluessel unter Linux gebunden?',
'Secret Service (gnome-keyring ueber D-Bus) oder systemd-creds mit TPM2. Blockiert L1 und damit den Zeitpunkt fuer MCP-Client und Browser-Tool. Empfehlung im Konzept: Secret Service als Standard, Backend ueber CLAWD_SECRET_BACKEND umschaltbar - mit dem Vorbehalt, dass Autologin den Keyring von selbst entsperrt und er dann nur noch gegen andere Benutzer schuetzt, nicht gegen den Zugang zur Maschine.', None),
('e1', 'DEC', 50, O, 'high', FEAT,
'E1 entscheiden: Matrix oder Rocket.Chat?',
'Rocket.Chat ist gebaut und bedient denselben Zweck, fuer den Matrix vorgesehen war. Loest drei blockierte Punkte auf einmal: Streaming (K4), ob der ChannelRouter Pflicht oder Kuer ist, und wo der MySQL-Spiegel wohnt. Kostet keine Umsetzung, nur eine Festlegung.', None),
('e4', 'DEC', 210, O, 'medium', FEAT,
'E4 entscheiden: ask_agent behalten oder ersatzlos streichen?',
'Eng gefasst mit Tiefe 1 behalten, oder alles ueber Tasks laufen lassen. Bestimmt den Zuschnitt von Phase 3 der Agentenkommunikation. Empfehlung: behalten, eng gefasst.', None),
('e2', 'DEC', 220, O, 'low', FEAT,
'E2 entscheiden: Nextcloud-Identitaet - Dienstkonto oder Benutzer je Agent?',
'Bestimmt den Zuschnitt des Nextcloud-Tools. Empfehlung: Dienstkonto mit Ordnern je Agent.', None),
('e3', 'DEC', 230, O, 'low', FEAT,
'E3 entscheiden: Oeffentliche Freigabe-Links erlauben oder hart sperren?',
'Bestimmt die Voreinstellung allowPublicShares. Offen.', None),
('e5', 'DEC', 240, O, 'low', FEAT,
'E5 entscheiden: Notfallkanal-Reihenfolge instanzweit oder je Agent?',
'Kleinigkeit am ChannelRouter. Empfehlung: instanzweit.', None),
('e6', 'DEC', 250, O, 'low', FEAT,
'E6 entscheiden: catchUp je Task abschaltbar machen?',
'Bestimmt das Verhalten des Scanners bei verpassten Terminen. Offen.', None),
('e7', 'DEC', 260, O, 'low', FEAT,
'E7 entscheiden: Prioritaet bei mehreren faelligen Tasks - strikt oder gewichtet?',
'Bestimmt die Reihenfolge im Scanner. Offen.', None),
('e8', 'DEC', 270, O, 'low', FEAT,
'E8 entscheiden: Aufbewahrungsdauer der Agentenkommunikation',
'Vorgabewert im Speicher. Empfehlung: unbegrenzt, bis der MySQL-Spiegel steht; dann 180 Tage lokal.', None),
('e9', 'DEC', 280, O, 'low', FEAT,
'E9 entscheiden: Automatische Orderausfuehrung - ueberhaupt?',
'Der gesamte Handels-Zweig haengt daran. Broker-API, Teilausfuehrungen, rechtlicher Rahmen - eigene Kategorie, kein Nebenprodukt der Analyse-Agenten. Empfehlung: bewusst nicht vorgesehen.', None),
# ---------------------------------------------------------------- SEC
('l1', 'SEC', 10, O, 'critical', BUG,
'L1: Der Hauptschluessel liegt unter Linux im Klartext',
'SecretKeyStore.Wrap gibt den Hauptschluessel unter Nicht-Windows ungewrappt zurueck (OperatingSystem.IsWindows() ? ProtectWithDpapi(key) : key); geschuetzt ist er nur durch 0600 aus AppPaths.RestrictToOwner. Auf einer dauerhaft laufenden, netzgebundenen Maschine ist das die erste Baustelle - alles, was neue Zugangsdaten anzieht, steht dahinter. Ersatz nach E10, gekapselt hinter der vorhandenen Wrap/Unwrap-Naht. Migration ueber Backup mit Passphrase (PBKDF2 + AES-GCM), weil bestehende Werte DPAPI-verschluesselt und auf Linux nicht lesbar sind; die Windows-Zweige bleiben stehen, bis der Umzug durch ist. Test gehoert dazu: Der Hauptschluessel wird unter Linux nie ungewrappt geschrieben, und der Migrationspfad Backup-mit-Passphrase nach Restore ist end-to-end gruen.', None),
('freigabe-ansicht', 'SEC', 20, O, 'high', FEAT,
'Freigabe-Ansicht: gestagte Aufrufe sichtbar machen und freigeben',
'Staging greift, aber niemand kann freigeben - damit ist der unbeaufsichtigte Betrieb, fuer den das Vorhaben da war, nicht moeglich. Seite in src/ClawdDotNet.Desktop: offene Vorschlaege listen, freigeben, ablehnen mit Grund. StagingService.ListPendingAsync/ApproveAsync/RejectAsync steht bereit, AppHost.Staging reicht ihn durch. Dritte Schaltflaeche "Immer erlauben" gleich mitnehmen: gibt frei und setzt die Policy fuer dieses Tool auf auto - der Unterschied zwischen einer Liste, die man taeglich abarbeitet, und einer, die von selbst leer wird.', None),
('output-scrubbing', 'SEC', 80, O, 'high', FEAT,
'Output-Scrubbing: Secret-Werte zentral aus allen Tool-Ergebnissen maskieren',
'Bekannte Werte aus dem Register des SecretProtector werden maskiert, bevor sie in Kontext, Historie, Audit-Arguments oder Spiegel gelangen. Ein Filter an ExecuteToolCallAsync. Schliesst die Luecke, die S3 nur fuer URLs schloss. Muss vor allem stehen, was Tool-Ergebnisse dauerhaft speichert - und vor Shell-Tool, MCP-Client und Browser-Tool, deren Ergebnisse voller Zugangsdaten sind.', None),
('untrusted-content', 'SEC', 300, O, 'medium', FEAT,
'Tool-Ergebnisse als untrusted content rahmen',
'Ergebnisse in <untrusted_content> rahmen und im Kern-Prompt verankern, dass daraus keine Anweisungen befolgt werden. Staging nimmt die Schaerfe, die Rahmung bleibt noetig - Finanzinhalte auf X und in Newslettern sind genau der Ort, an dem gezielt manipuliert wird. Rest aus K2.', None),
('secret-ui', 'SEC', 310, O, 'medium', FEAT,
'Secret-UI: Zugangsdaten setzen und rotieren',
'Oberflaeche zum Setzen und Rotieren der Zugangsdaten. Die Verschluesselung (S7, DPAPI) steht. Rest aus F-A6.', None),
# ---------------------------------------------------------------- CAP
('filerw-papierkorb', 'CAP', 60, O, 'medium', FEAT,
'FileRW-Papierkorb statt endgueltigem Loeschen',
'delete ist heute endgueltig; ein Aufruf mit path "." raeumt einen Workspace ab. Verschieben in einen Papierkorb je Workspace, ein Cron-Task raeumt nach X Tagen auf. Danach kann FileRW.delete im Staging von approve auf auto - das entlastet die Freigabe-Ansicht sofort. Kleinster Eingriff mit realer Wirkung. Bauplan: docs/umsetzungsplaene/UMSETZUNGSPLAN-FileRW-Papierkorb-Cleanup.md', None),
('l4-shell', 'CAP', 90, O, 'medium', FEAT,
'L4: Shell-Tool mit echter Sandbox',
'Kommandoausfuehrung mit Allowlist je Agent, in bwrap oder systemd-run mit eigenem Mount-Namespace auf den Workspace, ohne Netz sofern nicht angefordert, Zeit- und Ausgabekappung. Stand vorher im Ideenspeicher, weil "sandboxed" unter Windows ein Versprechen war, das man nicht halten kann - unter Linux geht es. Loest zugleich die hartverdrahteten yt-dlp/ffmpeg-Aufrufe im SocialMediaManagerTool ab. Policy approve, bis sich ein Kommandosatz eingespielt hat. Die Sandbox ist die Funktion, nicht das Drumherum: je Garantie ein Test, der den Ausbruch versucht - Schreiben ausserhalb des Workspace, Netzzugriff ohne Anforderung, Kommando ausserhalb der Allowlist, Zeit- und Ausgabekappung.', None),
('l5-mcp', 'CAP', 100, O, 'medium', FEAT,
'L5: MCP-Client fuer stdio- und HTTP-Server',
'Fremde Tools werden als IAgentTool eingehaengt und laufen damit durch PermissionGate, Staging, Audit und Budget wie eigene. Damit werden Gmail, Kalender, Drive, Notion, Jira zur Konfigurationssache statt zum C#-Projekt. Invariante, kein Vorschlag: fremde Tools stehen standardmaessig auf approve. Fremde Tool-Schemata sind Fremdeingaben - Namenskollisionen, Schema-Validierung, Ergebniskappung wie bei eigenen Tools. Nimmt dem Http-Tool aus dem Ideenspeicher die Dringlichkeit; zusammen mit den Toolsets denken.', None),
('a4-skills', 'CAP', 110, O, 'medium', FEAT,
'A4: Skills und Toolsets - dreischichtiger Prompt statt "alles immer"',
'Schlanker Dauerkern (haelt den Prompt-Cache stabil), nachladbare Skills als SKILL.md mit Frontmatter (list_toolsets nach load_toolset, bei vielen Skills stattdessen skill_search), Hot-Reload ueber FileSystemWatcher mit Debounce um 500 ms. Tool-Fehlermeldungen nennen die gueltigen Parameter, statt das Modell raten zu lassen. Agenten duerfen Skills aus Erfahrung selbst schreiben - aber erst nach Freigabe aktiv, sonst ist es ein Injection-Kanal in kuenftige Laeufe. Groesster Token-Hebel nach dem Caching, und ohne Toolsets ertrinkt der Prompt in dem, was der MCP-Client hereinbringt.', None),
('l6-vision', 'CAP', 120, O, 'medium', FEAT,
'L6: Vision - Bild-Content-Blocks im OpenRouterClient',
'Der Client kennt heute nur Text. Bild-Content-Blocks im Request, Quelle aus dem Workspace, harte Groessen- und Anzahlkappung. Rueckfallebene fuer das Browser-Tool, wenn das DOM nichts hergibt; nebenbei Charts und Screenshots fuer den Finanzteil.', None),
('l7-browser', 'CAP', 130, O, 'medium', FEAT,
'L7: Browser-Tool mit Live-Ansicht',
'Playwright, Chromium headed auf einem eigenen X-Display (Xvfb :99) - nicht auf der Sitzung des Benutzers, damit sich Agent und Mensch nicht ins Fenster greifen - in der Sandbox aus L4. Zuschauen ueber CDP Page.startScreencast in ein Image-Control, darunter der Schrittstrom. Uebernehmen ueber x11vnc hinter einer Schaltflaeche; das ist zugleich der Weg fuer Erstanmeldungen: der Mensch loggt sich einmal von Hand ein, die Session bleibt im Profilverzeichnis, das Modell sieht die Zugangsdaten nie. Domain-Allowlist je Agent, approve auf allem, was schreibt. Groesster Injection-Hebel im ganzen Projekt - eigener Bauplan zuerst, nicht als ein Auftrag umsetzbar. Zwischenstufe mit einem Grossteil des Nutzens: WebFetch um gerendertes DOM und Cookie-Sessions erweitern, ohne Klick-Automatik.', None),
('memory-flush', 'CAP', 320, O, 'medium', FEAT,
'Memory-Flush vor der Compaction',
'Bevor der ContextCompactor zusammenfasst, bekommt der Agent ein eng begrenztes Fenster, Dauerhaftes per memory_store zu sichern - sonst wirft die Compaction Wissen weg. Harte Grenzen: max. 3-5 Schritte, einziges Tool memory_store, Timeout, guenstiges Modell, hoechstens einmal je Zyklus. Die bestehenden Paarungs-Tests aus B1/B14 muessen unveraendert gruen bleiben.', None),
('memory-injection', 'CAP', 330, O, 'medium', FEAT,
'Memory-Auto-Injection relevanter Abstracts',
'Relevante Abstracts automatisch einblenden, mit Relevanzschwelle und Deckel bei etwa 200 Tokens. In die Nutzernachricht, nie in den System-Prompt - sonst verfaellt der Prompt-Cache.', None),
('proaktive-compaction', 'CAP', 340, O, 'medium', BUG,
'Die Compaction laeuft nach dem API-Aufruf - der ueberfuellte Prompt ist da schon bezahlt',
'CompactIfNeededAsync nutzt die promptTokens der gerade bezahlten Anfrage. Vor dem Senden pruefen - EstimateTokens gibt es -, gemessene Werte zur Kalibrierung.', None),
('agent-inspector', 'CAP', 350, O, 'medium', FEAT,
'AgentInspector: ein Supervisor-Agent, der die anderen beurteilen kann',
'Ueber Audit-Log, Taskboard und lesenden Zugriff auf fremde Workspaces. Heute geht nur die Selbstauskunft, und die ist als Kontrollinstrument wertlos. Bauplan: docs/umsetzungsplaene/UMSETZUNGSPLAN-AgentInspector-Supervisor.md', None),
('agent-editor', 'CAP', 360, O, 'medium', FEAT,
'AgentEditor haerten: Aenderungen an Identity.md und Soul.md freigabepflichtig',
'Bleiben im laufenden Betrieb moeglich, werden aber freigabepflichtig, nachvollziehbar und ruecknehmbar. Reihenfolge: nach dem AgentInspector. Bauplan: docs/umsetzungsplaene/UMSETZUNGSPLAN-AgentEditor-Haertung.md', None),
# ---------------------------------------------------------------- COMM
('b8-delegationstiefe', 'COMM', 70, O, 'high', BUG,
'B8: AgentComm.send_message hat keine Tiefenbegrenzung - A nach B nach A verklemmt',
'Synchroner Aufruf ohne Tiefenbegrenzung. A haelt sein Gate, waehrend es auf B wartet - ruft B nun A, wartet B auf As Gate. Loest nur der Timeout auf. Bekannt seit Juli, unveraendert. Fasst das Agent-Gate an, an dem schon ein Deadlock lauerte: erst Invarianten festlegen, dann bauen. Test A13 der Teststrategie deckt genau das ab und fehlt bis heute - er gehoert zwingend dazu.', None),
('comm-phase1', 'COMM', 400, O, 'medium', FEAT,
'Agentenkommunikation Phase 1: ParentRunId und RootRunId',
'In Audit-Log und Receipts, von der Engine gestempelt. Eine Delegationskette wird damit zu einer Abfrage. Voraussetzung fuer alles Weitere.', None),
('comm-phase2', 'COMM', 410, O, 'medium', FEAT,
'Agentenkommunikation Phase 2: Nachrichtenspeicher AgentMessages mit FTS5',
'Alle Wege gleich behandelt, beide Richtungen festgehalten. Erst nach dem Output-Scrubbing, weil die Nachrichten sonst ungefiltert dauerhaft gespeichert werden.', None),
('comm-phase3', 'COMM', 420, D, 'medium', FEAT,
'Agentenkommunikation Phase 3: ask_agent mit Tiefe 1 und Zykluspruefung',
'Rueckbau von AgentComm und AgentSpawn. Der Zuschnitt haengt an E4.',
'sobald E4 entschieden ist - ask_agent eng gefasst behalten oder ersatzlos streichen'),
('comm-phase4', 'COMM', 430, D, 'low', FEAT,
'Agentenkommunikation Phase 4: Paar-Ansicht an den Datenbestand anschliessen',
'AgentChatsPageView steht bereits - Auswahl, Filterung und Darstellung sind fertig. Es fehlt genau eine Abfrage in AgentChatsPageViewModel.LoadMessages.',
'sobald Phase 2 steht und die Tabelle AgentMessages Daten fuehrt'),
('comm-phase5', 'COMM', 440, O, 'low', FEAT,
'Agentenkommunikation Phase 5: AgentCommAnalysis',
'Analyse-Tool, das findet, wo die Zusammenarbeit klemmt. Wer es bekommt, ist offen.', None),
# ---------------------------------------------------------------- DATA
('historie-umzug', 'DATA', 150, O, 'medium', BUG,
'Historie-Umzug in die Instanz-DB (loest B10 und K6)',
'ChatHistory.json zieht in die Instanz-DB: aktive Tabelle plus Archiv-Tabelle mit FTS5 und history_search-Tool. Loest zugleich B10 - heute wird bei jedem Chat-Eintrag die vollstaendige Historie und der vollstaendige Kontext als eingeruecktes JSON neu geschrieben (O(n^2); bei 500 Nachrichten mehrere MB pro Nachricht) - und K6, die unbegrenzte Historie. Migration nur nach frischem Backup, alte JSON erst nach verifiziertem Import loeschen. Voraussetzung fuer den MySQL-Spiegel.', None),
('a6-mysql-spiegel', 'DATA', 450, D, 'medium', FEAT,
'A6: MySQL-Spiegel als nachgelagerte Kopie',
'SQLite bleibt die einzige Wahrheit. MySQL empfaengt nur vom Replikator, die App liest im Betrieb nie daraus. Outbox-Muster an der vorhandenen Schreib-Warteschlange, idempotente Upserts, nie blockierend, Zeilen tragen instance_id. Der getestete Restore-Pfad Spiegel nach frischer SQLite ist Teil der Definition of Done - gleiche Regel wie beim Backup.',
'sobald der Historie-Umzug durch ist und E1 entschieden - der Server kommt gegebenenfalls mit der Chat-Entscheidung'),
# ---------------------------------------------------------------- OPS
('l2-betriebsform', 'OPS', 30, O, 'high', FEAT,
'L2: 24/7-Betriebsform unter Linux',
'systemd --user-Unit mit Restart=always, loginctl enable-linger, Autologin, Sitzungssperre und Suspend unterbinden (Inhibit), Bildschirmschoner aus - dazu der vorhandene Watchdog je Instanz. Kein kopfloser Betrieb: Die Oberflaeche laeuft laut Zielbetrieb immer mit. Ohne das ist "laeuft durch" eine Behauptung. Klein, aber Voraussetzung fuer alles Unbeaufsichtigte.', None),
('l3-release-linux', 'OPS', 40, O, 'high', FEAT,
'L3: Release-Strecke linux-x64 (DC3)',
'deploy/publish.py nimmt --rid schon entgegen, die Vorgabe steht noch auf win-x64 - umstellen, CI auf Linux, prod-Kanal. Ohne das gibt es auf der Zielmaschine kein Update. Der win-x64-Zweig bleibt vorerst als Ausweichweg. Dazu gehoert die Linux-CI: die 585 gruenen Tests sind bis dahin eine Windows-Aussage.', None),
('dc1-fehler-melden', 'OPS', 160, O, 'medium', FEAT,
'DC1: Schaltflaeche "Fehler melden" in der Oberflaeche',
'Der Client steht, es fehlt die Schaltflaeche. Zwischendurch zu erledigen, unabhaengig von der uebrigen Reihenfolge.', None),
('dc2-bugtracker-tool', 'OPS', 500, O, 'medium', FEAT,
'DC2: Bugtracker-Tool fuer Agenten',
'Agenten-Tool, das den Claim/Lease-Workflow des Deploymentcenters nutzbar macht.', None),
('sdk-270', 'OPS', 505, O, 'medium', FEAT,
'Deploymentcenter-SDK von 2.6.0 auf 2.7.0 nachziehen',
'Der Changelog nennt zwischen 2.6.0 und 2.7.0 zehn action_items und zwei brechende Aenderungen: der Befundzustand aus /api/supervisor/v1/observe und /status heisst jetzt finding_status, und die Nachpruefungs-Status aus 2.6.1 (awaiting_verification, in_verification, fix_insufficient) muessen in eigenen Auswertungen eingeordnet werden - die ersten beiden zaehlen nicht als offener Fehler, fix_insufficient schon. Danach die Fassungszeile in CLAUDE.md hochsetzen und dc_link_project aufrufen.', None),
('dc4-sdk-submodul', 'OPS', 510, O, 'medium', BUG,
'DC4: SDK als Git-Submodul statt Cross-Repo-Pfad',
'Heute ein Cross-Repo-Pfad in ClawdDotNet.App.csproj - auf einem anderen Rechner baut das nicht. Als Git-Submodul unter external/. Betrifft auch die CI.', None),
('dc5-evaluator-cron', 'OPS', 520, O, 'medium', FEAT,
'DC5: Evaluator-Cron einrichten (Betreiberaufgabe)',
'Cron einrichten, Token ausstellen, parent_source pflegen. Ohne den Cron aendert sich ein Monitor-Zustand nur beim Eintreffen eines Heartbeats - eine abgestuerzte Instanz bliebe dauerhaft gruen. Ohne den Cron ist die Ueberwachung wertlos.', None),
('release-guard', 'OPS', 530, O, 'low', BUG,
'Release-Guard beim Upload: ein frisches Produktverzeichnis steht ohne .htaccess da',
'Veroeffentlichen loest regenerateForProject nicht aus; das Verzeichnis bleibt bis zum naechsten Sechs-Stunden-Turnus ungeschuetzt. Ein Aufruf am Ende von publish wuerde das Fenster schliessen. Betreiberseite.', None),
('dc8-signaturpflicht', 'OPS', 540, D, 'medium', BUG,
'DC8: Die Signaturpflicht laesst sich ueber den empfohlenen Weg nicht einschalten',
'Schluessel steht, ab 0.1.2 wird signiert, Pruefung beidseitig getestet. --require-signature gibt es aber nur als Kommandozeilenschalter; UpdateClient.LaunchUpdateAgent - der empfohlene Weg - reicht ihn nicht durch. Selbst nachbauen hiesse --restart, --wait-for-pid und --wait-timeout verlieren.',
'sobald LaunchUpdateAgent --require-signature durchreicht; ist unter deploymentcenter gemeldet'),
# ---------------------------------------------------------------- FIN
('c1-marktkalender', 'FIN', 140, O, 'medium', FEAT,
'C1: Marktkalender - Laeufe ausserhalb der Handelszeiten ueberspringen',
'Zwei Bausteine: onlyWhenMarketOpen "NYSE" im Task-Frontmatter (der Lauf wird schlicht uebersprungen, wirkt ohne Zutun des Modells) und ein MarketCalendar-Tool fuer Fragen des Agenten. Spart ab dem ersten Tag Geld: ein */30-Cron laeuft heute auch Sonntag um 3 Uhr. Handelskalender aendern sich selten - eine gepflegte Datei reicht, keine externe Abhaengigkeit. Der Haken im Scanner ist schon da (IMarketCalendar).', None),
('c2-indicators', 'FIN', 600, O, 'medium', FEAT,
'C2: Indikatoren im Code rechnen statt im Modell',
'Gleitende Durchschnitte, RSI, ATR, Volatilitaet, Korrelation, Drawdown, Positionsgroesse nach Risiko. Sprachmodelle rechnen unzuverlaessig, und die Zahlenkolonnen muessen dafuer durch den Kontext. Spart Tokens und verbessert die Qualitaet.', None),
('c3-datenaktualitaet', 'FIN', 610, O, 'medium', BUG,
'C3: dataAsOf wird geliefert, aber von nichts ausgewertet',
'Ein Agent kann ungehindert auf drei Tage alten Kursen argumentieren. maxAgeSeconds je Tool: ablehnen oder unuebersehbar kennzeichnen, nicht stillschweigend durchreichen.', None),
('c4-termine', 'FIN', 620, O, 'medium', FEAT,
'C4: Termine und Fundamentaldaten',
'Earnings, Dividenden, Splits, SEC EDGAR (8-K, 10-Q, 13F - frei und gut strukturiert), Wirtschaftstermine. Fuer "Finanznachrichten" ist der Kalender oft wichtiger als der Kurs.', None),
('c5-bestandsregister', 'FIN', 630, O, 'medium', FEAT,
'C5: Bestandsregister - stock_add ist eine Wissenssammlung, kein Bestand',
'Eigene Tabelle mit Positionen, auch rein zur Beobachtung. Grundlage fuer C7 und C8.', None),
('c6-entdopplung', 'FIN', 640, O, 'medium', FEAT,
'C6: Nachrichten-Entdopplung',
'Dieselbe Meldung laeuft ueber zehn Quellen - man zahlt zehnmal, und der Agent haelt es fuer zehn unabhaengige Signale. Das verzerrt die Einschaetzung systematisch. SeenItems mit Pruefsumme ueber den normalisierten Titel plus Aehnlichkeitsabgleich.', None),
('c7-ergebnisregister', 'FIN', 650, O, 'medium', FEAT,
'C7: Ergebnisregister AgentOutput am Lauf',
'Art, Betreff, Verweis. Macht aus "Kosten pro Lauf" die nuetzlichere Groesse Kosten pro Ergebnis. Faellt weitgehend aus den A3-Receipts ab.', None),
('c8-falsifizierbar', 'FIN', 660, O, 'medium', FEAT,
'C8: Falsifizierbare Aussagen als Leistungsmass',
'Der Agent legt sich fest (Subjekt, Aussage, Horizont, Konfidenz); ein Aufloesungs-Task prueft gegen die tatsaechlichen Kurse - kein Mensch muss bewerten. Daraus Trefferquote, Brier-Score (misst, ob die Konfidenz ehrlich war - ein Agent, der bei 0,9 nur in 60 Prozent recht hat, ist ueberheblich, das bleibt bei reiner Trefferquote unsichtbar) und Vergleich gegen eine Nulllinie. Unter 30 aufgeloesten Aussagen keine Rangliste, nur Rohzahlen. Braucht C5 und C7.', None),
('leerlaufquote', 'FIN', 670, O, 'low', FEAT,
'Leerlaufquote: Laeufe ohne greifbares Ergebnis zaehlen',
'Die wirksamste einfache Kennzahl. 40 Prozent Leerlauf heisst meist Zeitplan-Problem - genau das, was der Marktkalender loest. Aus vorhandenen Daten.', None),
('stock-add-herausloesen', 'FIN', 680, O, 'low', FEAT,
'stock_add aus FileRW herausloesen',
'Fachfremdes Feature, das das FileRW-Schema fuer alle aufblaeht, auch fuer Agenten, die es nie nutzen.', None),
# ---------------------------------------------------------------- HYG
('b9-index-count', 'HYG', 170, O, 'low', BUG,
'B9: index_Count-Race in FileRWTool',
'Der Aufruf erfolgt, nachdem das Lock freigegeben ist - Race mit parallelen stock_add, plus ein ueberfluessiges vollstaendiges Parsen. Der Zaehler ist innerhalb des Locks ohnehin bekannt.', None),
('b13-instanceid', 'HYG', 180, O, 'low', BUG,
'B13: instanceId-Inkonsistenz zwischen RunAsync und SendMessageAsync',
'RunAsync und ChatAsync bekommen instanceId als Parameter, SendMessageAsync und SpawnAgentAsync nutzen das Feld _instanceId, das nur bei SetAgentConfigProvider gesetzt wird - sonst leer. In Produktion unkritisch.', None),
('dst-randfall', 'HYG', 190, O, 'low', BUG,
'DST-Randfall: eine nicht existierende Ortszeit wird uebersprungen statt verschoben',
'Bei der Zeitumstellung im Fruehjahr existiert "02:30" nicht; der Termin faellt aus. Tests R4 und R5 der Teststrategie gehoeren dazu: Oktober feuert genau einmal, Maerz wird nicht uebersprungen.', None),
('nullable-warnungen', 'HYG', 200, O, 'low', BUG,
'45 Nullable-Build-Warnungen',
'Schwerpunkt Mail (basisnah) und WebFetch/DirectAPI (handelsnah).', None),
# ------------------------------------------------- Erledigt (Abschnitt 3)
('done-audit', 'SEC', None, R, 'medium', FEAT, 'Audit-Log und Receipts (A3, F-A2)',
'Append-only auf SQLite. Herkunft wird von der Engine gestempelt, nie vom Agenten behauptet. Eintraege unveraenderlich, Korrekturen sind neue Eintraege. Receipts verknuepfen Lauf und Task mit Schritten, Tokens, Kosten.', None),
('done-staging', 'SEC', None, R, 'medium', FEAT, 'Staging-Kern (A2, F-A1, S4)',
'PermissionGate zum Durchsetzungspunkt ausgebaut. Policy auto/approve/deny je Tool und Aktion, Plan-Freeze (der eingefrorene Aufruf wird ausgefuehrt, nicht ein neu formulierter), atomarer Uebergang pending nach approved, Fortsetzung ueber Folge-Task statt pausiertem Lauf.', None),
('done-taskboard', 'CAP', None, R, 'medium', FEAT, 'Taskboard (A1, F-A4, F-A5)',
'Markdown mit YAML-Frontmatter im SharedWorkspace, Zustand in der DB. Scanner im 60-Sekunden-Takt statt Delay-Schleifen, atomares Claiming (at-most-once), Startup-Reconciliation, blocked_by mit Auto-Dispatch, Blocker-Eskalation, Reopen ueber task_comment.', None),
('done-memory', 'CAP', None, R, 'medium', FEAT, 'Langzeitgedaechtnis (K1)',
'Typisierte Tabelle statt JSON im State-Store. Scope agent und shared, optionaler Key (erneutes Merken aktualisiert statt anzulegen), Wichtigkeit steuert die Reihenfolge bei Kappung.', None),
('done-atomar', 'DATA', None, R, 'medium', BUG, 'Atomares Schreiben nach dem File.Replace-Muster',
'Es war bereits eine TokenUsage.json beschaedigt worden.', None),
('done-speicherfundament', 'DATA', None, R, 'medium', BUG, 'Speicher-Fundament: WAL, busy_timeout, Pooling, Schreib-Warteschlange',
'Vorher gab es "database is locked". Das sah nach einer SQLite-Grenze aus, war aber fehlende Konfiguration.', None),
('done-backup', 'DATA', None, R, 'medium', FEAT, 'Sicherung und Wiederherstellung',
'ZIP mit Manifest und Pruefsummen, VACUUM INTO fuer die DB, Secrets wahlweise auf Passphrase umgeschluesselt (PBKDF2 + AES-GCM) oder ausgelassen - DPAPI allein ueberlebt den Rechnerwechsel nicht, und genau dann braucht man das Backup. Rotation, Restore mit Vorschau, Zeitplan.', None),
('done-zugangsschutz', 'OPS', None, R, 'medium', FEAT, 'Zugangsschutz: Lizenzschluessel als Basic-Auth zur Release-Ablage (DC 2.4)',
'Benutzername wird als lic_<sha256[0..16]> abgeleitet. Die Ableitung muss auf beiden Seiten zeichengenau uebereinstimmen - wer eine Seite aendert, sperrt die gesamte Installationsbasis aus.', None),
('done-update', 'OPS', None, R, 'medium', FEAT, 'Update anwenden (DC6)',
'Agent liegt im Paket, Pruefsumme geprueft, Rueckfrage in der Oberflaeche, geordnetes Herunterfahren vor dem Agentenstart, maintenance an den Watchdog.', None),
('done-erstinstallation', 'OPS', None, R, 'medium', FEAT, 'Erstinstallation (DC7)',
'--action install mit setup.json, live durchgespielt: Login, Katalog, Token mit Rechteschranke.', None),
('done-linux-portierung', 'OPS', None, R, 'medium', FEAT, 'Linux-Portierung der Bibliotheken',
'Alle Bibliotheks- und Testprojekte auf net10.0, kein DllImport, keine Registry, kein System.Drawing. PathBoundary waehlt auf Linux Ordinal statt OrdinalIgnoreCase, TimeZones konvertiert zwischen Windows- und IANA-IDs in beide Richtungen. AppPaths kennt XDG_CONFIG_HOME und XDG_DATA_HOME mit /var/lib-Rueckfall und setzt 0700/0600, SystemShell nutzt xdg-open. Offen bleibt genau eine Stelle: DPAPI, siehe L1.', None),
('done-paketanhebungen', 'HYG', None, R, 'medium', BUG, 'Paket-Anhebungen gegen verwundbare transitive Fassungen',
'SQLitePCLRaw.bundle_e_sqlite3 2.1.13 und SharpCompress 0.48.0 stehen als direkte Verweise, obwohl kein Code sie aufruft - sie heben verwundbare transitive Fassungen an. Sehen aus wie toter Ballast und sind es nicht.', None),
# ------------------------------------------------- Erledigt (Chronik, Abschnitt 5)
('done-s1', 'SEC', None, R, 'high', BUG, 'S1: SqlGuard statt Teilzeichenketten-Pruefung', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-s2', 'SEC', None, R, 'high', BUG, 'S2: YouTubeUrl und ArgumentList gegen Options-Injection', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-s3', 'SEC', None, R, 'high', BUG, 'S3: UrlSanitizer gegen API-Key-Leak ins Modell', 'Aus der Bestandsaufnahme 2026-07. Schloss die Luecke nur fuer URLs - der Rest ist das Output-Scrubbing.', None),
('done-s5', 'SEC', None, R, 'high', BUG, 'S5: UrlGuard, Redirects einzeln geprueft', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-s6', 'SEC', None, R, 'high', BUG, 'S6: WorkspacePath auf Verzeichnisgrenzen', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-s7', 'SEC', None, R, 'high', BUG, 'S7: SecretProtector mit DPAPI', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-k5', 'SEC', None, R, 'medium', FEAT, 'K5: Budget-Guard (F-A3)', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-b1', 'CAP', None, R, 'medium', BUG, 'B1 und B14: Compaction mit sicherem Schnittpunkt, kein doppelter System-Prompt',
'Aus der Bestandsaufnahme 2026-07. Die Paarungs-Tests dazu muessen bei kuenftigen Eingriffen gruen bleiben.', None),
('done-b2', 'CAP', None, R, 'medium', BUG, 'B2: Chat-Laeufe je Agent serialisiert', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-b3', 'CAP', None, R, 'medium', BUG, 'B3 und T5: maxCumulativeTokens von maxContextTokens getrennt', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-b4', 'CAP', None, R, 'medium', BUG, 'B4: Prompt und Completion getrennt erfasst, Preise live vom /models-Endpunkt', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-b5', 'CAP', None, R, 'medium', BUG, 'B5 und T2: Tool-Ergebnisse zentral gekappt', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-b6', 'CAP', None, R, 'low', BUG, 'B6 und B7: gegenstandslos, seit der Scanner die Delay-Schleifen ersetzt', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-b11', 'CAP', None, R, 'medium', BUG, 'B11 und T8: max_tokens aus LoopGuard.MaxResponseTokens', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-b12', 'CAP', None, R, 'medium', BUG, 'B12: Retry mit Backoff fuer 429 und 5xx', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-t1', 'CAP', None, R, 'medium', FEAT, 'T1 und T9: Prompt-Caching mit Breakpoints, cached_tokens gemessen', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-t3', 'CAP', None, R, 'medium', FEAT, 'T3: guenstiges Modell fuer die Zusammenfassung', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-t7', 'CAP', None, R, 'low', FEAT, 'T7: durch die Assignee-Semantik des Taskboards beantwortet', 'Aus der Bestandsaufnahme 2026-07.', None),
('done-k3', 'HYG', None, R, 'medium', FEAT, 'K3: Testfundament', 'Aus der Bestandsaufnahme 2026-07. Grundlage der Teststrategie, heute 585 gruene Tests.', None),
('done-nu1903', 'HYG', None, R, 'medium', BUG, 'Fruehjahrsputz: NU1903 und NU1902 geschlossen', 'Gefunden und behoben am 2026-08-23.', None),
('done-altscheduler', 'OPS', None, R, 'medium', FEAT, 'Alt-Scheduler abgeloest - alles ist ein Task',
'AgentScheduler, ToolJobScheduler und SchedulerDelay geloescht, konsolidiert auf den Taskboard-Scanner.', None),
('done-dc-ersetzt', 'OPS', None, R, 'medium', FEAT, 'Deploymentcenter ersetzt WatchDog und LicenseLabrador',
'Vorher zwei Fremdprojekte mit je eigenem Server, Schluessel und Anleitung. Jetzt eine Adresse, ein Token, dazu Update-Pruefung, Fehler-Stream und Bugtracker. Der Lizenz-HardwareId-v2-Vorschlag wurde damit gegenstandslos.', None),
('done-dc3-win', 'OPS', None, R, 'medium', FEAT, 'DC3: Release-Strecke win-x64', 'Der linux-x64-Zweig steht noch aus, siehe L3.', None),
('done-avalonia', 'OPS', None, R, 'medium', FEAT, 'Avalonia-Portierung, WinForms entfernt',
'Der teure Teil der Linux-Portierung - 8.900 Zeilen WinForms - ist damit entfallen.', None),
('done-backup-nie-gestartet', 'DATA', None, R, 'high', BUG, 'Fruehjahrsputz: die automatische Sicherung war nie gestartet',
'Gefunden am 2026-08-23.', None),
('done-backup-settings', 'DATA', None, R, 'medium', BUG, 'Fruehjahrsputz: doppelte Sicherungs-Einstellungen ueberschrieben einander',
'Die Felder auf der Einstellungsseite ueberschrieben die der Sicherungsseite. Gefunden am 2026-08-23.', None),
]
def anker(item_id, titel):
stueck = re.sub(r'[^a-z0-9]+', '-', titel.lower()).strip('-')
return '%s#%d-%s' % (ANKER_DATEI, item_id, stueck[:60])
def ruf(token, methode, pfad, query=None, koerper=None, versuche=6):
url = BASIS + pfad
if query:
url += '?' + urllib.parse.urlencode(query)
daten = json.dumps(koerper).encode('utf-8') if koerper is not None else None
for versuch in range(versuche):
anfrage = urllib.request.Request(url, data=daten, method=methode)
anfrage.add_header('Authorization', 'Bearer ' + token)
anfrage.add_header('Content-Type', 'application/json')
try:
with urllib.request.urlopen(anfrage, timeout=30) as antwort:
return json.loads(antwort.read().decode('utf-8'))
except urllib.error.HTTPError as fehler:
rumpf = fehler.read().decode('utf-8', 'replace')
if fehler.code == 429 and versuch < versuche - 1:
warten = 2 ** versuch
print(' gedrosselt, warte %ds' % warten)
time.sleep(warten)
continue
raise SystemExit('%s %s -> %d %s' % (methode, pfad, fehler.code, rumpf[:400]))
raise SystemExit('zu oft gedrosselt: %s %s' % (methode, pfad))
def pruefen():
refs = [it[0] for it in ITEMS]
doppelt = {r for r in refs if refs.count(r) > 1}
if doppelt:
raise SystemExit('doppelte client_ref: %s' % doppelt)
raenge = [it[2] for it in ITEMS if it[2] is not None]
doppelt = {r for r in raenge if raenge.count(r) > 1}
if doppelt:
raise SystemExit('doppelte Raenge: %s' % doppelt)
kuerzel = {b[0] for b in BEREICHE}
for it in ITEMS:
if it[1] not in kuerzel:
raise SystemExit('unbekannter Bereich: %s in %s' % (it[1], it[0]))
if it[3] == D and not it[8]:
raise SystemExit('zurueckgestellt ohne Ausloeser: %s' % it[0])
if it[3] != D and it[8]:
raise SystemExit('Ausloeser ohne Zurueckstellung: %s' % it[0])
if it[3] == R and it[2] is not None:
raise SystemExit('erledigt, aber gereiht: %s' % it[0])
def main():
p = argparse.ArgumentParser()
p.add_argument('--lesen', action='store_true')
p.add_argument('--uebernehmen', action='store_true')
a = p.parse_args()
pruefen()
if a.lesen:
nach_status, nach_bereich = {}, {}
for it in ITEMS:
nach_status[it[3]] = nach_status.get(it[3], 0) + 1
nach_bereich[it[1]] = nach_bereich.get(it[1], 0) + 1
print('%d Items, %d Bereiche' % (len(ITEMS), len(BEREICHE)))
print('nach Status :', nach_status)
print('nach Bereich:', nach_bereich)
print('Raenge und Kennungen eindeutig, jede Zurueckstellung hat einen Ausloeser.\n')
for kuerzel, titel, _, sortierung in BEREICHE:
print('%-5s %-34s (sort %d)' % (kuerzel, titel, sortierung))
for it in sorted([x for x in ITEMS if x[1] == kuerzel],
key=lambda x: (x[2] is None, x[2] or 0)):
rang = ('%4d' % it[2]) if it[2] is not None else ' -'
print(' %s %-9s %s' % (rang, it[3], it[6][:76]))
return
if not a.uebernehmen:
raise SystemExit('--lesen oder --uebernehmen angeben')
token = os.environ.get('DC_TOKEN', '')
if not token:
raise SystemExit('DC_TOKEN fehlt')
print('Themenbereiche anlegen ...')
for kuerzel, titel, zusammenfassung, sortierung in BEREICHE:
ruf(token, 'POST', '/api/bugtracker/v1/roadmap.php', {'action': 'epic'}, {
'project_slug': SLUG, 'epic': kuerzel, 'title': titel,
'summary': zusammenfassung, 'sort_order': sortierung,
})
print(' %-5s %s' % (kuerzel, titel))
print('\n%d Items anlegen und einplanen ...' % len(ITEMS))
ergebnis = []
for ref, bereich, rang, status, schwere, art, titel, beschreibung, ausloeser in ITEMS:
antwort = ruf(token, 'POST', '/api/bugtracker/v1/report', None, {
'project_slug': SLUG, 'type': art, 'title': titel,
'description': beschreibung + '\n\nUebernommen aus docs/Roadmap.md am 2026-08-30. Die Begruendung steht weiterhin in der Datei.',
'severity': schwere, 'environment': 'development',
'build_version': 'unknown', 'client_ref': 'roadmap-clawddotnet-' + ref,
})
item_id = antwort['item_id']
felder = {'id': item_id, 'epic': bereich}
if rang is not None:
felder['roadmap_rank'] = rang
if status != O:
felder['status'] = status
if ausloeser:
felder['defer_trigger'] = ausloeser
if status != R:
felder['doc_anchor'] = anker(item_id, titel)
ruf(token, 'POST', '/api/bugtracker/v1/roadmap.php', {'action': 'set'}, felder)
ergebnis.append({'ref': ref, 'id': item_id, 'epic': bereich, 'rank': rang,
'status': status, 'title': titel, 'neu': antwort['is_new']})
print(' #%-4d %-5s %s' % (item_id, bereich, titel[:70]))
time.sleep(1.1) # /report laesst 60 Meldungen je Minute zu
ziel = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'items.json')
with open(ziel, 'w', encoding='utf-8') as f:
json.dump(ergebnis, f, indent=2, ensure_ascii=False)
print('\nFertig. Zuordnung in items.json.')
if __name__ == '__main__':
main()