Skip to content

Conversation

@Sahilbhatane
Copy link
Collaborator

@Sahilbhatane Sahilbhatane commented Nov 23, 2025

Summary

Implements interactive package conflict resolution UI and persistent conflict-resolution preferences.
  • Add interactive conflict-resolution UI to cortex/cli.py.
    • Prompts user to choose which package to keep/remove when conflicts are detected.
    • Offers option to save the chosen resolution for the conflicting package pair.
    • Uses saved preferences automatically when present.
  • Add ConflictSettings to cortex/user_preferences.py and persist conflicts.saved_resolutions.
  • Update unit tests: test/test_conflict_ui.py expanded to cover saving and auto-applying preferences.
  • Update .gitignore to exclude generated data/ files while keeping contributors.json.

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • file structure changes.

Checklist

  • Tests passing
  • Docs updated
  • Fixes Package Conflict Resolution UI #42
  • AI usage - opus 4.5 x Gemini 3 pro x GPT 5.1-codex-max (personal LLM council) for enhance, debug and test respectively

Testing

  • Unit tests:
    • python -m pytest test/test_conflict_ui.py — 5 tests passed (interactive resolution and saving behavior).
    • Full test suite (python test/run_all_tests.py) passes (developer tests continue to succeed).
  • End-user note:
    • CLI flows that rely on LLMs require valid API keys (e.g., OPENAI_API_KEY) and model access.
    • System-level package operations require Linux with package manager tools (apt/dpkg) to fully exercise dependency checks and conflict detection.

Summary by CodeRabbit

  • New Features

    • Interactive conflict-resolution during installs with saved-preference support and multi-package install handling.
    • Full user preferences system exposed via CLI (list/get/set/reset/export/import) with YAML persistence and JSON import/export.
  • Improvements

    • Refined LLM provider enable/disable and environment-fallback behavior.
    • Best-effort cross-platform config directory permission handling.
    • Expanded .gitignore for editor artifacts, user configs, and data files (contributors.json exempt).
  • Chores / Tests

    • Added dev tooling entries, extensive tests for conflict UI/preferences and integrations, and shortened CI pytest tracebacks.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings November 23, 2025 11:33
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 23, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds a YAML-backed user preferences system and CLI config command, integrates an interactive dependency-conflict resolution flow into install (with saved-preference support), introduces an _UNSET sentinel for LLM keys, adjusts platform/import behaviors, updates tests/CI and dev deps, and updates .gitignore.

Changes

Cohort / File(s) Summary
User preferences
cortex/user_preferences.py
New PreferencesManager, dataclass schema/enums, YAML persistence (atomic writes + backups), dot-notation access, validation, export/import, and CLI helpers.
CLI: conflict resolution & config
cortex/cli.py, tests/test_conflict_ui.py
Adds interactive conflict-resolution flow (_resolve_conflicts_interactive, _ask_save_preference, _get_prefs_manager), config command surface, multi-package install(...) signature, InstallationCancelledError, DependencyResolver integration, and tests for UI/preferences.
LLM routing
cortex/llm_router.py
Adds module sentinel _UNSET and updates LLMRouter.__init__ to distinguish “not provided” vs explicit None for API keys.
API key lookup & config manager
cortex/api_key_detector.py, cortex/config_manager.py
Conditional inclusion of CWD .env when under HOME and enabled via env var; config manager does best-effort chmod on non-POSIX and skips chown when unavailable.
Sandbox & parallel install
cortex/sandbox/sandbox_executor.py, cortex/install_parallel.py
Reorders resource/import checks; normalizes Python invocation to sys.executable (via shlex) for validation and subprocess execution.
Tests & CI
tests/*, .github/workflows/automation.yml, tests/integration/test_end_to_end.py
Adds conflict UI tests, expands hardware-detection mocks, disables semantic cache in some tests, updates CI pytest --tb=short, and adds APT_BUILD_DEPS to integration bootstrap.
Dev requirements
requirements-dev.txt
Adds test, linting, typing and docs tools (PyYAML, black, ruff, isort, pre-commit, pylint, mypy, bandit, safety, sphinx, sphinx-rtd-theme).
Repo metadata
.gitignore
Adds editor/IDE entries, .cortex/, *.yaml.bak, top-level ignores for data/*.json and data/*.csv with allowlist !data/contributors.json.
Minor formatting / cleanup
multiple cortex/*.py, tests
Various import cleanups, formatting, small behavioral tweaks (e.g., ollama check using shutil.which), and minor public constant additions/removals used by tests.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant CLI as CortexCLI
    participant DR as DependencyResolver
    participant PM as PreferencesManager
    participant UI as Conflict UI
    participant FS as FileSystem

    User->>CLI: cortex install pkgA pkgB
    CLI->>DR: request install plan
    DR->>DR: detect dependency conflicts
    alt conflicts found
        DR-->>CLI: conflicts list
        CLI->>PM: query saved preference
        alt saved preference exists
            PM-->>CLI: resolution
            CLI->>CLI: adjust plan
        else
            CLI->>UI: present choices
            UI->>User: prompt resolution
            User-->>UI: selects option
            UI-->>CLI: chosen resolution
            CLI->>User: ask to save preference?
            User-->>CLI: yes/no
            alt save
                CLI->>PM: save preference
                PM->>FS: write YAML (atomic + backup)
            end
            CLI->>CLI: adjust plan
        end
    else
        DR-->>CLI: plan OK
    end
    CLI-->>User: show final plan / execute
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

enhancement, MVP

Suggested reviewers

  • dhvll
  • Suyashd999
  • mikejmorgan-ai

"I hopped through code with whiskers bright,
I nudged your prefs and set them right.
When packages quarrelled in the night,
I chose the peace and saved your choice — delight! 🐇"

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR contains extensive changes beyond issue #42 requirements: numerous cosmetic formatting changes across 30+ files (benchmarks, gpu_manager, wifi_driver, etc.), removed unused imports, enum additions (UpdateChannel), and platform-specific adjustments. These are not directly required by the conflict-resolution issue. Remove out-of-scope changes: exclude formatting-only commits and unrelated file modifications. Focus the PR on files directly supporting issue #42 (cli.py, user_preferences.py, conflict UI tests, .gitignore) and defer cosmetic refactoring to a separate PR.
Description check ❓ Inconclusive The PR description covers the summary, type of change, checklist items, and testing notes. However, it does not follow the required template structure (missing explicit 'Related Issue' section, 'AI Disclosure' section, and proper checklist format). Reorganize the description to match the template: add 'Related Issue' section with 'Closes #42', add 'AI Disclosure' checkbox selection, and use template checklist format for clarity.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly describes the main feature addition: interactive package conflict resolution with saved preferences, and references the related issue #42.
Linked Issues check ✅ Passed The PR implements core requirements from issue #42: interactive conflict-resolution UI with user prompts, saved preferences via ConflictSettings, unit test coverage, and .gitignore updates. The changes align with the acceptance criteria including conflict detection, presentation, user selection, and preference persistence.
Docstring Coverage ✅ Passed Docstring coverage is 96.17% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements interactive package conflict resolution UI and persistent conflict-resolution preferences to address Issue #42. The PR includes significant additions to the codebase including comprehensive test coverage, documentation, and various automation scripts.

Key highlights:

  • Adds interactive conflict resolution functionality with saved preferences
  • Includes extensive test coverage (500+ lines of user preferences tests)
  • Updates import paths across multiple test files for proper package structure
  • Improves security with ReDoS-resistant regex patterns
  • Adds comprehensive documentation and automation scripts

Reviewed changes

Copilot reviewed 21 out of 60 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/test_user_preferences.py New comprehensive test suite (501 lines) for user preferences system with 50+ test cases
test/test_conflict_ui.py New test suite for interactive conflict resolution (76 lines, 5 tests)
test/test_logging_system.py Updated import paths to use cortex. package structure
test/test_llm_router.py Updated import paths and mock decorators for new structure
test/test_installation_verifier.py Updated import paths for proper package imports
test/test_installation_history.py Updated import paths for cortex package
test/test_error_parser.py Updated import paths to cortex namespace
test/test_context_memory.py Updated import paths with proper package structure
cortex/installation_history.py Security improvements: ReDoS-resistant regex, clarified MD5 usage for non-cryptographic IDs
cortex/dependency_resolver.py Security improvements: ReDoS-resistant regex patterns for version constraint removal
scripts/* Multiple automation scripts for GitHub PR management, contributor onboarding, and bounty tracking
docs/* Extensive new documentation including user guides, implementation summaries, API docs, and FAQs
data/contributors.json New empty contributors tracking file
bounties_pending.json, payments_history.json, pr_status.json, issue_status.json Removed tracking files (likely moved to gitignore)
Comments suppressed due to low confidence (8)

cortex/installation_history.py:177

  • Good security improvement: The regex pattern has been changed from \(.*?\) to \([^)]*\) to prevent ReDoS (Regular Expression Denial of Service) attacks. The new pattern avoids nested quantifiers and backtracking, making it safer. This is a security best practice.
    cortex/installation_history.py:256
  • Good security clarification: The comment correctly clarifies that MD5 is being used for non-cryptographic purposes (generating unique IDs), not for security. This is appropriate for this use case. MD5 is fast and sufficient for collision-resistant ID generation when security is not a concern.
    cortex/dependency_resolver.py:152
  • Good security improvement: Similar to the other file, the regex pattern has been updated from \(.*?\) to \([^)]*\) to prevent ReDoS attacks. This is a consistent security improvement across the codebase.
    test/test_context_memory.py:24
  • Import of 'Pattern' is not used.
    Import of 'Suggestion' is not used.
    test/test_error_parser.py:16
  • Import of 'ErrorAnalysis' is not used.
    test/test_installation_history.py:20
  • Import of 'PackageSnapshot' is not used.
    Import of 'InstallationRecord' is not used.
    test/test_installation_verifier.py:16
  • Import of 'VerificationTest' is not used.
    test/test_llm_router.py:25
  • Import of 'RoutingDecision' is not used.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (14)
test/test_installation_verifier.py (1)

73-73: Remove redundant import.

The os module is already imported at the module level (line 8), making this import statement redundant.

Apply this diff:

-        import os
docs/USER_PREFERENCES_IMPLEMENTATION.md (1)

48-53: Minor Markdown hygiene for better tooling support

The doc is thorough and matches the implemented system. A few small cleanups would make linters happier and improve readability:

  • Add explicit languages to “fenced” code blocks that currently have none (e.g., the ~/.config/cortex/preferences.yaml and file-structure snippets) to satisfy MD040.
  • Consider wrapping bare URLs in Markdown links (e.g., [link](https://...)) for the references section.
  • Optional: some style guides prefer November 18, 2025, with a trailing comma; you can adjust or ignore depending on your house style.

Also applies to: 356-365, 671-684, 736-737, 721-723

test/test_conflict_ui.py (1)

7-74: Conflict UI tests provide good coverage; only minor polish opportunities

The test scenarios align well with _resolve_conflicts_interactive and _ask_save_preference:

  • Keep-first/keep-second paths assert the correct package ends up in resolutions['remove'] and that user-facing messages are printed.
  • Cancel path asserts SystemExit and checks the cancellation message.
  • Save-preference test verifies conflicts.saved_resolutions is written with the sorted key and that save() is called.
  • Saved-preference test confirms the fast-path is used without prompting, and the output reflects the stored choice.

Only optional cleanups:

  • Several patched arguments (mock_input, mock_stdout) are not referenced directly; you could rename them to _mock_input / _mock_stdout to quiet linters without changing behavior.
  • If you want to avoid constructing a real CortexCLI in tests, you could inject a minimal stub just for conflict resolution, but this is not necessary given current scope.
cortex/cli.py (5)

23-33: Preference manager initialization is reasonable; consider logging on load failure

Initializing PreferencesManager in __init__ and attempting a best-effort load() makes sense so the rest of the CLI can assume preferences are present. Swallowing all exceptions keeps the CLI usable if the config is corrupt or unreadable, but completely silent failures may make diagnosing broken configs harder.

Consider logging a brief warning (to stderr or via a logger) when load() fails, ideally including the config path, while still proceeding with defaults.

Also applies to: 34-49


34-49: Credentials helpers are currently unused

_cred_path() and _load_creds() are defined but not yet wired into _get_api_key() or any other flow. If credential files are part of a planned follow-up, this is fine; otherwise, you might either:

  • Integrate _load_creds() into _get_api_key() as a secondary source after env vars, or
  • Remove these helpers for now to avoid dead code.

84-145: Interactive conflict resolution + saved preferences logic looks sound

The conflict UI and preference-saving logic are coherent and match the tests and ConflictSettings schema:

  • Saved resolutions are keyed using f"{min(pkg1, pkg2)}:{max(pkg1, pkg2)}", ensuring that a conflict between pkgA/pkgB is recognized regardless of order.
  • When a saved preference is present, the code computes the package to remove and prints a clear “Using saved preference: …” message without prompting, which is what the tests assert.
  • New choices (1/2) correctly add the opposite package to resolutions['remove'] and delegate to _ask_save_preference if selected.
  • Choice 3 cancels the installation via sys.exit(1), which matches the test expectations.

Given this is used in an interactive CLI, exiting from here is acceptable, but if you ever want to reuse _resolve_conflicts_interactive programmatically (e.g., from a higher-level orchestration layer), you may eventually prefer raising a custom exception instead of calling sys.exit() directly.


159-223: DependencyResolver integration for conflict checks is safe but could log resolver failures

The install flow’s conflict-check integration is conservative:

  • It uses a simple heuristic (target_package = software.split()[0]) to pick a package to resolve, which is acceptable as a first cut for the UI demo.
  • If DependencyResolver.resolve_dependencies() finds conflicts, they’re passed through the interactive resolver and any chosen removals are prepended as apt-get remove commands unless already present.
  • Broadly catching Exception around the resolver and silently continuing avoids breaking installs when the resolver can’t handle a package, but you lose diagnostic visibility.

Consider logging a warning when resolver calls fail (e.g., missing apt-cache data or unsupported packages), so users and developers can see why conflict checks were skipped while still proceeding with the install.

Also applies to: 204-223


402-513: Config subcommand behavior and value parsing are coherent; consider surfacing validation on set

The config() method plus _parse_config_value() provide a straightforward interface:

  • list uses list_all() and prints YAML plus config metadata.
  • get/set/reset/validate/info/export/import map cleanly onto the PreferencesManager API.
  • _parse_config_value() handles booleans (true/false-like strings), ints, and simple comma-separated lists, and otherwise returns a string—sufficient for current preference types.

Two optional improvements:

  1. After a successful set, you could optionally run validate() and warn if the new value makes the config invalid (e.g., auto_update.frequency_hours = 0), instead of relying on a separate config validate step.
  2. For reset without a key, you currently ask for confirmation via input("Continue? (y/n): "). That’s good; just be aware this introduces interactivity into config reset that might surprise scripts. A --force flag at the argparse level could be a future enhancement if scripting becomes common.

Overall, the implementation matches the documented CLI usage.

Also applies to: 514-533

cortex/user_preferences.py (4)

156-258: load()/save() behavior and backup handling are reasonable

PreferencesManager.load():

  • Creates a default UserPreferences object and immediately saves it if the config file doesn’t exist, ensuring subsequent operations always have a concrete config.
  • Uses yaml.safe_load() and handles empty files by treating them as {}, which is robust.

save():

  • Requires _preferences to be non-None (enforcing a load() first).
  • Optionally creates a timestamped backup using with_suffix(".backup.<timestamp>") when a config already exists.
  • Dumps YAML with stable formatting (no flow style, no key sorting).

This is a solid, conservative approach. One optional enhancement would be to narrow the broad except Exception in load()/save()-related helpers (_create_backup, load) to more specific exceptions where practical, but it’s not strictly necessary.


259-387: Dot-notation get/set/reset behavior is consistent; conflicts.saved_resolutions is handled correctly

Key points:

  • get() walks the UserPreferences.to_dict() structure using dot notation and returns the provided default if any segment is missing. This is what the CLI and conflict UI expect.
  • set() validates per top-level section:
    • verbosity and ai.creativity restricted to known enum values.
    • Type checks for booleans/ints/lists/dicts as appropriate (e.g., auto_update.frequency_hours, ai.max_suggestions, packages.default_sources, conflicts.saved_resolutions).
    • For conflicts.saved_resolutions, requiring a dict lines up with how _ask_save_preference() passes in a Python dict of normalized keys to package names.
  • reset() either:
    • Replaces the entire preferences object with defaults and saves, or
    • For specific keys, derives the default value from a fresh UserPreferences().to_dict() and then calls set(key, default_value) followed by save().

This all matches how cortex.cli.config uses the manager. Only minor caveat: resetting a non-leaf key like "ai" would currently fail (since set() expects "ai.something"), but the documented and tested flows all use leaf keys (ai.model, etc.), so this is acceptable.


389-423: Validation rules are sensible and match the documented configuration options

The validate() method checks:

  • verbosity is within the VerbosityLevel enum.
  • ai.creativity is within AICreativity.
  • ai.model is one of ["claude-sonnet-4", "gpt-4", "gpt-4-turbo", "claude-3-opus"], which matches the docs.
  • auto_update.frequency_hours >= 1.
  • At least one package source in packages.default_sources.

This provides clear, actionable error messages and is sufficient for the current config surface. If/when you expand models or strategies (e.g. conflicts.default_strategy beyond "interactive"), this is a good place to enforce those constraints as well.


498-535: Embedded demo main() is fine but could be gated or moved later

The main() function at the bottom offers a handy demonstration of the preferences manager. It’s harmless in normal use (only runs under python -m cortex.user_preferences), but in some projects, such demos are moved to separate examples or guarded in documentation.

No need to change this now; just something to consider if the module grows further or if packaging policies prefer modules without executable demos.

test/test_user_preferences.py (2)

1-5: Resolve Ruff EXE001 by dropping the shebang (or making the file executable).

Ruff correctly notes the shebang on Line 1 but the file isn’t executable and is already invoked via python -m unittest / the __main__ guard. Simplest fix is to remove the shebang so the module just starts with the docstring.

-#!/usr/bin/env python3
-"""
-Unit tests for the User Preferences System
-Tests all functionality of the PreferencesManager class
-"""
+"""
+Unit tests for the User Preferences System
+Tests all functionality of the PreferencesManager class
+"""

Also applies to: 499-501


132-415: PreferencesManager tests are thorough; consider adding coverage for conflicts.saved_resolutions.

The suite here is strong: you cover default creation, load/save round‑trip, dot‑notation get/set, reset (all and specific), validation, backup creation, JSON export/import (valid and invalid), YAML parsing, basic concurrent access, invalid YAML, and directory creation. This gives very solid confidence in the manager’s behavior.

Given PreferencesManager.set has explicit handling for conflicts.saved_resolutions, it would be worth adding at least one positive round‑trip test (and optionally a type‑validation negative test) to keep the new conflict‑resolution preferences covered:

 class TestPreferencesManager(unittest.TestCase):
@@
     def test_import_invalid_json(self):
         """Test importing invalid JSON raises error"""
@@
         with self.assertRaises(ValueError):
             self.manager.import_json(import_path)
+
+    def test_conflicts_saved_resolutions_roundtrip(self):
+        """Test saving and reloading conflicts.saved_resolutions"""
+        self.manager.load()
+        resolutions = {"example_conflict": "example_resolution"}
+        self.manager.set("conflicts.saved_resolutions", resolutions)
+        self.manager.save()
+
+        reloaded = PreferencesManager(config_path=self.config_path)
+        reloaded.load()
+        self.assertEqual(
+            reloaded.get("conflicts.saved_resolutions"),
+            resolutions,
+        )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between deea55d and 64c8fbe.

⛔ Files ignored due to path filters (1)
  • bounties_owed.csv is excluded by !**/*.csv
