Files
talk/.scratch/talk-2-0/plan.md
T

21 KiB
Raw Blame History

Plan: talk 2.0 — V1 Implementationsplan

Plan: .scratch/talk-2-0/plan.md Spec: .scratch/talk-2-0/spec.md (bindende Autorität — Konflikte gehen zu ihren Lasten) Branch: feat/v1 (ab main; Repo wird in Task 01 initialisiert)

Global Constraints

(aus der Spec, bindend für alle Tasks — bei Konflikt gewinnt die Spec)

  • Sprache/Laufzeit: Go; statische Binaries für Windows/macOS/Linux × amd64/arm64; keine Runtime-Abhängigkeiten (kein ffmpeg, kein mpv, kein Python).
  • Binaries: talk (CLI) + talkd (Daemon). Modulname: code.m3ta.dev/m3tam3re/talk.
  • CLI-Surface (exakt): Default-Befehl talk "Text" = non-blocking; Flags --wait, --now, --priority, --voice, --lang, --provider. Subcommands: say, wrap, mute, unmute, status, cancel, voices, test, doctor, setup. --help auf jedem Subcommand.
  • Provider-Kette (Fallback-Reihenfolge): ElevenLabs → OpenAI TTS → Edge-TTS (frei, kein Key) → OS built-in (macOS say, Windows SAPI, Linux espeak/speech-dispatcher). Überschreibbar per Config/Flag. Keys Env-first: TALK_ELEVENLABS_KEY, TALK_OPENAI_KEY. Provider-Endpunkte konfigurierbar (Fake-bar für Tests).
  • Config: optionale config.toml im platform-nativen Verzeichnis: Linux ~/.config/talk, macOS ~/Library/Application Support/talk, Windows %APPDATA%\talk. Env schlägt Datei. Voice-Mapping voice.<provider>.<lang> = id.
  • Audio: Playback embedded via oto; Audio-Sink wählbar inkl. null (CI/Server). Notifications best effort via beeep.
  • Daemon: talkd besitzt Queue (Serialisierung, Prioritäten, Rate-Limit Default 1 Ansage/30 s, prioritätsbewusstes Verwerfen), persistenten Mute-State (Datei im Config-Dir), Ruhezeiten quiet_hours = "18:00-08:00" (lokale Zeit; still verwerfen oder toast-only), HTTP-API POST /speak auf 127.0.0.1 mit konfigurierbarem CORS, MCP-Endpoint (Streamable HTTP) auf localhost.
  • Degraded Mode: ohne talkd: Lock-File + sequenzielles Abspielen; nichts bricht still.
  • TUI (Bubble Tea): interaktiv nur setup (Wizard), status (Live-Dashboard), voices (Picker mit Preview); alle anderen Befehle plain. Kein TTY → alle Befehle plain. Agenten-Pfade (Args/stdin) starten nie eine TUI.
  • Etikette (nur Skill-Doku, kein Daemon-Session-State): Opt-in; Ansageinhalt = Status/nächster Schritt/Freigabe-Bedarf, nie Langantwort.
  • Testing: Primäre Seam = Prozessgrenze talkd+CLI als Black Box mit injizierbaren Fakes über Config (Provider-Endpunkt → Fake-HTTP-Server mit Audio-Bytes; audio.sink = "null"). Reine Unit-Seams: langdetect, Config-Auflösung. Echt-Audio und OS-Toasts ungetestet (doctor-Rendering manuell). Tests testen nur extern beobachtbares Verhalten.
  • CI: Gitea Actions — .gitea/workflows/test.yml läuft go vet + go test ./... bei jedem Push. go build ./... muss auf dem Dev-Host (Linux) grün sein; Cross-Compile via GOOS/GOARCH mindestens einmal in Task 11 verifiziert.
  • Lizenz: MIT (LICENSE-Datei ab Task 01).
  • Doku-Sprache: Deutsch (README, Skill).
  • Commits: Conventional Commits; keine Pushes; keine Änderungen außerhalb des Repos (Ausnahme Task 12, siehe dort).

Task 01: Repo-Skeleton & CI-Backbone

