python-voiceio 0.9.8__tar.gz → 0.9.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. {python_voiceio-0.9.8/python_voiceio.egg-info → python_voiceio-0.9.9}/PKG-INFO +1 -1
  2. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/pyproject.toml +1 -1
  3. {python_voiceio-0.9.8 → python_voiceio-0.9.9/python_voiceio.egg-info}/PKG-INFO +1 -1
  4. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_postcorrect.py +8 -16
  5. python_voiceio-0.9.9/voiceio/__init__.py +1 -0
  6. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/postcorrect.py +24 -23
  7. python_voiceio-0.9.8/voiceio/__init__.py +0 -1
  8. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/LICENSE +0 -0
  9. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/README.md +0 -0
  10. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/python_voiceio.egg-info/SOURCES.txt +0 -0
  11. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/python_voiceio.egg-info/dependency_links.txt +0 -0
  12. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/python_voiceio.egg-info/entry_points.txt +0 -0
  13. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/python_voiceio.egg-info/requires.txt +0 -0
  14. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/python_voiceio.egg-info/top_level.txt +0 -0
  15. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/setup.cfg +0 -0
  16. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_adjudicate.py +0 -0
  17. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_app_wiring.py +0 -0
  18. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_audio_quality.py +0 -0
  19. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_audit.py +0 -0
  20. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_autocorrect.py +0 -0
  21. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_autocorrect_state.py +0 -0
  22. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_backend_probes.py +0 -0
  23. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_cli.py +0 -0
  24. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_clipboard_read.py +0 -0
  25. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_commands.py +0 -0
  26. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_concurrency_lockdown.py +0 -0
  27. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_config.py +0 -0
  28. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_correct_batch.py +0 -0
  29. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_corrections.py +0 -0
  30. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_evaluate.py +0 -0
  31. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_fallback.py +0 -0
  32. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_health.py +0 -0
  33. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_hints.py +0 -0
  34. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_history.py +0 -0
  35. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_ibus_pending.py +0 -0
  36. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_ibus_ping.py +0 -0
  37. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_ibus_typer.py +0 -0
  38. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_llm.py +0 -0
  39. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_llm_api.py +0 -0
  40. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_numbers.py +0 -0
  41. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_platform.py +0 -0
  42. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_postprocess.py +0 -0
  43. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_prebuffer.py +0 -0
  44. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_prompt.py +0 -0
  45. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_recorder_integration.py +0 -0
  46. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_retention.py +0 -0
  47. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_robustness.py +0 -0
  48. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_security_hardening.py +0 -0
  49. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_snapshots.py +0 -0
  50. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_streaming.py +0 -0
  51. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_tokens.py +0 -0
  52. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_transcriber.py +0 -0
  53. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_tts.py +0 -0
  54. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_vad.py +0 -0
  55. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_vocabulary.py +0 -0
  56. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_wizard.py +0 -0
  57. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/tests/test_wordfreq.py +0 -0
  58. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/__main__.py +0 -0
  59. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/app.py +0 -0
  60. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/audit.py +0 -0
  61. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/autocorrect.py +0 -0
  62. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/autocorrect_state.py +0 -0
  63. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/backends.py +0 -0
  64. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/cli.py +0 -0
  65. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/clipboard_read.py +0 -0
  66. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/commands.py +0 -0
  67. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/config.py +0 -0
  68. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/consent.py +0 -0
  69. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/corrections.py +0 -0
  70. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/demo.py +0 -0
  71. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/evaluate.py +0 -0
  72. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/feedback.py +0 -0
  73. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/health.py +0 -0
  74. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/hints.py +0 -0
  75. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/history.py +0 -0
  76. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/hotkeys/__init__.py +0 -0
  77. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/hotkeys/base.py +0 -0
  78. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/hotkeys/chain.py +0 -0
  79. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/hotkeys/evdev.py +0 -0
  80. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/hotkeys/pynput_backend.py +0 -0
  81. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/hotkeys/socket_backend.py +0 -0
  82. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/ibus/__init__.py +0 -0
  83. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/ibus/engine.py +0 -0
  84. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/ibus/pending.py +0 -0
  85. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/llm.py +0 -0
  86. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/llm_api.py +0 -0
  87. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/models/__init__.py +0 -0
  88. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/models/silero_vad.onnx +0 -0
  89. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/numbers.py +0 -0
  90. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/pidlock.py +0 -0
  91. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/platform.py +0 -0
  92. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/postprocess.py +0 -0
  93. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/prompt.py +0 -0
  94. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/recorder.py +0 -0
  95. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/retention.py +0 -0
  96. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/service.py +0 -0
  97. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/snapshots.py +0 -0
  98. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/sounds/__init__.py +0 -0
  99. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/sounds/commit.wav +0 -0
  100. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/sounds/start.wav +0 -0
  101. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/sounds/stop.wav +0 -0
  102. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/streaming.py +0 -0
  103. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/tokens.py +0 -0
  104. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/transcriber.py +0 -0
  105. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/tray/__init__.py +0 -0
  106. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/tray/_icons.py +0 -0
  107. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/tray/_indicator.py +0 -0
  108. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/tray/_pystray.py +0 -0
  109. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/tts/__init__.py +0 -0
  110. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/tts/base.py +0 -0
  111. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/tts/chain.py +0 -0
  112. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/tts/edge_engine.py +0 -0
  113. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/tts/espeak.py +0 -0
  114. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/tts/piper_engine.py +0 -0
  115. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/tts/player.py +0 -0
  116. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/typers/__init__.py +0 -0
  117. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/typers/base.py +0 -0
  118. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/typers/chain.py +0 -0
  119. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/typers/clipboard.py +0 -0
  120. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/typers/ibus.py +0 -0
  121. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/typers/pynput_type.py +0 -0
  122. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/typers/wtype.py +0 -0
  123. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/typers/xdotool.py +0 -0
  124. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/typers/ydotool.py +0 -0
  125. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/vad.py +0 -0
  126. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/vocab_stats.py +0 -0
  127. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/vocabulary.py +0 -0
  128. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/wizard.py +0 -0
  129. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/wordfreq.py +0 -0
  130. {python_voiceio-0.9.8 → python_voiceio-0.9.9}/voiceio/worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 0.9.8
