python-voiceio 0.9.7__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.7/python_voiceio.egg-info → python_voiceio-0.9.9}/PKG-INFO +1 -1
  2. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/pyproject.toml +1 -1
  3. {python_voiceio-0.9.7 → python_voiceio-0.9.9/python_voiceio.egg-info}/PKG-INFO +1 -1
  4. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_platform.py +31 -0
  5. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_postcorrect.py +8 -16
  6. python_voiceio-0.9.9/voiceio/__init__.py +1 -0
  7. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/platform.py +21 -6
  8. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/postcorrect.py +37 -29
  9. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tray/__init__.py +1 -1
  10. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tray/_pystray.py +1 -1
  11. python_voiceio-0.9.7/voiceio/__init__.py +0 -1
  12. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/LICENSE +0 -0
  13. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/README.md +0 -0
  14. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/python_voiceio.egg-info/SOURCES.txt +0 -0
  15. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/python_voiceio.egg-info/dependency_links.txt +0 -0
  16. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/python_voiceio.egg-info/entry_points.txt +0 -0
  17. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/python_voiceio.egg-info/requires.txt +0 -0
  18. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/python_voiceio.egg-info/top_level.txt +0 -0
  19. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/setup.cfg +0 -0
  20. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_adjudicate.py +0 -0
  21. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_app_wiring.py +0 -0
  22. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_audio_quality.py +0 -0
  23. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_audit.py +0 -0
  24. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_autocorrect.py +0 -0
  25. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_autocorrect_state.py +0 -0
  26. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_backend_probes.py +0 -0
  27. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_cli.py +0 -0
  28. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_clipboard_read.py +0 -0
  29. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_commands.py +0 -0
  30. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_concurrency_lockdown.py +0 -0
  31. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_config.py +0 -0
  32. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_correct_batch.py +0 -0
  33. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_corrections.py +0 -0
  34. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_evaluate.py +0 -0
  35. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_fallback.py +0 -0
  36. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_health.py +0 -0
  37. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_hints.py +0 -0
  38. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_history.py +0 -0
  39. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_ibus_pending.py +0 -0
  40. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_ibus_ping.py +0 -0
  41. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_ibus_typer.py +0 -0
  42. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_llm.py +0 -0
  43. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_llm_api.py +0 -0
  44. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_numbers.py +0 -0
  45. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_postprocess.py +0 -0
  46. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_prebuffer.py +0 -0
  47. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_prompt.py +0 -0
  48. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_recorder_integration.py +0 -0
  49. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_retention.py +0 -0
  50. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_robustness.py +0 -0
  51. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_security_hardening.py +0 -0
  52. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_snapshots.py +0 -0
  53. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_streaming.py +0 -0
  54. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_tokens.py +0 -0
  55. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_transcriber.py +0 -0
  56. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_tts.py +0 -0
  57. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_vad.py +0 -0
  58. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_vocabulary.py +0 -0
  59. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_wizard.py +0 -0
  60. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_wordfreq.py +0 -0
  61. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/__main__.py +0 -0
  62. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/app.py +0 -0
  63. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/audit.py +0 -0
  64. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/autocorrect.py +0 -0
  65. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/autocorrect_state.py +0 -0
  66. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/backends.py +0 -0
  67. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/cli.py +0 -0
  68. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/clipboard_read.py +0 -0
  69. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/commands.py +0 -0
  70. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/config.py +0 -0
  71. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/consent.py +0 -0
  72. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/corrections.py +0 -0
  73. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/demo.py +0 -0
  74. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/evaluate.py +0 -0
  75. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/feedback.py +0 -0
  76. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/health.py +0 -0
  77. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hints.py +0 -0
  78. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/history.py +0 -0
  79. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hotkeys/__init__.py +0 -0
  80. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hotkeys/base.py +0 -0
  81. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hotkeys/chain.py +0 -0
  82. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hotkeys/evdev.py +0 -0
  83. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hotkeys/pynput_backend.py +0 -0
  84. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hotkeys/socket_backend.py +0 -0
  85. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/ibus/__init__.py +0 -0
  86. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/ibus/engine.py +0 -0
  87. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/ibus/pending.py +0 -0
  88. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/llm.py +0 -0
  89. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/llm_api.py +0 -0
  90. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/models/__init__.py +0 -0
  91. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/models/silero_vad.onnx +0 -0
  92. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/numbers.py +0 -0
  93. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/pidlock.py +0 -0
  94. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/postprocess.py +0 -0
  95. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/prompt.py +0 -0
  96. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/recorder.py +0 -0
  97. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/retention.py +0 -0
  98. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/service.py +0 -0
  99. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/snapshots.py +0 -0
  100. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/sounds/__init__.py +0 -0
  101. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/sounds/commit.wav +0 -0
  102. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/sounds/start.wav +0 -0
  103. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/sounds/stop.wav +0 -0
  104. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/streaming.py +0 -0
  105. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tokens.py +0 -0
  106. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/transcriber.py +0 -0
  107. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tray/_icons.py +0 -0
  108. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tray/_indicator.py +0 -0
  109. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/__init__.py +0 -0
  110. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/base.py +0 -0
  111. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/chain.py +0 -0
  112. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/edge_engine.py +0 -0
  113. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/espeak.py +0 -0
  114. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/piper_engine.py +0 -0
  115. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/player.py +0 -0
  116. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/__init__.py +0 -0
  117. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/base.py +0 -0
  118. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/chain.py +0 -0
  119. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/clipboard.py +0 -0
  120. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/ibus.py +0 -0
  121. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/pynput_type.py +0 -0
  122. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/wtype.py +0 -0
  123. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/xdotool.py +0 -0
  124. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/ydotool.py +0 -0
  125. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/vad.py +0 -0
  126. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/vocab_stats.py +0 -0
  127. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/vocabulary.py +0 -0
  128. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/wizard.py +0 -0
  129. {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/wordfreq.py +0 -0
  130. {python_voiceio-0.9.7 → 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.7
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.7"
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.7
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
@@ -72,3 +72,34 @@ def test_platform_frozen():
72
72
  import pytest
73
73
  with pytest.raises(AttributeError):
74
74
  p.os = "darwin"
75
+
76
+
77
+ class TestOpenInTerminal:
78
+ """The tray menu launches print-and-exit commands (history/doctor/logs);
79
+ hold=True must keep the window open instead of flashing it shut."""
80
+
81
+ def _capture(self, monkeypatch, *, hold):
82
+ import voiceio.platform as plat
83
+ monkeypatch.setattr(plat, "_detect_os", lambda: "linux")
84
+ monkeypatch.setenv("TERMINAL", "myterm")
85
+ monkeypatch.setattr(
86
+ plat.shutil, "which",
87
+ lambda b: "/usr/bin/myterm" if b == "myterm" else None,
88
+ )
89
+ calls = []
90
+ with patch("subprocess.Popen", side_effect=lambda args, *a, **k: calls.append(args)):
91
+ ok = plat.open_in_terminal(["voiceio", "history"], hold=hold)
92
+ assert ok is True
93
+ return calls[0]
94
+
95
+ def test_hold_wraps_in_shell_that_pauses(self, monkeypatch):
96
+ args = self._capture(monkeypatch, hold=True)
97
+ assert args[:2] == ["myterm", "-e"]
98
+ assert "sh" in args and "-c" in args
99
+ script = args[-1]
100
+ assert "voiceio history" in script
101
+ assert "read" in script # pauses so the window survives
102
+
103
+ def test_no_hold_runs_command_directly(self, monkeypatch):
104
+ args = self._capture(monkeypatch, hold=False)
105
+ assert args == ["myterm", "-e", "voiceio", "history"]
@@ -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"
@@ -311,8 +311,14 @@ def system_deps_install_cmd(keys: list[str] | None = None) -> str:
311
311
  return f"{prefix} {' '.join(pkgs)}"
312
312
 
313
313
 
314
- def open_in_terminal(cmd: list[str]) -> bool:
315
- """Launch a command in the user's terminal emulator. Returns success."""
314
+ def open_in_terminal(cmd: list[str], *, hold: bool = False) -> bool:
315
+ """Launch a command in the user's terminal emulator. Returns success.
316
+
317
+ ``hold=True`` keeps the window open after the command exits — required for
318
+ print-and-exit commands (history, doctor, logs) that would otherwise flash
319
+ a terminal that closes the instant they finish.
320
+ """
321
+ import shlex
316
322
  import subprocess
317
323
 
318
324
  plat_os = _detect_os()
@@ -334,11 +340,20 @@ def open_in_terminal(cmd: list[str]) -> bool:
334
340
  log.warning("Failed to open cmd.exe")
335
341
  return False
336
342
 
337
- # Linux: try common terminal emulators
343
+ # Linux: try common terminal emulators. When holding, wrap the command in a
344
+ # shell that pauses at the end so the window survives a print-and-exit.
345
+ launch = cmd
346
+ if hold:
347
+ inner = " ".join(shlex.quote(c) for c in cmd)
348
+ launch = [
349
+ "sh", "-c",
350
+ f'{inner}; printf "\\n[voiceio] Press Enter to close…"; read _',
351
+ ]
352
+
338
353
  term = os.environ.get("TERMINAL")
339
354
  if term and shutil.which(term):
340
355
  try:
341
- subprocess.Popen([term, "-e", *cmd])
356
+ subprocess.Popen([term, "-e", *launch])
342
357
  return True
343
358
  except OSError:
344
359
  pass
@@ -346,7 +361,7 @@ def open_in_terminal(cmd: list[str]) -> bool:
346
361
  # x-terminal-emulator (Debian/Ubuntu alternative)
347
362
  if shutil.which("x-terminal-emulator"):
348
363
  try:
349
- subprocess.Popen(["x-terminal-emulator", "-e", *cmd])
364
+ subprocess.Popen(["x-terminal-emulator", "-e", *launch])
350
365
  return True
351
366
  except OSError:
352
367
  pass
@@ -365,7 +380,7 @@ def open_in_terminal(cmd: list[str]) -> bool:
365
380
  for prefix, binary in _TERMINALS:
366
381
  if shutil.which(binary):
367
382
  try:
368
- subprocess.Popen([*prefix, *cmd])
383
+ subprocess.Popen([*prefix, *launch])
369
384
  return True
370
385
  except OSError:
371
386
  continue
@@ -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. "
@@ -59,18 +58,27 @@ _SYSTEM_PROMPT = (
59
58
  # Disfluency mode: also strip spoken filler, delete-only. The strict rules
60
59
  # mirror the guards — the model is told exactly what the diff check enforces.
61
60
  _SYSTEM_PROMPT_CLEAN = (
62
- "You convert dictated speech into clean written text. The user dictates "
63
- "about software engineering and their projects. Do exactly two things:\n"
61
+ "You convert dictated speech into clean, concise written text. The user "
62
+ "dictates about software engineering and their projects, and wants it to "
63
+ "read like something they wrote, not spoke. Do exactly two things:\n"
64
64
  "1. Fix words the recognizer misheard (wrong proper nouns, homophones, "
65
65
  "garbled technical terms).\n"
66
- "2. Remove speech disfluencies: filler sounds (um, uh, er); filler uses of "
67
- "'like', 'you know', 'I mean'; false starts and self-corrections (keep the "
68
- "corrected version); and stray word repetitions.\n"
69
- "STRICT RULES: Only DELETE disfluencies and FIX misheard words. NEVER add "
70
- "words. NEVER rephrase, reword, reorder, or summarize. NEVER drop real "
71
- "content, meaningful hedges, or negations. If unsure whether something is a "
72
- "disfluency, KEEP it. Preserve the speaker's own wording and punctuation. "
73
- "Return only the cleaned text, nothing else."
66
+ "2. Cut spoken disfluency so it reads tight and clear:\n"
67
+ " - filler sounds (um, uh, er);\n"
68
+ " - filler discourse markers with no content: leading 'so', 'yeah', "
69
+ "'okay', 'well', 'now', 'right', and 'you know', 'I mean', 'like', 'sort "
70
+ "of'/'kind of' when used as filler, 'or something'/'or whatever', a "
71
+ "trailing 'right?' tag;\n"
72
+ " - false starts and self-corrections (keep only the corrected version);\n"
73
+ " - stray word and phrase repetitions.\n"
74
+ "Lean toward the briefer version — remove hesitation and padding freely.\n"
75
+ "HARD LIMITS (these protect meaning): only DELETE and FIX — NEVER add "
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."
74
82
  )
75
83
 
76
84
  _MAX_RECENT = 3
@@ -307,11 +315,13 @@ class PostCorrector:
307
315
  def _guard(self, text: str, corrected: str) -> tuple[bool, str]:
308
316
  """Decide whether the LLM's edit is within bounds.
309
317
 
310
- Returns (accept, reject_reason). In disfluency mode the promise is
311
- "never change meaning", enforced on the word-level diff: zero
312
- insertions (nothing added/rephrased), few replacements (ASR fixes, not
313
- rewording), bounded deletions (can't strip real content). Otherwise the
314
- 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.
315
325
  """
316
326
  aw, bw = text.split(), corrected.split()
317
327
  if not self._remove_disfluencies:
@@ -322,18 +332,16 @@ class PostCorrector:
322
332
  return False, "editratio"
323
333
  return True, ""
324
334
 
335
+ na, nb = _norm_words(text), _norm_words(corrected)
336
+ aw, bw = na, nb
325
337
  inserted = replaced = deleted = 0
326
338
  for tag, i1, i2, j1, j2 in difflib.SequenceMatcher(a=aw, b=bw).get_opcodes():
327
339
  if tag == "insert":
328
340
  inserted += j2 - j1
329
341
  elif tag == "replace":
330
342
  replaced += max(i2 - i1, j2 - j1)
331
- if any(_is_meaning_critical(w) for w in aw[i1:i2]):
332
- return False, "negation" # e.g. "not" → something else
333
343
  elif tag == "delete":
334
344
  deleted += i2 - i1
335
- if any(_is_meaning_critical(w) for w in aw[i1:i2]):
336
- return False, "negation" # dropping "not" inverts meaning
337
345
  n = len(aw)
338
346
  if inserted > _MAX_INSERTED_WORDS:
339
347
  return False, "inserted" # added content — meaning changed
@@ -146,7 +146,7 @@ def _read_stdout(proc: subprocess.Popen, toggle_cb: Callable[[], None]) -> None:
146
146
  action = cmd[5:]
147
147
  cli_cmd = _MENU_COMMANDS.get(action)
148
148
  if cli_cmd:
149
- if not open_in_terminal(cli_cmd):
149
+ if not open_in_terminal(cli_cmd, hold=True):
150
150
  log.warning("Failed to open terminal for: %s", action)
151
151
  except (OSError, ValueError):
152
152
  pass
@@ -45,7 +45,7 @@ def start(
45
45
  from voiceio.platform import open_in_terminal
46
46
  cli_cmd = _MENU_COMMANDS.get(action)
47
47
  if cli_cmd:
48
- open_in_terminal(cli_cmd)
48
+ open_in_terminal(cli_cmd, hold=True)
49
49
  return _handler
50
50
 
51
51
  _icon = pystray.Icon(
@@ -1 +0,0 @@
1
- __version__ = "0.9.7"
File without changes
File without changes
File without changes