Keine Blocker. Liefert: initialisierbares, grünes Go-Repo.

  • git bereits initialisiert (main); dieser Task entsteht auf Branch feat/v1 (besteht bereits).
  • Go-Modul code.m3ta.dev/m3tam3re/talk (go.mod, aktuelle stabile Go-Version des Dev-Hosts).
  • Verzeichnisgerüst: cmd/talk/, cmd/talkd/, internal/{provider,queue,audio,notify,config,api,langdetect}/ (jeweils mit placehalterfreiem Miniinhalt oder leer + doc.go — kein toter Code).
  • cmd/talk/main.go: Subcommand-Gerüst (cobra oder stdlib flag — Entscheidung: stdlib flag + eigene Subcommand-Dispatch, keine externen CLI-Abhängigkeiten in T01) mit --version (zeigt dev + Commit via ldflags-Variablen, default dev) und allen Subcommands als not implemented-Stubs mit korrektem --help.
  • cmd/talkd/main.go: startet, loggt eine Zeile, exit 0 (Platzhalter).
  • .gitea/workflows/test.yml: go vet ./..., go test ./..., go build ./... bei push/PR.
  • .gitignore (Go-Binaries, dist/), LICENSE (MIT, Copyright 2026 m3tam3re), README.md-Gerüst (Projektname, Ein-Satz-Zweck, Status WIP, Hinweis auf Spec).
  • Mind. ein sinnvoller Test, der die CI-Struktur verifiziert (z. B. Version-String-Test).
  • Abnahme: go vet && go test && go build grün; Workflow-Datei valider YAML; README/LICENSE vorhanden; Commit(s) conventional.

Task 02: Erste Ansage end-to-end (OS-TTS)

Blockiert durch: 01. Liefert: talk "Hallo Welt" spricht über OS-TTS.

  • internal/langdetect: reine Funktion Detect(text string) string → "de" | "en" (Default "de"); Heuristik: Common-Word-Listen + Umlaute/ß; tischgesteuerte Tests (de/en/gemischt/leer).
  • internal/provider: Interface Provider mit Name() string, Available(ctx) bool, Synthesize(ctx, text string, lang string) ([]byte, error) (Rückgabe: dekodierbare Audio-Bytes, Format von der Implementierung dokumentiert; osbuiltin liefert WAV/PCM).
    • Implementierung osbuiltin: Linux über espeak/speech-dispatcher-Fallback (externer Prozess — OK, es ist der Notnagel-Provider; wenn Binary fehlt: Available() == false), macOS über say (Ausgabe in temporäre WAV-Datei), Windows über SAPI (PowerShell System.Speech). Fehlende Binary/Runtime → klarer Fehler, kein Panik.
  • internal/audio: Sink-Interface (Play(ctx, data []byte) error, Name() string); Implementierungen oto (embedded Playback, PCM/WAV) und null (verwirft, loggt). Auswahl per Config audio.sink = "oto"|"null" (Default oto).
  • internal/config: minimale Auflösung: Defaults + TOML-Datei (falls vorhanden) + Env-Overrides (TALK_AUDIO_SINK); Platform-Pfad-Auflösung (~/.config/talk | ~/Library/Application Support/talk | %APPDATA%\talk, respektive XDG_CONFIG_HOME/Env); tischgesteuerte Unit-Tests für Merge + Pfadlogik (Pfade injizierbar).
  • cmd/talk: say-Pfad implementiert: Text (Arg oder stdin) → langdetect → Providerauswahl (hier: osbuiltin, Kette kommt in Task 03; Struktur so, dass --provider-Flag schon existiert und osbuiltin default ist) → Synthesize → Sink.Play. Blocking (Daemon kommt in Task 04).
  • Abnahme: talk "Hallo Welt" erzeugt hörbare Ausgabe auf dem Dev-Host (Linux; espeak vorhanden oder als Dev-Abhängigkeit dokumentiert); TALK_AUDIO_SINK=null läuft ohne Ton durch; go test ./... grün (langdetect + config + provider-mit-fake-proc wo sinnvoll).

Task 03: Provider-Kette mit Cloud-Anbietern

