-
-
Notifications
You must be signed in to change notification settings - Fork 50
feat: AI-powered dependency conflict prediction (#428) #633
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -829,6 +829,39 @@ def install( | |||||||||||||||||||||||||||||||||||||||||||||||
| self._print_error(error) | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # Predict conflicts before installation | ||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||
| from cortex.conflict_predictor import ConflictPredictor | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| predictor = ConflictPredictor() | ||||||||||||||||||||||||||||||||||||||||||||||||
| # Extract package name (first word, remove any pip/apt prefix) | ||||||||||||||||||||||||||||||||||||||||||||||||
| package_name = software.split()[0].strip() | ||||||||||||||||||||||||||||||||||||||||||||||||
| for prefix in ["apt-get", "apt", "install", "pip", "pip3", "-y"]: | ||||||||||||||||||||||||||||||||||||||||||||||||
| if package_name == prefix: | ||||||||||||||||||||||||||||||||||||||||||||||||
| parts = software.split() | ||||||||||||||||||||||||||||||||||||||||||||||||
| for p in parts: | ||||||||||||||||||||||||||||||||||||||||||||||||
| if p not in ["apt-get", "apt", "install", "pip", "pip3", "-y", "sudo"]: | ||||||||||||||||||||||||||||||||||||||||||||||||
| package_name = p | ||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+832
to
+845
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle If the input starts with 🛠️ Suggested fix- package_name = software.split()[0].strip()
- for prefix in ["apt-get", "apt", "install", "pip", "pip3", "-y"]:
- if package_name == prefix:
- parts = software.split()
- for p in parts:
- if p not in ["apt-get", "apt", "install", "pip", "pip3", "-y", "sudo"]:
- package_name = p
- break
+ parts = software.split()
+ skip = {"sudo", "apt-get", "apt", "install", "pip", "pip3", "-y"}
+ package_name = next((p for p in parts if p not in skip), "")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| prediction = predictor.predict_conflicts(package_name) | ||||||||||||||||||||||||||||||||||||||||||||||||
| if prediction.conflicts: | ||||||||||||||||||||||||||||||||||||||||||||||||
| console.print() | ||||||||||||||||||||||||||||||||||||||||||||||||
| predictor.display_prediction(prediction) | ||||||||||||||||||||||||||||||||||||||||||||||||
| console.print() | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if not execute: | ||||||||||||||||||||||||||||||||||||||||||||||||
| console.print( | ||||||||||||||||||||||||||||||||||||||||||||||||
| "[yellow]Conflicts detected. Use --execute to proceed anyway, " | ||||||||||||||||||||||||||||||||||||||||||||||||
| "or resolve conflicts first.[/yellow]" | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||
| console.print("[yellow]Proceeding despite conflicts (--execute flag)...[/yellow]") | ||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||
| # Don't block installation if prediction fails | ||||||||||||||||||||||||||||||||||||||||||||||||
| self._debug(f"Conflict prediction skipped: {e}") | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+861
to
+863
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Catching a broad
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # Special-case the ml-cpu stack: | ||||||||||||||||||||||||||||||||||||||||||||||||
| # The LLM sometimes generates outdated torch==1.8.1+cpu installs | ||||||||||||||||||||||||||||||||||||||||||||||||
| # which fail on modern Python. For the "pytorch-cpu jupyter numpy pandas" | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -3866,8 +3899,8 @@ def main(): | |||||||||||||||||||||||||||||||||||||||||||||||
| "action", | ||||||||||||||||||||||||||||||||||||||||||||||||
| nargs="?", | ||||||||||||||||||||||||||||||||||||||||||||||||
| default="analyze", | ||||||||||||||||||||||||||||||||||||||||||||||||
| choices=["analyze", "parse", "check", "compare"], | ||||||||||||||||||||||||||||||||||||||||||||||||
| help="Action to perform (default: analyze)", | ||||||||||||||||||||||||||||||||||||||||||||||||
| choices=["analyze", "parse", "check", "compare", "predict"], | ||||||||||||||||||||||||||||||||||||||||||||||||
| help="Action to perform (default: analyze). Use 'predict' for AI conflict prediction.", | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
| deps_parser.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||
| "packages", | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -4027,13 +4060,28 @@ def main(): | |||||||||||||||||||||||||||||||||||||||||||||||
| verbose=getattr(args, "verbose", False), | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
| elif args.command == "deps": | ||||||||||||||||||||||||||||||||||||||||||||||||
| from cortex.semver_resolver import run_semver_resolver | ||||||||||||||||||||||||||||||||||||||||||||||||
| action = getattr(args, "action", "analyze") | ||||||||||||||||||||||||||||||||||||||||||||||||
| packages = getattr(args, "packages", None) | ||||||||||||||||||||||||||||||||||||||||||||||||
| verbose = getattr(args, "verbose", False) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return run_semver_resolver( | ||||||||||||||||||||||||||||||||||||||||||||||||
| action=getattr(args, "action", "analyze"), | ||||||||||||||||||||||||||||||||||||||||||||||||
| packages=getattr(args, "packages", None), | ||||||||||||||||||||||||||||||||||||||||||||||||
| verbose=getattr(args, "verbose", False), | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
| if action == "predict": | ||||||||||||||||||||||||||||||||||||||||||||||||
| from cortex.conflict_predictor import run_conflict_predictor | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if not packages: | ||||||||||||||||||||||||||||||||||||||||||||||||
| console.print("[yellow]Usage: cortex deps predict <package_name>[/yellow]") | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| return run_conflict_predictor( | ||||||||||||||||||||||||||||||||||||||||||||||||
| package_name=packages[0], | ||||||||||||||||||||||||||||||||||||||||||||||||
| verbose=verbose, | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||
| from cortex.semver_resolver import run_semver_resolver | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return run_semver_resolver( | ||||||||||||||||||||||||||||||||||||||||||||||||
| action=action, | ||||||||||||||||||||||||||||||||||||||||||||||||
| packages=packages, | ||||||||||||||||||||||||||||||||||||||||||||||||
| verbose=verbose, | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
| elif args.command == "health": | ||||||||||||||||||||||||||||||||||||||||||||||||
| from cortex.health_score import run_health_check | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
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.
The logic to extract the package name from the
softwarestring is not robust. It fails for commands that start withsudo, as"sudo"is not in the list of prefixes to be skipped. For example, if the input is"sudo apt-get install nginx", the extractedpackage_namewill incorrectly be"sudo". This should be refactored for more reliable package name extraction.