Skip to content

feat: TTS end-to-end intelligibility harness#75

Merged
JarbasAl merged 1 commit into
devfrom
feat/tts-intelligibility
Jun 15, 2026
Merged

feat: TTS end-to-end intelligibility harness#75
JarbasAl merged 1 commit into
devfrom
feat/tts-intelligibility

Conversation

@JarbasAl

@JarbasAl JarbasAl commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary

Adds an end-to-end TTS intelligibility harness to ovoscope: synthesise speech with a TTS plugin under test, transcribe the rendered audio back with a reference STT (faster-whisper tiny), and score the round-trip with WER/CER. This catches regressions that file-existence unit tests miss — garbled audio, wrong sample rate, broken transforms, silent output — and gives every TTS plugin a comparable intelligibility number.

What's included

  • ovoscope/tts_intelligibility.pyscore_tts_intelligibility(tts, utterances, *, lang, voice, reference_stt, mode) + TTSIntelligibilityHarness context manager, returning IntelligibilityReport (per-utterance UtteranceScore, mean_wer/mean_cer, to_dict(), to_markdown_row()).
    • mode="playback" (default) drives the full ovos-audio stack (speak → PlaybackService → get_ttstts_transformplay_audio) and captures the rendered WAV via a play_audio side_effect, copying it out before the cache prunes it.
    • mode="direct" calls tts.get_tts() directly (no bus) for engines that hang under the playback thread or when ovos_audio is absent.
    • Round-trip: AudioFile(wav).read()AudioDatareference_stt.execute() → normalise both sides via ovos-utterance-normalizerjiwer.wer/cer.
    • Reference STT is a lazy module-singleton FasterWhisperSTT({"model":"tiny","compute_type":"int8","beam_size":1}).
  • PlaybackServiceHarness extended with a tts= arg (default MockTTS(), backward compatible) and a captured_wavs list.
  • [tts] optional extra (ovos-audio, jiwer, ovos-utterance-normalizer, ovos-stt-plugin-fasterwhisper); same added to dev. Graceful optional import in __init__.py.

Reporting-only at launch

Intended to be wired report-only (per-plugin TTS_MAX_WER defaults to 1.0).

Tests

test/unittests/test_tts_intelligibility.py — MockTTS + a fixed-transcript MockSTT (no model download): WER/CER math, normalisation, report aggregation/serialisation, both modes, playback wav capture, and a graceful-import test (core import ovoscope works without the tts extra). All 11 pass; existing audio-harness suite (38) still green. The real faster-whisper round-trip was sanity-checked locally (kept out of the committed suite to keep CI light).

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added TTS intelligibility scoring to measure how accurately synthesized speech is transcribed, using word and character error rates
    • Generates detailed reports with per-utterance and aggregate statistics
    • Supports multiple synthesis modes for comprehensive testing
  • Chores

    • Added optional tts dependency extra for intelligibility scoring functionality
  • Tests

    • Added comprehensive unit tests for intelligibility scoring capabilities

Add ovoscope/tts_intelligibility.py: synthesise speech with a TTS plugin
under test, transcribe the rendered audio back with a reference STT
(faster-whisper tiny), and score the round-trip with WER/CER.

- score_tts_intelligibility() + TTSIntelligibilityHarness context manager
  returning an IntelligibilityReport (per-utterance UtteranceScore, mean
  WER/CER, to_dict/to_markdown_row).
- mode="playback" drives the full ovos-audio stack and captures the rendered
  WAV via a play_audio side_effect; mode="direct" calls tts.get_tts directly.
- Extend PlaybackServiceHarness with a tts= arg (default MockTTS, backward
  compatible) and a captured_wavs list.
- Add [tts] optional extra; graceful optional import in __init__.
- Unit tests (MockTTS + MockSTT, no model download) cover WER/CER math,
  report aggregation, playback wav capture, and graceful import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new ovoscope/tts_intelligibility.py module providing end-to-end TTS intelligibility scoring via faster-whisper STT and jiwer WER/CER metrics. Extends PlaybackServiceHarness with WAV capture support, introduces TTSIntelligibilityHarness and score_tts_intelligibility, re-exports new symbols from __init__.py, adds a tts optional-dependency extra to pyproject.toml, and adds comprehensive unit tests.