Blockiert durch: 02. Liefert: Vier-Stufen-Fallback mit Keys + Fake-barkeit.

  • Provider-Implementierungen (je internal/provider/<name>):
    • elevenlabs: POST <endpoint>/v1/text-to-speech/<voiceID>, Header xi-api-key, Body {text, model_id: "eleven_multilingual_v2", voice_settings:{stability:0.5,similarity_boost:0.75}}, Antwort = Audio-Bytes (mp3). Key: TALK_ELEVENLABS_KEY; Default-Voice-ID KXxZd16DiBqt82nbarJx. Endpunkt konfigurierbar (Default https://api.elevenlabs.io).
    • openai: POST <endpoint>/v1/audio/speech, Bearer-Key TALK_OPENAI_KEY, Body {model:"gpt-4o-mini-tts"|"tts-1", voice, input, response_format:"mp3"} — Entscheidungsnotiz: Modell-String konfigurierbar, Default tts-1. Endpunkt Default https://api.openai.com.
    • edge: Microsoft Edge-TTS-Endpunkt (kostenlos, kein Key) — WebSocket/REST nach aktuellem bekannten Protokoll (edge-tts-kompatibel, Stimmen z. B. de-DE-KatjaNeural/en-US-AriaNeural); Endpunkt konfigurierbar; bei Protokoll-Änderung: klarer Fehler.
    • osbuiltin: existiert (Task 02).
  • Kette internal/provider/chain.go: Resolver liefert geordnete Kette laut Config (Default-Reihenfolge oben); --provider-Flag und provider = "…"-Config pinnen; Available() prüft Key/Endpunkt; Synthesize fällt bei Fehler (HTTP != 2xx, Timeout, Netz) auf den nächsten durch; letzter Fehler wird geloggt/gemeldet, nie still.
  • Voice-Mapping: voice.<provider>.<lang> aus config.toml; Defaults je Provider+Sprache hinterlegt (neutral, dokumentiert).
  • Tests: jeder Cloud-Provider gegen httptest-Fake (200 mit Bytes; 500 → Fehler; 401 → Fehler) außer edge (WebSocket-Protokoll — edge wird über Interface + Fehlerpfad-Mock und eine als testing.Short überspringbare Integration-Testmarkierung behandelt; Dokumentationspflicht im Report, welche Teile fake-bar getestet sind); Kettentests: erster 500 → zweiter getroffen → Bytes.
  • Abnahme: talk "Test" --provider=elevenlabs mit echtem Key funktioniert am Dev-Host ( einmalig manuell, im Report dokumentiert); ohne jeden Key wählt die Kette edge bzw. osbuiltin; go test ./... grün.

Task 04: talkd-Daemon + Queue + Mute

Blockiert durch: 02, 03. Liefert: Daemon-betriebene, serialisierte Ansagen mit persistentem Mute.

  • cmd/talkd: Daemon-Prozess; HTTP-Server auf 127.0.0.1:<port> (Port Default 7800, konfigurierbar TALK_PORT/config server.port); Endpunkte:
    • POST /speak — JSON {text, priority?, provider?, voice?, lang?, now?} → enqueued, Antwort 202 {id}; bei Mute: 202 {muted:true} (verworfen, dokumentiertes Verhalten).
    • GET /status — JSON: Queue-Länge, aktuelles Item, Mute-State, aktiver Provider, Version.
    • POST /cancel — bricht aktive Ansage ab + leert Queue, Antwort 200 {cancelled:n}.
    • POST /mute, POST /unmute — toggelt persistenten Mute-State (Datei mute im Config-Dir).
  • internal/queue: serialisierte Abarbeitung (eine Ansage gleichzeitig), Priorität (high > normal > low; gleiches Level: FIFO), now-Items springen vor; IDs (z. B. fortlaufend/zufällig kurz); konfigurierbares Rate-Limit existiert als Struktur, Limit-Wiring folgt Task 05 (Default unendlich in T04-Tests, dokumentiert).
  • CLI-Anbindung: talk say (und Default) erkennt Daemon via GET /status; läuft er: enqueue + sofort zurück (non-blocking Default); --wait pollt bis abgearbeitet; --now setzt now:true; mute/unmute/status/cancel werden Daemon-Aufrufe mit plain Output.
  • Degraded Mode: Daemon antwortet nicht → Lock-File talk.lock im Config-Dir, sequenzielles lokale Abspielen (best-effort; Kollision = warten), mute wird respektiert.
  • Tests an der Prozessgrenze (primäre Seam): talkd mit Fake-Provider-Endpunkt + audio.sink=null starten; per CLI/HTTP enqueue; assert: Serialisierung (2 schnell nacheinander → nacheinander abgespielt, an Fake+Sink beobachtbar), Priorität, Mute-Persistenz über Daemon-Restart, cancel, non-blocking-Verhalten (say kehrt vor Abspielende zurück), Degraded Mode (Daemon tot → Lock-File-Pfad).
  • Abnahme: alle Prozessgrenzen-Tests grün; talkd & + 3 parallele talk-Aufrufe → drei Ansagen nacheinander; talk mute → Daemon-Restart → weiter stumm.

Task 05: Rate-Limit + Ruhezeiten

Blockiert durch: 04. Liefert: Daemon-Gesetz gegen Spam.

  • Rate-Limit in Queue: Default 1 Ansage / 30 s (konfigurierbar queue.rate_limit = "30s"); Tokens/Zeitfenster-Logik; now:true umgeht Warteschlangen-Wartezeit nicht das Rate-Limit; bei Überschreitung: Items mit priority=low werden verworfen, normal wartet, high darf konfigurierbar verstoßen (queue.allow_high_burst = true|false, Default true).
  • quiet_hours: Config quiet_hours = "18:00-08:00" (lokale Zeit, 24h, über Mitternacht); Verhalten configbar: quiet_mode = "discard"|"toast" (Default discard); Mute (manuell) hat immer Vorrang; now:true bricht auch quiet hours (bewusster Ausbruch, dokumentiert).
  • Tests an der Prozessgrenze: kurzes Rate-Limit (z. B. 1/s) konfigurieren; 5 enqueues → 1 abgespielt, low verworfen, normal geparkt; quiet_hours-Fenster künstlich um die aktuelle Zeit legen → discard/toast-Verhalten.
  • Abnahme: Tests grün; talk status zeigt Rate-Limit- und Quiet-Hours-State.

Task 06: CORS-Tür + MCP-Endpoint

Blockiert durch: 04. Liefert: Browser- und MCP-Anbindung auf localhost.

  • CORS: POST /speak (und nur dieser) beantwortet OPTIONS-Preflight und setzt Access-Control-Allow-Origin aus Config-Liste server.allowed_origins = ["https://chat.az-gruppe.com"] (Wildcard * erlaubt aber nicht Default; leere Liste = kein CORS-Header). Tests: Preflight 204 + Header; nicht gelistete Origin → kein ACAO-Header.
  • MCP: Streamable-HTTP-Endpoint POST /mcp (und ggf. GET /mcp für Stream) auf localhost; Implementierung des MCP-Protokolls (initialize, tools/list, tools/call) ohne schwergewichtige SDK — Decision: eigene schlanke Implementierung des JSON-RPC-Subsets (initialize, notifications/initialized, tools/list, tools/call), kein externer MCP-SDK-Dependency-Zwang; falls eine begründete leichte SDK existiert, im Report begründen.
  • Ein Tool speak: Input-Schema {text: string, priority?, provider?, lang?, now?} → verhält sich wie POST /speak; Response = MCP-Tool-Result (Text: "queued: " o. Ä.).
  • GET /status um Felder mcp: true, cors: [origins] ergänzt (Compat).
  • Tests: MCP-Handshake + tools/call an der Prozessgrenze (HTTP-Payloads literal getestet); CORS-Preflights.
  • Abnahme: curl-Preflight von gelisteter Origin ok; ein MCP-fähiger Client (z. B. manuelle JSON-RPC-Curl-Sequenz im Test/Report) sieht speak und enqueued erfolgreich.

Task 07: talk wrap

Blockiert durch: 04. Liefert: Befehlsummwicklung mit Ansage.

  • talk wrap -- <befehl> [args…]: führt Befehl aus (stdin durchreichen, stdout/stderr durchreichen), misst Dauer, Ansage nach Ende: Erfolg: „ erfolgreich. Dauer: X." / Fehler: „ fehlgeschlagen, Code N." — Label = Config wrap.label oder der Basisname des Befehls; Ansage läuft über den Daemon-Pfad (enqueue, non-blocking; --wait-Flag respektiert).
  • Exit-Code des gewrappten Befehls wird durchgereicht (talk-Fehler ≠ Befehls-Fehler: wrap exited mit Befehls-Code; wenn die Ansage selbst scheitert: Warning auf stderr, Befehls-Code bleibt).
  • Dauer-Formatierung: menschlich („3 Minuten 42 Sekunden", lokalisiert de/en nach langdetect des Labels).
  • Tests: Unit für Formatierung + Prozessgrenze: talk wrap -- sleep 1 mit Fake/Sink null → enqueue-Call mit Erfolgs-Ansage verifiziert; fehlschlagender Befehl → Code 2 durchgereicht + Fehler-Ansage.
  • Abnahme: talk wrap -- sleep 2 annonciert Erfolg + Dauer; Exit-Codes korrekt.

Task 08: doctor / test / voices (plain)

Blockiert durch: 03, 04. Liefert: Selbstdiagnose und plain Oberflächen.

  • talk doctor: prüft und berichtet zeilenweise mit ✔/✘/⚠: Daemon erreichbar (Port/Status), Audio-Sink (oto initialisierbar; null → Hinweis), Provider-Verfügbarkeit je Stufe (Key vorhanden? Endpunkt erreichbar — leichtgewichtiger Probe-Call konfigurierbar doctor.probe = true|false, Default false → nur Key/Config-Checks, keine Netz-Calls), Notification-Daemon (beeep available), Config-Pfad + effektiv geladene Werte (Keys maskiert), Mute/Rate-Limit/quiet_hours-State via Daemon. Exit-Code 0 wenn kritikalfrei, 1 wenn ✘ (Daemon tot zählt nicht als ✘, sondern ⚠ mit Hinweis Degraded Mode). Machine-lesbar via --json.
  • talk test: spricht feste Testphrase (de/en je --lang), zeigt verwendeten Provider + gemessene Latenz; ohne Daemon → direkter Pfad.
  • talk voices: plain Tabelle PROVIDER LANG VOICE-ID NAME DEFAULT aus der vereinheitlichten Voice-Registry (statische Defaults je Provider + config-overrides); --provider/--lang-Filter; --json.
  • Tests: doctor gegen laufenden/keinen Daemon an der Prozessgrenze (mit sink null + fake provider); voices-Output-Format Unit.
  • Abnahme: bei kaputtem Setup (falscher Port, Key weg) nennt doctor die Punkte explizit; talk test funktioniert einmal end-to-end am Dev-Host.

Task 09: Bubble-Tea-TUI: setup / status / voices

Blockiert durch: 03, 08. Liefert: geführtes Onboarding, Live-Dashboard, Voice-Picker.

  • Dependency: github.com/charmbracelet/bubbletea (+ minimal nötige charm-libs wie lipgloss/bubbles — begründete Auswahl im Report).
  • TTY-Erkennung: kein TTY (Pipe/CI/Agent) → plain-Fallback-Output derselben Info (nie Stacktrace, nie interaktive Schleife).
  • talk setup (Wizard): Schritt 1 Daemon/Version prüfen (Hinweis wenn tot, fortfahrbar) → Schritt 2 Provider-Keys erkennen (env) + optional Key-Eingabe (maskiert) + Probe → Schritt 3 Stimme je Provider/Sprache probehören (Auswahl → test-Absage mit dieser Stimme) → Schritt 4 config.toml schreiben (existierende Datei: zusammenführen, Backup .bak anlegen) → Abschlussbericht. Abbruch jederzeit mit q/Ctrl-C, nichts halbfertig schreiben.
  • talk status (interaktiv): Live-Ansicht Queue (Items mit ID/Priorität/Alter), aktuelles Item mit Fortschritt, Mute/quiet hours/rate-limit, Provider-Kette mit Verfügbarkeit; Aktualisierung via Poll; m = mute-toggle, c = cancel, q = quit — Tasten in Footer.
  • talk voices (interaktiv): Liste filterbar (/), Enter = Probe abspielen (über Daemon), s = als Default in config setzen (mit Bestätigung).
  • Plain-Pfade von 08 bleiben unangetastet (TTY-Weiche in derselben Command-Funktion).
  • Tests: Modell-Unit-Tests (Wizard-State-Machine: Schritte, Abbruch, Merge-Entscheidung; Status-Rendering-Funktion; Voices-Filter) — TUI-Renderlogik als reine Funktionen halten; Prozessgrenze: talk status < /dev/null → plain, exit 0.
  • Abnahme: talk setup durchläuft am Dev-Host komplett (mit Fake-Provider-Endpunkt demonstriert im Report/Tests); keine TTY-Pfade brechen.

Task 10: Skill + AGENTS.md-Snippet + README

Blockiert durch: 08. Liefert: Etikette-Doku als konsumierbares Skill-Artefakt.

  • skills/talk/SKILL.md (Frontmatter analog etablierter Skills: name, description mit Triggern) mit: When to Use (User-Trigger ODER Standing-Order in AGENTS.md — striktes Opt-in), When NOT to Use, Usage (CLI-Beispiele: say/wrap/status/mute; stdin-Pipe; --wait/--now/--priority), Etikette (Ansageinhalt = Status/nächster Schritt/was der Nutzer tun soll; max 1–2 Sätze; niemals Langantwort; session-sticky bis Widerruf; Mute/quiet hours respektieren — Daemon erzwingt es ohnehin), Degraded-Verhalten, Pitfalls (Key-Env, kein Ton → doctor).
  • skills/talk/AGENTS-snippet.md: 3-Zeilen-Block für Projekt-AGENTS.md (Standing-Order-Beispiel).
  • README.md ausbauen: Quickstart (Installation Release-Artefakt/Nix, talk setup, erster Test), CLI-Referenz (alle Befehle/Flags), Config-Referenz (vollständiges config.toml-Beispiel mit allen Keys aus den Tasks 02–09), Daemon (talkd, Autostart-Hinweise je OS), Provider-Tabellen (Keys, Kosten, Defaults), Fleet-Hinweis (Env-Pattern TALK_*, interne Spiegelung, doctor als Diagnose), Etikette-Sektion, FAQ (kein Ton? Browser/LibreChat-Später, MCP-Anbindung-Beispiel).
  • Abnahme: README deckt jede CLI-Surface aus den Global Constraints ab; Skill enthält die Opt-in-Policy wörtlich-diszipliniert; keine neuen Code-Änderungen nötig (Docs-only-Task — Tests: go test ./... bleibt grün, Markdown-Links intern valide).

Task 11: Release-Pipeline + flake.nix

Blockiert durch: 09, 10. Liefert: getaggte Multi-Platform-Artefakte + Nix-Bezug.

  • .goreleaser.yaml: Builds talk + talkd (zwei Binaries), Targets linux/darwin/windows × amd64/arm64 (Windows-Suffix .exe), Archiv je OS; Asset-Namensvertrag: talk_<os>_<arch> (+ .zip/.tar.gz je Konvention, Checksums-Datei checksums.txt), ldflags: Version-Injection (-X main.version={{.Version}} bzw. passend zur T01-Struktur).
  • .gitea/workflows/release.yml: bei Tag v*: test → goreleaser → Upload zu Gitea-Releases.
  • Cross-Compile-Verifikation: einmalig lokal GOOS/GOARCH-Matrix bauen (im Report dokumentiert); CGO-Grundsatz:oto braucht gopherus? → Verpflichtung: oto-Abhängigkeit muss cross-compilierbar sein (reiner Go-Audiopfad oder build-tag-gelöste Plattformpfade); falls CGO nötig würde: Stattdessen plattformnative Fallback-Player als build-tag-Varianten — im Report die gefundene Lösung dokumentieren; das ist der risikoreichste Punkt dieses Tasks.
  • flake.nix: Package talk + talkd (buildGoModule), apps/packages-Outputs, devShell; ersetzt langfristig m3ta-nixpkgs talk/crunch (Hinweis-Kommentar in flake.nix).
  • Abnahme: nix build .#talk funktioniert am Dev-Host; goreleaser build --snapshot --clean produziert alle Targets ohne Fehler; Workflow-YAML valide.

Task 12: Legacy-Migration (AGENTS-Repo)

Blockiert durch: 10, 11. Achtung: greift in ein ANDERES Repo (~/p/AI/AGENTS) — ausschließlich die zwei Skill-Dateien skills/voice-notify/SKILL.md und skills/crunch-jobs/SKILL.md als Redirect-Stubs umschreiben; kein Commit dort ohne die im Brief dokumentierte Reihenfolge; kein Push.

  • Beide SKILL.md-Stubs: Frontmatter behalten (description kürzt auf „DEPRECATED: replaced by talk 2.0 …"), Body: kurzer Umstiegs-Text (neues Repo, neuer Skill-Name, CLI-Beispiel, Link zur talk-README), keine alten Anleitungen mehr.
  • docs/migration-notes.md im talk-Repo: Checkliste Restarbeiten (PRs #24/#7 in m3ta-nixpkgs als superseded schließen — Nutzer-Aktion; m3ta-nixpkgs talk/crunch deprecaten — Nutzer-Aktion mit Vorlage-Text; Fleet-Ausrollung plant).
  • Abnahme: Stubs existieren und sind eindeutig; migration-notes listet alle Nutzer-Aktionen; im AGENTS-Repo liegt ein unpushed Commit auf einem neuen Branch chore/talk2-migration (kein main-Commit!).

Task 13: Altlast-Entsorgung talk.sh (Nutzer-gated)

Blockiert durch: KEINE Tasks, aber durch Nutzer-Aktion: ElevenLabs-Key-Rotation im Dashboard. Ohne bestätigte Rotation NICHT ausführen (destruktiv + sicherheitsrelevant). Wenn der Nutzer Rotation bestätigt hat: ~/talk.sh löschen (rm), in migration-notes abhaken, Ledger-Eintrag. Dieser Task bleibt parked bis zur Bestätigung.