From 80e9b40bd67c7246b80e7d497de37d1d164eb679 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 15:17:27 +0530 Subject: [PATCH 01/19] chore: ignore .worktrees/ directory Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 89e8747..152c570 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ htmlcov/ # pixi environments .pixi/* !.pixi/config.toml + +# Git worktrees +.worktrees/ From 7d57020c9140c934c3b7d8dcbe943910ecae5183 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 15:32:06 +0530 Subject: [PATCH 02/19] fix: toggle_plugin writes TOML not JSON (IC-001) Co-Authored-By: Claude Sonnet 4.6 --- sidecar/tests/test_vault_brain.py | 18 ++++++++++++++++++ sidecar/vault_brain.py | 5 +++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/sidecar/tests/test_vault_brain.py b/sidecar/tests/test_vault_brain.py index 85015b3..f77a636 100644 --- a/sidecar/tests/test_vault_brain.py +++ b/sidecar/tests/test_vault_brain.py @@ -248,6 +248,24 @@ async def dummy_cmd(): pass mock_plugin_instance.on_load.assert_called_once() + @pytest.mark.asyncio + async def test_toggle_plugin_writes_valid_toml(self, valid_vault, mock_ws_server): + """toggle_plugin must write TOML, not JSON, to .vault.toml.""" + import tomllib + + brain = VaultBrain(valid_vault, mock_ws_server) + await brain.initialize() + + await brain.toggle_plugin(plugin_id="demo_plugin", enabled=True) + + config_path = valid_vault / ".vault.toml" + # If this raises, the file was written as JSON (corruption bug) + with open(config_path, "rb") as f: + parsed = tomllib.load(f) + + assert parsed["plugins"]["demo_plugin"]["enabled"] is True + + @pytest.mark.unit class TestCommandRegistry: """Test command registry functionality.""" diff --git a/sidecar/vault_brain.py b/sidecar/vault_brain.py index ff3f9f9..3402931 100644 --- a/sidecar/vault_brain.py +++ b/sidecar/vault_brain.py @@ -1080,8 +1080,9 @@ async def toggle_plugin( config["plugins"][plugin_id] = {"enabled": enabled} # Write back - with open(config_path, "w", encoding="utf-8") as f: - json.dump(config, f, indent=4) + import tomli_w + with open(config_path, "wb") as f: + tomli_w.dump(config, f) # Update in-memory config self.config = config From 78c7fa4dfa97ef09f63573c7e37e12d18076f934 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 15:33:59 +0530 Subject: [PATCH 03/19] docs: mark IC-001 resolved --- docs/system/INCONSISTENCIES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/system/INCONSISTENCIES.md b/docs/system/INCONSISTENCIES.md index 52c8944..11d0f19 100644 --- a/docs/system/INCONSISTENCIES.md +++ b/docs/system/INCONSISTENCIES.md @@ -6,7 +6,8 @@ Surfaced during codebase audit (Feb 2026). Add new findings here; mark resolved ## Critical -### IC-001 — Config write uses JSON instead of TOML +### ~~IC-001 — Config write uses JSON instead of TOML~~ +**Resolved:** `fix: toggle_plugin writes TOML not JSON (IC-001)` — commit 7d57020 **Component**: Python Sidecar **File**: `sidecar/vault_brain.py:1084` **Severity**: Critical — causes data corruption From 56bf684dd40bb6bcbb1e4a24329a5c31f6eb1a21 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 15:39:10 +0530 Subject: [PATCH 04/19] fix: move tomli_w to top-level import (IC-001 followup) Co-Authored-By: Claude Sonnet 4.6 --- sidecar/vault_brain.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sidecar/vault_brain.py b/sidecar/vault_brain.py index 3402931..6e7390d 100644 --- a/sidecar/vault_brain.py +++ b/sidecar/vault_brain.py @@ -8,6 +8,7 @@ import asyncio import json import tomllib +import tomli_w import importlib.util import time import inspect @@ -601,7 +602,6 @@ async def set_model_category( # Save to config try: - import tomli_w config_path = utils.get_vault_config_path(self.vault_path) config = self._load_config() @@ -1080,7 +1080,6 @@ async def toggle_plugin( config["plugins"][plugin_id] = {"enabled": enabled} # Write back - import tomli_w with open(config_path, "wb") as f: tomli_w.dump(config, f) From 494c5a6ab6a80dcc210d644f54717fa99a466e46 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 15:44:02 +0530 Subject: [PATCH 05/19] fix: VaultBrain singleton init guard always-False bug (IC-002) Replace self._initialized = False / if self._initialized pattern with hasattr check so the guard actually fires on re-initialization. Also adds reset_singleton fixture to TestIntegration to prevent cross-test singleton leakage now that the guard is effective. Co-Authored-By: Claude Sonnet 4.6 --- sidecar/tests/test_integration.py | 7 +++++++ sidecar/tests/test_vault_brain.py | 18 ++++++++++++++++++ sidecar/vault_brain.py | 3 +-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/sidecar/tests/test_integration.py b/sidecar/tests/test_integration.py index f5189d3..0dc14bb 100644 --- a/sidecar/tests/test_integration.py +++ b/sidecar/tests/test_integration.py @@ -75,6 +75,13 @@ def integration_vault(tmp_path): @pytest.mark.asyncio class TestIntegration: + @pytest.fixture(autouse=True) + def reset_singleton(self): + """Reset VaultBrain singleton before and after each test.""" + VaultBrain._instance = None + yield + VaultBrain._instance = None + async def test_plugin_lifecycle_and_execution( self, integration_vault, mock_ws_server ): diff --git a/sidecar/tests/test_vault_brain.py b/sidecar/tests/test_vault_brain.py index f77a636..b316d5a 100644 --- a/sidecar/tests/test_vault_brain.py +++ b/sidecar/tests/test_vault_brain.py @@ -266,6 +266,24 @@ async def test_toggle_plugin_writes_valid_toml(self, valid_vault, mock_ws_server assert parsed["plugins"]["demo_plugin"]["enabled"] is True + def test_singleton_reinit_is_guarded(self, valid_vault, mock_ws_server, tmp_path): + """Calling VaultBrain.__init__ a second time must be a no-op.""" + brain = VaultBrain(valid_vault, mock_ws_server) + original_path = brain.vault_path + + # Create a second distinct vault to pass to the re-init attempt + other_vault = tmp_path / "other_vault" + other_vault.mkdir() + (other_vault / ".vault.toml").write_text("") + + # Simulate __init__ being called again on the same instance + brain.__init__(other_vault, mock_ws_server) + + # vault_path must be unchanged — re-init was a no-op + assert brain.vault_path == original_path + assert brain._initialized is True + + @pytest.mark.unit class TestCommandRegistry: """Test command registry functionality.""" diff --git a/sidecar/vault_brain.py b/sidecar/vault_brain.py index 6e7390d..01027d0 100644 --- a/sidecar/vault_brain.py +++ b/sidecar/vault_brain.py @@ -69,8 +69,7 @@ def __init__(self, vault_path: Path, ws_server: Any): Note: Heavy initialization happens in self.initialize() """ # Prevent re-initialization if already initialized - self._initialized: bool = False - if self._initialized: + if hasattr(self, "_initialized") and self._initialized: return self.vault_path = utils.validate_vault_path(vault_path) From cbb2bd704c0ae52a165d51ddc29e571c9a9306b7 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 15:44:22 +0530 Subject: [PATCH 06/19] docs: mark IC-002 resolved --- docs/system/INCONSISTENCIES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/system/INCONSISTENCIES.md b/docs/system/INCONSISTENCIES.md index 11d0f19..372b022 100644 --- a/docs/system/INCONSISTENCIES.md +++ b/docs/system/INCONSISTENCIES.md @@ -24,12 +24,14 @@ with open(config_path, "wb") as f: ## High -### IC-002 — VaultBrain singleton init check is backwards +### ~~IC-002 — VaultBrain singleton init check is backwards~~ **Component**: Python Sidecar **File**: `sidecar/vault_brain.py:70-73` **Severity**: High — silent logic error -`_initialized` is set to `False` immediately before the guard check, so the guard never fires. Should use `hasattr(self, '_initialized') and self._initialized`. +~~`_initialized` is set to `False` immediately before the guard check, so the guard never fires. Should use `hasattr(self, '_initialized') and self._initialized`.~~ + +**Resolved**: commit `494c5a6` — replaced always-False pattern with `hasattr` check; added `reset_singleton` fixture to `TestIntegration` to prevent cross-test leakage. ### IC-003 — Inconsistent parameter format between layers **Component**: Cross-layer (Frontend → Sidecar) From eb1f29742f16023a7726e19807ec8f6b40003171 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 15:50:11 +0530 Subject: [PATCH 07/19] fix: register_command raises on duplicate without override=True (IC-006) Co-Authored-By: Claude Sonnet 4.6 --- sidecar/tests/test_vault_brain.py | 32 +++++++++++++++++++++++++++++++ sidecar/vault_brain.py | 7 ++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/sidecar/tests/test_vault_brain.py b/sidecar/tests/test_vault_brain.py index b316d5a..0f9dab9 100644 --- a/sidecar/tests/test_vault_brain.py +++ b/sidecar/tests/test_vault_brain.py @@ -284,6 +284,38 @@ def test_singleton_reinit_is_guarded(self, valid_vault, mock_ws_server, tmp_path assert brain._initialized is True + + @pytest.mark.asyncio + async def test_register_command_no_override_raises(self, valid_vault, mock_ws_server): + """Registering a duplicate command with override=False must raise.""" + brain = VaultBrain(valid_vault, mock_ws_server) + await brain.initialize() + + async def handler_a(**kwargs): return {} + async def handler_b(**kwargs): return {} + + brain.register_command("test.cmd", handler_a) + + with pytest.raises(exceptions.CommandRegistrationError): + brain.register_command("test.cmd", handler_b, override=False) + + assert brain.commands["test.cmd"]["handler"] is handler_a + + @pytest.mark.asyncio + async def test_register_command_with_override_succeeds(self, valid_vault, mock_ws_server): + """Registering a duplicate command with override=True must succeed.""" + brain = VaultBrain(valid_vault, mock_ws_server) + await brain.initialize() + + async def handler_a(**kwargs): return {} + async def handler_b(**kwargs): return {} + + brain.register_command("test.cmd", handler_a) + brain.register_command("test.cmd", handler_b, override=True) + + assert brain.commands["test.cmd"]["handler"] is handler_b + + @pytest.mark.unit class TestCommandRegistry: """Test command registry functionality.""" diff --git a/sidecar/vault_brain.py b/sidecar/vault_brain.py index 01027d0..8af987c 100644 --- a/sidecar/vault_brain.py +++ b/sidecar/vault_brain.py @@ -375,9 +375,10 @@ def register_command( if command_id in self.commands: if not override: - logger.warning(f"Overwriting command '{command_id}'") - else: - logger.debug(f"Overriding command '{command_id}'") + raise exceptions.CommandRegistrationError( + command_id, "Command already registered. Use override=True to replace it." + ) + logger.debug(f"Overriding command '{command_id}'") self.commands[command_id] = { "handler": handler, From 0f7e771911542e4f724ff9b566734bc5c84ca941 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 15:50:27 +0530 Subject: [PATCH 08/19] docs: mark IC-006 resolved --- docs/system/INCONSISTENCIES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/system/INCONSISTENCIES.md b/docs/system/INCONSISTENCIES.md index 372b022..c0d3a9d 100644 --- a/docs/system/INCONSISTENCIES.md +++ b/docs/system/INCONSISTENCIES.md @@ -60,12 +60,14 @@ Affected handlers: `handle_chat` (line 452), `store_api_key` (line 495), `chat_s Code comments mention "list style legacy" plugin config format, but if `plugins.plugin_name` is a list, it falls back to `{}` (empty dict) instead of migrating or logging. Old-format vaults silently lose plugin config. -### IC-006 — `register_command` override logic is inverted +### ~~IC-006 — `register_command` override logic is inverted~~ **Component**: Python Sidecar **File**: `sidecar/vault_brain.py:363-387` **Severity**: Medium — confusing API -If a command already exists and `override=False`, the code logs a warning but still overwrites. The `override` flag only controls log level, not behavior. Should either raise on non-override collision or actually skip the overwrite. +~~If a command already exists and `override=False`, the code logs a warning but still overwrites. The `override` flag only controls log level, not behavior. Should either raise on non-override collision or actually skip the overwrite.~~ + +**Resolved**: `register_command` now raises `CommandRegistrationError` when `override=False` and the command already exists. Commit: eb1f297 ### IC-007 — Frontend state is module-local, no centralized store **Component**: Frontend From ae7fabd8f4ac15dc285906ab2d1edf76e0f2000c Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 15:52:06 +0530 Subject: [PATCH 09/19] fix: warn on malformed plugin config instead of silent discard (IC-005) Co-Authored-By: Claude Sonnet 4.6 --- sidecar/tests/test_vault_brain.py | 25 +++++++++++++++++++++++++ sidecar/vault_brain.py | 5 ++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/sidecar/tests/test_vault_brain.py b/sidecar/tests/test_vault_brain.py index 0f9dab9..4afcfba 100644 --- a/sidecar/tests/test_vault_brain.py +++ b/sidecar/tests/test_vault_brain.py @@ -316,6 +316,31 @@ async def handler_b(**kwargs): return {} assert brain.commands["test.cmd"]["handler"] is handler_b + + @pytest.mark.asyncio + async def test_legacy_plugin_config_logs_warning(self, valid_vault, mock_ws_server): + """Non-dict plugin config entry must log a warning, not silently discard.""" + (valid_vault / ".vault.toml").write_text( + '[plugins]\nexplorer = ["legacy", "list"]\n' + ) + # Create a minimal plugin directory so the loader reaches the config check + plugin_dir = valid_vault / "plugins" / "explorer" + plugin_dir.mkdir(parents=True) + (plugin_dir / "main.py").write_text( + """from sidecar.api.plugin_base import PluginBase +class Plugin(PluginBase): + def register_commands(self): pass +""" + ) + brain = VaultBrain(valid_vault, mock_ws_server) + + with patch("sidecar.vault_brain.logger") as mock_logger: + await brain.initialize() + warning_calls = [str(c) for c in mock_logger.warning.call_args_list] + assert any("explorer" in w for w in warning_calls), ( + "Expected a warning about malformed plugin config for 'explorer'" + ) + @pytest.mark.unit class TestCommandRegistry: """Test command registry functionality.""" diff --git a/sidecar/vault_brain.py b/sidecar/vault_brain.py index 8af987c..e727360 100644 --- a/sidecar/vault_brain.py +++ b/sidecar/vault_brain.py @@ -273,7 +273,10 @@ def _load_plugins(self) -> None: overrides = vault_apps_config.get(plugin_name, {}) if not isinstance(overrides, dict): - # Fallback if config is malformed or just a list + logger.warning( + f"Plugin config for '{plugin_name}' in .vault.toml is not a dict " + f"(got {type(overrides).__name__}). Ignoring overrides — update your .vault.toml." + ) overrides = {} # 3. Merge Configs (Override > Default) From b6219c867bbb6a8e98c306ef858b608e9c805abc Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 15:52:19 +0530 Subject: [PATCH 10/19] docs: mark IC-005 resolved --- docs/system/INCONSISTENCIES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/system/INCONSISTENCIES.md b/docs/system/INCONSISTENCIES.md index c0d3a9d..38c162e 100644 --- a/docs/system/INCONSISTENCIES.md +++ b/docs/system/INCONSISTENCIES.md @@ -53,12 +53,14 @@ Affected handlers: `handle_chat` (line 452), `store_api_key` (line 495), `chat_s `EventBus` is initialized and stored in app state but event routing happens directly via Tauri's `Emitter` trait in `ipc_router.rs`. Almost all methods in `event_bus.rs` are marked `#[allow(dead_code)]`. Either wire it up or remove it. -### IC-005 — Plugin config legacy format fallback is incomplete +### ~~IC-005 — Plugin config legacy format fallback is incomplete~~ **Component**: Python Sidecar **File**: `sidecar/vault_brain.py:268-278` **Severity**: Medium — silent data loss -Code comments mention "list style legacy" plugin config format, but if `plugins.plugin_name` is a list, it falls back to `{}` (empty dict) instead of migrating or logging. Old-format vaults silently lose plugin config. +~~Code comments mention "list style legacy" plugin config format, but if `plugins.plugin_name` is a list, it falls back to `{}` (empty dict) instead of migrating or logging. Old-format vaults silently lose plugin config.~~ + +**Resolved**: Non-dict plugin config now logs a `logger.warning` with the plugin name and actual type before falling back to `{}`. Commit: ae7fabd ### ~~IC-006 — `register_command` override logic is inverted~~ **Component**: Python Sidecar From 9593be9c0c9431a13f7b7b3f6419fc1fc09796f3 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 15:54:13 +0530 Subject: [PATCH 11/19] fix: remove nested params fallback, enforce flat kwargs contract (IC-003) Co-Authored-By: Claude Sonnet 4.6 --- sidecar/vault_brain.py | 86 ------------------------------------------ 1 file changed, 86 deletions(-) diff --git a/sidecar/vault_brain.py b/sidecar/vault_brain.py index e727360..8149538 100644 --- a/sidecar/vault_brain.py +++ b/sidecar/vault_brain.py @@ -461,11 +461,6 @@ async def handle_chat( **kwargs, ) -> Dict[str, Any]: if self.pipeline: - # Check kwargs for chat_id if not passed directly (legacy frontend support) - if chat_id == "default": - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - chat_id = p.get("chat_id", chat_id) ctx = await self.pipeline.run( message=message, history=history or [], metadata={"chat_id": chat_id} @@ -495,12 +490,6 @@ async def store_api_key( self, provider: str = "", api_key: str = "", **kwargs ) -> Dict[str, Any]: """Store an API key in secure OS storage.""" - # Handle nested params - if not provider: - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - provider = p.get("provider", provider) - api_key = p.get("api_key", api_key) if not provider or not api_key: return {"status": "error", "error": "provider and api_key are required"} @@ -521,11 +510,6 @@ async def store_api_key( @command("settings.delete_api_key", constants.CORE_PLUGIN_NAME) async def delete_api_key(self, provider: str = "", **kwargs) -> Dict[str, Any]: """Delete an API key from secure storage.""" - if not provider: - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - provider = p.get("provider", provider) - if not provider: return {"status": "error", "error": "provider is required"} @@ -540,11 +524,6 @@ async def list_providers(self, **kwargs) -> Dict[str, Any]: @command("settings.verify_api_key", constants.CORE_PLUGIN_NAME) async def verify_api_key(self, provider: str = "", **kwargs) -> Dict[str, Any]: """Verify an API key by making a test request.""" - if not provider: - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - provider = p.get("provider", provider) - if not provider: return {"status": "error", "error": "provider is required"} @@ -590,12 +569,6 @@ async def set_model_category( self, category: str = "", model: str = "", **kwargs ) -> Dict[str, Any]: """Set the model for a category and save to .vault.toml.""" - # Handle nested params - if not category: - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - category = p.get("category", category) - model = p.get("model", model) if not category or not model: return {"status": "error", "error": "category and model are required"} @@ -649,11 +622,6 @@ async def detect_ollama(self, **kwargs) -> Dict[str, Any]: @command("settings.get_model_info", constants.CORE_PLUGIN_NAME) async def get_model_info(self, model_id: str = "", **kwargs) -> Dict[str, Any]: """Get detailed information about a specific model including pricing and specs.""" - # Handle nested params - if not model_id: - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - model_id = p.get("model_id", model_id) if not model_id: return {"status": "error", "error": "model_id is required"} @@ -681,13 +649,6 @@ async def chat_set_model( model_id: Specific model ID (e.g., 'openai/gpt-4o') - takes precedence category: Category name (e.g., 'thinking') - used if model_id not provided """ - # Handle nested params - if not chat_id: - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - chat_id = p.get("chat_id", chat_id) - model_id = p.get("model_id", model_id) - category = p.get("category", category) if not chat_id: return {"status": "error", "error": "chat_id is required"} @@ -761,16 +722,6 @@ async def chat_send( stream_id: Optional ID to identify this stream (for multiple concurrent streams) chat_id: Optional chat ID for memory persistence """ - # Handle nested params - if not message: - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - message = p.get("message", message) - history = p.get("history", history) - category = p.get("category", category) - stream = p.get("stream", stream) - stream_id = p.get("stream_id", stream_id) - chat_id = p.get("chat_id", chat_id) if not message: return {"status": "error", "error": "message is required"} @@ -963,15 +914,6 @@ async def _stream_chat_response( async def install_plugin( self, download_url: str = "", repo_url: str = "", plugin_id: str = "", **kwargs ) -> Dict[str, Any]: - # Backward compatibility: Check if args are inside 'p' or 'params' dict in kwargs - # This handles cases where frontend sends { "p": { ... } } - if not plugin_id: - # Try to find in 'p' - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - download_url = p.get("download_url", download_url) - repo_url = p.get("repo_url", repo_url) - plugin_id = p.get("plugin_id", plugin_id) if not plugin_id: return {"status": "error", "error": "plugin_id is required"} @@ -995,12 +937,6 @@ async def install_plugin( @command("plugins.update", constants.CORE_PLUGIN_NAME) async def update_plugin(self, plugin_id: str = "", **kwargs) -> Dict[str, Any]: - if not plugin_id: - # check 'p' - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - plugin_id = p.get("plugin_id", plugin_id) - if not plugin_id: return {"status": "error", "error": "plugin_id is required"} @@ -1013,12 +949,6 @@ async def update_plugin(self, plugin_id: str = "", **kwargs) -> Dict[str, Any]: @command("plugins.uninstall", constants.CORE_PLUGIN_NAME) async def uninstall_plugin(self, plugin_id: str = "", **kwargs) -> Dict[str, Any]: - if not plugin_id: - # check 'p' - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - plugin_id = p.get("plugin_id", plugin_id) - if not plugin_id: return {"status": "error", "error": "plugin_id is required"} @@ -1053,12 +983,6 @@ async def toggle_plugin( self, plugin_id: str = "", enabled: bool = True, **kwargs ) -> Dict[str, Any]: """Toggle plugin enabled state in .vault.toml.""" - # Handle nested params - if not plugin_id: - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - plugin_id = p.get("plugin_id", plugin_id) - enabled = p.get("enabled", enabled) if not plugin_id: return {"status": "error", "error": "plugin_id is required"} @@ -1104,11 +1028,6 @@ async def toggle_plugin( @command("system.unload_plugin", constants.CORE_PLUGIN_NAME) async def unload_plugin(self, plugin_id: str = "", **kwargs) -> Dict[str, Any]: """Unload a specific plugin from memory.""" - if not plugin_id: - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - plugin_id = p.get("plugin_id", plugin_id) - if not plugin_id: return {"status": "error", "error": "plugin_id is required"} @@ -1151,11 +1070,6 @@ async def unload_plugin(self, plugin_id: str = "", **kwargs) -> Dict[str, Any]: @command("system.reload_plugin", constants.CORE_PLUGIN_NAME) async def reload_plugin(self, plugin_id: str = "", **kwargs) -> Dict[str, Any]: """Reload a specific plugin from disk.""" - if not plugin_id: - p = kwargs.get("p") or kwargs.get("params") - if isinstance(p, dict): - plugin_id = p.get("plugin_id", plugin_id) - if not plugin_id: return {"status": "error", "error": "plugin_id is required"} From f3892edafdc20697dc2b26a6359aaa0ff0b1b655 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 15:55:39 +0530 Subject: [PATCH 12/19] docs: mark IC-003 resolved --- docs/system/INCONSISTENCIES.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/system/INCONSISTENCIES.md b/docs/system/INCONSISTENCIES.md index 38c162e..5aab165 100644 --- a/docs/system/INCONSISTENCIES.md +++ b/docs/system/INCONSISTENCIES.md @@ -33,14 +33,16 @@ with open(config_path, "wb") as f: **Resolved**: commit `494c5a6` — replaced always-False pattern with `hasattr` check; added `reset_singleton` fixture to `TestIntegration` to prevent cross-test leakage. -### IC-003 — Inconsistent parameter format between layers +### ~~IC-003 — Inconsistent parameter format between layers~~ **Component**: Cross-layer (Frontend → Sidecar) **Files**: `sidecar/vault_brain.py` (multiple), `src/vault/connection.js` **Severity**: High — implicit coupling, fragile -Some command handlers look for params nested under `p` or `params` key (`kwargs.get("p") or kwargs.get("params")`), while others expect flat kwargs. The frontend sends flat params. Works via fallback, but the contract is implicit and breaks silently when a new command is added. +~~Some command handlers look for params nested under `p` or `params` key (`kwargs.get("p") or kwargs.get("params")`), while others expect flat kwargs. The frontend sends flat params. Works via fallback, but the contract is implicit and breaks silently when a new command is added.~~ -Affected handlers: `handle_chat` (line 452), `store_api_key` (line 495), `chat_send` (line 686), `toggle_plugin` (line 1057). +~~Affected handlers: `handle_chat` (line 452), `store_api_key` (line 495), `chat_send` (line 686), `toggle_plugin` (line 1057).~~ + +**Resolved**: All 14 `kwargs.get("p") or kwargs.get("params")` fallback blocks removed from `vault_brain.py`. All handlers now require flat kwargs exclusively. Commit: 9593be9 --- From d5d5d6c896006b5c8bdbf05ee0f3e3907c1a6cc0 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 16:04:02 +0530 Subject: [PATCH 13/19] fix: remove unimplemented stub Tauri commands (IC-008) Co-Authored-By: Claude Sonnet 4.6 --- src-tauri/src/ipc_router.rs | 12 ------------ src-tauri/src/main.rs | 2 -- 2 files changed, 14 deletions(-) diff --git a/src-tauri/src/ipc_router.rs b/src-tauri/src/ipc_router.rs index 5d44f69..18b374a 100644 --- a/src-tauri/src/ipc_router.rs +++ b/src-tauri/src/ipc_router.rs @@ -399,18 +399,6 @@ async fn register_vault_in_registry( Ok(()) } -/// Search plugins in the community store -#[tauri::command] -pub async fn search_plugins(_query: String, _category: Option) -> Result, String> { - Ok(vec![]) -} - -/// Get plugin details -#[tauri::command] -pub async fn get_plugin_details(_plugin_id: String) -> Result { - Err("Plugin details not yet implemented".to_string()) -} - /// Install plugin to vault #[tauri::command] pub async fn install_plugin( diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 439041e..a8bc14c 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -51,8 +51,6 @@ fn main() { ipc_router::get_vault_info, ipc_router::update_plugin_config, ipc_router::create_vault, - ipc_router::search_plugins, - ipc_router::get_plugin_details, ipc_router::install_plugin, ipc_router::get_installed_plugins, ipc_router::get_global_settings, From dc9d9860c2daaabf9fdff895fa59864621b95a53 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 16:04:25 +0530 Subject: [PATCH 14/19] docs: mark IC-008 resolved --- docs/system/INCONSISTENCIES.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/system/INCONSISTENCIES.md b/docs/system/INCONSISTENCIES.md index 5aab165..8d23899 100644 --- a/docs/system/INCONSISTENCIES.md +++ b/docs/system/INCONSISTENCIES.md @@ -84,12 +84,14 @@ Each module holds its own state (`conversationHistory`, `isWaitingForResponse`, ## Low -### IC-008 — Stub commands not implemented -**Component**: Rust Backend -**File**: `src-tauri/src/ipc_router.rs:404-411` -**Severity**: Low — misleading surface area +### ~~IC-008 — Stub commands not implemented~~ +~~**Component**: Rust Backend~~ +~~**File**: `src-tauri/src/ipc_router.rs:404-411`~~ +~~**Severity**: Low — misleading surface area~~ + +~~`search_plugins()` returns empty vec. `get_plugin_details()` returns `NotImplemented` error. Should be removed from the IPC surface until implemented.~~ -`search_plugins()` returns empty vec. `get_plugin_details()` returns `NotImplemented` error. Should be removed from the IPC surface until implemented. +**Resolved**: Removed both stub commands in commit d5d5d6c. ### IC-009 — Event type names are inconsistent across layers **Component**: Cross-layer From 77a1ba9996faf66f3260ac9e67348b2ec20f0127 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 16:05:10 +0530 Subject: [PATCH 15/19] fix: remove unused dead_code methods in Rust backend (IC-011) Co-Authored-By: Claude Sonnet 4.6 --- src-tauri/src/sidecar_manager.rs | 5 ----- src-tauri/src/window_manager.rs | 1 - 2 files changed, 6 deletions(-) diff --git a/src-tauri/src/sidecar_manager.rs b/src-tauri/src/sidecar_manager.rs index 3ca0cd2..c92c92e 100644 --- a/src-tauri/src/sidecar_manager.rs +++ b/src-tauri/src/sidecar_manager.rs @@ -164,11 +164,6 @@ impl SidecarManager { } /// Check if sidecar is still running - #[allow(dead_code)] - pub async fn is_running(&self, window_label: &str) -> bool { - self.processes.lock().await.contains_key(window_label) - } - /// Allocate next available port by actually checking port availability async fn allocate_port(&self) -> u16 { let mut port = self.next_port.lock().await; diff --git a/src-tauri/src/window_manager.rs b/src-tauri/src/window_manager.rs index 9e39e45..40cb669 100644 --- a/src-tauri/src/window_manager.rs +++ b/src-tauri/src/window_manager.rs @@ -60,7 +60,6 @@ impl WindowManager { } /// Get all active window labels - #[allow(dead_code)] pub fn get_active_windows(&self) -> Vec { self.windows.keys().cloned().collect() } From bc5b0d92913b669890ef3aa81645f709610c3439 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 16:05:31 +0530 Subject: [PATCH 16/19] docs: mark IC-011 resolved --- docs/system/INCONSISTENCIES.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/system/INCONSISTENCIES.md b/docs/system/INCONSISTENCIES.md index 8d23899..bfdd09e 100644 --- a/docs/system/INCONSISTENCIES.md +++ b/docs/system/INCONSISTENCIES.md @@ -111,12 +111,14 @@ No unified constant. Should centralize at least the cross-process names. Default settings are embedded as a `serde_json::json!` macro literal. A `settings.toml` exists at the project root but is not consistently used as the source of truth. -### IC-011 — Unused Rust methods with dead_code suppression -**Component**: Rust Backend -**Files**: `sidecar_manager.rs:167`, `window_manager.rs:63`, `event_bus.rs` (pervasive) -**Severity**: Low — noise +### ~~IC-011 — Unused Rust methods with dead_code suppression~~ +~~**Component**: Rust Backend~~ +~~**Files**: `sidecar_manager.rs:167`, `window_manager.rs:63`, `event_bus.rs` (pervasive)~~ +~~**Severity**: Low — noise~~ + +~~`is_running()`, `get_active_windows()`, and most of `EventBus` are suppressed with `#[allow(dead_code)]`. Either exercise them or remove them.~~ -`is_running()`, `get_active_windows()`, and most of `EventBus` are suppressed with `#[allow(dead_code)]`. Either exercise them or remove them. +**Resolved**: Removed `is_running()` entirely; removed `#[allow(dead_code)]` from `get_active_windows()` (it is exercised by a unit test). Commit 77a1ba9. ### IC-012 — Missing error handling in chat history fetch **Component**: Frontend From fe3c205a39c91cfd918a1415c5d9b9604c7c43f9 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 16:08:01 +0530 Subject: [PATCH 17/19] fix: remove orphaned doc comment after is_running deletion --- src-tauri/src/sidecar_manager.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src-tauri/src/sidecar_manager.rs b/src-tauri/src/sidecar_manager.rs index c92c92e..0ab869e 100644 --- a/src-tauri/src/sidecar_manager.rs +++ b/src-tauri/src/sidecar_manager.rs @@ -163,7 +163,6 @@ impl SidecarManager { .map(|p| p.ws_port) } - /// Check if sidecar is still running /// Allocate next available port by actually checking port availability async fn allocate_port(&self) -> u16 { let mut port = self.next_port.lock().await; From 06ed8c438d52eb67b307fcdbb81129a36a0f49d1 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 16:09:22 +0530 Subject: [PATCH 18/19] fix: guard against missing res.result in loadHistory (IC-012) Co-Authored-By: Claude Sonnet 4.6 --- src/vault/chat/chat.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vault/chat/chat.js b/src/vault/chat/chat.js index 38b9086..588a615 100644 --- a/src/vault/chat/chat.js +++ b/src/vault/chat/chat.js @@ -124,7 +124,12 @@ async function loadHistory(chatId) { window.activeChatId = chatId; } - const result = res.result || {}; + if (!res || res.error || !res.result) { + console.warn('[Chat] Failed to load history:', res?.error || 'No result'); + setStatus('Ready'); + return; + } + const result = res.result; if (result.status === 'success') { conversationHistory = result.history || []; From 38ebab9fb6266beffd58a526b7616976b18f1894 Mon Sep 17 00:00:00 2001 From: ARC345 Date: Sat, 28 Feb 2026 16:09:46 +0530 Subject: [PATCH 19/19] docs: mark IC-012 resolved --- docs/system/INCONSISTENCIES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/system/INCONSISTENCIES.md b/docs/system/INCONSISTENCIES.md index bfdd09e..72aca25 100644 --- a/docs/system/INCONSISTENCIES.md +++ b/docs/system/INCONSISTENCIES.md @@ -120,12 +120,14 @@ Default settings are embedded as a `serde_json::json!` macro literal. A `setting **Resolved**: Removed `is_running()` entirely; removed `#[allow(dead_code)]` from `get_active_windows()` (it is exercised by a unit test). Commit 77a1ba9. -### IC-012 — Missing error handling in chat history fetch +### ~~IC-012 — Missing error handling in chat history fetch~~ **Component**: Frontend **File**: `src/vault/chat/chat.js:117-150` **Severity**: Low -`loadHistory()` accesses `res.result` without checking if the response is an error. Malformed or error responses will fail silently or throw uncaught exceptions. +~~`loadHistory()` accesses `res.result` without checking if the response is an error. Malformed or error responses will fail silently or throw uncaught exceptions.~~ + +**Resolved**: Added explicit guard (`if (!res || res.error || !res.result)`) before accessing `res.result` in `loadHistory()`; logs a warning and returns early on error responses. Commit 06ed8c4. ### IC-013 — Streaming stream_id contract is undocumented **Component**: Cross-layer