Changes

TTS Intelligibility Scoring

Layer / File(s) Summary
Optional deps and PlaybackServiceHarness WAV capture
pyproject.toml, ovoscope/audio.py
Defines a new tts optional-dependency extra (jiwer, ovos-utterance-normalizer, ovos-stt-plugin-fasterwhisper, ovos-audio) and extends the dev extra. Updates PlaybackServiceHarness.__init__ to accept an optional injected tts, adds self.captured_wavs, and changes the play_audio patch from a fixed mock to a side_effect that records the first positional argument (rendered WAV path).
Scoring primitives, normalization, and data models
ovoscope/tts_intelligibility.py
Adds the faster-whisper reference STT singleton (get_reference_stt) with double-checked locking, transcript normalization (_normalize) using the utterance normalizer, WER/CER computation (_score_pair) with deterministic empty-reference handling, and the UtteranceScore / IntelligibilityReport dataclasses with to_dict and to_markdown_row.
TTSIntelligibilityHarness, convenience wrapper, and public export
ovoscope/tts_intelligibility.py, ovoscope/__init__.py
Implements TTSIntelligibilityHarness as a context manager supporting "playback" (via PlaybackServiceHarness) and "direct" (tts.get_tts) synthesis modes, WAV temp-directory management, per-utterance transcription, normalization, scoring, and IntelligibilityReport aggregation. Adds score_tts_intelligibility as a convenience wrapper. Re-exports all four public symbols from ovoscope/__init__.py with targeted ImportError suppression for missing optional modules.
Unit tests
test/unittests/test_tts_intelligibility.py
Adds TTS_AVAILABLE-gated TestScoring (WER/CER, normalization, report aggregation, serialization), MockSTT stub, TestHarnessDirectMode and TestHarnessPlaybackMode for end-to-end harness flows in both modes, and TestGracefulImport using a subprocess with a custom MetaPathFinder to assert core import works without the [tts] extra.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant TTSIntelligibilityHarness
    participant PlaybackServiceHarness
    participant TTS
    participant ReferenceSTT

    Caller->>TTSIntelligibilityHarness: score(utterances)

    rect rgba(100, 149, 237, 0.5)
        Note over TTSIntelligibilityHarness,PlaybackServiceHarness: playback mode
        TTSIntelligibilityHarness->>PlaybackServiceHarness: __enter__
        TTSIntelligibilityHarness->>TTS: speak(utterance)
        PlaybackServiceHarness-->>TTSIntelligibilityHarness: captured_wavs[0]
    end

    rect rgba(60, 179, 113, 0.5)
        Note over TTSIntelligibilityHarness,TTS: direct mode
        TTSIntelligibilityHarness->>TTS: get_tts(utterance, tmp_wav_path)
        TTS-->>TTSIntelligibilityHarness: wav bytes
    end

    TTSIntelligibilityHarness->>ReferenceSTT: execute(wav_bytes, lang)
    ReferenceSTT-->>TTSIntelligibilityHarness: transcript
    TTSIntelligibilityHarness->>TTSIntelligibilityHarness: _normalize(reference) + _normalize(transcript)
    TTSIntelligibilityHarness->>TTSIntelligibilityHarness: _score_pair → UtteranceScore
    TTSIntelligibilityHarness-->>Caller: IntelligibilityReport
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • OpenVoiceOS/ovoscope#44: Introduced the PlaybackServiceHarness infrastructure in ovoscope/audio.py that this PR directly extends with optional TTS injection and WAV-capture behavior.

Suggested labels

feature

