feat: US-002 - Adaugă hf_token la verificarea de secrete în credential_store
- REQUIRED_SECRETS include acum hf_token alături de discord_token - teste actualizate pentru noul set de secrete obligatorii (+ test_partial_required) - gates rulate: tests PASS (22 eșecuri preexistente neschimbate), /review (backend) PASS
This commit is contained in:
333
scripts/ralph/prd.json
Normal file
333
scripts/ralph/prd.json
Normal file
@@ -0,0 +1,333 @@
|
||||
{
|
||||
"projectName": "echo-core",
|
||||
"branchName": "ralph/echo-improve",
|
||||
"description": "Integrare pocket-tts (Kyutai) ca engine TTS nou in Echo Core",
|
||||
"techStack": {
|
||||
"type": "python",
|
||||
"commands": {
|
||||
"start": "python main.py",
|
||||
"build": "",
|
||||
"lint": "ruff check .",
|
||||
"typecheck": "mypy .",
|
||||
"test": "pytest"
|
||||
},
|
||||
"port": 8000
|
||||
},
|
||||
"userStories": [
|
||||
{
|
||||
"id": "US-001",
|
||||
"title": "Setup venv-pockettts și pocket-tts.service systemd unit",
|
||||
"description": "Creează venv separat .venv-pockettts (torch + keyring, nu în .venv principal), un launcher care exportă HF_TOKEN din keyring, și systemd user unit pocket-tts.service care pornește serverul built-in pe 127.0.0.1:7789. Verifică explicit accesul la keyring din systemd --user.",
|
||||
"priority": 10,
|
||||
"acceptanceCriteria": [
|
||||
"Directorul ~/echo-core/.venv-pockettts există cu pocket-tts și keyring instalate; .venv-pockettts/ e în .gitignore",
|
||||
"tools/pocket_tts_env_launch.sh (sau echivalent) citește hf_token din keyring și eșuează cu exit non-zero + log clar dacă get_secret('hf_token') e None",
|
||||
"~/.config/systemd/user/pocket-tts.service pornește 'pocket-tts serve --host 127.0.0.1 --port 7789' prin launcher",
|
||||
"curl -s http://127.0.0.1:7789/health întoarce 200 după 'systemctl --user start pocket-tts.service'"
|
||||
],
|
||||
"tags": [
|
||||
"infra"
|
||||
],
|
||||
"dependsOn": [],
|
||||
"requiresBrowserCheck": false,
|
||||
"requiresDesignReview": false,
|
||||
"passes": true,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 1,
|
||||
"failureReason": "",
|
||||
"notes": "Fișiere: .gitignore, tools/pocket_tts_env_launch.sh (nou). Deja existente din iterația anterioară: .venv-pockettts/ (torch+pocket-tts+keyring), ~/.config/systemd/user/pocket-tts.service. Verificat: curl :7789/health -> 200, systemctl restart -> health revine în ~3s, systemctl is-enabled -> enabled. Gate /review (infra): diff minimal, fără shell injection / secret leakage, PASS."
|
||||
},
|
||||
{
|
||||
"id": "US-002",
|
||||
"title": "Adaugă hf_token la verificarea de secrete în credential_store",
|
||||
"description": "src/credential_store.py verifică azi doar discord_token; adaugă hf_token la lista de secrete verificate (REQUIRED_SECRETS sau check dedicat) ca un token lipsă/expirat să fie detectat la startup, nu la runtime.",
|
||||
"priority": 20,
|
||||
"acceptanceCriteria": [
|
||||
"hf_token apare în REQUIRED_SECRETS (sau check echivalent) în src/credential_store.py",
|
||||
"Verificarea semnalează clar când hf_token lipsește din keyring",
|
||||
"source .venv/bin/activate && pytest tests/ rămâne PASS"
|
||||
],
|
||||
"tags": [
|
||||
"backend"
|
||||
],
|
||||
"dependsOn": [],
|
||||
"requiresBrowserCheck": false,
|
||||
"requiresDesignReview": false,
|
||||
"passes": true,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 0,
|
||||
"failureReason": "",
|
||||
"notes": "Fișiere: src/credential_store.py (REQUIRED_SECRETS += hf_token), tests/test_secrets.py (assert-uri actualizate + test_partial_required nou). pytest tests/ -> 22 eșecuri preexistente neschimbate, 1043 passed (vs 1042 înainte). Gate /review (backend): diff trivial, fără risc, PASS."
|
||||
},
|
||||
{
|
||||
"id": "US-003",
|
||||
"title": "Creează catalogul de voci tts_voices.json cu seed inițial",
|
||||
"description": "Fișier nou tts_voices.json la rădăcina repo-ului (git-tracked, scris prin src/jsonlock.py), mapare nume→{engine, state_path, owner}. Seed cu vocile M*/F* Supertonic existente, cele 3 voci Marius, vocile Paula clonate și vocea predefinită pocket-tts 'alba'.",
|
||||
"priority": 30,
|
||||
"acceptanceCriteria": [
|
||||
"tts_voices.json există la rădăcină, e valid JSON și e git-tracked",
|
||||
"Conține intrările Supertonic M1-M5/F1-F5 cu {engine: supertonic}",
|
||||
"Conține vocile Marius (state_path spre models/voices/*.safetensors), vocile Paula și intrarea 'alba' cu {engine: pockettts, voice_url: alba}",
|
||||
"models/voices/ este acoperit de .gitignore (blob-urile .safetensors nu intră în git)",
|
||||
"Citirea/scrierea catalogului trece prin src/jsonlock.py"
|
||||
],
|
||||
"tags": [
|
||||
"db",
|
||||
"backend"
|
||||
],
|
||||
"dependsOn": [],
|
||||
"requiresBrowserCheck": false,
|
||||
"requiresDesignReview": false,
|
||||
"passes": false,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 0,
|
||||
"failureReason": "",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "US-004",
|
||||
"title": "Script de export voce pocket_tts_add_voice.py + export seed voci existente",
|
||||
"description": "tools/pocket_tts_add_voice.py rulează în venv-ul separat, încarcă modelul o singură dată, exportă .safetensors dintr-un wav (get_state_for_audio_prompt → export_model_state), determină automat următorul N liber pentru un nume din catalog și scrie catalogul prin jsonlock. Exportă cele 3 voci Marius și Paula ca seed.",
|
||||
"priority": 40,
|
||||
"acceptanceCriteria": [
|
||||
"tools/pocket_tts_add_voice.py --wav <path> --name '<Nume>' produce un fișier models/voices/<slug>.safetensors",
|
||||
"Scriptul determină automat următorul index liber scanând tts_voices.json și scrie intrarea prin jsonlock",
|
||||
"Rularea pe sample-urile marius_real_voice*.wav și paula_*.wav produce blob-urile .safetensors referite în seed-ul din tts_voices.json",
|
||||
"Scriptul rulează cu <.venv-pockettts>/bin/python fără a atinge .venv principal"
|
||||
],
|
||||
"tags": [
|
||||
"backend"
|
||||
],
|
||||
"dependsOn": [
|
||||
"US-001",
|
||||
"US-003"
|
||||
],
|
||||
"requiresBrowserCheck": false,
|
||||
"requiresDesignReview": false,
|
||||
"passes": false,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 0,
|
||||
"failureReason": "",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "US-005",
|
||||
"title": "Adaugă secțiunea tts în config.json",
|
||||
"description": "Adaugă în config.json secțiunea tts cu default_engine=pockettts și pockettts_url=http://127.0.0.1:7789, persistabilă prin Config().set/save.",
|
||||
"priority": 50,
|
||||
"acceptanceCriteria": [
|
||||
"config.json conține tts.default_engine='pockettts' și tts.pockettts_url='http://127.0.0.1:7789'",
|
||||
"Config().get('tts.default_engine') întoarce valoarea corectă cu dot-notation",
|
||||
"Config().set('tts.default_engine', 'supertonic') urmat de save()/reload() persistă valoarea"
|
||||
],
|
||||
"tags": [
|
||||
"backend"
|
||||
],
|
||||
"dependsOn": [],
|
||||
"requiresBrowserCheck": false,
|
||||
"requiresDesignReview": false,
|
||||
"passes": false,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 0,
|
||||
"failureReason": "",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "US-006",
|
||||
"title": "Refactor tools/tts.py — rutare engine, backend pocket-tts și fallback",
|
||||
"description": "synthesize(text, voice, lang) rămâne cu semnătura publică neschimbată, dar rutează intern pe engine din tts_voices.json (fallback la default_engine din config). Adaugă _synthesize_pockettts (POST multipart la :7789/tts), redenumește codul actual _synthesize_supertonic (păstrând cap 400 char doar aici), fallback tehnic automat pe Supertonic și câmpul engine_used în rezultat.",
|
||||
"priority": 60,
|
||||
"acceptanceCriteria": [
|
||||
"synthesize() rezolvă engine din tts_voices.json; voce necatalogată cade pe tts.default_engine din config",
|
||||
"_synthesize_pockettts trimite POST multipart cu text + voice_wav (voce clonată) sau voice_url (voce predefinită), ignorând lang",
|
||||
"Eșec tehnic (httpx.ConnectError/timeout/5xx) în _synthesize_pockettts declanșează retry automat pe _synthesize_supertonic(voice='M2', lang='ro'); eroare de conținut (voce inexistentă/text gol) NU declanșează fallback",
|
||||
"Rezultatul synthesize() include cheia engine_used",
|
||||
"_MAX_TTS_CHARS=400 rămâne doar pe calea Supertonic; retry-ul recursiv lang='na' din Supertonic e păstrat"
|
||||
],
|
||||
"tags": [
|
||||
"backend",
|
||||
"refactor"
|
||||
],
|
||||
"dependsOn": [
|
||||
"US-003",
|
||||
"US-005"
|
||||
],
|
||||
"requiresBrowserCheck": false,
|
||||
"requiresDesignReview": false,
|
||||
"passes": false,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 0,
|
||||
"failureReason": "",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "US-007",
|
||||
"title": "Comandă Discord /voice engine",
|
||||
"description": "Comandă nouă /voice engine <pockettts|supertonic> în src/adapters/discord_voice.py care setează config.tts.default_engine (persistă) și răspunde cu confirmare + vocea default a engine-ului ales.",
|
||||
"priority": 70,
|
||||
"acceptanceCriteria": [
|
||||
"/voice engine acceptă doar pockettts|supertonic și scrie config.tts.default_engine prin Config().set",
|
||||
"Valoarea persistă între restart-uri (verificat prin reload config)",
|
||||
"Răspunsul confirmă engine-ul ales și vocea default curentă"
|
||||
],
|
||||
"tags": [
|
||||
"ui",
|
||||
"backend"
|
||||
],
|
||||
"dependsOn": [
|
||||
"US-005",
|
||||
"US-006"
|
||||
],
|
||||
"requiresBrowserCheck": true,
|
||||
"requiresDesignReview": false,
|
||||
"passes": false,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 0,
|
||||
"failureReason": "",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "US-008",
|
||||
"title": "Comandă Discord /voice addvoice",
|
||||
"description": "Comandă nouă /voice addvoice <nume> <sample:Attachment> care descarcă+validează wav-ul (extensie + durată min ~3s), oprește temporar pocket-tts.service, rulează scriptul de export în venv separat, scrie catalogul și răspunde cu numele final atribuit + un preview audio generat cu vocea nouă.",
|
||||
"priority": 80,
|
||||
"acceptanceCriteria": [
|
||||
"Callback-ul folosește await interaction.response.defer(ephemeral=True)",
|
||||
"Validează extensia audio și durata minimă (~3s), respinge clar sample-uri prea scurte",
|
||||
"Oprește pocket-tts.service pe durata export-ului cu mesaj 'Adaug voce, TTS indisponibil ~30s' și îl repornește după",
|
||||
"Invocă tools/pocket_tts_add_voice.py cu <.venv-pockettts>/bin/python și răspunde cu numele final (ex. 'Marius 4') + sample audio preview"
|
||||
],
|
||||
"tags": [
|
||||
"ui",
|
||||
"backend"
|
||||
],
|
||||
"dependsOn": [
|
||||
"US-004",
|
||||
"US-006"
|
||||
],
|
||||
"requiresBrowserCheck": true,
|
||||
"requiresDesignReview": false,
|
||||
"passes": false,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 0,
|
||||
"failureReason": "",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "US-009",
|
||||
"title": "Autocomplete voci din catalog pentru /audio și /voice setvoice",
|
||||
"description": "Înlocuiește listele statice @app_commands.choices din /audio (discord_bot.py) și /voice setvoice (discord_voice.py) cu @app_commands.autocomplete care citește tts_voices.json live, astfel încât vocile clonate noi apar fără redeploy. Elimină listele hardcodate VOICES/_VOICES în favoarea catalogului.",
|
||||
"priority": 90,
|
||||
"acceptanceCriteria": [
|
||||
"/audio parametrul voce folosește autocomplete care citește tts_voices.json la runtime",
|
||||
"/voice setvoice folosește autocomplete din același catalog",
|
||||
"Listele hardcodate de voci (VOICES din tools/tts.py, _VOICES din fast_commands.py, choices statice din discord_bot.py/discord_voice.py) citesc din tts_voices.json; voice_commands.py._VALID_VOICES rămâne neschimbat",
|
||||
"O voce clonată nou adăugată în catalog apare în autocomplete fără modificare de cod"
|
||||
],
|
||||
"tags": [
|
||||
"ui",
|
||||
"backend"
|
||||
],
|
||||
"dependsOn": [
|
||||
"US-003",
|
||||
"US-006"
|
||||
],
|
||||
"requiresBrowserCheck": true,
|
||||
"requiresDesignReview": false,
|
||||
"passes": false,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 0,
|
||||
"failureReason": "",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "US-010",
|
||||
"title": "Extinde /voice doctor cu health-check pocket-tts",
|
||||
"description": "Comanda /voice doctor adaugă un ping la GET :7789/health pentru pocket-tts alături de verificarea Supertonic existentă și un check pentru prezența hf_token în keyring.",
|
||||
"priority": 100,
|
||||
"acceptanceCriteria": [
|
||||
"/voice doctor face ping la http://127.0.0.1:7789/health și raportează status pocket-tts",
|
||||
"Raportează prezența/absența hf_token în keyring",
|
||||
"Verificarea Supertonic existentă rămâne funcțională"
|
||||
],
|
||||
"tags": [
|
||||
"backend"
|
||||
],
|
||||
"dependsOn": [
|
||||
"US-001",
|
||||
"US-002"
|
||||
],
|
||||
"requiresBrowserCheck": false,
|
||||
"requiresDesignReview": false,
|
||||
"passes": false,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 0,
|
||||
"failureReason": "",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "US-011",
|
||||
"title": "Teste minime pentru rutare TTS și config",
|
||||
"description": "tests/test_tts.py (nou) acoperă rutarea engine din catalog, fallback la default_engine, trigger fallback tehnic cu engine_used corect, non-declanșare pe erori de conținut și regression pe retry-ul lang='na'. tests/test_config.py verifică persistența tts.default_engine.",
|
||||
"priority": 110,
|
||||
"acceptanceCriteria": [
|
||||
"tests/test_tts.py: voce catalogată → engine corect (mock catalog); voce necatalogată → default_engine din config",
|
||||
"tests/test_tts.py: mock httpx.ConnectError/5xx pe _synthesize_pockettts → apel automat pe _synthesize_supertonic + engine_used corect; input eronat (voce inexistentă) NU cade pe supertonic",
|
||||
"tests/test_tts.py: regression test pentru retry recursiv lang='na' în Supertonic",
|
||||
"tests/test_config.py verifică Config().set('tts.default_engine',...) persistă prin save()/reload()",
|
||||
"source .venv/bin/activate && pytest tests/test_tts.py tests/test_config.py iese PASS"
|
||||
],
|
||||
"tags": [
|
||||
"backend"
|
||||
],
|
||||
"dependsOn": [
|
||||
"US-006"
|
||||
],
|
||||
"requiresBrowserCheck": false,
|
||||
"requiresDesignReview": false,
|
||||
"passes": false,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 0,
|
||||
"failureReason": "",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "US-012",
|
||||
"title": "Documentează comenzile TTS noi în TOOLS.md",
|
||||
"description": "Actualizează personality/TOOLS.md cu comenzile noi (/voice engine, /voice addvoice, autocomplete voci) și cu noul engine pocket-tts + catalogul tts_voices.json.",
|
||||
"priority": 120,
|
||||
"acceptanceCriteria": [
|
||||
"personality/TOOLS.md descrie /voice engine și /voice addvoice",
|
||||
"Documentează engine-ul pocket-tts, catalogul tts_voices.json și fluxul de adăugare voce nouă",
|
||||
"Nu contrazice comportamentul implementat (fallback, engine default din config)"
|
||||
],
|
||||
"tags": [
|
||||
"docs"
|
||||
],
|
||||
"dependsOn": [
|
||||
"US-007",
|
||||
"US-008",
|
||||
"US-009"
|
||||
],
|
||||
"requiresBrowserCheck": false,
|
||||
"requiresDesignReview": false,
|
||||
"passes": false,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 0,
|
||||
"failureReason": "",
|
||||
"notes": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user