Skip to content

Conversation

@kr16h
Copy link
Contributor

@kr16h kr16h commented Dec 12, 2025

Related Issue

Closes #252

Summary

  • Added stacks.json with mentioned stacks:
    • cortex stack ml / cortex stack ml-cpu – installs the ML stack; ml automatically falls back to ml-cpu on systems without a detected NVIDIA GPU
    • cortex stack webdev – installs the web development stack
    • cortex stack devops – installs the DevOps tooling stack
    • cortex stack data – installs the data analysis stack
  • Implemented StackManager to load, describe, and resolve hardware-aware stacks.
  • Added cortex stack CLI subcommands:
    • cortex stack --list - list all stacks
    • cortex stack --describe <name> - show stack details
    • cortex stack <name> --dry-run - preview packages that would be installed
    • cortex stack <name> - install the stack via the existing install flow

Demo

Checklist

  • Tests pass (pytest)
  • PR title format: [#252] Pre-built Smart Stacks
  • MVP label added

Summary by CodeRabbit

  • New Features

    • Curated package stacks for common workflows (Machine Learning, web, DevOps, data)
    • CLI command to list, describe, and install stacks with optional dry-run and per-package progress
    • Hardware-aware recommendations that suggest GPU vs CPU stacks and automatic suggestion/fallback
  • Tests

    • Added tests for hardware-aware stack recommendations
  • Documentation

    • User guide describing Smart Stacks, usage, and examples

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 12, 2025

Walkthrough

Adds a Smart Stacks feature: new StackManager with a stacks.json registry and hardware-aware suggestions, a cortex stack CLI command (list/describe/install/dry-run), unit tests for suggestion behavior, and documentation.

Changes

Cohort / File(s) Summary
Stack Manager Core
cortex/stack_manager.py
New StackManager class: loads/caches stacks.json, lists and finds stacks, returns package lists, suggests hardware-aware variants (queries has_nvidia_gpu), and renders human-readable descriptions with error handling.
CLI Integration
cortex/cli.py
Adds StackManager import, typing additions, new stack(self, args) command and handlers _handle_stack_list, _handle_stack_describe, _handle_stack_install, _handle_stack_dry_run, _handle_stack_real_install. Wires parser args --list, --describe, --dry-run, and stack name; integrates dry-run and install flows.
Stack Registry
cortex/stacks.json
New JSON registry defining stacks (ml, ml-cpu, webdev, devops, data) with ids, names, descriptions, package lists, and hardware target fields.
Tests
test/test_smart_stacks.py
New test covering StackManager.suggest_stack: monkeypatches GPU detector to assert "ml""ml-cpu" when GPU absent and "ml" when GPU present.
Docs
docs/smart_stack.md
New documentation describing Smart Stacks usage, available stacks, behavior (GPU detection and fallback), involved files, tests, and a demo reference.

Sequence Diagram(s)

sequenceDiagram
    participant User as User/CLI
    participant CLI as CortexCLI
    participant Stack as StackManager
    participant HW as HardwareDetector
    participant Installer as PackageInstaller

    User->>CLI: cortex stack ml [--list|--describe|--dry-run|install]
    CLI->>Stack: suggest_stack("ml")
    Stack->>HW: has_nvidia_gpu()
    alt GPU present
        HW-->>Stack: true
        Stack-->>CLI: "ml"
    else no GPU
        HW-->>Stack: false
        Stack-->>CLI: "ml-cpu"
    end
    CLI->>Stack: get_stack_packages(<suggested>)
    Stack-->>CLI: [packages]
    alt list/describe
        CLI-->>User: formatted list/description
    else dry-run
        CLI-->>User: preview packages (dry-run)
    else install
        CLI->>Installer: install(packages)
        Installer-->>CLI: per-package progress/results
        CLI-->>User: completion/status
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Inspect cortex/stack_manager.py for robust JSON loading, caching, and error handling for missing/invalid stacks.json.
  • Review suggest_stack and its dependency on the has_nvidia_gpu detection for edge cases and testability.
  • Verify CLI handlers in cortex/cli.py for argument validation, dry-run semantics, per-package progress, and error paths.
  • Check stacks.json content consistency with docs/tests and test monkeypatch correctness in test/test_smart_stacks.py.

"I'm a small rabbit, hopping through the trees,
I sniff out stacks with a curious breeze.
GPU or not, I nudge the way,
Packages lined up, ready to play.
🐇📦✨"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.22% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature added: Smart Stacks functionality for ML, Web, and DevOps with issue reference.
Description check ✅ Passed The PR description covers summary, implementation details, demo, and mostly completes the checklist; all critical sections are present.
Linked Issues check ✅ Passed The PR implementation meets all core requirements: stacks.json with curated packages, StackManager for loading/describing stacks, CLI commands (--list, --describe, install, --dry-run), and hardware-aware ml/ml-cpu variants.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the Smart Stacks feature specified in issue #252; no unrelated modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@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: 5

🧹 Nitpick comments (3)
cortex/stacks.json (1)

10-16: Inconsistency: ml-cpu missing matplotlib.

The GPU variant includes matplotlib but the CPU variant does not. Consider adding it for consistency unless there's a specific reason to omit it.

     {
       "id": "ml-cpu",
       "name": "Machine Learning (CPU)",
       "description": "PyTorch CPU-only version",
-      "packages": ["pytorch-cpu", "jupyter", "numpy", "pandas"],
+      "packages": ["pytorch-cpu", "jupyter", "numpy", "pandas", "matplotlib"],
       "hardware": "cpu"
     },
cortex/stack_manager.py (2)

45-58: Consider more specific type hints.

Both methods work correctly. Optional improvement: use Dict[str, Any] instead of bare Dict for more precise type hints.

-    def find_stack(self, stack_id: str) -> Optional[Dict]:
+    def find_stack(self, stack_id: str) -> Optional[Dict[str, Any]]:
         """Find a stack by ID"""

Add the import at the top:

-from typing import Dict, List, Optional
+from typing import Any, Dict, List, Optional

60-72: Consider making hardware suggestions more extensible.

The current hardcoded approach works for the "ml" stack but isn't easily extensible to other hardware-aware stacks in the future. Consider using the "hardware" field from stacks.json for more data-driven logic.

Example of a more extensible approach:

def suggest_stack(self, base_stack: str) -> str:
    """
    Suggest appropriate variant based on hardware.
    E.g., if user asks for 'ml' but has no GPU, suggest 'ml-cpu'
    """
    stack = self.find_stack(base_stack)
    if not stack:
        return base_stack
    
    # If stack requires GPU but none detected, look for CPU variant
    if stack.get("hardware") == "gpu" and not has_nvidia_gpu():
        cpu_variant = f"{base_stack}-cpu"
        if self.find_stack(cpu_variant):
            return cpu_variant
    
    return base_stack

This approach would automatically handle any future GPU/CPU stack pairs without hardcoding.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a1755c and fed0072.

📒 Files selected for processing (4)
  • cortex/cli.py (5 hunks)
  • cortex/stack_manager.py (1 hunks)
  • cortex/stacks.json (1 hunks)
  • test/test_smart_stacks.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • test/test_smart_stacks.py
  • cortex/stack_manager.py
  • cortex/cli.py
🧬 Code graph analysis (3)
test/test_smart_stacks.py (1)
cortex/stack_manager.py (2)
  • StackManager (18-93)
  • suggest_stack (60-72)
cortex/stack_manager.py (2)
cortex/hardware_detection.py (1)
  • has_nvidia_gpu (668-670)
cortex/cli.py (1)
  • stack (180-252)
cortex/cli.py (1)
cortex/stack_manager.py (5)
  • StackManager (18-93)
  • list_stacks (40-43)
  • describe_stack (74-93)
  • suggest_stack (60-72)
  • find_stack (45-51)
🔇 Additional comments (10)
test/test_smart_stacks.py (1)

1-2: LGTM!

The dual import pattern is correct: line 1 imports the class for instantiation, while line 2 imports the module to enable monkeypatching has_nvidia_gpu at module scope.

cortex/cli.py (2)

43-43: LGTM!

The import is correctly placed and follows the existing pattern.


696-701: LGTM!

The argument parser configuration is correct: optional positional name allows --list and --describe to work without specifying a stack, and all flags are properly defined.

cortex/stack_manager.py (4)

1-8: LGTM!

Excellent module-level documentation with clear usage examples.


10-15: LGTM!

Imports are clean and all are used within the module.


40-43: LGTM!

Clean implementation with defensive programming using .get() with a default value.


74-93: LGTM with optional style improvement.

The method correctly formats stack descriptions with good error handling. The string concatenation approach works fine for this use case.

Optional: For slightly better performance with many packages, consider using a list and '\n'.join():

def describe_stack(self, stack_id: str) -> str:
    """Get formatted stack description"""
    stack = self.find_stack(stack_id)
    if not stack:
        return f"Stack '{stack_id}' not found"
    
    lines = [
        f"\n📦 Stack: {stack['name']}",
        f"Description: {stack['description']}\n",
        "Packages included:"
    ]
    
    for idx, pkg in enumerate(stack.get("packages", []), 1):
        lines.append(f"  {idx}. {pkg}")
    
    tags = stack.get("tags", [])
    if tags:
        lines.append(f"\nTags:  {', '.join(tags)}")
    
    hardware = stack.get("hardware", "any")
    lines.append(f"Hardware: {hardware}")
    
    return '\n'.join(lines) + '\n'
cortex/stacks.json (3)

24-37: Package names in stacks are handled by AI interpretation, not used directly.

The stack installation flow (lines 233-245 of cli.py) passes each package through self.install(), which uses CommandInterpreter to parse the package name as natural language and generate appropriate commands via Claude/OpenAI. This means:

  • "docker" → AI translates to "docker-ce", "docker.io", or distro-appropriate variant
  • "pandas", "sqlalchemy" → AI knows these are Python libraries and generates pip/apt commands accordingly
  • The system is designed to handle distro-specific names and package type variations

Your concerns about distro-specific names and mixed package types are already addressed by the architecture.


3-9: Verify CommandInterpreter correctly handles "pytorch" and "cuda" package name interpretation.

The stack installation flow relies on an LLM-based CommandInterpreter to convert semantic package names ("pytorch", "cuda") into appropriate bash install commands. While this approach is flexible, there's risk that the LLM may generate incorrect commands:

  • "pytorch": LLM might generate apt install pytorch (incorrect) instead of pip install torch
  • "cuda": LLM might generate apt install cuda (incorrect) instead of the actual package like nvidia-cuda-toolkit

The system prompt emphasizes apt-based installation but lacks concrete examples for Python packages. Consider:

  1. Adding explicit examples to the system prompt showing how to handle Python packages vs system packages
  2. Testing that actual LLM responses for "install pytorch" and "install cuda" produce correct commands
  3. Alternatively, replace semantic package names with specific package manager identifiers (e.g., "torch" instead of "pytorch")

17-23: No changes needed. Both package listings are correct for Ubuntu 22.04+ / Debian 12+:

  • npm: Not bundled with nodejs in official Debian/Ubuntu repositories; must be listed separately as a distinct package.
  • postgresql: Correct metapackage name that resolves to the appropriate version (postgresql-15 on Debian 12, postgresql-14 on Ubuntu 22.04).

cortex/cli.py Outdated
Comment on lines 180 to 252
def stack(self, args):
manager = StackManager()

# List stacks (default when no name/describe)
if args.list or (not args.name and not args.describe):
stacks = manager.list_stacks()
cx_print("\n📦 Available Stacks:\n", "info")
for stack in stacks:
pkg_count = len(stack.get("packages", []))
console.print(f" [green]{stack['id']}[/green]")
console.print(f" {stack['name']}")
console.print(f" {stack['description']}")
console.print(f" [dim]({pkg_count} packages)[/dim]\n")
cx_print("Use: cortex stack <name> to install a stack", "info")
return 0

# Describe a specific stack
if args.describe:
description = manager.describe_stack(args.describe)
console.print(description)
return 0

# Install a stack
if args.name:
# Hardware-aware suggestion
original_name = args.name
suggested_name = manager.suggest_stack(args.name)

if suggested_name != original_name:
cx_print(
f"💡 No GPU detected, using '{suggested_name}' instead of '{original_name}'",
"info"
)

stack = manager.find_stack(suggested_name)
if not stack:
self._print_error(
f"Stack '{suggested_name}' not found. Use --list to see available stacks."
)
return 1

packages = stack.get("packages", [])

# Dry run mode
if args.dry_run:
cx_print(f"\n📋 Stack: {stack['name']}", "info")
console.print("\nPackages that would be installed:")
for pkg in packages:
console.print(f" • {pkg}")
console.print(f"\nTotal: {len(packages)} packages")
cx_print("\nDry run only - no commands executed", "warning")
return 0

# Real install: delegate to existing install() per package
cx_print(f"\n🚀 Installing stack: {stack['name']}\n", "success")
total = len(packages)

for idx, pkg in enumerate(packages, 1):
cx_print(f"[{idx}/{total}] Installing {pkg}...", "info")
# Use the existing install flow with execution enabled
result = self.install(pkg, execute=True, dry_run=False)
if result != 0:
self._print_error(
f"Failed to install {pkg} from stack '{stack['name']}'"
)
return 1

self._print_success(f"\n✅ Stack '{stack['name']}' installed successfully!")
console.print(f"Installed {len(packages)} packages")
return 0

self._print_error("No stack name provided. Use --list to see available stacks.")
return 1
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the stack() method definition to check type hints and docstring
rg -n -A 15 'def stack\(self, args' cortex/cli.py

Repository: cortexlinux/cortex

Length of output: 848


🏁 Script executed:

# Check the self.install() method to understand the flow
rg -n -A 20 'def install\(' cortex/cli.py

Repository: cortexlinux/cortex

Length of output: 804


🏁 Script executed:

# Search for CommandInterpreter class and parse method
fd -e py | xargs rg -l 'class CommandInterpreter'

Repository: cortexlinux/cortex

Length of output: 80


🏁 Script executed:

# Check for any existing batch processing in the codebase
rg -n 'batch' --type py -i

Repository: cortexlinux/cortex

Length of output: 777


🏁 Script executed:

# Check CommandInterpreter class and parse method
rg -n -A 15 'class CommandInterpreter' LLM/interpreter.py

Repository: cortexlinux/cortex

Length of output: 566


🏁 Script executed:

# Look for parse method in CommandInterpreter
rg -n -A 20 'def parse' LLM/interpreter.py | head -50

Repository: cortexlinux/cortex

Length of output: 1296


🏁 Script executed:

# Check if install() method calls CommandInterpreter or parser
rg -n -A 30 'def install\(self, software:' cortex/cli.py | head -60

Repository: cortexlinux/cortex

Length of output: 1196


🏁 Script executed:

# Search for CommandInterpreter usage in cli.py
rg -n 'CommandInterpreter' cortex/cli.py

Repository: cortexlinux/cortex

Length of output: 196


🏁 Script executed:

# Check imports at top of cortex/cli.py to see if argparse is imported
head -30 cortex/cli.py

Repository: cortexlinux/cortex

Length of output: 851


🏁 Script executed:

# Verify the exact line where stack() method is defined and what follows
sed -n '179,195p' cortex/cli.py

Repository: cortexlinux/cortex

Length of output: 839


🏁 Script executed:

# Check how many packages are typically in stacks to understand the impact
rg -n 'packages.*:' --type py -A 2 | grep -i stack | head -20

Repository: cortexlinux/cortex

Length of output: 486


Add type hints and docstring; address LLM overhead from per-package installation.

The method is missing type hints and docstring (coding guideline violations). Additionally, lines 237–245 call self.install() for each package individually, which creates a new CommandInterpreter instance per call, triggering a separate LLM API invocation per package. This is expensive and slow for multi-package stacks.

Type hints and docstring (required by coding guidelines):

+    def stack(self, args: argparse.Namespace) -> int:
+        """
+        Handle 'cortex stack' commands for pre-built package stacks.
+        
+        Supports listing, describing, and installing hardware-aware stacks.
+        
+        Args:
+            args: Parsed command-line arguments containing stack options
+            
+        Returns:
+            Exit code (0 for success, 1 for failure)
+        """
-    def stack(self, args):
         manager = StackManager()

LLM overhead: Each package in the install loop (line 240) calls self.install(pkg, execute=True, dry_run=False), which instantiates CommandInterpreter (line 278 in install()) and invokes parse(), making a separate LLM API call. For a 6-package stack, this means 6 API calls. Consider batching packages into a single request or caching/pre-generating commands for known stack packages to reduce cost and latency.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def stack(self, args):
manager = StackManager()
# List stacks (default when no name/describe)
if args.list or (not args.name and not args.describe):
stacks = manager.list_stacks()
cx_print("\n📦 Available Stacks:\n", "info")
for stack in stacks:
pkg_count = len(stack.get("packages", []))
console.print(f" [green]{stack['id']}[/green]")
console.print(f" {stack['name']}")
console.print(f" {stack['description']}")
console.print(f" [dim]({pkg_count} packages)[/dim]\n")
cx_print("Use: cortex stack <name> to install a stack", "info")
return 0
# Describe a specific stack
if args.describe:
description = manager.describe_stack(args.describe)
console.print(description)
return 0
# Install a stack
if args.name:
# Hardware-aware suggestion
original_name = args.name
suggested_name = manager.suggest_stack(args.name)
if suggested_name != original_name:
cx_print(
f"💡 No GPU detected, using '{suggested_name}' instead of '{original_name}'",
"info"
)
stack = manager.find_stack(suggested_name)
if not stack:
self._print_error(
f"Stack '{suggested_name}' not found. Use --list to see available stacks."
)
return 1
packages = stack.get("packages", [])
# Dry run mode
if args.dry_run:
cx_print(f"\n📋 Stack: {stack['name']}", "info")
console.print("\nPackages that would be installed:")
for pkg in packages:
console.print(f" • {pkg}")
console.print(f"\nTotal: {len(packages)} packages")
cx_print("\nDry run only - no commands executed", "warning")
return 0
# Real install: delegate to existing install() per package
cx_print(f"\n🚀 Installing stack: {stack['name']}\n", "success")
total = len(packages)
for idx, pkg in enumerate(packages, 1):
cx_print(f"[{idx}/{total}] Installing {pkg}...", "info")
# Use the existing install flow with execution enabled
result = self.install(pkg, execute=True, dry_run=False)
if result != 0:
self._print_error(
f"Failed to install {pkg} from stack '{stack['name']}'"
)
return 1
self._print_success(f"\n✅ Stack '{stack['name']}' installed successfully!")
console.print(f"Installed {len(packages)} packages")
return 0
self._print_error("No stack name provided. Use --list to see available stacks.")
return 1
def stack(self, args: argparse.Namespace) -> int:
"""
Handle 'cortex stack' commands for pre-built package stacks.
Supports listing, describing, and installing hardware-aware stacks.
Args:
args: Parsed command-line arguments containing stack options
Returns:
Exit code (0 for success, 1 for failure)
"""
manager = StackManager()
# List stacks (default when no name/describe)
if args.list or (not args.name and not args.describe):
stacks = manager.list_stacks()
cx_print("\n📦 Available Stacks:\n", "info")
for stack in stacks:
pkg_count = len(stack.get("packages", []))
console.print(f" [green]{stack['id']}[/green]")
console.print(f" {stack['name']}")
console.print(f" {stack['description']}")
console.print(f" [dim]({pkg_count} packages)[/dim]\n")
cx_print("Use: cortex stack <name> to install a stack", "info")
return 0
# Describe a specific stack
if args.describe:
description = manager.describe_stack(args.describe)
console.print(description)
return 0
# Install a stack
if args.name:
# Hardware-aware suggestion
original_name = args.name
suggested_name = manager.suggest_stack(args.name)
if suggested_name != original_name:
cx_print(
f"💡 No GPU detected, using '{suggested_name}' instead of '{original_name}'",
"info"
)
stack = manager.find_stack(suggested_name)
if not stack:
self._print_error(
f"Stack '{suggested_name}' not found. Use --list to see available stacks."
)
return 1
packages = stack.get("packages", [])
# Dry run mode
if args.dry_run:
cx_print(f"\n📋 Stack: {stack['name']}", "info")
console.print("\nPackages that would be installed:")
for pkg in packages:
console.print(f" • {pkg}")
console.print(f"\nTotal: {len(packages)} packages")
cx_print("\nDry run only - no commands executed", "warning")
return 0
# Real install: delegate to existing install() per package
cx_print(f"\n🚀 Installing stack: {stack['name']}\n", "success")
total = len(packages)
for idx, pkg in enumerate(packages, 1):
cx_print(f"[{idx}/{total}] Installing {pkg}...", "info")
# Use the existing install flow with execution enabled
result = self.install(pkg, execute=True, dry_run=False)
if result != 0:
self._print_error(
f"Failed to install {pkg} from stack '{stack['name']}'"
)
return 1
self._print_success(f"\n✅ Stack '{stack['name']}' installed successfully!")
console.print(f"Installed {len(packages)} packages")
return 0
self._print_error("No stack name provided. Use --list to see available stacks.")
return 1

Comment on lines +5 to +12
def test_suggest_stack_ml_gpu_and_cpu(monkeypatch):
manager = StackManager()

monkeypatch.setattr(stack_manager, "has_nvidia_gpu", lambda: False)
assert manager.suggest_stack("ml") == "ml-cpu"

monkeypatch.setattr(stack_manager, "has_nvidia_gpu", lambda: True)
assert manager.suggest_stack("ml") == "ml" No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add type hints, docstring, and expand test coverage.

The test logic is correct but has several areas for improvement:

  1. Missing type hint for the monkeypatch parameter (PEP 8 guideline).
  2. Missing docstring explaining test purpose (coding guideline).
  3. Limited coverage — only tests the "ml" stack. Consider testing that non-ml stacks (e.g., "webdev", "devops") are returned unchanged by suggest_stack.

Apply this diff to add type hints and docstring:

+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from _pytest.monkeypatch import MonkeyPatch
+
 from cortex.stack_manager import StackManager
 import cortex.stack_manager as stack_manager


-def test_suggest_stack_ml_gpu_and_cpu(monkeypatch):
+def test_suggest_stack_ml_gpu_and_cpu(monkeypatch: "MonkeyPatch") -> None:
+    """Test hardware-aware ML stack suggestion based on GPU presence."""
     manager = StackManager()

     monkeypatch.setattr(stack_manager, "has_nvidia_gpu", lambda: False)
     assert manager.suggest_stack("ml") == "ml-cpu"

     monkeypatch.setattr(stack_manager, "has_nvidia_gpu", lambda: True)
     assert manager.suggest_stack("ml") == "ml"

Additionally, consider adding a test for non-ml stacks to verify they're returned unchanged:

def test_suggest_stack_non_ml_unchanged(monkeypatch: "MonkeyPatch") -> None:
    """Test that non-ML stacks are returned unchanged regardless of GPU."""
    manager = StackManager()
    
    # Test with GPU absent
    monkeypatch.setattr(stack_manager, "has_nvidia_gpu", lambda: False)
    assert manager.suggest_stack("webdev") == "webdev"
    assert manager.suggest_stack("devops") == "devops"
    assert manager.suggest_stack("data") == "data"
    
    # Test with GPU present
    monkeypatch.setattr(stack_manager, "has_nvidia_gpu", lambda: True)
    assert manager.suggest_stack("webdev") == "webdev"

As per coding guidelines, type hints and docstrings are required for Python code.

🤖 Prompt for AI Agents
In test/test_smart_stacks.py around lines 5 to 12, the test lacks a type hint
for the monkeypatch parameter and a docstring, and test coverage is limited to
only the "ml" stack; update the existing test function to add a MonkeyPatch type
hint for the monkeypatch parameter and a short docstring describing the test
purpose, and add a new test function (with type hints and docstring) that
verifies non-ML stacks like "webdev", "devops", and "data" are returned
unchanged both when has_nvidia_gpu is False and when it is True by using
monkeypatch.setattr to toggle GPU presence and asserting suggest_stack returns
the original stack names.

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

♻️ Duplicate comments (1)
cortex/cli.py (1)

179-252: Add type hints + docstring, and handle missing/invalid stacks.json gracefully.

This is a public CLI API and currently violates the project guideline (“Type hints required” + “Docstrings required for all public APIs”). Also, StackManager.load_stacks() can raise FileNotFoundError / ValueError; catching these here would avoid the generic top-level “Unexpected error”.

-    #Handle 'cortex stack' commands using StackManager
-    def stack(self, args):
+    # Handle 'cortex stack' commands using StackManager
+    def stack(self, args: argparse.Namespace) -> int:
+        """Handle `cortex stack` commands (list/describe/install/dry-run)."""
         manager = StackManager()
+
+        try:
+            # List stacks (default when no name/describe)
+            if args.list or (not args.name and not args.describe):
+                stacks = manager.list_stacks()
+                ...
+                return 0
+        except (FileNotFoundError, ValueError) as e:
+            self._print_error(str(e))
+            return 1

Per-package install triggers repeated planning overhead; consider batching/caching.

🧹 Nitpick comments (1)
cortex/cli.py (1)

696-702: Prefer mutually-exclusive args for stack (--list vs --describe vs install).

Consider an argparse mutually exclusive group so users can’t pass conflicting combinations (e.g., cortex stack ml --describe webdev), and the handler logic can be simplified.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fed0072 and 405ebfe.

📒 Files selected for processing (2)
  • cortex/cli.py (5 hunks)
  • cortex/stack_manager.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cortex/stack_manager.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (1)
cortex/stack_manager.py (5)
  • StackManager (18-93)
  • list_stacks (40-43)
  • describe_stack (74-93)
  • suggest_stack (60-72)
  • find_stack (45-51)
🔇 Additional comments (3)
cortex/cli.py (3)

43-43: Import is fine; consider catching StackManager load errors at call sites.


731-734: Dispatch wiring for stack looks correct.


745-746: sys.exit(main()) entrypoint is standard and fine.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 405ebfe and c821864.

📒 Files selected for processing (1)
  • docs/smart_stack.md (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
docs/smart_stack.md

79-79: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (1)
docs/smart_stack.md (1)

1-85: Documentation quality is solid and aligns with PR objectives.

The documentation accurately describes the Smart Stacks feature, stack definitions, usage examples, and integration points. All five stacks (ml, ml-cpu, webdev, devops, data) and their package lists match the PR requirements. The "How It Works" section correctly describes the hardware-aware fallback mechanism (GPU detection → ml-cpu) and the dry-run capability. File references (stacks.json, stack_manager.py, cli.py, test_smart_stacks.py) align with the implementation scope.

## Demo Video

Video walkthrough:
https://drive.google.com/file/d/1MkXEWCsVUmbzXbKyKnETfNaOZKZ3mnld/view?usp=sharing
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

Wrap bare URL in markdown link syntax.

The bare URL should be wrapped to comply with markdown standards.

Apply this diff to wrap the URL properly:

-https://drive.google.com/file/d/1MkXEWCsVUmbzXbKyKnETfNaOZKZ3mnld/view?usp=sharing
+[Video walkthrough](https://drive.google.com/file/d/1MkXEWCsVUmbzXbKyKnETfNaOZKZ3mnld/view?usp=sharing)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
https://drive.google.com/file/d/1MkXEWCsVUmbzXbKyKnETfNaOZKZ3mnld/view?usp=sharing
[Video walkthrough](https://drive.google.com/file/d/1MkXEWCsVUmbzXbKyKnETfNaOZKZ3mnld/view?usp=sharing)
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

79-79: Bare URL used

(MD034, no-bare-urls)

🤖 Prompt for AI Agents
In docs/smart_stack.md around line 79, a bare Google Drive URL is present and
should be wrapped in Markdown link syntax; replace the plain URL with a Markdown
link (e.g., [Descriptive
text](https://drive.google.com/file/d/1MkXEWCsVUmbzXbKyKnETfNaOZKZ3mnld/view?usp=sharing))
using a short, descriptive label relevant to the linked content.

@kr16h kr16h marked this pull request as draft December 12, 2025 16:31
@kr16h kr16h marked this pull request as ready for review December 12, 2025 17:08
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: 3

♻️ Duplicate comments (2)
cortex/cli.py (2)

245-262: Per-package self.install(...) likely triggers multiple LLM calls (cost/latency)
This repeats the earlier concern: each loop iteration calls install() which instantiates CommandInterpreter and parses separately. Consider batching into a single plan (one LLM call) for the whole stack, or adding an install_many(packages=[...]) path.


741-744: Fix inconsistent indentation in main() dispatch (regressed)
This was previously flagged and appears reintroduced; it’s not PEP8 and may fail stricter linters.

-        elif args.command == 'stack':
-                    return cli.stack(args)
+        elif args.command == 'stack':
+            return cli.stack(args)
🧹 Nitpick comments (4)
cortex/cli.py (4)

6-6: Tighten typing + remove trailing whitespace in StackManager import
Dict without key/value types bleeds Any implicitly; also Line 43 has trailing whitespace (often trips linters).

-from typing import List, Optional, Dict
+from typing import Any, Dict, List, Optional
@@
-from cortex.stack_manager import StackManager 
+from cortex.stack_manager import StackManager

Also applies to: 43-43


195-206: Guard against malformed stacks.json entries to avoid KeyError
stack['id'], stack['name'], stack['description'] will crash if the config is missing fields. Since this is CLI-facing, consider defensive .get(...) with a fallback.


236-244: Type stack more precisely than Dict
stack: Dict loses all structure. Prefer Mapping[str, Any] or a TypedDict for id/name/description/packages/tags/hardware.


706-712: Make stack CLI flags mutually exclusive (or validate combinations)
As-is, users can pass conflicting combinations like cortex stack ml --list. Consider an argparse mutually-exclusive group for --list vs --describe vs “install by name”, or validate in stack().

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c821864 and 6f05370.

📒 Files selected for processing (1)
  • cortex/cli.py (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (1)
cortex/stack_manager.py (5)
  • StackManager (18-93)
  • list_stacks (40-43)
  • describe_stack (74-93)
  • suggest_stack (60-72)
  • find_stack (45-51)
🪛 GitHub Check: SonarCloud Code Analysis
cortex/cli.py

[warning] 189-189: Fix this condition that always evaluates to true.

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

🔇 Additional comments (1)
cortex/cli.py (1)

755-756: sys.exit(main()) wiring looks good
No issues.

@kr16h kr16h marked this pull request as draft December 12, 2025 17:37
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 (3)
cortex/cli.py (3)

254-254: Type hint requires missing import.

Dict[str, Any] requires Any to be imported from typing. See comment on line 6 for the fix.


265-265: Type hint requires missing import.

Dict[str, Any] requires Any to be imported from typing. See comment on line 6 for the fix.


270-278: LLM overhead: per-package installation triggers multiple API calls.

The loop at lines 270-278 calls self.install(pkg) for each package individually. Each call instantiates a new CommandInterpreter (line 310 in the install method), which invokes parse() and makes a separate LLM API call. For a 6-package stack, this results in 6 LLM API calls, significantly increasing cost and latency.

This issue was flagged in previous reviews and remains unaddressed. Consider:

  • Batching: Pass all packages to a single install() call or create a batch installation method
  • Pre-generation: For known stack packages, pre-generate and cache commands to avoid LLM calls entirely
  • Coordinator enhancement: Extend InstallationCoordinator to handle package lists directly

Based on past review feedback, this architectural issue should be addressed to improve user experience and reduce API costs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f05370 and a602b9f.

📒 Files selected for processing (1)
  • cortex/cli.py (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (2)
cortex/stack_manager.py (5)
  • StackManager (18-93)
  • list_stacks (40-43)
  • find_stack (45-51)
  • describe_stack (74-93)
  • suggest_stack (60-72)
cortex/branding.py (1)
  • cx_print (55-75)
🔇 Additional comments (6)
cortex/cli.py (6)

179-197: Good implementation of stack command routing.

The method correctly validates arguments, handles the list/describe/install flows, and delegates to focused helper methods. Type hints and docstring are present per coding guidelines.


200-211: Clean implementation of stack listing.

Proper type hints, docstring, and user-friendly output with package counts.


214-222: Proper error handling for missing stacks.

Correctly returns non-zero exit code when stack is not found, addressing previous review feedback.


225-251: Well-structured stack installation handler.

Hardware-aware suggestion logic provides good UX, and empty package validation prevents silent failures. Addresses previous review feedback.


728-733: Well-structured argparse configuration for stack command.

Clear help text and appropriate argument structure for all stack subcommand options (list, describe, install, dry-run).


764-765: Proper command dispatch for stack subcommand.

Correctly routes stack commands to the handler with proper indentation and argument passing.

@kr16h kr16h marked this pull request as ready for review December 12, 2025 17:42
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: 3

♻️ Duplicate comments (1)
cortex/cli.py (1)

265-283: Per-package self.install(...) will trigger multiple LLM plans; consider batching/caching.
This repeats the earlier concern: each package likely incurs a separate CommandInterpreter.parse() call, which is slow/expensive for multi-package stacks.

🧹 Nitpick comments (3)
cortex/cli.py (3)

200-212: List output looks good; consider a clear message for “no stacks configured”.
If stacks.json exists but contains zero stacks, the command currently prints a header then nothing—maybe print “No stacks available” for clarity.


225-252: Make _handle_stack_install defensively reject missing args.name.
Today it’s logically unreachable (because stack() routes earlier), but it prevents accidental misuse if _handle_stack_install() is called from elsewhere and also avoids “Optional[str] passed where str expected” issues.

 def _handle_stack_install(self, manager: StackManager, args: argparse.Namespace) -> int:
     """Install a stack with optional hardware-aware selection."""
+    if not args.name:
+        self._print_error("No stack name provided. Use --list to see available stacks.")
+        return 1
     original_name = args.name
     suggested_name = manager.suggest_stack(args.name)

728-734: Argparse wiring looks correct; consider mutual-exclusion + add stack to show_rich_help().
Right now users can provide name + --list together; a mutually-exclusive group for --list/--describe reduces ambiguous invocations. Also, the Rich help table currently doesn’t advertise the new stack command.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a602b9f and 089d4d1.

📒 Files selected for processing (1)
  • cortex/cli.py (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧠 Learnings (1)
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to **/*.py : Type hints required in Python code

Applied to files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (2)
cortex/stack_manager.py (5)
  • StackManager (18-93)
  • list_stacks (40-43)
  • find_stack (45-51)
  • describe_stack (74-93)
  • suggest_stack (60-72)
cortex/branding.py (1)
  • cx_print (55-75)
🔇 Additional comments (4)
cortex/cli.py (4)

6-6: Good: Any is now imported, fixing runtime/type-hint resolution.
This addresses the earlier Dict[str, Any] annotation issue cleanly. As per coding guidelines, type hints must be valid.


214-223: Good: missing stack returns non-zero and messaging is consistent.
This is scripting-friendly and matches other CLI error patterns.


254-263: Dry-run UX is clear and scoped (no side effects).
Output is concise and matches the CLI intent.


778-778: Entry-point exit wiring is correct.

cortex/cli.py Outdated
Comment on lines 763 to 766

elif args.command == 'stack':
return cli.stack(args)

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

Trim whitespace-only line(s) around dispatch.
There appears to be stray whitespace on the blank line(s) near the new stack dispatch; worth removing to avoid lint noise.

🤖 Prompt for AI Agents
In cortex/cli.py around lines 763 to 766 there are stray whitespace-only blank
line(s) surrounding the new `stack` command dispatch; remove the extra blank
line(s) so the dispatch block reads compactly (no trailing/leading
whitespace-only lines) to satisfy linting and keep consistent formatting.

@sonarqubecloud
Copy link

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

♻️ Duplicate comments (2)
cortex/cli.py (2)

43-43: Verify trailing whitespace removal on import line.

A past review flagged trailing whitespace on this import. Ensure no trailing space exists after StackManager.

#!/bin/bash
# Check for trailing whitespace on line 43
sed -n '43p' cortex/cli.py | cat -A | grep -E '\s+\$$' && echo "Trailing whitespace found" || echo "No trailing whitespace"

274-291: Note: Per-package LLM overhead remains.

Each self.install(pkg) call instantiates a new CommandInterpreter and makes a separate LLM API call. For a 6-package stack, this results in 6 API calls with associated latency and cost. This was flagged in a previous review. Consider batching packages or pre-generating commands for known stack packages in a future iteration.

🧹 Nitpick comments (4)
cortex/cli.py (4)

199-207: Include original error details in exception messages.

The exception handlers provide generic messages but lose the original error context. Consider including str(e) in the error message for better debugging.

         except FileNotFoundError:
-            self._print_error("stacks.json not found. Make sure it exists in the expected location.")
+            self._print_error("stacks.json not found. Ensure cortex/stacks.json exists.")
             return 1
-        except ValueError:
-            self._print_error("stacks.json is invalid or malformed.")
+        except ValueError as e:
+            self._print_error(f"stacks.json is invalid or malformed: {e}")
             return 1
-
-        
-

Also, the extra blank lines at 205-207 should be removed for PEP 8 compliance (two blank lines max between top-level definitions).


209-220: LGTM with minor defensive coding suggestion.

The method has proper type hints and docstring. Consider using .get() for defensive access to stack fields in case of malformed entries, though this may be better addressed by JSON schema validation in StackManager.

         for stack in stacks:
             pkg_count = len(stack.get("packages", []))
-            console.print(f"  [green]{stack['id']}[/green]")
-            console.print(f"    {stack['name']}")
-            console.print(f"    {stack['description']}")
+            console.print(f"  [green]{stack.get('id', 'unknown')}[/green]")
+            console.print(f"    {stack.get('name', 'Unnamed Stack')}")
+            console.print(f"    {stack.get('description', 'No description')}")
             console.print(f"    [dim]({pkg_count} packages)[/dim]\n")

737-742: Consider clarifying mutually exclusive arguments.

The parser allows both name and --describe STACK to be provided simultaneously (e.g., cortex stack ml --describe webdev). In stack(), --describe takes precedence over name, which may confuse users. Consider making them mutually exclusive or documenting the precedence clearly.

     # Stack command
     stack_parser = subparsers.add_parser('stack', help='Manage pre-built package stacks')
-    stack_parser.add_argument('name', nargs='?', help='Stack name (ml, ml-cpu, webdev, devops, data)')
-    stack_parser.add_argument('--list', '-l', action='store_true', help='List all available stacks')
-    stack_parser.add_argument('--describe', '-d', metavar='STACK', help='Show details about a stack')
-    stack_parser.add_argument('--dry-run', action='store_true', help='Show what would be installed')
+    stack_parser.add_argument('name', nargs='?', help='Stack name to install (ml, ml-cpu, webdev, devops, data)')
+    stack_group = stack_parser.add_mutually_exclusive_group()
+    stack_group.add_argument('--list', '-l', action='store_true', help='List all available stacks')
+    stack_group.add_argument('--describe', '-d', metavar='STACK', help='Show details about a stack')
+    stack_parser.add_argument('--dry-run', action='store_true', help='Show what would be installed (requires stack name)')

771-774: Clean up trailing whitespace.

Lines 771 and 774 appear to have trailing whitespace. Remove trailing spaces for PEP 8 compliance.

         elif args.command == 'notify':
-            return cli.notify(args)                
+            return cli.notify(args)
         elif args.command == 'stack':
             return cli.stack(args)
-        
+
         else:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 089d4d1 and 1995118.

📒 Files selected for processing (1)
  • cortex/cli.py (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧠 Learnings (1)
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to **/*.py : Type hints required in Python code

Applied to files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (2)
cortex/stack_manager.py (5)
  • StackManager (18-93)
  • list_stacks (40-43)
  • find_stack (45-51)
  • describe_stack (74-93)
  • suggest_stack (60-72)
cortex/branding.py (1)
  • cx_print (55-75)
🔇 Additional comments (3)
cortex/cli.py (3)

223-231: LGTM!

Proper type hints, docstring, and error handling for missing stacks (returns 1 as requested in past review).


234-261: LGTM!

Clean implementation with hardware-aware stack suggestion, proper empty package validation (as requested in past review), and clear delegation to dry-run or real install handlers.


263-271: LGTM!

Proper type hints, docstring, and clear dry-run preview output.

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.

[MVP] Pre-built package stacks (ML, Web, DevOps)

2 participants