🐇 A rabbit hops through sound and word,
Whisper-fast, each syllable heard.
WER and CER, measured right,
WAV files captured in the night.
The harness scores each TTS dream —
Intelligibility's on the team! 🎙️✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat: TTS end-to-end intelligibility harness' clearly and concisely summarizes the main change: adding a TTS intelligibility testing harness with end-to-end scoring capabilities.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tts-intelligibility

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ovoscope/tts_intelligibility.py`:
- Around line 307-311: The WAV file path generation in the direct mode TTS
method uses a hash-based filename that can suffer from collisions and cause the
same path to be reused for repeated utterances, overwriting previous audio files
and corrupting earlier UtteranceScore.wav_path references. Replace the
hash-based filename (the f-string that constructs the filename using
abs(hash(utterance)) & 0xffffffff) with a unique identifier per call, such as a
UUID generated using Python's uuid module, to ensure each utterance gets a
distinct and non-colliding output file path.
- Around line 273-279: The __enter__ method creates a temporary directory via
tempfile.mkdtemp() but if the subsequent PlaybackServiceHarness initialization
or its __enter__ call raises an exception, the __exit__ method is never invoked
and the temp directory is left behind. Wrap the playback harness setup code (the
PlaybackServiceHarness instantiation and __enter__ call) in a try-except block,
and in the except handler, clean up the temporary directory using
shutil.rmtree(self._tmpdir) before re-raising the exception to ensure the temp
directory is always removed on failed context entry.

In `@test/unittests/test_tts_intelligibility.py`:
- Around line 164-177: The test's simulation of the missing [tts] extra is
incomplete. First, add the module ovos_audio to the BLOCKED set (which currently
contains jiwer, ovos_utterance_normalizer, ovos_stt_plugin_fasterwhisper, and
faster_whisper) since ovos_audio is part of the [tts] extra. Second, strengthen
the assertion that validates the harness is absent by checking not just for the
TTSIntelligibilityHarness symbol but for multiple symbols from the TTS export
surface to ensure the entire optional surface is properly blocked when the [tts]
extra is unavailable.
- Around line 180-183: Add a timeout parameter to the subprocess.run() call in
the test to prevent indefinite blocking if import resolution stalls. Include the
timeout argument alongside the existing capture_output and text parameters in
the subprocess.run() invocation to ensure the test has a defined execution
boundary and won't hang CI.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 12b23a44-e14b-4333-b799-221afef2af3c

📥 Commits

Reviewing files that changed from the base of the PR and between 194c83f and 03e115d.

📒 Files selected for processing (5)
  • ovoscope/__init__.py
  • ovoscope/audio.py
  • ovoscope/tts_intelligibility.py
  • pyproject.toml
  • test/unittests/test_tts_intelligibility.py

Comment on lines +273 to +279
def __enter__(self) -> "TTSIntelligibilityHarness":
self._tmpdir = tempfile.mkdtemp(prefix="ovoscope-tts-")
if self.mode == "playback":
from ovoscope.audio import PlaybackServiceHarness
self._playback = PlaybackServiceHarness(tts=self.tts)
self._playback.__enter__()
return self

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ensure temp-directory cleanup when context entry fails.

If playback harness setup raises during __enter__, the temp directory is left behind because __exit__ is never reached on failed context entry.

Proposed fix
@@
     def __enter__(self) -> "TTSIntelligibilityHarness":
         self._tmpdir = tempfile.mkdtemp(prefix="ovoscope-tts-")
-        if self.mode == "playback":
-            from ovoscope.audio import PlaybackServiceHarness
-            self._playback = PlaybackServiceHarness(tts=self.tts)
-            self._playback.__enter__()
-        return self
+        try:
+            if self.mode == "playback":
+                from ovoscope.audio import PlaybackServiceHarness
+                self._playback = PlaybackServiceHarness(tts=self.tts)
+                self._playback.__enter__()
+            return self
+        except Exception:
+            if self._tmpdir and os.path.isdir(self._tmpdir):
+                shutil.rmtree(self._tmpdir, ignore_errors=True)
+                self._tmpdir = None
+            raise
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def __enter__(self) -> "TTSIntelligibilityHarness":
self._tmpdir = tempfile.mkdtemp(prefix="ovoscope-tts-")
if self.mode == "playback":
from ovoscope.audio import PlaybackServiceHarness
self._playback = PlaybackServiceHarness(tts=self.tts)
self._playback.__enter__()
return self
def __enter__(self) -> "TTSIntelligibilityHarness":
self._tmpdir = tempfile.mkdtemp(prefix="ovoscope-tts-")
try:
if self.mode == "playback":
from ovoscope.audio import PlaybackServiceHarness
self._playback = PlaybackServiceHarness(tts=self.tts)
self._playback.__enter__()
return self
except Exception:
if self._tmpdir and os.path.isdir(self._tmpdir):
shutil.rmtree(self._tmpdir, ignore_errors=True)
self._tmpdir = None
raise
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ovoscope/tts_intelligibility.py` around lines 273 - 279, The __enter__ method
creates a temporary directory via tempfile.mkdtemp() but if the subsequent
PlaybackServiceHarness initialization or its __enter__ call raises an exception,
the __exit__ method is never invoked and the temp directory is left behind. Wrap
the playback harness setup code (the PlaybackServiceHarness instantiation and
__enter__ call) in a try-except block, and in the except handler, clean up the
temporary directory using shutil.rmtree(self._tmpdir) before re-raising the
exception to ensure the temp directory is always removed on failed context
entry.