📒 Files selected for processing (21)
  • .gitignore (1 hunks)
  • LLM/requirements.txt (1 hunks)
  • bounties_pending.json (0 hunks)
  • cortex/cli.py (12 hunks)
  • cortex/dependency_resolver.py (2 hunks)
  • cortex/installation_history.py (2 hunks)
  • cortex/user_preferences.py (1 hunks)
  • deploy_jesse_system (1).sh (0 hunks)
  • deploy_jesse_system.sh (0 hunks)
  • docs/USER_PREFERENCES_IMPLEMENTATION.md (1 hunks)
  • issue_status.json (0 hunks)
  • payments_history.json (0 hunks)
  • pr_status.json (0 hunks)
  • test/test_conflict_ui.py (1 hunks)
  • test/test_context_memory.py (1 hunks)
  • test/test_error_parser.py (1 hunks)
  • test/test_installation_history.py (1 hunks)
  • test/test_installation_verifier.py (1 hunks)
  • test/test_llm_router.py (10 hunks)
  • test/test_logging_system.py (1 hunks)
  • test/test_user_preferences.py (1 hunks)
💤 Files with no reviewable changes (6)
  • bounties_pending.json
  • payments_history.json
  • deploy_jesse_system (1).sh
  • deploy_jesse_system.sh
  • pr_status.json
  • issue_status.json
🧰 Additional context used
🧬 Code graph analysis (4)
cortex/cli.py (3)
cortex/installation_history.py (1)
  • InstallationHistory (68-659)
cortex/user_preferences.py (10)
  • PreferencesManager (144-495)
  • load (200-226)
  • get (259-282)
  • save (228-257)
  • list_all (485-495)
  • get_config_info (465-483)
  • reset (358-387)
  • validate (389-422)
  • export_json (424-440)
  • import_json (442-463)
cortex/dependency_resolver.py (2)
  • DependencyResolver (39-401)
  • resolve_dependencies (217-282)
test/test_user_preferences.py (1)
cortex/user_preferences.py (24)
  • PreferencesManager (144-495)
  • UserPreferences (96-141)
  • VerbosityLevel (21-26)
  • AICreativity (29-33)
  • ConfirmationSettings (37-45)
  • AutoUpdateSettings (49-56)
  • AISettings (60-70)
  • PackageSettings (74-82)
  • to_dict (44-45)
  • to_dict (55-56)
  • to_dict (69-70)
  • to_dict (81-82)
  • to_dict (91-92)
  • to_dict (108-120)
  • from_dict (123-141)
  • load (200-226)
  • save (228-257)
  • get (259-282)
  • reset (358-387)
  • validate (389-422)
  • export_json (424-440)
  • import_json (442-463)
  • get_config_info (465-483)
  • list_all (485-495)
test/test_conflict_ui.py (2)
cortex/cli.py (3)
  • CortexCLI (23-533)
  • _resolve_conflicts_interactive (84-133)
  • main (536-615)
cortex/user_preferences.py (3)
  • get (259-282)
  • save (228-257)
  • main (498-531)
cortex/user_preferences.py (1)
cortex/cli.py (1)
  • main (536-615)
🪛 LanguageTool
docs/USER_PREFERENCES_IMPLEMENTATION.md

[style] ~737-~737: Some style guides suggest that commas should set off the year in a month-day-year date.
Context: ...** 1.0 Last Updated: November 18, 2025 Author: Development Team **Revi...

(MISSING_COMMA_AFTER_YEAR)

🪛 markdownlint-cli2 (0.18.1)
docs/USER_PREFERENCES_IMPLEMENTATION.md

51-51: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


356-356: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


721-721: Bare URL used

(MD034, no-bare-urls)


722-722: Bare URL used

(MD034, no-bare-urls)


723-723: Bare URL used

(MD034, no-bare-urls)

🪛 Ruff (0.14.5)
cortex/cli.py

30-32: try-except-pass detected, consider logging the exception

(S110)


30-30: Do not catch blind exception: Exception

(BLE001)


46-48: try-except-pass detected, consider logging the exception

(S110)


46-46: Do not catch blind exception: Exception

