21 KiB
Plan: talk 2.0 — V1 Implementationsplan
Plan: .scratch/talk-2-0/plan.md Spec: docs/spec.md (bindende Autorität — Konflikte gehen zu ihren Lasten; Nachtrag 2026-10-24 beachten: ein Binary, kein talkd) 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.--helpauf 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.tomlim platform-nativen Verzeichnis: Linux~/.config/talk, macOS~/Library/Application Support/talk, Windows%APPDATA%\talk. Env schlägt Datei. Voice-Mappingvoice.<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-APIPOST /speakauf 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.ymlläuftgo vet+go test ./...bei jedem Push.go build ./...muss auf dem Dev-Host (Linux) grün sein; Cross-Compile viaGOOS/GOARCHmindestens 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 1: Repo-Skeleton & CI-Backbone
Keine Blocker. Liefert: initialisierbares, grünes Go-Repo.
gitbereits initialisiert (main); dieser Task entsteht auf Branchfeat/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: stdlibflag+ eigene Subcommand-Dispatch, keine externen CLI-Abhängigkeiten in T01) mit--version(zeigtdev+ Commit via ldflags-Variablen, defaultdev) und allen Subcommands alsnot 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 buildgrün; Workflow-Datei valider YAML; README/LICENSE vorhanden; Commit(s) conventional.
Task 2: Erste Ansage end-to-end (OS-TTS)
Blockiert durch: 01. Liefert: talk "Hallo Welt" spricht über OS-TTS.
internal/langdetect: reine FunktionDetect(text string) string→"de"|"en"(Default"de"); Heuristik: Common-Word-Listen + Umlaute/ß; tischgesteuerte Tests (de/en/gemischt/leer).internal/provider: InterfaceProvidermitName() 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 überespeak/speech-dispatcher-Fallback (externer Prozess — OK, es ist der Notnagel-Provider; wenn Binary fehlt:Available() == false), macOS übersay(Ausgabe in temporäre WAV-Datei), Windows über SAPI (PowerShellSystem.Speech). Fehlende Binary/Runtime → klarer Fehler, kein Panik.
- Implementierung
internal/audio:Sink-Interface (Play(ctx, data []byte) error,Name() string); Implementierungenoto(embedded Playback, PCM/WAV) undnull(verwirft, loggt). Auswahl per Configaudio.sink = "oto"|"null"(Defaultoto).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, respektiveXDG_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 undosbuiltindefault 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=nullläuft ohne Ton durch;go test ./...grün (langdetect + config + provider-mit-fake-proc wo sinnvoll).
Task 3: 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>, Headerxi-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-IDKXxZd16DiBqt82nbarJx. Endpunkt konfigurierbar (Defaulthttps://api.elevenlabs.io).openai: POST<endpoint>/v1/audio/speech, Bearer-KeyTALK_OPENAI_KEY, Body{model:"gpt-4o-mini-tts"|"tts-1", voice, input, response_format:"mp3"}— Entscheidungsnotiz: Modell-String konfigurierbar, Defaulttts-1. Endpunkt Defaulthttps://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 undprovider = "…"-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 alstesting.Shortüberspringbare Integration-Testmarkierung behandelt; Dokumentationspflicht im Report, welche Teile fake-bar getestet sind); Kettentests: erster 500 → zweiter getroffen → Bytes. - Abnahme:
talk "Test" --provider=elevenlabsmit 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 4: talkd-Daemon + Queue + Mute
Blockiert durch: 02, 03. Liefert: Daemon-betriebene, serialisierte Ansagen mit persistentem Mute.
cmd/talkd: Daemon-Prozess; HTTP-Server auf127.0.0.1:<port>(Port Default7800, konfigurierbarTALK_PORT/configserver.port); Endpunkte:POST /speak— JSON{text, priority?, provider?, voice?, lang?, now?}→ enqueued, Antwort202 {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, Antwort200 {cancelled:n}.POST /mute,POST /unmute— toggelt persistenten Mute-State (Dateimuteim 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 viaGET /status; läuft er: enqueue + sofort zurück (non-blocking Default);--waitpollt bis abgearbeitet;--nowsetztnow:true;mute/unmute/status/cancelwerden Daemon-Aufrufe mit plain Output. - Degraded Mode: Daemon antwortet nicht → Lock-File
talk.lockim 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=nullstarten; 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 paralleletalk-Aufrufe → drei Ansagen nacheinander;talk mute→ Daemon-Restart → weiter stumm.
Task 5: 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:trueumgeht Warteschlangen-Wartezeit nicht das Rate-Limit; bei Überschreitung: Items mitpriority=lowwerden verworfen,normalwartet,highdarf konfigurierbar verstoßen (queue.allow_high_burst = true|false, Default true). quiet_hours: Configquiet_hours = "18:00-08:00"(lokale Zeit, 24h, über Mitternacht); Verhalten configbar:quiet_mode = "discard"|"toast"(Defaultdiscard); Mute (manuell) hat immer Vorrang;now:truebricht 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 statuszeigt Rate-Limit- und Quiet-Hours-State.
Task 6: CORS-Tür + MCP-Endpoint
Blockiert durch: 04. Liefert: Browser- und MCP-Anbindung auf localhost.
- CORS:
POST /speak(und nur dieser) beantwortetOPTIONS-Preflight und setztAccess-Control-Allow-Originaus Config-Listeserver.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 /mcpfü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 /statusum Feldermcp: 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
speakund enqueued erfolgreich.
Task 7: 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 = Configwrap.labeloder 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 1mit Fake/Sink null → enqueue-Call mit Erfolgs-Ansage verifiziert; fehlschlagender Befehl → Code 2 durchgereicht + Fehler-Ansage. - Abnahme:
talk wrap -- sleep 2annonciert Erfolg + Dauer; Exit-Codes korrekt.
Task 8: 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 konfigurierbardoctor.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 TabellePROVIDER LANG VOICE-ID NAME DEFAULTaus 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 testfunktioniert einmal end-to-end am Dev-Host.
Task 9: 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.bakanlegen) → 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 setupdurchlä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.mdausbauen: 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: Buildstalk+talkd(zwei Binaries), Targets linux/darwin/windows × amd64/arm64 (Windows-Suffix.exe), Archiv je OS; Asset-Namensvertrag:talk_<os>_<arch>(+.zip/.tar.gzje Konvention, Checksums-Dateichecksums.txt), ldflags: Version-Injection (-X main.version={{.Version}}bzw. passend zur T01-Struktur)..gitea/workflows/release.yml: bei Tagv*: 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: Packagetalk+talkd(buildGoModule),apps/packages-Outputs, devShell; ersetzt langfristig m3ta-nixpkgs talk/crunch (Hinweis-Kommentar in flake.nix).- Abnahme:
nix build .#talkfunktioniert am Dev-Host;goreleaser build --snapshot --cleanproduziert 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.mdim 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.