Comment on lines +307 to +311
wav_path = os.path.join(
self._tmpdir, f"direct_{abs(hash(utterance)) & 0xffffffff}.wav"
)
self.tts.get_tts(utterance, wav_path, lang=self.lang, voice=self.voice)
return wav_path if os.path.isfile(wav_path) else None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a unique output WAV path per utterance in direct mode.

The current hash-based filename can reuse the same path for repeated utterances (and collide across different utterances), so earlier UtteranceScore.wav_path entries can point to overwritten audio artifacts.

Proposed fix
@@
 import dataclasses
 import os
 import re
 import shutil
 import string
 import tempfile
 import threading
+import uuid
@@
     def _render_direct(self, utterance: str) -> Optional[str]:
         """Synthesise via ``tts.get_tts`` directly; return the WAV path."""
         wav_path = os.path.join(
-            self._tmpdir, f"direct_{abs(hash(utterance)) & 0xffffffff}.wav"
+            self._tmpdir, f"direct_{uuid.uuid4().hex}.wav"
         )
         self.tts.get_tts(utterance, wav_path, lang=self.lang, voice=self.voice)
         return wav_path if os.path.isfile(wav_path) else None
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
wav_path = os.path.join(
self._tmpdir, f"direct_{abs(hash(utterance)) & 0xffffffff}.wav"
)
self.tts.get_tts(utterance, wav_path, lang=self.lang, voice=self.voice)
return wav_path if os.path.isfile(wav_path) else None
wav_path = os.path.join(
self._tmpdir, f"direct_{uuid.uuid4().hex}.wav"
)
self.tts.get_tts(utterance, wav_path, lang=self.lang, voice=self.voice)
return wav_path if os.path.isfile(wav_path) else None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ovoscope/tts_intelligibility.py` around lines 307 - 311, The WAV file path
generation in the direct mode TTS method uses a hash-based filename that can
suffer from collisions and cause the same path to be reused for repeated
utterances, overwriting previous audio files and corrupting earlier
UtteranceScore.wav_path references. Replace the hash-based filename (the
f-string that constructs the filename using abs(hash(utterance)) & 0xffffffff)
with a unique identifier per call, such as a UUID generated using Python's uuid
module, to ensure each utterance gets a distinct and non-colliding output file
path.

Comment on lines +164 to +177
"BLOCKED = {'jiwer', 'ovos_utterance_normalizer', "
"'ovos_stt_plugin_fasterwhisper', 'faster_whisper'}\n"
"class _Block(importlib.abc.MetaPathFinder):\n"
" def find_spec(self, name, path, target=None):\n"
" if name.split('.')[0] in BLOCKED:\n"
" raise ModuleNotFoundError(name=name.split('.')[0])\n"
" return None\n"
"sys.meta_path.insert(0, _Block())\n"
"for m in list(sys.modules):\n"
" if m.split('.')[0] in BLOCKED:\n"
" del sys.modules[m]\n"
"import ovoscope\n"
"assert not hasattr(ovoscope, 'TTSIntelligibilityHarness'), "
"'harness should be absent without the tts extra'\n"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Strengthen the “no [tts] extra” simulation and export-surface assertion.

Line 164 omits ovos_audio from BLOCKED even though [tts] includes it, and Line 176 validates only one symbol. This can pass while still leaking part of the optional surface.

Suggested test hardening
-            "BLOCKED = {'jiwer', 'ovos_utterance_normalizer', "
-            "'ovos_stt_plugin_fasterwhisper', 'faster_whisper'}\n"
+            "BLOCKED = {'jiwer', 'ovos_audio', 'ovos_utterance_normalizer', "
+            "'ovos_stt_plugin_fasterwhisper', 'faster_whisper'}\n"
@@
-            "assert not hasattr(ovoscope, 'TTSIntelligibilityHarness'), "
-            "'harness should be absent without the tts extra'\n"
+            "for sym in ('TTSIntelligibilityHarness', 'score_tts_intelligibility', "
+            "'IntelligibilityReport', 'UtteranceScore'):\n"
+            "    assert not hasattr(ovoscope, sym), "
+            "'tts symbols should be absent without the tts extra'\n"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"BLOCKED = {'jiwer', 'ovos_utterance_normalizer', "
"'ovos_stt_plugin_fasterwhisper', 'faster_whisper'}\n"
"class _Block(importlib.abc.MetaPathFinder):\n"
" def find_spec(self, name, path, target=None):\n"
" if name.split('.')[0] in BLOCKED:\n"
" raise ModuleNotFoundError(name=name.split('.')[0])\n"
" return None\n"
"sys.meta_path.insert(0, _Block())\n"
"for m in list(sys.modules):\n"
" if m.split('.')[0] in BLOCKED:\n"
" del sys.modules[m]\n"
"import ovoscope\n"
"assert not hasattr(ovoscope, 'TTSIntelligibilityHarness'), "
"'harness should be absent without the tts extra'\n"
"BLOCKED = {'jiwer', 'ovos_audio', 'ovos_utterance_normalizer', "
"'ovos_stt_plugin_fasterwhisper', 'faster_whisper'}\n"
"class _Block(importlib.abc.MetaPathFinder):\n"
" def find_spec(self, name, path, target=None):\n"
" if name.split('.')[0] in BLOCKED:\n"
" raise ModuleNotFoundError(name=name.split('.')[0])\n"
" return None\n"
"sys.meta_path.insert(0, _Block())\n"
"for m in list(sys.modules):\n"
" if m.split('.')[0] in BLOCKED:\n"
" del sys.modules[m]\n"
"import ovoscope\n"
"for sym in ('TTSIntelligibilityHarness', 'score_tts_intelligibility', "
"'IntelligibilityReport', 'UtteranceScore'):\n"
" assert not hasattr(ovoscope, sym), "
"'tts symbols should be absent without the tts extra'\n"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/unittests/test_tts_intelligibility.py` around lines 164 - 177, The
test's simulation of the missing [tts] extra is incomplete. First, add the
module ovos_audio to the BLOCKED set (which currently contains jiwer,
ovos_utterance_normalizer, ovos_stt_plugin_fasterwhisper, and faster_whisper)
since ovos_audio is part of the [tts] extra. Second, strengthen the assertion
that validates the harness is absent by checking not just for the
TTSIntelligibilityHarness symbol but for multiple symbols from the TTS export
surface to ensure the entire optional surface is properly blocked when the [tts]
extra is unavailable.

