-
Notifications
You must be signed in to change notification settings - Fork 19
feat: Smart Stacks for ML, Web, DevOps (Issue #252) #296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds a Smart Stacks feature: new StackManager with a Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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
matplotlibbut 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 bareDictfor 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 fromstacks.jsonfor 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_stackThis approach would automatically handle any future GPU/CPU stack pairs without hardcoding.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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.pycortex/stack_manager.pycortex/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_gpuat 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
nameallows--listand--describeto 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 usesCommandInterpreterto 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
CommandInterpreterto 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 ofpip install torch- "cuda": LLM might generate
apt install cuda(incorrect) instead of the actual package likenvidia-cuda-toolkitThe system prompt emphasizes apt-based installation but lacks concrete examples for Python packages. Consider:
- Adding explicit examples to the system prompt showing how to handle Python packages vs system packages
- Testing that actual LLM responses for "install pytorch" and "install cuda" produce correct commands
- 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
| 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 |
There was a problem hiding this comment.
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.pyRepository: 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.pyRepository: 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 -iRepository: cortexlinux/cortex
Length of output: 777
🏁 Script executed:
# Check CommandInterpreter class and parse method
rg -n -A 15 'class CommandInterpreter' LLM/interpreter.pyRepository: cortexlinux/cortex
Length of output: 566
🏁 Script executed:
# Look for parse method in CommandInterpreter
rg -n -A 20 'def parse' LLM/interpreter.py | head -50Repository: 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 -60Repository: cortexlinux/cortex
Length of output: 1196
🏁 Script executed:
# Search for CommandInterpreter usage in cli.py
rg -n 'CommandInterpreter' cortex/cli.pyRepository: 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.pyRepository: 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.pyRepository: 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 -20Repository: 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.
| 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 |
| 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 |
There was a problem hiding this comment.
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:
- Missing type hint for the
monkeypatchparameter (PEP 8 guideline). - Missing docstring explaining test purpose (coding guideline).
- 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.
There was a problem hiding this 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/invalidstacks.jsongracefully.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 raiseFileNotFoundError/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 1Per-package install triggers repeated planning overhead; consider batching/caching.
🧹 Nitpick comments (1)
cortex/cli.py (1)
696-702: Prefer mutually-exclusive args forstack(--listvs--describevs install).Consider an
argparsemutually 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
📒 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 catchingStackManagerload errors at call sites.
731-734: Dispatch wiring forstacklooks correct.
745-746:sys.exit(main())entrypoint is standard and fine.
There was a problem hiding this 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
📒 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
There was a problem hiding this 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-packageself.install(...)likely triggers multiple LLM calls (cost/latency)
This repeats the earlier concern: each loop iteration callsinstall()which instantiatesCommandInterpreterand parses separately. Consider batching into a single plan (one LLM call) for the whole stack, or adding aninstall_many(packages=[...])path.
741-744: Fix inconsistent indentation inmain()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 inStackManagerimport
Dictwithout key/value types bleedsAnyimplicitly; 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 StackManagerAlso applies to: 43-43
195-206: Guard against malformedstacks.jsonentries 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: Typestackmore precisely thanDict
stack: Dictloses all structure. PreferMapping[str, Any]or aTypedDictforid/name/description/packages/tags/hardware.
706-712: Make stack CLI flags mutually exclusive (or validate combinations)
As-is, users can pass conflicting combinations likecortex stack ml --list. Consider an argparse mutually-exclusive group for--listvs--describevs “install by name”, or validate instack().
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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.
🔇 Additional comments (1)
cortex/cli.py (1)
755-756:sys.exit(main())wiring looks good
No issues.
There was a problem hiding this 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]requiresAnyto be imported fromtyping. See comment on line 6 for the fix.
265-265: Type hint requires missing import.
Dict[str, Any]requiresAnyto be imported fromtyping. 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 newCommandInterpreter(line 310 in the install method), which invokesparse()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
InstallationCoordinatorto handle package lists directlyBased 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
📒 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
stackcommands to the handler with proper indentation and argument passing.
There was a problem hiding this 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-packageself.install(...)will trigger multiple LLM plans; consider batching/caching.
This repeats the earlier concern: each package likely incurs a separateCommandInterpreter.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”.
Ifstacks.jsonexists but contains zero stacks, the command currently prints a header then nothing—maybe print “No stacks available” for clarity.
225-252: Make_handle_stack_installdefensively reject missingargs.name.
Today it’s logically unreachable (becausestack()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 + addstacktoshow_rich_help().
Right now users can providename+--listtogether; a mutually-exclusive group for--list/--describereduces ambiguous invocations. Also, the Rich help table currently doesn’t advertise the newstackcommand.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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:Anyis now imported, fixing runtime/type-hint resolution.
This addresses the earlierDict[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
|
|
||
| elif args.command == 'stack': | ||
| return cli.stack(args) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
|
There was a problem hiding this 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 newCommandInterpreterand 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 inStackManager.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
nameand--describe STACKto be provided simultaneously (e.g.,cortex stack ml --describe webdev). Instack(),--describetakes precedence overname, 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
📒 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.



Related Issue
Closes #252
Summary
stacks.jsonwith mentioned stacks:cortex stack ml/cortex stack ml-cpu– installs the ML stack;mlautomatically falls back toml-cpuon systems without a detected NVIDIA GPUcortex stack webdev– installs the web development stackcortex stack devops– installs the DevOps tooling stackcortex stack data– installs the data analysis stackStackManagerto load, describe, and resolve hardware-aware stacks.cortex stackCLI subcommands:cortex stack --list- list all stackscortex stack --describe <name>- show stack detailscortex stack <name> --dry-run- preview packages that would be installedcortex stack <name>- install the stack via the existing install flowDemo
Checklist
pytest)[#252] Pre-built Smart StacksSummary by CodeRabbit
New Features
Tests
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.