3
+ Version: 0.9.9
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-voiceio"
7
- version = "0.9.8"
7
+ version = "0.9.9"
8
8
  description = "Voice dictation for Linux. Speak → text, locally, instantly."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 0.9.8
3
+ Version: 0.9.9
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -121,23 +121,15 @@ def test_disfluency_mode_rejects_rewording():
121
121
  assert pc.correct(original) == original
122
122
 
123
123
 
124
- def test_disfluency_mode_rejects_dropped_negation():
125
- """Dropping 'not' is one deletion under every fraction cap but inverts
126
- meaning the negation guard must reject it."""
124
+ def test_disfluency_mode_allows_trailing_shrug_with_negation():
125
+ """No mechanical negation guard the LLM decides. Dropping a trailing
126
+ 'I don't know' shrug (plus other filler) is accepted; the model, not a
127
+ regex, is trusted to avoid genuine polarity flips (see the prompt)."""
127
128
  pc = PostCorrector(_cfg(remove_disfluencies=True))
128
- original = "I do not want the feature to ship today at all"
129
- inverted = "I do want the feature to ship today at all"
130
- with patch("voiceio.llm_api.chat", return_value=inverted):
131
- assert pc.correct(original) == original
132
-
133
-
134
- def test_disfluency_mode_rejects_replaced_negation():
135
- """Replacing a negation with something else also inverts meaning."""
136
- pc = PostCorrector(_cfg(remove_disfluencies=True))
137
- original = "we should never deploy on a friday afternoon"
138
- changed = "we should always deploy on a friday afternoon"
139
- with patch("voiceio.llm_api.chat", return_value=changed):
140
- assert pc.correct(original) == original
129
+ original = "the build passed but I don't know we should still check it carefully today"
130
+ cleaned = "the build passed we should still check it carefully today"
131
+ with patch("voiceio.llm_api.chat", return_value=cleaned):
132
+ assert pc.correct(original) == cleaned
141
133
 
142
134
 
143
135
  def test_fix_mode_still_rejects_big_deletion():
@@ -0,0 +1 @@
1
+ __version__ = "0.9.9"
@@ -34,18 +34,17 @@ _MAX_INSERTED_WORDS = 0 # adding any word = altering meaning → reject
34
34
  _MAX_REPLACE_FRAC = 0.15 # ASR word-fixes only, never wholesale rewording
35
35
  _MAX_DELETE_FRAC = 0.4 # backstop against deleting real content
36
36
 
37
- # Words whose deletion/replacement flips meaning — the fraction caps can't catch
38
- # a single dropped "not". If the edit touches any of these on the original side,
39
- # reject outright. (Contractions ending in "n't" are handled separately.)
40
- _MEANING_CRITICAL = frozenset({
41
- "not", "no", "never", "none", "nor", "neither", "without", "cannot",
42
- "nothing", "nobody", "nowhere", "n't",
43
- })
44
37
 