Comment on lines +180 to +183
result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True, text=True,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether subprocess.run calls in this test file include an explicit timeout.
python - <<'PY'
import ast
from pathlib import Path

path = Path("test/unittests/test_tts_intelligibility.py")
tree = ast.parse(path.read_text())
for node in ast.walk(tree):
    if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
        if isinstance(node.func.value, ast.Name) and node.func.value.id == "subprocess" and node.func.attr == "run":
            has_timeout = any(k.arg == "timeout" for k in node.keywords if k.arg is not None)
            print(f"Line {node.lineno}: subprocess.run timeout={has_timeout}")
PY

Repository: OpenVoiceOS/ovoscope

Length of output: 103


Add a timeout to the subprocess invocation to prevent test hanging.

The subprocess.run() call at line 180 lacks a timeout parameter. If import resolution stalls, this test can block CI indefinitely.

Minimal fix
         result = subprocess.run(
             [sys.executable, "-c", code],
-            capture_output=True, text=True,
+            capture_output=True, text=True, timeout=30,
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True, text=True,
)
result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True, text=True, timeout=30,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/unittests/test_tts_intelligibility.py` around lines 180 - 183, Add a
timeout parameter to the subprocess.run() call in the test to prevent indefinite
blocking if import resolution stalls. Include the timeout argument alongside the
existing capture_output and text parameters in the subprocess.run() invocation
to ensure the test has a defined execution boundary and won't hang CI.

Source: Linters/SAST tools

@JarbasAl JarbasAl merged commit 536c932 into dev Jun 15, 2026
13 of 14 checks passed
@JarbasAl JarbasAl deleted the feat/tts-intelligibility branch June 15, 2026 20:50
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

I've gathered some intelligence on your latest changes. 🕵️‍♀️

I've aggregated the results of the automated checks for this PR below.

🔍 Lint

Beep boop! Standard processing sub-routine complete. 🦾

ruff: issues found — see job log

🔒 Security (pip-audit)

Looking for any Trojan horses in the dependencies. 🐎

✅ No known vulnerabilities found (72 packages scanned).

📋 Repo Health

I've checked the repo's social skills (aka issue response time). 🗣️

✅ All required files present.

Latest Version: 0.19.1a1

ovoscope/version.py — Version file
README.md — README
LICENSE — License file
pyproject.toml — pyproject.toml
⚠️ setup.py — setup.py
CHANGELOG.md — Changelog
ovoscope/version.py has valid version block markers

📊 Coverage

Measuring the safety net for your changes. 🥅

50.8% total coverage

⚠️ Some tests failed — coverage figures may be incomplete.

Files below 80% coverage (16 files)
File Coverage Missing lines
ovoscope/ocp.py 0.0% 83
ovoscope/simple_listener.py 0.0% 55
ovoscope/version.py 0.0% 5
ovoscope/intent_cases.py 14.0% 141
ovoscope/classic_listener.py 15.8% 117
ovoscope/voice_loop.py 18.8% 224
ovoscope/pipeline.py 31.5% 76
ovoscope/cli.py 32.8% 162
ovoscope/e2e.py 35.3% 97
ovoscope/listener.py 38.9% 162
ovoscope/pytest_plugin.py 41.7% 221
ovoscope/tts_intelligibility.py 54.1% 68
ovoscope/media.py 57.9% 90
ovoscope/__init__.py 59.1% 301
ovoscope/coverage.py 63.3% 69
ovoscope/audio.py 63.8% 111

Full report: download the coverage-report artifact.

🏷️ Release Preview

Here's what the next release might look like! 🚀

Current: 0.19.1a1Next: 0.20.0a1

Signal Value
Label (none)
PR title feat: TTS end-to-end intelligibility harness
Bump minor

✅ PR title follows conventional commit format.


🚀 Release Channel Compatibility

Predicted next version: 0.20.0a1

Channel Status Note Current Constraint
Stable Not in channel -
Testing Compatible ovoscope>=0.7.2,<1.0.0
Alpha Compatible ovoscope>=0.19.0a1

⚖️ License Check

Ensuring we're respecting the rights of others. 🤝

✅ No license violations found.

Policy: Apache 2.0 (universal donor). StrongCopyleft / NetworkCopyleft / WeakCopyleft / Other / Error categories fail. MPL allowed.

🔨 Build Tests

Structural analysis of your contribution is complete. 🔬

✅ All versions pass

Python Build Install Tests
3.10
3.11
3.12
3.13
3.14

Code quality is our top priority ✨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant