feat(identity): rename user-visible identity to voculinux with one-time legacy import
The fork takes its own name everywhere a user can see it: binaries (voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus namespace (com.voculinux.Voculinux, no alias for the old name), config/data/cache dirs, single-instance lock, IBus engine identity, tray icons, resource prefixes, VOCULINUX_* environment names, and the update-checker user agent. The internal Python module stays vocalinux and the distribution metadata keeps the upstream URLs as provenance. On first run a previous Vocalinux install's config.json, custom dictionary, and portal restore token are copied once into the new locations (new config always wins, no merge, no write-back); models re-resolve through the pinned-checksum download path. An offline naming-sweep test proves no old-name strings remain on user-visible surfaces outside the allowlist. Refs #18
This commit is contained in:
@@ -91,10 +91,10 @@ def test_terms_are_live_reloaded_and_scanner_friendly(tmp_path: Path, monkeypatc
|
||||
"""The UTF-8 line file accepts comments and applies an external replacement."""
|
||||
manager = manager_at(tmp_path, monkeypatch, FakeConfig({"dictionary": {"enabled": True}}))
|
||||
terms_file = tmp_path / TERMS_FILENAME
|
||||
terms_file.write_text("# scanner comment\nVocaLinux\nvocalinux\nSupabase\n", encoding="utf-8")
|
||||
terms_file.write_text("# scanner comment\nVoculinux\nvoculinux\nSupabase\n", encoding="utf-8")
|
||||
|
||||
assert manager.get_terms() == ["VocaLinux", "Supabase"]
|
||||
assert manager.build_initial_prompt() == "VocaLinux Supabase"
|
||||
assert manager.get_terms() == ["Voculinux", "Supabase"]
|
||||
assert manager.build_initial_prompt() == "Voculinux Supabase"
|
||||
|
||||
terms_file.write_text("PyGObject\n", encoding="utf-8")
|
||||
assert manager.build_initial_prompt() == "PyGObject"
|
||||
@@ -105,8 +105,8 @@ def test_terms_save_is_atomic_shape_and_transient_override_is_not_written(
|
||||
) -> None:
|
||||
"""UI saves standard terms safely while CLI terms remain session-only."""
|
||||
manager = manager_at(tmp_path, monkeypatch)
|
||||
assert manager.save_terms(["VocaLinux", "vocalinux", "# ignored", " PyGObject "])
|
||||
assert (tmp_path / TERMS_FILENAME).read_text(encoding="utf-8") == "VocaLinux\nPyGObject\n"
|
||||
assert manager.save_terms(["Voculinux", "voculinux", "# ignored", " PyGObject "])
|
||||
assert (tmp_path / TERMS_FILENAME).read_text(encoding="utf-8") == "Voculinux\nPyGObject\n"
|
||||
|
||||
override = tmp_path / "override.txt"
|
||||
override.write_text("Override\n", encoding="utf-8")
|
||||
@@ -122,15 +122,15 @@ def test_incremental_term_edits_preserve_comments_and_blank_lines(
|
||||
"""GTK add/remove operations retain unrelated scanner-managed source lines."""
|
||||
manager = manager_at(tmp_path, monkeypatch)
|
||||
terms_file = tmp_path / TERMS_FILENAME
|
||||
terms_file.write_text("# scanner note\n\nVocaLinux\n# retain me\nPyGObject\n", encoding="utf-8")
|
||||
terms_file.write_text("# scanner note\n\nVoculinux\n# retain me\nPyGObject\n", encoding="utf-8")
|
||||
|
||||
assert manager.add_term("Supabase")
|
||||
assert terms_file.read_text(encoding="utf-8") == (
|
||||
"# scanner note\n\nVocaLinux\n# retain me\nPyGObject\nSupabase\n"
|
||||
"# scanner note\n\nVoculinux\n# retain me\nPyGObject\nSupabase\n"
|
||||
)
|
||||
assert manager.remove_term("PyGObject")
|
||||
assert terms_file.read_text(encoding="utf-8") == (
|
||||
"# scanner note\n\nVocaLinux\n# retain me\nSupabase\n"
|
||||
"# scanner note\n\nVoculinux\n# retain me\nSupabase\n"
|
||||
)
|
||||
|
||||
|
||||
@@ -278,7 +278,7 @@ def test_pr_767_dictionary_configuration_keys_and_contract_are_preserved(
|
||||
) -> None:
|
||||
"""Existing enabled, file_path, and max_words settings remain effective."""
|
||||
configured_path = tmp_path / TERMS_FILENAME
|
||||
configured_path.write_text("VocaLinux\nPyGObject\n", encoding="utf-8")
|
||||
configured_path.write_text("Voculinux\nPyGObject\n", encoding="utf-8")
|
||||
config = FakeConfig(
|
||||
{
|
||||
"dictionary": {
|
||||
@@ -294,7 +294,7 @@ def test_pr_767_dictionary_configuration_keys_and_contract_are_preserved(
|
||||
assert Path(DEFAULT_TERMS_PATH).name == TERMS_FILENAME
|
||||
assert manager.terms_enabled()
|
||||
assert manager.terms_path() == configured_path
|
||||
assert manager.build_initial_prompt() == "VocaLinux"
|
||||
assert manager.build_initial_prompt() == "Voculinux"
|
||||
|
||||
|
||||
def test_default_terms_path_follows_xdg_config_home(tmp_path: Path, monkeypatch) -> None:
|
||||
@@ -306,10 +306,10 @@ def test_default_terms_path_follows_xdg_config_home(tmp_path: Path, monkeypatch)
|
||||
assert manager.corrections_path() == tmp_path / CORRECTIONS_FILENAME
|
||||
|
||||
|
||||
def test_legacy_vocalinux_terms_root_keeps_corrections_at_config_dir(
|
||||
def test_legacy_voculinux_terms_root_keeps_corrections_at_config_dir(
|
||||
tmp_path: Path, monkeypatch
|
||||
) -> None:
|
||||
"""Persisted ~/.config/vocalinux terms do not move the corrections file."""
|
||||
"""Legacy ~/.config/vocalinux terms do not move the corrections file."""
|
||||
monkeypatch.setattr("vocalinux.custom_dictionary.config_dir", lambda: str(tmp_path))
|
||||
legacy_corrections = Path.home() / ".config" / "vocalinux" / CORRECTIONS_FILENAME
|
||||
expanded_legacy = str(Path.home() / ".config" / "vocalinux" / TERMS_FILENAME)
|
||||
@@ -434,7 +434,7 @@ def test_corrections_fallback_survives_copy_failure(tmp_path: Path, monkeypatch)
|
||||
def test_corrections_stay_at_config_dir_with_arbitrary_terms_parent(
|
||||
tmp_path: Path, monkeypatch
|
||||
) -> None:
|
||||
"""Corrections ignore the terms file location outside a vocalinux directory."""
|
||||
"""Corrections ignore the terms file location outside a voculinux directory."""
|
||||
xdg_dir = tmp_path / "xdg-config"
|
||||
xdg_dir.mkdir()
|
||||
monkeypatch.setattr("vocalinux.custom_dictionary.config_dir", lambda: str(xdg_dir))
|
||||
@@ -568,7 +568,7 @@ def test_invalid_or_unreadable_configured_terms_path_is_not_persisted(
|
||||
config = FakeConfig({"dictionary": {"file_path": previous_path}})
|
||||
manager = manager_at(tmp_path, monkeypatch, config)
|
||||
|
||||
assert not manager.set_terms_path("~vocalinux-user-does-not-exist/dictionary.txt")
|
||||
assert not manager.set_terms_path("~voculinux-user-does-not-exist/dictionary.txt")
|
||||
assert config.get("dictionary", "file_path") == previous_path
|
||||
|
||||
assert not manager.set_terms_path(str(tmp_path))
|
||||
@@ -581,7 +581,7 @@ def test_invalid_saved_terms_path_is_safe_to_read_and_report(tmp_path: Path, mon
|
||||
{
|
||||
"dictionary": {
|
||||
"enabled": True,
|
||||
"file_path": "~vocalinux-user-does-not-exist/dictionary.txt",
|
||||
"file_path": "~voculinux-user-does-not-exist/dictionary.txt",
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -609,7 +609,7 @@ def test_terms_path_save_failure_preserves_the_previous_configured_path(
|
||||
def test_invalid_transient_terms_path_does_not_crash(tmp_path: Path, monkeypatch) -> None:
|
||||
"""An unresolved CLI tilde path disables only its session's terms safely."""
|
||||
manager_at(tmp_path, monkeypatch)
|
||||
manager = CustomDictionaryManager(FakeConfig(), "~vocalinux-user-does-not-exist/dictionary.txt")
|
||||
manager = CustomDictionaryManager(FakeConfig(), "~voculinux-user-does-not-exist/dictionary.txt")
|
||||
|
||||
assert manager.terms_enabled()
|
||||
assert manager.terms_path() is None
|
||||
@@ -638,7 +638,7 @@ def test_file_edits_preserve_existing_permissions(tmp_path: Path, monkeypatch) -
|
||||
manager = manager_at(tmp_path, monkeypatch)
|
||||
|
||||
terms_file = tmp_path / TERMS_FILENAME
|
||||
terms_file.write_text("VocaLinux\n", encoding="utf-8")
|
||||
terms_file.write_text("Voculinux\n", encoding="utf-8")
|
||||
terms_file.chmod(0o600)
|
||||
assert manager.save_terms(["Supabase"])
|
||||
assert stat.S_IMODE(terms_file.stat().st_mode) == 0o600
|
||||
@@ -652,7 +652,7 @@ def test_file_edits_preserve_existing_permissions(tmp_path: Path, monkeypatch) -
|
||||
|
||||
def test_dictionary_file_argument_is_available_for_a_session_override(monkeypatch) -> None:
|
||||
"""The retained CLI option accepts a terms file without changing config."""
|
||||
monkeypatch.setattr("sys.argv", ["vocalinux", "--dictionary-file", "/tmp/terms.txt"])
|
||||
monkeypatch.setattr("sys.argv", ["voculinux", "--dictionary-file", "/tmp/terms.txt"])
|
||||
|
||||
assert parse_arguments().dictionary_file == "/tmp/terms.txt"
|
||||
|
||||
@@ -961,7 +961,7 @@ def test_invalid_terms_file_is_ignored_and_explained(tmp_path: Path, monkeypatch
|
||||
def test_whisper_receives_the_live_custom_terms_prompt(tmp_path: Path, monkeypatch) -> None:
|
||||
"""Whisper gets the current file contents as initial_prompt at transcription time."""
|
||||
dictionary = manager_at(tmp_path, monkeypatch, FakeConfig({"dictionary": {"enabled": True}}))
|
||||
(tmp_path / TERMS_FILENAME).write_text("VocaLinux\n", encoding="utf-8")
|
||||
(tmp_path / TERMS_FILENAME).write_text("Voculinux\n", encoding="utf-8")
|
||||
manager = recognition_manager("whisper", dictionary)
|
||||
manager.model = MagicMock()
|
||||
manager.model.transcribe.return_value = {"text": "ok"}
|
||||
@@ -970,13 +970,13 @@ def test_whisper_receives_the_live_custom_terms_prompt(tmp_path: Path, monkeypat
|
||||
with patch.dict("sys.modules", {"numpy": mock_numpy(), "torch": MagicMock()}):
|
||||
assert manager._transcribe_with_whisper([b"\x00\x00"]) == "ok"
|
||||
|
||||
assert manager.model.transcribe.call_args.kwargs["initial_prompt"] == "VocaLinux"
|
||||
assert manager.model.transcribe.call_args.kwargs["initial_prompt"] == "Voculinux"
|
||||
|
||||
|
||||
def test_faster_whisper_receives_the_live_custom_terms_prompt(tmp_path: Path, monkeypatch) -> None:
|
||||
"""Faster Whisper receives the same live vocabulary bias as OpenAI Whisper."""
|
||||
dictionary = manager_at(tmp_path, monkeypatch, FakeConfig({"dictionary": {"enabled": True}}))
|
||||
(tmp_path / TERMS_FILENAME).write_text("VocaLinux\n", encoding="utf-8")
|
||||
(tmp_path / TERMS_FILENAME).write_text("Voculinux\n", encoding="utf-8")
|
||||
manager = object.__new__(SpeechRecognitionManager)
|
||||
manager.dictionary_manager = dictionary
|
||||
manager._faster_whisper_engine = MagicMock()
|
||||
@@ -985,7 +985,7 @@ def test_faster_whisper_receives_the_live_custom_terms_prompt(tmp_path: Path, mo
|
||||
|
||||
assert manager._transcribe_with_faster_whisper([b"\x00\x00"]) == "ok"
|
||||
manager._faster_whisper_engine.transcribe.assert_called_once_with(
|
||||
[b"\x00\x00"], language="auto", initial_prompt="VocaLinux"
|
||||
[b"\x00\x00"], language="auto", initial_prompt="Voculinux"
|
||||
)
|
||||
|
||||
|
||||
@@ -994,7 +994,7 @@ def test_whispercpp_composes_and_clears_live_terms_prompt(tmp_path: Path, monkey
|
||||
config = FakeConfig({"dictionary": {"enabled": True}})
|
||||
dictionary = manager_at(tmp_path, monkeypatch, config)
|
||||
terms_file = tmp_path / TERMS_FILENAME
|
||||
terms_file.write_text("VocaLinux\n", encoding="utf-8")
|
||||
terms_file.write_text("Voculinux\n", encoding="utf-8")
|
||||
manager = recognition_manager("whisper_cpp", dictionary, advanced_prompt="Explicit context")
|
||||
manager._model_lock = threading.Lock()
|
||||
manager.model = MagicMock()
|
||||
@@ -1008,7 +1008,7 @@ def test_whispercpp_composes_and_clears_live_terms_prompt(tmp_path: Path, monkey
|
||||
assert manager._transcribe_with_whispercpp([b"\x00\x00"]) == "ok"
|
||||
|
||||
prompts = [call.kwargs["initial_prompt"] for call in manager.model.transcribe.call_args_list]
|
||||
assert prompts == ["Explicit context VocaLinux", "Explicit context", ""]
|
||||
assert prompts == ["Explicit context Voculinux", "Explicit context", ""]
|
||||
|
||||
|
||||
def test_vosk_warns_once_for_bias_while_corrections_remain_available(
|
||||
@@ -1016,7 +1016,7 @@ def test_vosk_warns_once_for_bias_while_corrections_remain_available(
|
||||
) -> None:
|
||||
"""VOSK rejects vocabulary bias only; its transcript can still be corrected."""
|
||||
dictionary = manager_at(tmp_path, monkeypatch, FakeConfig({"dictionary": {"enabled": True}}))
|
||||
(tmp_path / TERMS_FILENAME).write_text("VocaLinux\n", encoding="utf-8")
|
||||
(tmp_path / TERMS_FILENAME).write_text("Voculinux\n", encoding="utf-8")
|
||||
manager = recognition_manager("vosk", dictionary)
|
||||
manager._get_vosk_model_path = MagicMock(return_value="/models/vosk")
|
||||
vosk = MagicMock()
|
||||
@@ -1041,11 +1041,11 @@ def test_whispercpp_prompt_composes_advanced_and_terms() -> None:
|
||||
recognition_manager = object.__new__(SpeechRecognitionManager)
|
||||
recognition_manager.whispercpp_initial_prompt = "Write product names accurately."
|
||||
recognition_manager.dictionary_manager = type(
|
||||
"Dictionary", (), {"build_initial_prompt": lambda self: "VocaLinux"}
|
||||
"Dictionary", (), {"build_initial_prompt": lambda self: "Voculinux"}
|
||||
)()
|
||||
|
||||
assert (
|
||||
recognition_manager._get_whispercpp_prompt() == "Write product names accurately. VocaLinux"
|
||||
recognition_manager._get_whispercpp_prompt() == "Write product names accurately. Voculinux"
|
||||
)
|
||||
recognition_manager.dictionary_manager = type(
|
||||
"Dictionary", (), {"build_initial_prompt": lambda self: None}
|
||||
|
||||
Reference in New Issue
Block a user