38
+ def _norm_words(text: str) -> list[str]:
39
+ """Lowercased, punctuation-stripped word list for the guard's diff, so a
40
+ sentence split or capitalization isn't misread as a content change."""
41
+ out = []
42
+ for w in text.split():
43
+ t = w.lower().strip(".,;:!?\"'()[]{}—–-")
44
+ if t:
45
+ out.append(t)
46
+ return out
45
47
 
46
- def _is_meaning_critical(word: str) -> bool:
47
- w = word.lower().strip(".,;:!?\"'()")
48
- return w in _MEANING_CRITICAL or w.endswith("n't")
49
48
 
50
49
  _SYSTEM_PROMPT = (
51
50
  "You fix automatic speech recognition errors in dictated text. "
@@ -74,10 +73,12 @@ _SYSTEM_PROMPT_CLEAN = (
74
73
  " - stray word and phrase repetitions.\n"
75
74
  "Lean toward the briefer version — remove hesitation and padding freely.\n"
76
75
  "HARD LIMITS (these protect meaning): only DELETE and FIX — NEVER add "
77
- "words, rephrase, reword, reorder, or summarize. NEVER drop or alter actual "
78
- "content, a negation ('not', 'never', \"n't\"), or a hedge that changes "
79
- "certainty ('I think', 'maybe', 'probably', 'might'). Keep the speaker's own "
80
- "wording. Return only the cleaned text, nothing else."
76
+ "words, rephrase, reword, reorder, or summarize. NEVER change what a "
77
+ "statement asserts above all never flip its polarity (do NOT turn 'I "
78
+ "don't think we should ship' into 'we should ship'), and keep hedges that "
79
+ "carry real uncertainty about a claim ('maybe', 'probably'). A trailing "
80
+ "verbal shrug like 'I don't know' with nothing after it is filler you may "
81
+ "drop. Keep the speaker's own wording. Return only the cleaned text."
81
82
  )
82
83
 
83
84
  _MAX_RECENT = 3
@@ -314,11 +315,13 @@ class PostCorrector:
314
315
  def _guard(self, text: str, corrected: str) -> tuple[bool, str]:
315
316
  """Decide whether the LLM's edit is within bounds.
316
317
 
317
- Returns (accept, reject_reason). In disfluency mode the promise is
318
- "never change meaning", enforced on the word-level diff: zero
319
- insertions (nothing added/rephrased), few replacements (ASR fixes, not
320
- rewording), bounded deletions (can't strip real content). Otherwise the
321
- original conservative fix-only guards apply.
318
+ Returns (accept, reject_reason). In disfluency mode the LLM is trusted
319
+ to judge what's filler; the diff only backstops gross failure — nothing
320
+ ADDED (no rewrite/hallucination) and not more than a bounded fraction
321
+ deleted or word-swapped. Comparison is on normalized tokens (lowercased,
322
+ punctuation stripped) so cosmetic restructuring — a sentence split, a
323
+ capital letter, a comma — is never miscounted as rewording. Outside
324
+ disfluency mode the original conservative fix-only guards apply.
322
325
  """
323
326
  aw, bw = text.split(), corrected.split()
324
327
  if not self._remove_disfluencies:
@@ -329,18 +332,16 @@ class PostCorrector:
329
332
  return False, "editratio"
330
333
  return True, ""
331
334
 
335
+ na, nb = _norm_words(text), _norm_words(corrected)
336
+ aw, bw = na, nb
332
337
  inserted = replaced = deleted = 0
333
338
  for tag, i1, i2, j1, j2 in difflib.SequenceMatcher(a=aw, b=bw).get_opcodes():
334
339
  if tag == "insert":
335
340
  inserted += j2 - j1
336
341
  elif tag == "replace":
337
342
  replaced += max(i2 - i1, j2 - j1)
338
- if any(_is_meaning_critical(w) for w in aw[i1:i2]):
339
- return False, "negation" # e.g. "not" → something else
340
343
  elif tag == "delete":
341
344
  deleted += i2 - i1
342
- if any(_is_meaning_critical(w) for w in aw[i1:i2]):
343
- return False, "negation" # dropping "not" inverts meaning
344
345
  n = len(aw)
345
346
  if inserted > _MAX_INSERTED_WORDS:
346
347
  return False, "inserted" # added content — meaning changed
@@ -1 +0,0 @@
1
- __version__ = "0.9.8"
File without changes
File without changes
File without changes