(BLE001)


219-222: try-except-pass detected, consider logging the exception

(S110)


219-219: Do not catch blind exception: Exception

(BLE001)


510-510: Do not catch blind exception: Exception

(BLE001)


511-511: Use explicit conversion flag

Replace with conversion flag

(RUF010)

test/test_user_preferences.py

1-1: Shebang is present but file is not executable

(EXE001)

test/test_conflict_ui.py

18-18: Unused method argument: mock_input

(ARG002)


29-29: Unused method argument: mock_input

(ARG002)


39-39: Unused method argument: mock_input

(ARG002)


48-48: Unused method argument: mock_stdout

(ARG002)


48-48: Unused method argument: mock_input

(ARG002)

cortex/user_preferences.py

1-1: Shebang is present but file is not executable

(EXE001)


196-196: Consider moving this statement to an else block

(TRY300)


197-197: Do not catch blind exception: Exception

(BLE001)


198-198: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


198-198: Avoid specifying long messages outside the exception class

(TRY003)


198-198: Use explicit conversion flag

Replace with conversion flag

(RUF010)


221-221: Consider moving this statement to an else block

(TRY300)


224-224: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


224-224: Avoid specifying long messages outside the exception class

(TRY003)


224-224: Use explicit conversion flag

Replace with conversion flag

(RUF010)


225-225: Do not catch blind exception: Exception

(BLE001)


226-226: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


226-226: Avoid specifying long messages outside the exception class

(TRY003)


226-226: Use explicit conversion flag

Replace with conversion flag

(RUF010)


239-239: Avoid specifying long messages outside the exception class

(TRY003)


254-254: Consider moving this statement to an else block

(TRY300)


256-256: Do not catch blind exception: Exception

(BLE001)


257-257: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


257-257: Avoid specifying long messages outside the exception class

(TRY003)


257-257: Use explicit conversion flag

Replace with conversion flag

(RUF010)


304-304: Abstract raise to an inner function

(TRY301)


304-304: Avoid specifying long messages outside the exception class

(TRY003)


309-309: Abstract raise to an inner function

(TRY301)


309-309: Avoid specifying long messages outside the exception class

(TRY003)


311-311: Abstract raise to an inner function

(TRY301)


311-311: Avoid specifying long messages outside the exception class

(TRY003)


316-316: Abstract raise to an inner function

(TRY301)


316-316: Avoid specifying long messages outside the exception class

(TRY003)


318-318: Abstract raise to an inner function

(TRY301)


318-318: Avoid specifying long messages outside the exception class

(TRY003)


320-320: Abstract raise to an inner function

(TRY301)


320-320: Avoid specifying long messages outside the exception class

(TRY003)


325-325: Abstract raise to an inner function

(TRY301)


325-325: Avoid specifying long messages outside the exception class

(TRY003)


328-328: Abstract raise to an inner function

(TRY301)


328-328: Avoid specifying long messages outside the exception class

(TRY003)


330-330: Abstract raise to an inner function

(TRY301)


330-330: Avoid specifying long messages outside the exception class

(TRY003)


335-335: Abstract raise to an inner function

(TRY301)


335-335: Avoid specifying long messages outside the exception class

(TRY003)


337-337: Abstract raise to an inner function

(TRY301)


337-337: Avoid specifying long messages outside the exception class

(TRY003)


342-342: Abstract raise to an inner function

(TRY301)


342-342: Avoid specifying long messages outside the exception class

(TRY003)


344-344: Abstract raise to an inner function

(TRY301)


344-344: Avoid specifying long messages outside the exception class

(TRY003)


351-351: Abstract raise to an inner function

(TRY301)


351-351: Avoid specifying long messages outside the exception class

(TRY003)


353-353: Consider moving this statement to an else block

(TRY300)


356-356: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


356-356: Avoid specifying long messages outside the exception class

(TRY003)


356-356: Use explicit conversion flag

Replace with conversion flag

(RUF010)


383-383: Avoid specifying long messages outside the exception class

(TRY003)


460-460: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (25)
test/test_installation_verifier.py (2)

7-10: LGTM! Import path setup is correct.

The sys.path manipulation correctly enables importing the cortex package from the test directory by adding the parent directory to the Python path.


12-12: Import path correctly updated.

The change from installation_verifier to cortex.installation_verifier aligns with the project's move to centralize modules under the cortex package.

.gitignore (1)

153-164: LGTM!

The additions appropriately exclude generated artifacts (cleanup scripts, deployment scripts) and data files while preserving contributors.json. The negation pattern !data/contributors.json correctly ensures that specific file is tracked.

test/test_error_parser.py (1)

7-16: LGTM!

The import path updates correctly standardize test imports to use the cortex.* namespace, aligning with the broader repository restructuring.

test/test_logging_system.py (1)

10-11: LGTM!

The import path updates correctly align with the standardized cortex.* namespace used across the test suite.

test/test_context_memory.py (1)

16-24: LGTM!

The import path updates correctly standardize module resolution to use the cortex.* namespace, with a helpful comment explaining the sys.path adjustment.

cortex/dependency_resolver.py (2)

150-152: Good security improvement!

The regex change from r'\s*\(.*?\)' to r'\s*\([^)]*\)' reduces ReDoS risk by using a negated character class instead of a quantifier, which is both safer and more performant.


166-167: Good security improvement!

Consistent application of the safer regex pattern for version constraint removal.

test/test_llm_router.py (2)

15-25: LGTM!

The import path updates correctly standardize module resolution to use the cortex.* namespace.


247-247: LGTM!

All patch decorators correctly updated to reference cortex.llm_router.*, which is necessary for the mocks to target the correct module paths after the import restructuring.

Also applies to: 279-279, 316-316, 351-351, 383-383, 425-426, 460-461, 502-502, 522-522

test/test_installation_history.py (1)

9-20: LGTM!

The import path updates correctly align with the standardized cortex.* namespace used across the test suite.

LLM/requirements.txt (1)

3-3: PyYAML>=6.0 constraint is secure and appropriate.

No widely reported direct security vulnerabilities affect PyYAML 6.0 or later, as known RCE issues were fixed in the 5.4+ line. The version constraint >=6.0 appropriately ensures that installed versions avoid historically vulnerable releases.

cortex/installation_history.py (2)

168-181: Dependency cleanup regex change looks correct and safer

Switching to re.sub(r'\s*\([^)]*\)', '', dep) keeps the intended behavior (stripping version constraints like pkg (>= 1.2)) while avoiding potentially problematic patterns and aligns with similar resolver logic elsewhere. No functional issues spotted.


246-256: MD5-based ID generation is adequately documented for non-security use

The expanded docstring and inline comment make it clear MD5 is used only for non-cryptographic IDs, matching the actual usage. Given the timestamp + sorted package list and truncation to 16 hex chars, collision risk is acceptable for this context.

cortex/cli.py (4)

65-73: Standardized status and result messages improve UX

Switching to _print_status, _print_error, and _print_success, and using consistent labels like [INFO], [SUCCESS], and [FAILED] (including in the progress_callback and history messages) makes CLI output much easier to scan.

No functional issues here; this is a solid readability/UX improvement.

Also applies to: 249-255, 270-299


135-145: Conflict preference persistence aligns with UserPreferences.conflicts.saved_resolutions

_ask_save_preference():

  • Normalizes the conflict key using min/max, matching the read path in _resolve_conflicts_interactive.
  • Retrieves the existing conflicts.saved_resolutions dict (or {}), updates it, and writes it back via prefs_manager.set("conflicts.saved_resolutions", ...) followed by prefs_manager.save().

This matches the ConflictSettings.saved_resolutions: Dict[str, str] data model and the tests’ expectations for keys and values. No issues spotted.


224-245: History tracking integration remains correct after conflict handling

The existing pattern of:

  • Extracting packages from commands via history._extract_packages_from_commands(commands),
  • Recording the installation when execute or dry_run is enabled, and
  • Updating the record on success/failure or dry run

is preserved even with added conflict-removal commands inserted at the front. The additional [INFO] Installation recorded (ID: …) messages in both success and failure paths are consistent and helpful.

Also applies to: 236-237


550-555: Argparse wiring for the config subcommand is consistent and clear

Adding the config subparser with the documented actions and wiring it through main() (including the updated epilog examples) is consistent with the rest of the CLI structure. Argument order (action, optional key, optional value) matches how config() expects its parameters.

No issues found here.

Also applies to: 582-589, 598-607

cortex/user_preferences.py (2)

21-107: Data models and defaults are coherent and align with documented configuration

The enums and dataclasses (VerbosityLevel, AICreativity, ConfirmationSettings, AutoUpdateSettings, AISettings, PackageSettings, ConflictSettings, UserPreferences) are well-structured:

  • Defaults match the documentation (e.g., verbosity=normal, ai.model="claude-sonnet-4", sensible confirmation and auto-update defaults).
  • ConflictSettings introduces default_strategy and saved_resolutions in a way that matches how cortex.cli.CortexCLI reads/writes conflicts.saved_resolutions.
  • UserPreferences.to_dict() and from_dict() provide a clean dict representation, suitable for YAML/JSON storage and CLI inspection.

No structural issues identified here.


424-496: JSON import/export and config introspection align with the rest of the API

  • export_json() exports the current preferences via to_dict() to a JSON file, matching the YAML schema.
  • import_json() loads JSON, constructs a UserPreferences via from_dict(), validates it, and saves only if validation passes, which is a safe migration path.
  • get_config_info() and list_all() provide useful metadata and are exactly what the CLI’s config info and config list commands require.

These operations appear correct and are coherent with the rest of the design.

test/test_user_preferences.py (5)

31-70: UserPreferences tests are aligned with the model and cover key paths.

The defaults and to_dict/from_dict assertions match UserPreferences and the enum values, and they exercise both serialization and partial override behavior; no changes needed here.


72-130: Dataclass default/serialization tests for settings look good.

The tests for ConfirmationSettings, AutoUpdateSettings, AISettings, and PackageSettings correctly assert defaults and to_dict structure and give good safety against regressions in the config schema; nothing to fix.


417-432: Enum tests are precise and acceptable as-is.

Verifying the exact VerbosityLevel and AICreativity string values is appropriate here and will catch accidental renames; just be aware these tests will need updating if new enum values or names are introduced.


434-476: Edge-case coverage for load/save behavior is solid.

The tests for empty config files, save without prior load, implicit load on set, and nested key access nicely cover subtle behaviors around lazy initialization and yaml parsing. No issues spotted.


478-497: Custom run_tests() helper is fine but optional.

The explicit test suite assembly and run_tests() wrapper are correct and can be handy for local runs. Most runners will still rely on unittest discovery, so this is more of a convenience layer than a requirement; no changes needed.

@mikejmorgan-ai
Copy link
Member

mikejmorgan-ai commented Nov 23, 2025

🔥 CRITICAL PATH REVIEW

Hi @Sahilbhatane! This PR is blocking our MVP completion.

Urgent Review In Progress:

  • ✅ Technical review by @dhvll
  • ✅ Final approval by @mikejmorgan-ai
  • ⏱️ Target decision: Within 24 hours

Payment Ready:
💰 Bounty will be paid via Discord crypto (BTC/USDC) within 24 hours of merge

Join Discord for payment coordination:
👉 https://discord.gg/uCqHvxjU83

We're prioritizing this merge! Thanks for the critical work. 🚀

@dhvll
Copy link
Collaborator

dhvll commented Nov 25, 2025

@Sahilbhatane just keep files related to this issue in this PR. Don't need to restructure repository

And add video implementation. And did you test it on your system?

@Sahilbhatane
Copy link
Collaborator Author

video implementation

Untitled.video.-.Made.with.Clipchamp.1.mp4

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cortex/cli.py (1)

515-534: Epilog example uses llm.provider key, which is not a defined preference

The CLI help shows:

cortex config set llm.provider openai

But the preferences schema only defines keys like ai.model, ai.creativity, etc., and PreferencesManager.set() will reject llm.provider with “Unknown preference key”.

Update the example to a valid key (e.g., ai.model) or add an llm section to UserPreferences if that’s the intended schema.

♻️ Duplicate comments (1)
test/test_conflict_ui.py (1)

438-450: Align test_preference_validation with PreferencesManager.validate() behavior

This test assumes that setting prefs.ai.max_suggestions = -999 will cause validate() to return at least one error. At present, validate() does not check max_suggestions, so errors remains empty and the assertion will fail.

Once validate() is extended to enforce a lower bound on max_suggestions (e.g., >= 1), this test will pass as written. If you decide not to constrain max_suggestions, this test will need to be relaxed accordingly.

🧹 Nitpick comments (11)
cortex/user_preferences.py (2)

283-355: PreferencesManager.set() is very branch-heavy; consider splitting into helpers

The set() method encodes all preference families (verbosity, confirmations, auto_update, ai, packages, conflicts, theme/language/timezone) in a single nested if/elif chain, which Sonar flags for high cognitive complexity and will be hard to extend.

Consider refactoring to dedicated helpers per top-level section, e.g.:

def set(self, key: str, value: Any) -> bool:
    if self._preferences is None:
        self.load()
    parts = key.split(".")
    if parts[0] == "verbosity":
        self._set_verbosity(value)
    elif parts[0] == "confirmations":
        self._set_confirmations(parts[1:], value)
    # ...

This will make new preference sections (or extra validation rules) much easier to add and reason about.


193-199: Broad except Exception and re-wrapped errors hide root causes

Several methods (_create_backup, load, save, set, import_json) catch bare Exception and immediately raise a new IOError/ValueError with a stringified message, without preserving the original traceback (raise ... from e). This pattern makes debugging configuration problems harder and is what Ruff/Sonar are complaining about.

It would be safer to:

  • Narrow the caught exceptions where possible (e.g., OSError, yaml.YAMLError, json.JSONDecodeError).
  • Re-raise with raise ... from e so the original stack is preserved.
  • Optionally log the error before rethrowing.

Functionality is fine as-is, but tightening this would improve diagnostics without changing behavior.

Also applies to: 222-225, 243-257, 353-355, 448-450

test/test_conflict_ui.py (1)

61-62: Remove unused result assignments in assertRaises blocks

In test_interactive_conflict_resolution_skip and test_conflict_detected_triggers_ui, result = self.cli._resolve_conflicts_interactive(conflicts) is assigned but never used inside a with self.assertRaises(SystemExit) block.

You can drop the assignment and just call the method:

-        with self.assertRaises(SystemExit):
-            result = self.cli._resolve_conflicts_interactive(conflicts)
+        with self.assertRaises(SystemExit):
+            self.cli._resolve_conflicts_interactive(conflicts)

This will clear the Ruff/Sonar warnings without changing test behavior.

Also applies to: 362-363

docs/IMPLEMENTATION_SUMMARY.md (1)

73-85: Minor markdownlint issues: add code fence languages and avoid emphasis-as-heading

markdownlint is flagging:

  • Fenced blocks without a language (e.g., the “Example Output” and workflow snippets).
  • The “PR Title” line using bold emphasis instead of a proper heading.

To quiet this without changing meaning:

  • Add languages, e.g. text for console output blocks and bash for shell command examples.
  • Replace emphasized headings like **"feat: ... "** with a Markdown heading (###), or leave as plain text.

Purely cosmetic, but it will keep automated doc checks clean.

Also applies to: 219-235, 291-325

cortex/dependency_resolver.py (2)

226-246: Conflict detection can emit duplicate pairs for the same packages

conflict_patterns is symmetric for some packages (e.g., both 'mysql-server': ['mariadb-server'] and 'mariadb-server': ['mysql-server']). With the current loop, a dependency set containing both can yield both ('mysql-server', 'mariadb-server') and ('mariadb-server', 'mysql-server').

This can lead to duplicate conflict prompts in the CLI. Consider normalizing pairs (e.g., tuple(sorted((dep_name, conflicting)))) and storing them in a set before returning a list, so each conflict pair appears only once.


171-225: resolve_dependencies is doing several distinct jobs and is over-complex

resolve_dependencies() currently:

  • Fetches apt and predefined deps.
  • Merges/deduplicates them.
  • Optionally traverses transitive deps.
  • Detects conflicts.
  • Computes installation order.
  • Populates and caches DependencyGraph.

Sonar’s complexity warning here is justified; future changes (e.g., more sources, richer conflict rules) will be hard to slot in. Consider extracting helpers like _collect_direct_dependencies, _collect_transitive_dependencies, and _build_graph to keep this method focused on orchestration.

cortex/cli.py (5)

31-38: Swallowing all exceptions when loading preferences hides configuration problems

In __init__, self.prefs_manager.load() is wrapped in except Exception: pass. If the YAML is malformed or the file is unreadable, this will silently skip loading, leaving _preferences unset and deferring the real error to a later call.

Prefer at least logging the failure, or restricting the catch to known benign cases (e.g., file-not-found) while surfacing real configuration errors early.


73-123: Conflict-resolution UI behavior looks correct; consider minor polish

The interactive flow correctly:

  • Applies saved preferences first using the min:max conflict key.
  • Prompts the user with three options and loops on invalid input.
  • Records packages to remove and optionally persists preferences.

Two small polish ideas:

  • Tighten the type hint to List[Tuple[str, str]] instead of List[tuple].
  • Extract "conflicts.saved_resolutions" into a module/class constant to avoid magic strings.

Functionality otherwise aligns with the tests and issue requirements.


172-188: Dependency conflict integration is defensive but may fail silently and mis-identify target package

The install flow now:

  • Picks target_package = software.split()[0].
  • Wraps the entire dependency resolution + conflict handling in a bare except Exception: pass.

Two implications:

  • For natural-language requests ("python 3.11 with pip"), only the first token is checked for conflicts, which may not match the actual apt package the interpreter chooses.
  • Any resolver failure (e.g., missing dpkg/apt-cache) quietly disables conflict handling with no user feedback.

Consider:

  • Letting CommandInterpreter surface the concrete package name(s) and using that instead of software.split()[0].
  • Narrowing the exception handling to expected system errors and emitting a [INFO] Skipping conflict check: ... message when it’s disabled.

369-483: config() mixes many concerns and is over the usual complexity budget

config() currently handles eight subcommands, I/O formatting, interactive prompts, path handling, and error reporting in a single method. Sonar’s cognitive complexity warning is expected here.

A simple refactor would be to delegate each action to a helper:

def config(self, action: str, key: Optional[str] = None, value: Optional[str] = None):
    try:
        if action == "list":
            return self._config_list()
        if action == "get":
            return self._config_get(key)
        # ...
    except ValueError as e:
        ...

This keeps the public API unchanged, but makes each subcommand easier to test and evolve.


585-590: main() still emits emoji-style errors, contrary to the “[LABEL] only” output style

The exception handlers in main() print messages prefixed with , while the rest of the CLI intentionally uses [ERROR]/[SUCCESS] labels and the docs explicitly call out “No Emojis”.

For consistency, consider switching these lines to use the same label style, e.g.:

-    except KeyboardInterrupt:
-        print("\n❌ Operation cancelled by user", file=sys.stderr)
+    except KeyboardInterrupt:
+        print("\n[ERROR] Operation cancelled by user", file=sys.stderr)

and similarly for the generic exception case.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64c8fbe and 3a775ca.

📒 Files selected for processing (6)
  • .gitignore (1 hunks)
  • cortex/cli.py (14 hunks)
  • cortex/dependency_resolver.py (1 hunks)
  • cortex/user_preferences.py (1 hunks)
  • docs/IMPLEMENTATION_SUMMARY.md (1 hunks)
  • test/test_conflict_ui.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/test_conflict_ui.py (3)
cortex/cli.py (4)
  • _resolve_conflicts_interactive (73-123)
  • _ask_save_preference (125-141)
  • config (369-482)
  • main (510-590)
cortex/user_preferences.py (9)
  • PreferencesManager (144-485)
  • ConflictSettings (86-92)
  • get (258-281)
  • save (227-256)
  • load (200-225)
  • reset (356-383)
  • validate (385-413)
  • export_json (415-431)
  • import_json (433-453)
cortex/dependency_resolver.py (2)
  • DependencyResolver (37-274)
  • resolve_dependencies (171-224)
🪛 GitHub Check: SonarCloud Code Analysis
cortex/cli.py

[failure] 84-84: Define a constant instead of duplicating this literal "conflicts.saved_resolutions" 3 times.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZqwfhAiXrFv6bfNWu4i&open=AZqwfhAiXrFv6bfNWu4i&pullRequest=203


[failure] 156-156: Define a constant instead of duplicating this literal "[INFO]" 4 times.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZqwfhAiXrFv6bfNWu4j&open=AZqwfhAiXrFv6bfNWu4j&pullRequest=203


[failure] 369-369: Refactor this function to reduce its Cognitive Complexity from 37 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZqwfhAiXrFv6bfNWu4k&open=AZqwfhAiXrFv6bfNWu4k&pullRequest=203

test/test_conflict_ui.py

[warning] 62-62: Remove the unused local variable "result".

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZq7hhrPtl8i2EI0Vq_e&open=AZq7hhrPtl8i2EI0Vq_e&pullRequest=203


[warning] 363-363: Remove the unused local variable "result".

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZq7hhrPtl8i2EI0Vq_f&open=AZq7hhrPtl8i2EI0Vq_f&pullRequest=203

cortex/dependency_resolver.py

[failure] 171-171: Refactor this function to reduce its Cognitive Complexity from 20 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZq7hhoDtl8i2EI0Vq_d&open=AZq7hhoDtl8i2EI0Vq_d&pullRequest=203

cortex/user_preferences.py

[failure] 283-283: Refactor this function to reduce its Cognitive Complexity from 42 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZqwfg-2XrFv6bfNWu4e&open=AZqwfg-2XrFv6bfNWu4e&pullRequest=203


[failure] 356-356: Refactor this method to not always return the same value.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZqwfg-2XrFv6bfNWu4f&open=AZqwfg-2XrFv6bfNWu4f&pullRequest=203

🪛 markdownlint-cli2 (0.18.1)
docs/IMPLEMENTATION_SUMMARY.md

73-73: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


219-219: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


287-287: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🪛 Ruff (0.14.5)
cortex/cli.py

37-38: try-except-pass detected, consider logging the exception

(S110)


37-37: Do not catch blind exception: Exception

(BLE001)


188-189: try-except-pass detected, consider logging the exception

(S110)


188-188: Do not catch blind exception: Exception

(BLE001)


480-480: Do not catch blind exception: Exception

(BLE001)


481-481: Use explicit conversion flag

Replace with conversion flag

(RUF010)

test/test_conflict_ui.py

62-62: Local variable result is assigned to but never used

Remove assignment to unused variable result

(F841)


270-270: Unused method argument: mock_stdout

(ARG002)


283-283: Unused method argument: mock_stdout

(ARG002)


363-363: Local variable result is assigned to but never used

Remove assignment to unused variable result

(F841)

cortex/dependency_resolver.py

1-1: Shebang is present but file is not executable

(EXE001)


40-57: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


67-67: subprocess call: check for execution of untrusted input

(S603)


73-73: Consider moving this statement to an else block

(TRY300)


76-76: Do not catch blind exception: Exception

(BLE001)

cortex/user_preferences.py

1-1: Shebang is present but file is not executable

(EXE001)


196-196: Consider moving this statement to an else block

(TRY300)


197-197: Do not catch blind exception: Exception

(BLE001)


198-198: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


198-198: Avoid specifying long messages outside the exception class

(TRY003)


198-198: Use explicit conversion flag

Replace with conversion flag

(RUF010)


220-220: Consider moving this statement to an else block

(TRY300)


223-223: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


223-223: Avoid specifying long messages outside the exception class

(TRY003)


223-223: Use explicit conversion flag

Replace with conversion flag

(RUF010)


224-224: Do not catch blind exception: Exception

(BLE001)


225-225: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


225-225: Avoid specifying long messages outside the exception class

(TRY003)


225-225: Use explicit conversion flag

Replace with conversion flag

(RUF010)


238-238: Avoid specifying long messages outside the exception class

(TRY003)


253-253: Consider moving this statement to an else block

(TRY300)


255-255: Do not catch blind exception: Exception

(BLE001)


256-256: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


256-256: Avoid specifying long messages outside the exception class

(TRY003)


256-256: Use explicit conversion flag

Replace with conversion flag

(RUF010)


302-302: Abstract raise to an inner function

(TRY301)


302-302: Avoid specifying long messages outside the exception class

(TRY003)


307-307: Abstract raise to an inner function

(TRY301)


307-307: Avoid specifying long messages outside the exception class

(TRY003)


309-309: Abstract raise to an inner function

(TRY301)


309-309: Avoid specifying long messages outside the exception class

(TRY003)


314-314: Abstract raise to an inner function

(TRY301)


314-314: Avoid specifying long messages outside the exception class

(TRY003)


316-316: Abstract raise to an inner function

(TRY301)


316-316: Avoid specifying long messages outside the exception class

(TRY003)


318-318: Abstract raise to an inner function

(TRY301)


318-318: Avoid specifying long messages outside the exception class

(TRY003)


323-323: Abstract raise to an inner function

(TRY301)


323-323: Avoid specifying long messages outside the exception class

(TRY003)


326-326: Abstract raise to an inner function

(TRY301)


326-326: Avoid specifying long messages outside the exception class

(TRY003)


328-328: Abstract raise to an inner function

(TRY301)


328-328: Avoid specifying long messages outside the exception class

(TRY003)


333-333: Abstract raise to an inner function

(TRY301)


333-333: Avoid specifying long messages outside the exception class

(TRY003)


335-335: Abstract raise to an inner function

(TRY301)


335-335: Avoid specifying long messages outside the exception class

(TRY003)


340-340: Abstract raise to an inner function

(TRY301)


340-340: Avoid specifying long messages outside the exception class

(TRY003)


342-342: Abstract raise to an inner function

(TRY301)


342-342: Avoid specifying long messages outside the exception class

(TRY003)


349-349: Abstract raise to an inner function

(TRY301)


349-349: Avoid specifying long messages outside the exception class

(TRY003)


351-351: Consider moving this statement to an else block

(TRY300)


354-354: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


354-354: Avoid specifying long messages outside the exception class

(TRY003)


354-354: Use explicit conversion flag

Replace with conversion flag

(RUF010)


379-379: Avoid specifying long messages outside the exception class

(TRY003)


450-450: Avoid specifying long messages outside the exception class

(TRY003)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
test/test_conflict_ui.py (1)

282-297: Previous review concern addressed.

The test now properly patches builtins.input to avoid blocking on the confirmation prompt. The unused mock_input parameter is required due to the @patch decorator stacking order.

🧹 Nitpick comments (5)
cortex/user_preferences.py (4)

122-141: from_dict silently ignores unknown keys and may raise TypeError on extra keys in nested dicts.

The nested dataclass instantiations (e.g., ConfirmationSettings(**data.get("confirmations", {}))) will raise TypeError if the loaded YAML/JSON contains unexpected keys. This can happen when users manually edit config files or when migrating from older versions with different schemas.

Consider filtering keys to match dataclass fields:

+    @staticmethod
+    def _filter_dataclass_keys(data: Dict, dataclass_type) -> Dict:
+        """Filter dict to only include keys that are valid dataclass fields."""
+        valid_keys = {f.name for f in dataclass_type.__dataclass_fields__.values()}
+        return {k: v for k, v in data.items() if k in valid_keys}
+
     @classmethod
     def from_dict(cls, data: Dict[str, Any]) -> 'UserPreferences':
         """Create UserPreferences from dictionary"""
-        confirmations = ConfirmationSettings(**data.get("confirmations", {}))
+        confirmations = ConfirmationSettings(**cls._filter_dataclass_keys(
+            data.get("confirmations", {}), ConfirmationSettings))

This improves forward/backward compatibility and prevents crashes on schema mismatches.


193-198: Add exception chaining for better traceability.

Multiple except blocks catch Exception and re-raise without chaining (also flagged at lines 223-225, 255-256, 354). This loses the original traceback and makes debugging harder.

         try:
             import shutil
             shutil.copy2(self.config_path, backup_path)
             return backup_path
-        except Exception as e:
-            raise IOError(f"Failed to create backup: {str(e)}")
+        except Exception as e:
+            raise IOError(f"Failed to create backup: {e}") from e

Apply the same pattern (raise ... from e) to the other locations for consistent error handling.


327-329: Consider adding range validation for max_suggestions in set() to match validate() logic.

set() only checks that max_suggestions is an integer, but validate() (line 407-408) also requires it to be at least 1. This allows invalid values to be set and persisted before validation catches them.

             elif parts[1] == "max_suggestions" and not isinstance(value, int):
                 raise ValueError("max_suggestions must be an integer")
+            elif parts[1] == "max_suggestions" and value < 1:
+                raise ValueError("max_suggestions must be at least 1")

403-405: Hardcoded model list may become stale.

The valid_models list is hardcoded in validate(). As new models are released and old ones deprecated, this list will require manual updates.

Consider extracting this to a module-level constant or making it configurable to simplify future maintenance.

test/test_conflict_ui.py (1)

248-251: Assertion at line 250 may be fragile due to YAML output format.

The test asserts 'ai.model' in output, but yaml.dump() produces nested YAML with ai: and model: on separate lines, not the dot-notation key. The current assertion might pass if ai.model or gpt-4 appears elsewhere, but it doesn't precisely validate the intended behavior.

Consider asserting on the actual YAML structure or just the value:

         output = mock_stdout.getvalue()
-        self.assertIn('ai.model', output)
-        self.assertIn('gpt-4', output)
+        self.assertIn('model: gpt-4', output)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a775ca and 3c8bce2.

📒 Files selected for processing (3)
  • .gitignore (1 hunks)
  • cortex/user_preferences.py (1 hunks)
  • test/test_conflict_ui.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .gitignore
🧰 Additional context used
🧬 Code graph analysis (2)
test/test_conflict_ui.py (3)
cortex/cli.py (3)
  • _resolve_conflicts_interactive (73-123)
  • _ask_save_preference (125-141)
  • config (369-482)
cortex/user_preferences.py (7)
  • ConflictSettings (86-92)
  • get (258-281)
  • save (227-256)
  • reset (356-383)
  • validate (385-416)
  • export_json (418-434)
  • import_json (436-456)
cortex/dependency_resolver.py (2)
  • DependencyResolver (37-274)
  • resolve_dependencies (171-224)
cortex/user_preferences.py (1)
logging_system.py (1)
  • info (211-213)
🪛 Ruff (0.14.5)
test/test_conflict_ui.py

62-62: Local variable result is assigned to but never used

Remove assignment to unused variable result

(F841)


270-270: Unused method argument: mock_stdout

(ARG002)


284-284: Unused method argument: mock_stdout

(ARG002)


284-284: Unused method argument: mock_input

(ARG002)


364-364: Local variable result is assigned to but never used

Remove assignment to unused variable result

(F841)

cortex/user_preferences.py

1-1: Shebang is present but file is not executable

(EXE001)


196-196: Consider moving this statement to an else block

(TRY300)


197-197: Do not catch blind exception: Exception

(BLE001)


198-198: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


198-198: Avoid specifying long messages outside the exception class

(TRY003)


198-198: Use explicit conversion flag

Replace with conversion flag

(RUF010)


220-220: Consider moving this statement to an else block

(TRY300)


223-223: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


223-223: Avoid specifying long messages outside the exception class

(TRY003)


223-223: Use explicit conversion flag

Replace with conversion flag

(RUF010)


224-224: Do not catch blind exception: Exception

(BLE001)


225-225: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


225-225: Avoid specifying long messages outside the exception class

(TRY003)


225-225: Use explicit conversion flag

Replace with conversion flag

(RUF010)


238-238: Avoid specifying long messages outside the exception class

(TRY003)


253-253: Consider moving this statement to an else block

(TRY300)


255-255: Do not catch blind exception: Exception

(BLE001)


256-256: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


256-256: Avoid specifying long messages outside the exception class

(TRY003)


256-256: Use explicit conversion flag

Replace with conversion flag

(RUF010)


302-302: Abstract raise to an inner function

(TRY301)


302-302: Avoid specifying long messages outside the exception class

(TRY003)


307-307: Abstract raise to an inner function

(TRY301)


307-307: Avoid specifying long messages outside the exception class

(TRY003)


309-309: Abstract raise to an inner function

(TRY301)


309-309: Avoid specifying long messages outside the exception class

(TRY003)


314-314: Abstract raise to an inner function

(TRY301)


314-314: Avoid specifying long messages outside the exception class

(TRY003)


316-316: Abstract raise to an inner function

(TRY301)


316-316: Avoid specifying long messages outside the exception class

(TRY003)


318-318: Abstract raise to an inner function

(TRY301)


318-318: Avoid specifying long messages outside the exception class

(TRY003)


323-323: Abstract raise to an inner function

(TRY301)


323-323: Avoid specifying long messages outside the exception class

(TRY003)


326-326: Abstract raise to an inner function

(TRY301)


326-326: Avoid specifying long messages outside the exception class

(TRY003)


328-328: Abstract raise to an inner function

(TRY301)


328-328: Avoid specifying long messages outside the exception class

(TRY003)


333-333: Abstract raise to an inner function

(TRY301)


333-333: Avoid specifying long messages outside the exception class

(TRY003)


335-335: Abstract raise to an inner function

(TRY301)


335-335: Avoid specifying long messages outside the exception class

(TRY003)


340-340: Abstract raise to an inner function

(TRY301)


340-340: Avoid specifying long messages outside the exception class

(TRY003)


342-342: Abstract raise to an inner function

(TRY301)


342-342: Avoid specifying long messages outside the exception class

(TRY003)


349-349: Abstract raise to an inner function

(TRY301)


349-349: Avoid specifying long messages outside the exception class

(TRY003)


351-351: Consider moving this statement to an else block

(TRY300)


354-354: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


354-354: Avoid specifying long messages outside the exception class

(TRY003)


354-354: Use explicit conversion flag

Replace with conversion flag

(RUF010)


379-379: Avoid specifying long messages outside the exception class

(TRY003)


453-453: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (6)
cortex/user_preferences.py (1)

385-416: Validation implementation looks complete.

The validate() method now includes the max_suggestions check (lines 407-408), addressing the previous review feedback. The validation covers all critical fields appropriately.

test/test_conflict_ui.py (5)

15-16: The sys import is used on line 25.

The static analysis flagged sys as unused, but it's actually used for sys.path.insert(0, ...) on line 25. This is a false positive.


49-68: Test correctly validates cancel flow and SystemExit behavior.

The test properly mocks user input, verifies the UI output, and checks that option 3 triggers SystemExit. The unused result variable (line 62) is acceptable since the test focuses on the exception behavior.


136-218: Preference saving tests are thorough and correctly validate the key format.

Good coverage of save/skip flows, persistence across sessions, and multiple preferences. The tests correctly verify the min:max key format used by the implementation.


439-451: Validation test correctly bypasses set() to test edge cases.

The test directly modifies prefs.ai.max_suggestions = -999 to bypass the setter's type checking, then verifies that validate() catches the invalid value. This is a valid testing approach for the validation layer.


35-47: Good test setup with proper isolation and cleanup.

The use of temporary directories for config files and proper cleanup in tearDown ensures tests don't interfere with each other or leave artifacts.

@Sahilbhatane Sahilbhatane requested a review from dhvll November 25, 2025 15:23
@Sahilbhatane Sahilbhatane changed the title Feature: Interactive Package Conflict Resolution + Saved Preferences and structured files for managment (Issue #42) Feature: Interactive Package Conflict Resolution + Saved Preferences(Issue #42) Nov 25, 2025
@Sahilbhatane
Copy link
Collaborator Author

@Sahilbhatane just keep files related to this issue in this PR. Don't need to restructure repository

And add video implementation. And did you test it on your system?

reset to origin/main, no file structure changes, created separate issue for that... all test passed, used OPENAI API for testing on vs terminal (is same as ubuntu's terminal) (video).

@dhvll
Copy link
Collaborator

dhvll commented Nov 25, 2025

Did you tested on your machine before raising PR?

@Sahilbhatane
Copy link
Collaborator Author

Did you tested on your machine before raising PR?

yes, I always do, check the video for how i tested implemented functionalities

@dhvll
Copy link
Collaborator

dhvll commented Nov 25, 2025

Did you tested on your machine before raising PR?

yes, I always do, check the video for how i tested implemented functionalities

Revert changes of dependency_resolver.py file unless those changes are related to your feature.
https://github.com/cortexlinux/cortex/blob/main/dependency_resolver.py more than half of the file was not there.

@Sahilbhatane
Copy link
Collaborator Author

that's just copied file from root folder, I can just rm the ../cortex/dependancy_resolver.py, and call root one.

@dhvll
Copy link
Collaborator

dhvll commented Nov 25, 2025

that's just copied file from root folder, I can just rm the ../cortex/dependancy_resolver.py, and call root one.

address all the reviews

@Sahilbhatane
Copy link
Collaborator Author

No changes done to the main dependancy_resolver.py as said only files created were user_prefrence.py, summary doc and test.py of the implementation, and added that into cli.py with config function, check files changed section of this PR,
Previous dependency resolver file was copy pasted (not cut copied) file which was same as main but added still for better structure (changed now for root file).

@Sahilbhatane
Copy link
Collaborator Author

I have mentioned all changes done in summary doc, please review that if any conflict with existing code then please specify it so I can understand and change it accordingly.

@mikejmorgan-ai
Copy link
Member

@Sahilbhatane - Please respond to @dhvll l's questions:

  1. What's the "video implementation" he mentioned?
  2. Have you tested this on an actual Linux system?

Once you confirm and @dhvll approves, we'll merge immediately. Bounty ready for Issue #42.

@dhvll
Copy link
Collaborator

dhvll commented Nov 25, 2025

@Sahilbhatane - Please respond to @dhvll l's questions:

  1. What's the "video implementation" he mentioned?
  2. Have you tested this on an actual Linux system?

Once you confirm and @dhvll approves, we'll merge immediately. Bounty ready for Issue #42.

yeah he have added video implementation.
I have added few review changes for code refactoring after that its good to go.
But I have not tested it on my ubuntu will do it now and report it.

@mikejmorgan-ai
Copy link
Member

Hi @Sahilbhatane! This PR has merge conflicts with the latest main branch. Could you please rebase?

git fetch origin
git rebase origin/main
# resolve conflicts
git push --force-with-lease

Great work on the conflict resolution UI - will merge as soon as it's rebased!

@Sahilbhatane
Copy link
Collaborator Author

Hi @Sahilbhatane! This PR has merge conflicts with the latest main branch. Could you please rebase?

git fetch origin
git rebase origin/main
# resolve conflicts
git push --force-with-lease

Great work on the conflict resolution UI - will merge as soon as it's rebased!

Sure

@sonarqubecloud
Copy link

@Sahilbhatane Sahilbhatane marked this pull request as ready for review January 15, 2026 15:25
@Sahilbhatane Sahilbhatane marked this pull request as draft January 15, 2026 15:30
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
cortex/licensing.py (1)

204-220: ASCII box borders are incomplete on dynamic content lines.

Lines 210, 214, and 216 are missing the closing character, which will cause the upgrade prompt box to render with broken right edges in the terminal.

Proposed fix to complete the box borders
 ┌─────────────────────────────────────────────────────────┐
 │  ⚡ UPGRADE REQUIRED                                    │
 ├─────────────────────────────────────────────────────────┤
 │                                                         │
-│  '{feature_display}' requires Cortex {tier_display}
+│  '{feature_display}' requires Cortex {tier_display:<15}│
 │                                                         │
 │  ✅ Upgrade now:  cortex upgrade                        │
 │                                                         │
-│  Plans start at {price}/month with 14-day free trial.
+│  Plans start at {price}/month with 14-day free trial.  │
 │                                                         │
-│  🌐 {PRICING_URL}
+│  🌐 {PRICING_URL:<40}│
 │                                                         │
 └─────────────────────────────────────────────────────────┘

Note: The exact padding widths may need adjustment based on the maximum expected lengths of feature_display, tier_display, and PRICING_URL to align properly with the 57-character box width.

cortex/gpu_manager.py (2)

198-229: Avoid hard-coded NVIDIA PCI sysfs path (breaks on most machines).
Line 217 hard-codes 0000:01:00.0, so power_state/is_active becomes unreliable when the dGPU sits elsewhere.

Proposed fix
@@
     def _detect_nvidia_gpu(self) -> GPUDevice | None:
         """Detect NVIDIA GPU with detailed info."""
         returncode, stdout, _ = self._run_command(
@@
         memory = int(float(parts[1].strip())) if len(parts) > 1 else 0
 
-        # Check power state
-        power_returncode, power_stdout, _ = self._run_command(
-            ["cat", "/sys/bus/pci/devices/0000:01:00.0/power/runtime_status"]
-        )
-        power_state = power_stdout.strip() if power_returncode == 0 else "unknown"
+        # Check power state (best-effort; PCI address varies by machine)
+        power_state = "unknown"
+        for candidate in Path("/sys/bus/pci/devices").glob("*/power/runtime_status"):
+            power_returncode, power_stdout, _ = self._run_command(["cat", str(candidate)])
+            if power_returncode == 0:
+                # Heuristic: first readable runtime_status is better than a wrong hard-coded one.
+                power_state = power_stdout.strip()
+                break
 
         return GPUDevice(

528-575: switch UX: include compute in the “Specify a mode” / “Use: …” text.
mode_map supports compute, but the user-facing hints don’t.

Proposed fix
@@
     elif action == "switch":
         if not mode:
-            cx_print("Specify a mode: integrated, hybrid, nvidia", "error")
+            cx_print("Specify a mode: integrated, hybrid, nvidia, compute", "error")
             return 1
@@
         if not target_mode:
-            cx_print(f"Unknown mode: {mode}. Use: integrated, hybrid, nvidia", "error")
+            cx_print(
+                f"Unknown mode: {mode}. Use: integrated, hybrid, nvidia, compute",
+                "error",
+            )
             return 1
cortex/cli.py (1)

991-1110: Conflict detection should skip non-package installs (pip/shell pipelines), or it may prompt bogus removals.
After the pytorch-cpu ... rewrite, software becomes a pip/&& pipeline, but Line 1085+ will still attempt resolver.resolve_dependencies() for tokens like torch, numpy, etc. That can (a) waste time, (b) throw, or (c) detect irrelevant “conflicts” and inject sudo apt-get remove commands.

Safer gating approach
@@
-            # Detect package conflicts and apply interactive resolutions when possible.
+            # Detect apt package conflicts and apply interactive resolutions when possible.
             try:
@@
-                for token in software.split():
+                # Only attempt apt conflict detection for simple "package list" requests.
+                if any(x in software for x in ("&&", "||", "|", ";")) or "pip" in software:
+                    raise RuntimeError("Skipping apt conflict detection for non-apt install request")
+
+                for token in software.split():
                     if not is_valid_package_token(token):
                         continue
                     graph = resolver.resolve_dependencies(token)
🤖 Fix all issues with AI agents
In `@cortex/cli.py`:
- Around line 466-471: _get_prefs_manager currently returns whatever is in
self._prefs_manager even if it's None or the wrong type; change it to validate
the attribute: if self has no _prefs_manager or its value is None or not an
instance of PreferencesManager, instantiate PreferencesManager and assign it to
self._prefs_manager, then return it. Use isinstance(self._prefs_manager,
PreferencesManager) to check type and ensure a valid PreferencesManager is
always returned.

In `@tests/test_conflict_ui.py`:
- Around line 190-212: test_multiple_conflict_preferences can raise if
self.prefs_manager.get("conflicts.saved_resolutions") returns None; when
building the merged dict in the loop use a safe default (e.g., treat None as
{}), so update the logic around prefs_manager.get("conflicts.saved_resolutions")
in test_multiple_conflict_preferences (and any similar usage) to coalesce None
to an empty dict before merging and then call prefs_manager.set(...) and
prefs_manager.save() as before.
♻️ Duplicate comments (5)
.gitignore (1)

155-163: Home-directory patterns use literal ~ and won't match $HOME paths.

The patterns on lines 160-161 (~/.config/cortex/preferences.yaml and ~/.config/cortex/*.backup.*) treat ~ as a literal directory name, not the user's home directory. Since these config files live outside the repository, these rules have no effect and should be removed.

🔧 Suggested fix
 # User preferences and configuration
 .cortex/
 *.yaml.bak
-~/.config/cortex/preferences.yaml
-~/.config/cortex/*.backup.*
 /tmp/
 .env
tests/test_conflict_ui.py (2)

276-292: Remove unused input patch in test_config_reset_command.
The implementation of config("reset") doesn’t prompt, so the patch is misleading noise.


376-391: Strengthen test_dependency_resolver_detects_conflicts assertions.
Right now it doesn’t verify conflict extraction, only that subprocess was called and a graph exists.

Proposed fix
@@
         graph = resolver.resolve_dependencies("nginx")
 
-        # Verify the resolver was called
-        self.assertTrue(mock_run.called)
-        # Verify graph object was created
-        self.assertIsNotNone(graph)
+        mock_run.assert_called()
+        self.assertIsNotNone(graph)
+        self.assertTrue(getattr(graph, "conflicts", None))
cortex/cli.py (1)

534-556: Treat KeyboardInterrupt as “don’t save” in _ask_save_preference() (don’t abort install).
Line 541 only handles EOFError; Ctrl+C here can bubble up after the user already picked a resolution.

Proposed fix
@@
-        except EOFError:
+        except (EOFError, KeyboardInterrupt):
             return
cortex/user_preferences.py (1)

263-265: Add error handling for invalid key paths in set().

If a user provides an invalid key path like "nonexistent.key", getattr(obj, part) on line 265 raises an AttributeError with an unclear message. This was noted in a previous review.

🔧 Suggested fix
         # Navigate to parent object
-        for part in parts[:-1]:
-            obj = getattr(obj, part)
+        try:
+            for part in parts[:-1]:
+                obj = getattr(obj, part)
+        except AttributeError:
+            raise PreferencesError(f"Invalid preference key: '{key}'") from None
 
         # Set the final attribute
         attr_name = parts[-1]
-        current_value = getattr(obj, attr_name)
+        try:
+            current_value = getattr(obj, attr_name)
+        except AttributeError:
+            raise PreferencesError(f"Unknown preference: '{key}'") from None
🧹 Nitpick comments (12)
cortex/stdin_handler.py (1)

227-232: Clarify conditional precedence with explicit parentheses.

The multi-line formatting makes the conditional expression's scope ambiguous. Due to Python's operator precedence, the ternary if len(lines) > 1 else False binds only to the equality check, not the entire and expression. While functionally correct, the visual layout suggests otherwise.

♻️ Suggested clarification
-    if (
-        "," in first_line and lines[0].count(",") == lines[1].count(",")
-        if len(lines) > 1
-        else False
-    ):
+    if len(lines) > 1 and "," in first_line and lines[0].count(",") == lines[1].count(","):
         return "csv"

This reordering makes the guard check explicit and avoids the ternary expression entirely.

tests/test_interpreter.py (1)

178-185: Unused mock_cache setup creates misleading test code.

The mock_cache is created and passed to the constructor at lines 178-181, but immediately overwritten to None at line 185. This leaves the mock setup unused and makes the test logic confusing.

If the intent is to disable caching entirely for determinism, simplify by passing cache=None to the constructor directly:

♻️ Suggested simplification
-        mock_cache = Mock()
-        mock_cache.get_commands.return_value = None
-
-        interpreter = CommandInterpreter(api_key=self.api_key, provider="openai", cache=mock_cache)
+        interpreter = CommandInterpreter(api_key=self.api_key, provider="openai", cache=None)
         interpreter.client = mock_client
-        # Disable semantic cache to make this test deterministic even if a prior run
-        # populated a persistent cache on disk.
-        interpreter.cache = None
cortex/api_key_detector.py (1)

407-422: Security improvement approved, but update module docstring for consistency.

The guard logic is sound: checking CORTEX_DISABLE_CWD_DOTENV, ensuring HOME exists, and verifying CWD is under HOME prevents accidental pickup of .env files from untrusted directories. The ValueError fallback for is_relative_to() correctly handles cross-filesystem paths.

However, the module docstring (lines 16-17) still lists ".env in current directory" as detection item 9 unconditionally. Consider updating it to note this is conditional, e.g., "(only when under $HOME)".

📝 Suggested docstring update
 Detection order (highest priority first):
 1. CORTEX_PROVIDER=ollama environment variable (for explicit Ollama mode)
 2. API key environment variables: ANTHROPIC_API_KEY, OPENAI_API_KEY
 3. Encrypted storage (~/.cortex/environments/cortex.json) - secure Fernet-encrypted storage
 4. Cached key location (~/.cortex/.api_key_cache)
 5. Saved Ollama provider preference in ~/.cortex/.env (CORTEX_PROVIDER=ollama)
 6. API keys in ~/.cortex/.env
 7. ~/.config/anthropic/credentials.json (Claude CLI location)
 8. ~/.config/openai/credentials.json
-9. .env in current directory
+9. .env in current directory (only when CWD is under $HOME and not disabled via CORTEX_DISABLE_CWD_DOTENV)
cortex/branding.py (1)

14-14: Consider removing unused Optional import.

The file uses the modern X | None union syntax throughout (e.g., str | None on lines 144, 226, 296) rather than Optional[X]. The Optional import appears unused.

Proposed fix
-from typing import Optional
+# No typing imports needed - using built-in generics and union syntax
cortex/printer_setup.py (2)

223-242: Simplify printer state parsing for readability/maintenance.
The nested conditional at Line 223–231 is hard to scan and easy to mis-edit.

Proposed refactor
@@
                     name = parts[1]
-                    state = (
-                        "idle"
-                        if "is idle" in line
-                        else (
-                            "printing"
-                            if "printing" in line
-                            else "disabled" if "disabled" in line else "unknown"
-                        )
-                    )
+                    if "is idle" in line:
+                        state = "idle"
+                    elif "printing" in line:
+                        state = "printing"
+                    elif "disabled" in line:
+                        state = "disabled"
+                    else:
+                        state = "unknown"
 
                     devices.append(

347-410: Add dry_run parameter to setup_printer() to align with project guidelines.

The method performs a real system mutation via lpadmin without offering a preview-only mode. Per coding guidelines, "Use dry-run mode by default for all installation operations." Add a dry_run: bool = False parameter; when True, return the commands that would execute instead of applying them. This matches the pattern used across the codebase in installation_history.rollback(), config_manager.import_configuration(), updater.update(), and similar methods.

tests/test_conflict_ui.py (1)

15-29: Remove sys.path.insert() from test files for cleaner imports.
CI already installs the package in editable mode (pip install -e .), making the sys.path manipulation unnecessary. This pattern appears across 25+ test files and is acknowledged in the docs as fragile. Consider removing all occurrences when refactoring the test suite.

cortex/user_preferences.py (5)

58-86: Clarify the relationship between AISettings.model and LLMSettings.model.

Both AISettings (line 62) and LLMSettings (line 85) define a model field with different defaults (claude-sonnet-4 vs gpt-4). This could confuse users about which setting controls which behavior.

Consider either:

  1. Adding docstrings clarifying the distinct purposes of each setting, or
  2. Consolidating into a single location if they serve the same purpose

126-128: Consider setting restrictive permissions on the config directory.

When creating the config directory, the default permissions depend on the system umask. If the preferences file may contain sensitive data (e.g., conflict resolutions that reveal system packages), consider explicitly setting restrictive permissions.

config_dir.mkdir(parents=True, exist_ok=True, mode=0o700)

170-184: Handle unexpected keys in configuration data to avoid TypeError on load.

If the YAML file contains keys not present in the dataclass definitions (e.g., from manual editing or a newer version of Cortex), the **spread on lines 172-180 will pass them to the dataclass constructors, causing a TypeError.

Consider filtering to known fields:

# Example for ConfirmationSettings
known_fields = {f.name for f in dataclasses.fields(ConfirmationSettings)}
confirmations_data = {k: v for k, v in data.get("confirmations", {}).items() if k in known_fields}
confirmations=ConfirmationSettings(**confirmations_data),

Alternatively, catch TypeError and log a warning about unknown keys while falling back to defaults.


207-210: Consider handling backup creation failures gracefully.

If shutil.copy2 fails (e.g., due to permissions or disk space), the entire save() operation fails. Since the actual write uses an atomic pattern that protects the original file, a backup failure shouldn't necessarily abort the save.

🔧 Suggested fix
         # Create backup if file exists
         if self.config_path.exists():
             backup_path = self.config_path.with_suffix(".yaml.bak")
-            shutil.copy2(self.config_path, backup_path)
+            try:
+                shutil.copy2(self.config_path, backup_path)
+            except OSError as e:
+                print(f"[WARNING] Could not create backup: {e}")

414-419: Consider removing or gating the main block for production.

The if __name__ == "__main__" block is useful for development testing but writes to the actual user config directory (~/.config/cortex/). Consider either removing it before release or adding a note that it modifies real user preferences.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f678678 and 33c5be7.

📒 Files selected for processing (53)
  • .github/workflows/automation.yml
  • .gitignore
  • cortex/api_key_detector.py
  • cortex/benchmark.py
  • cortex/branding.py
  • cortex/cli.py
  • cortex/config_manager.py
  • cortex/context_memory.py
  • cortex/doctor.py
  • cortex/gpu_manager.py
  • cortex/health_score.py
  • cortex/install_parallel.py
  • cortex/installation_history.py
  • cortex/kernel_features/accelerator_limits.py
  • cortex/kernel_features/kv_cache_manager.py
  • cortex/licensing.py
  • cortex/llm_router.py
  • cortex/output_formatter.py
  • cortex/printer_setup.py
  • cortex/sandbox/docker_sandbox.py
  • cortex/sandbox/sandbox_executor.py
  • cortex/semver_resolver.py
  • cortex/stdin_handler.py
  • cortex/systemd_helper.py
  • cortex/update_checker.py
  • cortex/user_preferences.py
  • cortex/version_manager.py
  • cortex/wifi_driver.py
  • docs/IMPLEMENTATION_SUMMARY_ISSUE_42.md
  • requirements-dev.txt
  • tests/integration/test_end_to_end.py
  • tests/langchain_py313/test_basic_imports.py
  • tests/test_api_key_detector.py
  • tests/test_benchmark.py
  • tests/test_conflict_ui.py
  • tests/test_dependency_importer.py
  • tests/test_env_loader.py
  • tests/test_env_manager.py
  • tests/test_gpu_manager.py
  • tests/test_hardware_detection.py
  • tests/test_health_score.py
  • tests/test_interpreter.py
  • tests/test_network_config.py
  • tests/test_ollama_integration.py
  • tests/test_parallel_llm.py
  • tests/test_permission_manager.py
  • tests/test_printer_setup.py
  • tests/test_shell_env_analyzer.py
  • tests/test_stdin_handler.py
  • tests/test_systemd_helper.py
  • tests/test_update_checker.py
  • tests/test_updater.py
  • tests/test_wifi_driver.py
💤 Files with no reviewable changes (12)
  • cortex/kernel_features/accelerator_limits.py
  • cortex/doctor.py
  • tests/test_dependency_importer.py
  • cortex/kernel_features/kv_cache_manager.py
  • tests/test_env_manager.py
  • cortex/installation_history.py
  • tests/langchain_py313/test_basic_imports.py
  • cortex/context_memory.py
  • tests/test_network_config.py
  • tests/test_env_loader.py
  • cortex/sandbox/docker_sandbox.py
  • tests/test_permission_manager.py
✅ Files skipped from review due to trivial changes (8)
  • tests/test_shell_env_analyzer.py
  • cortex/update_checker.py
  • cortex/benchmark.py
  • tests/test_api_key_detector.py
  • cortex/systemd_helper.py
  • cortex/wifi_driver.py
  • tests/test_updater.py
  • tests/test_health_score.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • .github/workflows/automation.yml
  • tests/test_hardware_detection.py
  • cortex/sandbox/sandbox_executor.py
  • cortex/config_manager.py
  • requirements-dev.txt
  • cortex/install_parallel.py
  • tests/integration/test_end_to_end.py
  • cortex/llm_router.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide for Python code
Include type hints in Python code
Add docstrings for all public APIs in Python code
Use dry-run mode by default for all installation operations
Do not use silent sudo - require explicit user confirmation for privilege escalation
Implement Firejail sandboxing for execution of untrusted code
Log all operations to ~/.cortex/history.db for audit purposes

Files:

  • tests/test_ollama_integration.py
  • tests/test_update_checker.py
  • cortex/stdin_handler.py
  • cortex/version_manager.py
  • tests/test_conflict_ui.py
  • tests/test_gpu_manager.py
  • tests/test_benchmark.py
  • cortex/gpu_manager.py
  • tests/test_interpreter.py
  • cortex/health_score.py
  • tests/test_stdin_handler.py
  • tests/test_wifi_driver.py
  • cortex/semver_resolver.py
  • cortex/output_formatter.py
  • cortex/api_key_detector.py
  • cortex/branding.py
  • cortex/printer_setup.py
  • cortex/licensing.py
  • cortex/user_preferences.py
  • tests/test_printer_setup.py
  • tests/test_parallel_llm.py
  • cortex/cli.py
  • tests/test_systemd_helper.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain test coverage above 80% for pull requests

Files:

  • tests/test_ollama_integration.py
  • tests/test_update_checker.py
  • tests/test_conflict_ui.py
  • tests/test_gpu_manager.py
  • tests/test_benchmark.py
  • tests/test_interpreter.py
  • tests/test_stdin_handler.py
  • tests/test_wifi_driver.py
  • tests/test_printer_setup.py
  • tests/test_parallel_llm.py
  • tests/test_systemd_helper.py
🧠 Learnings (1)
📚 Learning: 2026-01-12T20:51:13.828Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-12T20:51:13.828Z
Learning: Applies to **/*.py : Log all operations to ~/.cortex/history.db for audit purposes

Applied to files:

  • .gitignore
🧬 Code graph analysis (10)
tests/test_update_checker.py (1)
cortex/update_checker.py (1)
  • UpdateCheckResult (102-111)
tests/test_conflict_ui.py (1)
cortex/cli.py (4)
  • InstallationCancelledError (61-62)
  • _resolve_conflicts_interactive (472-532)
  • _ask_save_preference (534-555)
  • config (557-632)
tests/test_gpu_manager.py (1)
cortex/gpu_manager.py (3)
  • GPUDevice (46-56)
  • GPUVendor (36-42)
  • GPUState (60-76)
tests/test_benchmark.py (1)
cortex/benchmark.py (2)
  • BenchmarkReport (55-78)
  • BenchmarkResult (44-51)
cortex/gpu_manager.py (3)
cortex/health_score.py (1)
  • _run_command (146-161)
cortex/printer_setup.py (1)
  • _run_command (106-114)
cortex/wifi_driver.py (1)
  • _run_command (193-208)
tests/test_interpreter.py (1)
tests/test_graceful_degradation.py (1)
  • cache (28-31)
cortex/health_score.py (3)
cortex/gpu_manager.py (1)
  • _run_command (131-139)
cortex/printer_setup.py (1)
  • _run_command (106-114)
cortex/wifi_driver.py (1)
  • _run_command (193-208)
tests/test_stdin_handler.py (1)
cortex/stdin_handler.py (1)
  • StdinData (37-50)
cortex/cli.py (2)
cortex/user_preferences.py (4)
  • format_preference_value (391-401)
  • get (231-250)
  • save (205-229)
  • get_all_settings (370-372)
cortex/first_run_wizard.py (1)
  • _print_error (778-780)
tests/test_systemd_helper.py (1)
cortex/systemd_helper.py (2)
  • ServiceConfig (104-123)
  • _run_systemctl (162-171)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
🔇 Additional comments (51)
tests/test_systemd_helper.py (4)

7-20: LGTM!

The import reorganization follows alphabetical ordering for unittest.mock members, and the new constant imports (FAILURE_SOLUTIONS, SERVICE_STATE_EXPLANATIONS, SUB_STATE_EXPLANATIONS) are properly used in the TestServiceStateExplanations and TestFailureSolutions test classes.


26-34: LGTM!

The single-line instantiation is clean and readable. The test adequately verifies the default values for ServiceConfig.


133-139: LGTM!

The single-line MagicMock instantiation is readable and the test properly verifies successful systemctl command execution.


141-149: LGTM!

The test correctly handles the timeout scenario. The local subprocess import for accessing TimeoutExpired is acceptable given it's only needed in this specific test.

tests/test_update_checker.py (2)

13-20: LGTM!

Import reordering is cosmetic and maintains proper alphabetical ordering. All imported symbols (CACHE_TTL_SECONDS, ReleaseInfo, UpdateChecker, UpdateCheckResult, check_for_updates, should_notify_update) are used throughout the test file.


98-116: LGTM!

The collapsed single-line patch is functionally equivalent and improves readability. The test fixture setup properly:

  • Uses a temporary directory for isolation
  • Patches both CACHE_DIR and UPDATE_CACHE_FILE
  • Cleans up patches and temp directory in tearDown
cortex/stdin_handler.py (4)

139-147: LGTM!

The MIDDLE truncation mode logic correctly preserves head and tail portions while inserting a clear truncation indicator. The single-line concatenation is readable and functionally equivalent.


151-156: LGTM!

The byte-limit truncation correctly uses errors="replace" to handle potential mid-character splits when slicing at arbitrary byte boundaries. This is an acceptable trade-off for size-limiting purposes.


234-236: LGTM!

The container log detection using any() with a generator expression is idiomatic and efficient.


238-240: LGTM!

The system log detection follows the same idiomatic pattern as container log detection.

tests/test_benchmark.py (4)

11-21: LGTM — Import ordering improvements.

The import reordering follows PEP 8 conventions with proper grouping (stdlib → third-party → local) and alphabetical ordering within blocks. No functional changes.


29-31: LGTM — Cleaner single-line formatting.

The reformatted BenchmarkResult initializations are readable and consistent with other single-line usages in the file.

Also applies to: 37-40


56-62: LGTM — Consistent formatting.

The BenchmarkReport initialization formatting is appropriate for each context — single-line where concise and multi-line where readability benefits.

Also applies to: 239-241


286-294: LGTM — Concise mock setup.

The single-line MagicMock configurations are clear and maintainable.

tests/test_ollama_integration.py (2)

53-55: LGTM! Clean refactor to use shutil.which.

Using shutil.which() is the idiomatic Python approach for checking executable availability—cleaner and more efficient than spawning a subprocess. This also properly utilizes the shutil import.


92-101: LGTM! Fixes the NameError by calling the correct function.

The function now correctly delegates to is_ollama_installed() instead of the undefined _ollama_installed(), which would have caused a runtime NameError.

cortex/api_key_detector.py (1)

399-405: LGTM!

Good refactor to extract Path.home() into a local variable, avoiding repeated calls. The explicit type annotation on locations improves readability.

cortex/licensing.py (4)

185-194: LGTM!

The blank line formatting in the decorator follows PEP 8 conventions for readability.


233-240: LGTM!

The reformatted print statement maintains correct ASCII box alignment with proper closing borders.


289-300: LGTM!

The inline json.dumps() call is clean and correctly serializes the license data. The structure includes all necessary fields for license validation.


326-330: LGTM!

The function correctly returns the hostname with proper type annotation. The lazy import of platform is acceptable for this utility function.

cortex/output_formatter.py (1)

14-14: LGTM!

The removal of List and Tuple from typing imports is correct — the file uses Python 3.9+ built-in generic types (list[...], tuple[...]) throughout.

tests/test_parallel_llm.py (1)

12-12: LGTM!

The removal of patch from imports is correct — the test file only uses Mock for creating mock objects and doesn't use patch anywhere.

cortex/version_manager.py (1)

17-17: LGTM!

This is a minor formatting adjustment adding a blank line before the UpdateChannel enum for better visual separation.

cortex/branding.py (1)

319-323: LGTM!

The multi-line reformatting of cx_warning improves readability without changing behavior.

tests/test_stdin_handler.py (4)

10-10: LGTM!

Import order is now alphabetical (MagicMock before patch), which is consistent with PEP 8 conventions.


191-192: LGTM!

Consistent string literal style.


341-341: LGTM!

Single-line StdinData initialization is more concise for simple test cases.


479-480: Good modernization.

Using OSError instead of IOError is correct — since Python 3.3, IOError is an alias for OSError. Using the canonical class name is the preferred approach.

tests/test_wifi_driver.py (1)

7-21: LGTM!

The import reordering and addition of BLUETOOTH_DRIVERS and DRIVER_DATABASE constants are appropriate. The tests at lines 113-129 properly validate these newly exported constants from the cortex.wifi_driver module.

tests/test_gpu_manager.py (4)

7-20: LGTM!

Import reordering and addition of APP_GPU_RECOMMENDATIONS and BATTERY_IMPACT constants are appropriate. These constants are properly tested in TestBatteryImpact (lines 110-123) and TestAppGPURecommendations (lines 125-140).


50-55: LGTM!

Single-line construction of GPUDevice maintains the same behavior and improves readability.


167-170: LGTM!

Condensed MagicMock initialization is cleaner and functionally equivalent.


294-309: LGTM!

The reformatted GPUState and GPUDevice constructions maintain identical behavior while improving code layout consistency.

.gitignore (2)

125-125: LGTM!

Adding .cursor/ to the IDE ignore section is appropriate for users of the Cursor editor.


185-189: LGTM!

The data file ignore pattern with negation (!data/contributors.json) correctly excludes all JSON/CSV files in the data/ directory while preserving contributors.json for version control.

cortex/health_score.py (4)

146-161: LGTM!

The _run_command method follows the established pattern used across other modules (gpu_manager.py, printer_setup.py, wifi_driver.py) with proper exception handling for TimeoutExpired, FileNotFoundError, and generic exceptions.


310-313: LGTM!

Single-line formatting for the _run_command call maintains the same behavior.


483-484: LGTM!

The condensed dict comprehension for factors is functionally equivalent and improves readability.


584-590: LGTM!

The single-line conditional expression for score_color is cleaner while maintaining the same logic.

cortex/semver_resolver.py (6)

143-147: LGTM!

The tilde constraint logic is correct. The condition properly enforces that for ~1.2.3:

  1. Line 145-146 rejects versions below the constraint (version < self.version)
  2. Line 147 ensures the version stays within the same major.minor range

203-229: LGTM!

The _constraints_compatible method signature reformatting maintains the same logic for detecting compatibility between version constraints.


401-422: LGTM!

Single-line signature formatting for add_dependency preserves functionality.


442-504: LGTM!

The suggest_resolutions method signature reformatting maintains the resolution strategy logic.


506-537: LGTM!

The _find_common_version_strategy signature reformatting preserves the heuristic logic for finding compatible versions.


699-700: LGTM!

Condensed console output maintains the same behavior.

tests/test_printer_setup.py (1)

7-20: LGTM: tests updated to validate newly-exported constants + minor cleanup.

Also applies to: 165-175

cortex/cli.py (1)

3259-3270: LGTM: CLI now accepts multi-package install args and passes them through cleanly.

Also applies to: 3847-3855

cortex/user_preferences.py (3)

305-329: LGTM on validation logic.

The validation method correctly checks bounds for max_suggestions (1-20), frequency_hours (≥1), and validates language codes. The enum validation is effectively handled by the type coercion in set() which raises PreferencesError for invalid enum values.


331-368: LGTM on export/import error handling.

Both export_json() and import_json() now have comprehensive error handling for file I/O and JSON parsing errors, with clear error messages wrapped in PreferencesError.


390-411: LGTM on CLI helper functions.

The format_preference_value and print_all_preferences functions provide clean formatting for different value types and integrate well with the YAML-based storage.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +466 to +471
def _get_prefs_manager(self) -> PreferencesManager:
"""Get preferences manager instance."""
if not hasattr(self, "_prefs_manager"):
self._prefs_manager = PreferencesManager()
return self._prefs_manager

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Harden _get_prefs_manager() against _prefs_manager = None / wrong type.
Right now, if something sets self._prefs_manager = None, you’ll return None and fail later.

Proposed fix
 def _get_prefs_manager(self) -> PreferencesManager:
     """Get preferences manager instance."""
-    if not hasattr(self, "_prefs_manager"):
-        self._prefs_manager = PreferencesManager()
+    if not isinstance(getattr(self, "_prefs_manager", None), PreferencesManager):
+        self._prefs_manager = PreferencesManager()
     return self._prefs_manager
🤖 Prompt for AI Agents
In `@cortex/cli.py` around lines 466 - 471, _get_prefs_manager currently returns
whatever is in self._prefs_manager even if it's None or the wrong type; change
it to validate the attribute: if self has no _prefs_manager or its value is None
or not an instance of PreferencesManager, instantiate PreferencesManager and
assign it to self._prefs_manager, then return it. Use
isinstance(self._prefs_manager, PreferencesManager) to check type and ensure a
valid PreferencesManager is always returned.

Comment on lines +190 to +212
def test_multiple_conflict_preferences(self):
"""Test saving and retrieving multiple conflict preferences."""
# Save multiple preferences (using min:max format)
resolutions = {
"apache2:nginx": "nginx",
"mariadb-server:mysql-server": "mariadb-server",
"emacs:vim": "vim",
}

for conflict, choice in resolutions.items():
self.prefs_manager.set(
"conflicts.saved_resolutions",
{**self.prefs_manager.get("conflicts.saved_resolutions"), conflict: choice},
)

self.prefs_manager.save()

# Verify all preferences were saved
saved = self.prefs_manager.get("conflicts.saved_resolutions")
for conflict, choice in resolutions.items():
self.assertIn(conflict, saved)
self.assertEqual(saved[conflict], choice)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against None when merging conflict-resolution dicts.
If self.prefs_manager.get("conflicts.saved_resolutions") can ever be None, Line 202 will throw.

Proposed fix
-                {**self.prefs_manager.get("conflicts.saved_resolutions"), conflict: choice},
+                {
+                    **(self.prefs_manager.get("conflicts.saved_resolutions") or {}),
+                    conflict: choice,
+                },
🤖 Prompt for AI Agents
In `@tests/test_conflict_ui.py` around lines 190 - 212,
test_multiple_conflict_preferences can raise if
self.prefs_manager.get("conflicts.saved_resolutions") returns None; when
building the merged dict in the loop use a safe default (e.g., treat None as
{}), so update the logic around prefs_manager.get("conflicts.saved_resolutions")
in test_multiple_conflict_preferences (and any similar usage) to coalesce None
to an empty dict before merging and then call prefs_manager.set(...) and
prefs_manager.save() as before.

Copy link
Collaborator

@Anshgrover23 Anshgrover23 left a comment

Choose a reason for hiding this comment

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

@Sahilbhatane Kindly resolve conflicts.

@Anshgrover23 Anshgrover23 marked this pull request as ready for review January 16, 2026 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Package Conflict Resolution UI

7 participants