Skip to content

fix: lyrics language verdicts without destructive default repair - #141

Merged
IAnMove merged 2 commits into
mainfrom
fix/lyrics-language-contract
Sep 5, 2026
Merged

fix: lyrics language verdicts without destructive default repair#141
IAnMove merged 2 commits into
mainfrom
fix/lyrics-language-contract

Conversation

@IAnMove

@IAnMove IAnMove commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Resumen ejecutivo

Qué cambia

El validador de letras distingue valid / invalid / unevaluable.
No da por buena una letra vocal vacía ni un idioma sin soporte (francés,
estonio). La reparación ya no pisa el original: propone un diff.

Para qué sirve

#137/#139 cubrían contaminación española y alias. Seguían pasando en silencio
casos vacíos o no evaluables, y repair podía dejar una letra vacía como OK.

Impacto para el usuario

Ninguno visible todavía: la librería no está cableada a Generate (fase 6).

Riesgo

  • Medio

Estado

  • Listo para revisión
  • Bloqueado por CI o Cursor del head

Summary

Phase 2 remainder after merged #139. Library only. No launch / StoryLabPanel.

Validation

  • pytest tests/test_lyrics_language.py: 20 passed
  • TS corpus: 15 passed
  • bash scripts/validate_local.sh: passed (no GPU)
  • ratchet vs d4263ce6

Follow-up

Phase 6 wires the guard. Phase 3 is independent.

Base: d4263ce6. Head: d58d05686b6d48abf251dcdf64c763c25670d18f.
Generación real: NO EJECUTADA.


Note

Medium Risk
Contract and default behavior change for a shared validation library (verdicts, stricter empty/unsupported handling, non-destructive repair); impact is limited today because the guard is not yet wired into enqueue paths.

Overview
Tightens the provider-free lyrics language guard (Python + mirrored TS) so validation cannot silently pass empty vocals or unsupported languages, and repair no longer overwrites user text.

Reports now expose verdict (valid | invalid | unevaluable); ok is true only for valid. Empty vocal lyrics are invalid; recognized but unscored languages (e.g. French, Estonian) are unevaluable instead of implied success. Only es and en are scored. Required protected_segments must appear verbatim (including newlines) before language checks; missing spans are invalid even when the language is unevaluable.

repair_lyrics_language keeps lyrics as the original and returns proposal / proposal_diffs for stripped foreign-script runs; a repair that would empty the vocal lyric stays invalid. assert_lyrics_language defaults to repair=False.

Adds a shared JSON corpus exercised by Python and TS tests, plus updated LYRICS_LANGUAGE.md and phase-2 checklist notes. No wiring to Generate / Story Lab yet (phase 6).

Reviewed by Cursor Bugbot for commit b1a2c3d. Configure here.

Empty vocal lyrics and unsupported languages are invalid or unevaluable,
never a silent pass. Repair returns a proposal and keeps the original.
Protected spans must appear exactly. Shared Python/TS corpus.
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

PR Review — Loreframe Studio

Risk: low
Scope: 7 file(s); +430/-239; React UI, backend services, docs

Automated review from scripts/analyze_pr.py. This is a heuristic pass (no LLM) so humans still own the merge decision.

Findings

  • low — UI changed — rebuild before merge
    Run cd ui && npm run build (CI already does this). Pinokio Update rebuilds for end users; keep ui/dist untracked.

Changed files

  • added: tests/fixtures/lyrics_language_corpus.json
  • modified: app/services/lyrics_language.py, docs/development/LYRICS_LANGUAGE.md, fase2.md, tests/test_lyrics_language.py, ui/src/lib/lyricsLanguageGuard.ts, ui/tests/lyricsLanguageGuard.test.ts

CONTRIBUTING checklist

  • python scripts/verify_clean_repo.py
  • python -m compileall -q app/services app/launch.py scripts
  • cd ui && npm run build if the UI changed
  • No weights, CivitAI sidecars, or generated guides
  • Stays local-first (no required accounts / telemetry)

Posted by the repo PR review workflow. Re-runs on each push to the PR.

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Code health

Quality score: 49.8/100

Higher is better. The score is a trend dashboard; the independent ratchet below remains the CI gate.

Component Weight Current Change
Cyclomatic health 45% 52.5 -0.1
File concentration 25% 55.3 +0.1
Oversized-file debt 20% 30.6 +0.1
Modularity 10% 62.2 +0.0

Change vs PR base: +0.0 points.

Metric Value
Production LOC 244,660
Production files 539
Test LOC 71,849
Functions measured 15,310
Functions complexity ≥ 15 790
Maximum complexity 667

Markdown, JSON catalogs and tests are out of this table. Only app/ runtime + ui/src TS/JS count.

Most complex functions

Complexity Where
667 app/wgp.py:7164 generate_video
374 ui/src/stores/useStore.ts:4024 Async method 'startGeneration'
355 app/_launch_runtime.py:23508 _run_generation
308 app/wgp.py:12281 generate_video_tab
271 ui/src/components/Sidebar/SceneAnimatorPanel.tsx:474 Function 'SceneAnimatorPanel'
266 ui/src/stores/useStore.ts:8566 Async method 'loadSettingsFromOutput'
258 app/services/director/planners/short_film.py:3433 ShortFilmPlanner._plan_story_driven
248 app/services/director_pipeline.py:13735 _run_video_generation
245 app/services/director_pipeline.py:7860 _run_pipeline
243 ui/src/features/agent/agentActions.ts:1128 Function 'parseAction'
226 app/services/director_pipeline.py:6689 update_comic_preview
225 ui/src/features/agent/agentActions.ts:2796 Async function 'executeAgentActions'

Trend vs baseline

Metric Δ
Production LOC +100
Test LOC +8
Functions ≥ 15 +2
Maximum complexity +0

Warnings

  • production LOC increased by +100
  • functions at complexity >= 15 increased by +2

Ratchet passed.

@IAnMove

IAnMove commented Sep 5, 2026

Copy link
Copy Markdown
Owner Author

cursor review

1 similar comment
@IAnMove

IAnMove commented Sep 5, 2026

Copy link
Copy Markdown
Owner Author

cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Protected spans skipped when unevaluable
    • Moved the language-independent protected-span presence check before unknown/unsupported language early returns in both Python and TypeScript validators so a missing required span is invalid even for French, Estonian, or unrecognized codes.

Create PR

Or push these changes by commenting:

@cursor push 8ee83b95bb
Preview (8ee83b95bb)
diff --git a/app/services/lyrics_language.py b/app/services/lyrics_language.py
--- a/app/services/lyrics_language.py
+++ b/app/services/lyrics_language.py
@@ -239,6 +239,14 @@
             reasons=["A vocal song must contain lyrics."],
         )
 
+    missing = [span for span in protected if span not in text]
+    if missing:
+        return _report(
+            verdict="invalid",
+            lyrics=text,
+            reasons=["A required verbatim span is missing from the lyric."],
+        )
+
     code = canonical_lyrics_language(lyrics_language)
     if not code:
         return _report(
@@ -253,14 +261,6 @@
             reasons=[f"Language {code!r} is not scored by this guard."],
         )
 
-    missing = [span for span in protected if span not in text]
-    if missing:
-        return _report(
-            verdict="invalid",
-            lyrics=text,
-            reasons=["A required verbatim span is missing from the lyric."],
-        )
-
     masked, _present = _mask_protected(text, protected)
     sample = _strip_section_tags(masked)
     sample = PROTECTED_TOKEN_RE.sub(" ", sample)

diff --git a/ui/src/lib/lyricsLanguageGuard.ts b/ui/src/lib/lyricsLanguageGuard.ts
--- a/ui/src/lib/lyricsLanguageGuard.ts
+++ b/ui/src/lib/lyricsLanguageGuard.ts
@@ -165,15 +165,15 @@
   if (!text.trim()) {
     return report('invalid', text, ['A vocal song must contain lyrics.'])
   }
+  const protectedList = protectedTexts(options.protectedSegments)
+  if (protectedList.some(span => !text.includes(span))) {
+    return report('invalid', text, ['A required verbatim span is missing from the lyric.'])
+  }
   const code = canonicalLyricsLanguage(lyricsLanguage)
   if (!code) return report('unevaluable', text, ['The requested lyrics language is not recognized.'])
   if (!SCORED.has(code)) {
     return report('unevaluable', text, [`Language '${code}' is not scored by this guard.`])
   }
-  const protectedList = protectedTexts(options.protectedSegments)
-  if (protectedList.some(span => !text.includes(span))) {
-    return report('invalid', text, ['A required verbatim span is missing from the lyric.'])
-  }
   const masked = maskProtected(text, protectedList)
   const sample = stripTags(masked).replace(/\{\{PROTECTED_\d+\}\}/g, ' ')
   let reasons: string[] = []

You can send follow-ups to the cloud agent here.

Comment thread app/services/lyrics_language.py Outdated
Verbatim presence is language-independent. Returning unevaluable first
hid missing required spans when lyrics_language was unsupported.
@IAnMove

IAnMove commented Sep 5, 2026

Copy link
Copy Markdown
Owner Author

cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit b1a2c3d. Configure here.

@IAnMove
IAnMove merged commit 58b7a08 into main Sep 5, 2026
5 checks passed
@IAnMove
IAnMove deleted the fix/lyrics-language-contract branch September 5, 2026 11:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant