ace-git-copilot 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. ace_git_copilot-0.1.0/.env.example +13 -0
  2. ace_git_copilot-0.1.0/.github/workflows/tests.yml +34 -0
  3. ace_git_copilot-0.1.0/.gitignore +45 -0
  4. ace_git_copilot-0.1.0/PKG-INFO +113 -0
  5. ace_git_copilot-0.1.0/README.md +93 -0
  6. ace_git_copilot-0.1.0/ace/__init__.py +1 -0
  7. ace_git_copilot-0.1.0/ace/ai/changelog_generator.py +70 -0
  8. ace_git_copilot-0.1.0/ace/ai/code_reviewer.py +81 -0
  9. ace_git_copilot-0.1.0/ace/ai/commit_generator.py +73 -0
  10. ace_git_copilot-0.1.0/ace/ai/conflict_resolver.py +115 -0
  11. ace_git_copilot-0.1.0/ace/ai/gitignore_generator.py +45 -0
  12. ace_git_copilot-0.1.0/ace/ai/history_analyzer.py +188 -0
  13. ace_git_copilot-0.1.0/ace/ai/intent_parser.py +65 -0
  14. ace_git_copilot-0.1.0/ace/ai/llm_factory.py +116 -0
  15. ace_git_copilot-0.1.0/ace/ai/pr_drafter.py +61 -0
  16. ace_git_copilot-0.1.0/ace/ai/prompts/changelog.py +30 -0
  17. ace_git_copilot-0.1.0/ace/ai/prompts/commit.py +74 -0
  18. ace_git_copilot-0.1.0/ace/ai/prompts/conflict.py +40 -0
  19. ace_git_copilot-0.1.0/ace/ai/prompts/explain.py +20 -0
  20. ace_git_copilot-0.1.0/ace/ai/prompts/ignore.py +19 -0
  21. ace_git_copilot-0.1.0/ace/ai/prompts/intent.py +111 -0
  22. ace_git_copilot-0.1.0/ace/ai/prompts/pr.py +29 -0
  23. ace_git_copilot-0.1.0/ace/ai/prompts/review.py +52 -0
  24. ace_git_copilot-0.1.0/ace/ai/prompts/search.py +18 -0
  25. ace_git_copilot-0.1.0/ace/ai/prompts/undo.py +47 -0
  26. ace_git_copilot-0.1.0/ace/cli.py +1191 -0
  27. ace_git_copilot-0.1.0/ace/core/config.py +129 -0
  28. ace_git_copilot-0.1.0/ace/core/context.py +172 -0
  29. ace_git_copilot-0.1.0/ace/core/git_ops.py +193 -0
  30. ace_git_copilot-0.1.0/ace/core/safety.py +110 -0
  31. ace_git_copilot-0.1.0/ace/ui/banner.py +64 -0
  32. ace_git_copilot-0.1.0/ace/ui/dashboard.py +200 -0
  33. ace_git_copilot-0.1.0/ace/ui/display.py +133 -0
  34. ace_git_copilot-0.1.0/ace/ui/prompts.py +69 -0
  35. ace_git_copilot-0.1.0/ace/ui/themes.py +22 -0
  36. ace_git_copilot-0.1.0/ace/utils/conflict_parser.py +62 -0
  37. ace_git_copilot-0.1.0/ace/utils/diff_parser.py +94 -0
  38. ace_git_copilot-0.1.0/ace/utils/json_utils.py +35 -0
  39. ace_git_copilot-0.1.0/pyproject.toml +31 -0
  40. ace_git_copilot-0.1.0/tests/conftest.py +27 -0
  41. ace_git_copilot-0.1.0/tests/test_changelog_generator.py +48 -0
  42. ace_git_copilot-0.1.0/tests/test_code_reviewer.py +88 -0
  43. ace_git_copilot-0.1.0/tests/test_conflict_resolver.py +93 -0
  44. ace_git_copilot-0.1.0/tests/test_diff_trimmer.py +31 -0
  45. ace_git_copilot-0.1.0/tests/test_git_ops.py +63 -0
  46. ace_git_copilot-0.1.0/tests/test_help.py +47 -0
  47. ace_git_copilot-0.1.0/tests/test_history_analyzer.py +67 -0
  48. ace_git_copilot-0.1.0/tests/test_ignore.py +38 -0
  49. ace_git_copilot-0.1.0/tests/test_intent_parser.py +75 -0
  50. ace_git_copilot-0.1.0/tests/test_llm_factory.py +51 -0
  51. ace_git_copilot-0.1.0/tests/test_pr_drafter.py +85 -0
  52. ace_git_copilot-0.1.0/tests/test_safety.py +46 -0
  53. ace_git_copilot-0.1.0/tests/test_search.py +44 -0
  54. ace_git_copilot-0.1.0/tests/test_undo.py +43 -0
@@ -0,0 +1,13 @@
1
+ # Ace AI Git Copilot Environment Variables
2
+
3
+ # AI Provider: 'nvidia' (default) or 'ollama' (offline)
4
+ ACE_PROVIDER=nvidia
5
+
6
+ # NVIDIA NIM Configuration
7
+ # Get your free key at https://build.nvidia.com/
8
+ NVIDIA_API_KEY=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
9
+ NVIDIA_MODEL=meta/llama-3.3-70b-instruct
10
+
11
+ # Ollama Configuration (Fallback/Offline)
12
+ OLLAMA_MODEL=qwen2.5-coder:7b
13
+ OLLAMA_URL=http://localhost:11434
@@ -0,0 +1,34 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, master ]
6
+ pull_request:
7
+ branches: [ main, master ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+ cache: 'pip'
24
+
25
+ - name: Install dependencies
26
+ run: |
27
+ python -m pip install --upgrade pip
28
+ pip install .[dev]
29
+
30
+ - name: Lint with Ruff
31
+ run: ruff check
32
+
33
+ - name: Run tests with Pytest
34
+ run: pytest
@@ -0,0 +1,45 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ share/python-wheels/
20
+ *.egg-info/
21
+ .installed.cfg
22
+ *.egg
23
+ MANIFEST
24
+
25
+ # Virtual Environment
26
+ .venv/
27
+ venv/
28
+ ENV/
29
+ env/
30
+
31
+ # IDEs
32
+ .idea/
33
+ .vscode/
34
+ *.swp
35
+ *.swo
36
+
37
+ # Environment secrets
38
+ *.env
39
+ .env.local
40
+
41
+ # Testing / Coverage
42
+ .pytest_cache/
43
+ .coverage
44
+ htmlcov/
45
+ .cov/
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: ace-git-copilot
3
+ Version: 0.1.0
4
+ Summary: AI-powered Git copilot — talk to Git in plain English
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: click>=8.0
7
+ Requires-Dist: gitpython>=3.1
8
+ Requires-Dist: langchain-nvidia-ai-endpoints>=0.3
9
+ Requires-Dist: langchain-ollama>=0.3
10
+ Requires-Dist: langchain>=0.3
11
+ Requires-Dist: python-dotenv>=1.0
12
+ Requires-Dist: rich>=13.0
13
+ Requires-Dist: toml>=0.10.2
14
+ Requires-Dist: typer[all]>=0.12
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest; extra == 'dev'
17
+ Requires-Dist: pytest-cov; extra == 'dev'
18
+ Requires-Dist: ruff; extra == 'dev'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Ace — AI-Powered Git Copilot
22
+
23
+ > Ace is an intelligent terminal tool that understands what you want to do with Git and does it for you. Talk to Git in plain English — Ace figures out the right git commands, explains what it's doing, and executes safely.
24
+
25
+ ---
26
+
27
+ ## 🚀 Installation
28
+
29
+ Install in editable mode globally/user-wide:
30
+ ```bash
31
+ python -m pip install --user -e D:\Ace
32
+ ```
33
+
34
+ *Note: Make sure your Python scripts path (e.g., `AppData\Roaming\Python\Python3xx\Scripts`) is added to your Windows PATH environment variable.*
35
+
36
+ ---
37
+
38
+ ## ⚙️ Configuration
39
+
40
+ Configure your AI provider (NVIDIA NIM or local Ollama) by running:
41
+ ```bash
42
+ ace setup
43
+ ```
44
+ This will save your preferences to `~/.ace/config.toml`.
45
+
46
+ ---
47
+
48
+ ## 🛠️ Usage & Commands
49
+
50
+ ### 🧠 Natural Language Input
51
+ Run anything you want to do directly:
52
+ ```bash
53
+ ace "stage all changes"
54
+ ace "undo my last commit but keep changes"
55
+ ace "create feature branch named login"
56
+ ```
57
+
58
+ ### 📋 Command Reference
59
+
60
+ | Command | Shorthand | Description |
61
+ |---------|-----------|-------------|
62
+ | `ace add <files>` | `ace stage` | Stage files (`git add`) for commit. |
63
+ | `ace commit` | — | Generate a smart Conventional Commit message and commit. |
64
+ | `ace review` | — | AI code review of staged/unstaged changes. |
65
+ | `ace resolve` | — | AI-assisted interactive merge conflict resolution. |
66
+ | `ace changelog` | — | Generate markdown release notes since the last tag. |
67
+ | `ace stats` | — | Enhanced visual repo overview, extension counts, and lines altered. |
68
+ | `ace explain <cmd>` | — | Explain Git commands or concepts in plain English. |
69
+ | `ace undo` | — | Context-aware smart undo (analyzes Git state to safely revert last action). |
70
+ | `ace pr` | — | Generate a markdown Pull Request description comparing branches. |
71
+ | `ace search <query>` | — | Semantic search across repository commits. |
72
+ | `ace ignore <query>` | — | Generate and append rules to `.gitignore`. |
73
+ | `ace dash` | — | Launch the interactive terminal dashboard TUI. |
74
+ | `ace config` | — | View the active configuration settings. |
75
+
76
+ ---
77
+
78
+ ## 📁 Codebase Directory & File Functions
79
+
80
+ Here is the list of files in the project and what each one does:
81
+
82
+ ### 📍 Core CLI & Setup
83
+ * **`pyproject.toml`**: Configures the project, entry points (`ace = ace.cli:app`), dependencies (`typer`, `rich`, `gitpython`, `langchain`), and build systems.
84
+ * **`ace/cli.py`**: The entrypoint of the Typer application. Defines all CLI commands, parses command line flags, and routes calls.
85
+
86
+ ### ⚙️ Core Modules (`ace/core/`)
87
+ * **`ace/core/config.py`**: Manages reading, writing, and parsing the user's TOML settings file (`config.toml`).
88
+ * **`ace/core/git_ops.py`**: Wraps the GitPython library to perform programmatic Git commands (e.g. status, diff, log, commit, push).
89
+ * **`ace/core/safety.py`**: Classifies Git commands into safety levels (Safe, Moderate, Destructive) and guards against destructive operations.
90
+ * **`ace/core/context.py`**: Collects repository-wide details (branch, diffs, untracked files) to provide context for AI requests.
91
+
92
+ ### 🧠 AI Engine (`ace/ai/`)
93
+ * **`ace/ai/llm_factory.py`**: Instantiates ChatNVIDIA (cloud) or ChatOllama (local) depending on configuration settings.
94
+ * **`ace/ai/intent_parser.py`**: Translates the user's natural language input into Git commands.
95
+ * **`ace/ai/commit_generator.py`**: Analyzes staged diffs and generates Conventional Commit messages.
96
+ * **`ace/ai/code_reviewer.py`**: Analyzes unstaged or staged diffs to locate bugs, style flaws, security issues, and rate code.
97
+ * **`ace/ai/conflict_resolver.py`**: Parses file-level merge conflicts and suggests correct merges.
98
+ * **`ace/ai/changelog_generator.py`**: Collates commit messages and formats release logs.
99
+ * **`ace/ai/history_analyzer.py`**: Runs semantic searches across log files and computes repository overview metrics.
100
+ * **`ace/ai/pr_drafter.py`**: Drafts Pull Request titles and bodies comparing branch changes.
101
+ * **`ace/ai/gitignore_generator.py`**: Generates `.gitignore` syntax templates.
102
+ * **`ace/ai/prompts/`**: Houses all prompt templates for the LLMs (intent, commit, review, conflict, explain, undo, pr, search, ignore).
103
+
104
+ ### 🎨 User Interface (`ace/ui/`)
105
+ * **`ace/ui/display.py`**: Directs Rich panel printouts, warning/error panels, and load spinners.
106
+ * **`ace/ui/themes.py`**: Houses colors, styling, and markdown rules.
107
+ * **`ace/ui/prompts.py`**: Handles keyboard selection options (e.g., commit/review prompts).
108
+ * **`ace/ui/dashboard.py`**: Implements the interactive terminal dashboard (TUI) showing branch states and command routing menus.
109
+
110
+ ### 🔧 Utilities (`ace/utils/`)
111
+ * **`ace/utils/json_utils.py`**: Extracts and parses clean JSON structures from Markdown code blocks.
112
+ * **`ace/utils/diff_parser.py`**: Splits unified diff files into per-file chunks.
113
+ * **`ace/utils/conflict_parser.py`**: Parses git conflict blocks inside file lines.
@@ -0,0 +1,93 @@
1
+ # Ace — AI-Powered Git Copilot
2
+
3
+ > Ace is an intelligent terminal tool that understands what you want to do with Git and does it for you. Talk to Git in plain English — Ace figures out the right git commands, explains what it's doing, and executes safely.
4
+
5
+ ---
6
+
7
+ ## 🚀 Installation
8
+
9
+ Install in editable mode globally/user-wide:
10
+ ```bash
11
+ python -m pip install --user -e D:\Ace
12
+ ```
13
+
14
+ *Note: Make sure your Python scripts path (e.g., `AppData\Roaming\Python\Python3xx\Scripts`) is added to your Windows PATH environment variable.*
15
+
16
+ ---
17
+
18
+ ## ⚙️ Configuration
19
+
20
+ Configure your AI provider (NVIDIA NIM or local Ollama) by running:
21
+ ```bash
22
+ ace setup
23
+ ```
24
+ This will save your preferences to `~/.ace/config.toml`.
25
+
26
+ ---
27
+
28
+ ## 🛠️ Usage & Commands
29
+
30
+ ### 🧠 Natural Language Input
31
+ Run anything you want to do directly:
32
+ ```bash
33
+ ace "stage all changes"
34
+ ace "undo my last commit but keep changes"
35
+ ace "create feature branch named login"
36
+ ```
37
+
38
+ ### 📋 Command Reference
39
+
40
+ | Command | Shorthand | Description |
41
+ |---------|-----------|-------------|
42
+ | `ace add <files>` | `ace stage` | Stage files (`git add`) for commit. |
43
+ | `ace commit` | — | Generate a smart Conventional Commit message and commit. |
44
+ | `ace review` | — | AI code review of staged/unstaged changes. |
45
+ | `ace resolve` | — | AI-assisted interactive merge conflict resolution. |
46
+ | `ace changelog` | — | Generate markdown release notes since the last tag. |
47
+ | `ace stats` | — | Enhanced visual repo overview, extension counts, and lines altered. |
48
+ | `ace explain <cmd>` | — | Explain Git commands or concepts in plain English. |
49
+ | `ace undo` | — | Context-aware smart undo (analyzes Git state to safely revert last action). |
50
+ | `ace pr` | — | Generate a markdown Pull Request description comparing branches. |
51
+ | `ace search <query>` | — | Semantic search across repository commits. |
52
+ | `ace ignore <query>` | — | Generate and append rules to `.gitignore`. |
53
+ | `ace dash` | — | Launch the interactive terminal dashboard TUI. |
54
+ | `ace config` | — | View the active configuration settings. |
55
+
56
+ ---
57
+
58
+ ## 📁 Codebase Directory & File Functions
59
+
60
+ Here is the list of files in the project and what each one does:
61
+
62
+ ### 📍 Core CLI & Setup
63
+ * **`pyproject.toml`**: Configures the project, entry points (`ace = ace.cli:app`), dependencies (`typer`, `rich`, `gitpython`, `langchain`), and build systems.
64
+ * **`ace/cli.py`**: The entrypoint of the Typer application. Defines all CLI commands, parses command line flags, and routes calls.
65
+
66
+ ### ⚙️ Core Modules (`ace/core/`)
67
+ * **`ace/core/config.py`**: Manages reading, writing, and parsing the user's TOML settings file (`config.toml`).
68
+ * **`ace/core/git_ops.py`**: Wraps the GitPython library to perform programmatic Git commands (e.g. status, diff, log, commit, push).
69
+ * **`ace/core/safety.py`**: Classifies Git commands into safety levels (Safe, Moderate, Destructive) and guards against destructive operations.
70
+ * **`ace/core/context.py`**: Collects repository-wide details (branch, diffs, untracked files) to provide context for AI requests.
71
+
72
+ ### 🧠 AI Engine (`ace/ai/`)
73
+ * **`ace/ai/llm_factory.py`**: Instantiates ChatNVIDIA (cloud) or ChatOllama (local) depending on configuration settings.
74
+ * **`ace/ai/intent_parser.py`**: Translates the user's natural language input into Git commands.
75
+ * **`ace/ai/commit_generator.py`**: Analyzes staged diffs and generates Conventional Commit messages.
76
+ * **`ace/ai/code_reviewer.py`**: Analyzes unstaged or staged diffs to locate bugs, style flaws, security issues, and rate code.
77
+ * **`ace/ai/conflict_resolver.py`**: Parses file-level merge conflicts and suggests correct merges.
78
+ * **`ace/ai/changelog_generator.py`**: Collates commit messages and formats release logs.
79
+ * **`ace/ai/history_analyzer.py`**: Runs semantic searches across log files and computes repository overview metrics.
80
+ * **`ace/ai/pr_drafter.py`**: Drafts Pull Request titles and bodies comparing branch changes.
81
+ * **`ace/ai/gitignore_generator.py`**: Generates `.gitignore` syntax templates.
82
+ * **`ace/ai/prompts/`**: Houses all prompt templates for the LLMs (intent, commit, review, conflict, explain, undo, pr, search, ignore).
83
+
84
+ ### 🎨 User Interface (`ace/ui/`)
85
+ * **`ace/ui/display.py`**: Directs Rich panel printouts, warning/error panels, and load spinners.
86
+ * **`ace/ui/themes.py`**: Houses colors, styling, and markdown rules.
87
+ * **`ace/ui/prompts.py`**: Handles keyboard selection options (e.g., commit/review prompts).
88
+ * **`ace/ui/dashboard.py`**: Implements the interactive terminal dashboard (TUI) showing branch states and command routing menus.
89
+
90
+ ### 🔧 Utilities (`ace/utils/`)
91
+ * **`ace/utils/json_utils.py`**: Extracts and parses clean JSON structures from Markdown code blocks.
92
+ * **`ace/utils/diff_parser.py`**: Splits unified diff files into per-file chunks.
93
+ * **`ace/utils/conflict_parser.py`**: Parses git conflict blocks inside file lines.
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,70 @@
1
+ from typing import Optional
2
+ from langchain_core.messages import SystemMessage, HumanMessage
3
+ from ace.core.git_ops import GitOps
4
+ from ace.ai.llm_factory import get_llm
5
+ from ace.ai.prompts.changelog import CHANGELOG_SYSTEM_PROMPT, USER_PROMPT_TEMPLATE
6
+
7
+ class ChangelogGeneratorError(Exception):
8
+ """Raised when changelog generation fails."""
9
+ pass
10
+
11
+ class ChangelogGenerator:
12
+ def __init__(self, git_ops: GitOps):
13
+ self.git_ops = git_ops
14
+
15
+ def get_commits_in_range(self, from_ref: Optional[str] = None, to_ref: Optional[str] = None) -> str:
16
+ """
17
+ Retrieve formatted commit log between from_ref and to_ref.
18
+ If from_ref is None, attempts to find the latest tag.
19
+ If no latest tag exists, falls back to the last 30 commits.
20
+ """
21
+ to_revision = to_ref or "HEAD"
22
+ from_revision = from_ref
23
+
24
+ # If from_ref is not provided, try to find the latest tag
25
+ if not from_revision:
26
+ try:
27
+ # git describe --tags --abbrev=0 to get latest tag
28
+ latest_tag = self.git_ops.execute("describe --tags --abbrev=0").strip()
29
+ from_revision = latest_tag
30
+ except Exception:
31
+ # No tags found
32
+ from_revision = None
33
+
34
+ log_args = ["log"]
35
+ if from_revision:
36
+ log_args.append(f"{from_revision}..{to_revision}")
37
+ else:
38
+ # Fallback to last 30 commits
39
+ log_args.extend(["-n", "30"])
40
+
41
+ # Format: hash|date|author|subject\nbody
42
+ log_args.append('--format=%H|%ad|%an|%s%n%b')
43
+
44
+ try:
45
+ cmd = " ".join(log_args)
46
+ return self.git_ops.execute(cmd).strip()
47
+ except Exception as e:
48
+ raise ChangelogGeneratorError(f"Failed to fetch commit log: {e}")
49
+
50
+ def generate_changelog(self, from_ref: Optional[str] = None, to_ref: Optional[str] = None, offline: bool = False) -> str:
51
+ """
52
+ Retrieve commits and generate a release changelog in Markdown.
53
+ """
54
+ commit_log = self.get_commits_in_range(from_ref, to_ref)
55
+ if not commit_log.strip():
56
+ return "No commits found in the specified range."
57
+
58
+ llm = get_llm(offline_override=offline)
59
+
60
+ user_prompt = USER_PROMPT_TEMPLATE.format(
61
+ commit_log=commit_log[:20000] # Cap log length to respect context window
62
+ )
63
+
64
+ messages = [
65
+ SystemMessage(content=CHANGELOG_SYSTEM_PROMPT),
66
+ HumanMessage(content=user_prompt)
67
+ ]
68
+
69
+ response = llm.invoke(messages)
70
+ return response.content.strip()
@@ -0,0 +1,81 @@
1
+ from typing import Dict, Any, List, Tuple
2
+ from langchain_core.messages import SystemMessage, HumanMessage
3
+ from ace.core.git_ops import GitOps
4
+ from ace.utils.diff_parser import split_diff_by_file, trim_diff
5
+ from ace.ai.llm_factory import get_llm
6
+ from ace.ai.prompts.review import REVIEW_SYSTEM_PROMPT, USER_PROMPT_TEMPLATE
7
+ from ace.utils.json_utils import extract_json
8
+
9
+ class CodeReviewerError(Exception):
10
+ """Raised when code review processing fails."""
11
+ pass
12
+
13
+ class CodeReviewer:
14
+ def __init__(self, git_ops: GitOps):
15
+ self.git_ops = git_ops
16
+
17
+ def review_diff(self, diff_text: str, offline: bool = False) -> Tuple[List[Dict[str, Any]], float]:
18
+ """
19
+ Perform a code review on a raw diff string.
20
+
21
+ Returns a tuple of (findings, overall_score).
22
+ """
23
+ if not diff_text.strip():
24
+ return [], 10.0
25
+
26
+ # Split diff by file
27
+ file_diffs = split_diff_by_file(diff_text)
28
+ if not file_diffs:
29
+ return [], 10.0
30
+
31
+ all_findings = []
32
+ scores = []
33
+ llm = get_llm(offline_override=offline)
34
+
35
+ for filename, diff_content in file_diffs.items():
36
+ # Skip binary diffs or very small metadata updates
37
+ if "Binary files" in diff_content or len(diff_content.strip()) < 20:
38
+ continue
39
+
40
+ user_prompt = USER_PROMPT_TEMPLATE.format(
41
+ filename=filename,
42
+ diff_content=trim_diff(diff_content, max_chars=15000) # Cap file diff size
43
+ )
44
+
45
+ messages = [
46
+ SystemMessage(content=REVIEW_SYSTEM_PROMPT),
47
+ HumanMessage(content=user_prompt)
48
+ ]
49
+
50
+ try:
51
+ response = llm.invoke(messages)
52
+ parsed = extract_json(response.content)
53
+
54
+ # Extract score and findings
55
+ score = float(parsed.get("score", 10.0))
56
+ scores.append(score)
57
+
58
+ findings = parsed.get("findings", [])
59
+ if isinstance(findings, list):
60
+ for finding in findings:
61
+ # Annotate with the filename
62
+ finding["file"] = filename
63
+ all_findings.append(finding)
64
+ except Exception as e:
65
+ # Log or handle exception per file, but don't crash the whole review
66
+ # We can add a fallback warning finding
67
+ all_findings.append({
68
+ "file": filename,
69
+ "category": "suggestion",
70
+ "severity": "info",
71
+ "line": None,
72
+ "description": f"AI review failed for this file: {e}",
73
+ "fix": None
74
+ })
75
+ scores.append(10.0)
76
+
77
+ overall_score = sum(scores) / len(scores) if scores else 10.0
78
+ # Round overall score
79
+ overall_score = round(overall_score, 1)
80
+
81
+ return all_findings, overall_score
@@ -0,0 +1,73 @@
1
+ from langchain_core.messages import SystemMessage, HumanMessage
2
+ from ace.core.git_ops import GitOps
3
+ from ace.core.context import RepoContext
4
+ from ace.ai.llm_factory import get_llm
5
+ from ace.utils.diff_parser import trim_diff
6
+ from ace.ai.prompts.commit import (
7
+ CONVENTIONAL_COMMIT_SYSTEM_PROMPT,
8
+ SIMPLE_COMMIT_SYSTEM_PROMPT,
9
+ DETAILED_COMMIT_SYSTEM_PROMPT,
10
+ USER_PROMPT_TEMPLATE,
11
+ )
12
+
13
+ class NoStagedChangesError(Exception):
14
+ """Raised when trying to generate a commit message but no changes are staged."""
15
+ pass
16
+
17
+ class CommitGenerator:
18
+ def __init__(self, git_ops: GitOps):
19
+ self.git_ops = git_ops
20
+ self.context_builder = RepoContext(git_ops)
21
+
22
+ def generate_message(self, format_type: str = "conventional", offline: bool = False) -> str:
23
+ """
24
+ Analyze staged changes and generate a commit message using the configured AI.
25
+ """
26
+ # Ensure we have staged changes
27
+ status = self.git_ops.get_status()
28
+ if not status["staged"]:
29
+ raise NoStagedChangesError("No changes are staged for commit. Stage files first using 'git add'.")
30
+
31
+ staged_diff = self.git_ops.get_staged_diff()
32
+ if not staged_diff.strip():
33
+ # Sometimes status has staged but diff is empty (e.g. only file permissions or empty files)
34
+ raise NoStagedChangesError("Staged diff is empty. Cannot generate commit message.")
35
+
36
+ # Format context
37
+ repo_context = self.context_builder.format_context_for_prompt()
38
+
39
+ # Select system prompt based on format
40
+ if format_type == "conventional":
41
+ system_prompt = CONVENTIONAL_COMMIT_SYSTEM_PROMPT
42
+ elif format_type == "simple":
43
+ system_prompt = SIMPLE_COMMIT_SYSTEM_PROMPT
44
+ elif format_type == "detailed":
45
+ system_prompt = DETAILED_COMMIT_SYSTEM_PROMPT
46
+ else:
47
+ system_prompt = CONVENTIONAL_COMMIT_SYSTEM_PROMPT
48
+
49
+ user_prompt = USER_PROMPT_TEMPLATE.format(
50
+ repo_context=repo_context,
51
+ staged_diff=trim_diff(staged_diff, max_chars=25000) # Cap diff to avoid context window limit
52
+ )
53
+
54
+ messages = [
55
+ SystemMessage(content=system_prompt),
56
+ HumanMessage(content=user_prompt)
57
+ ]
58
+
59
+ # Get LLM and run inference
60
+ llm = get_llm(offline_override=offline)
61
+ response = llm.invoke(messages)
62
+
63
+ # Clean response (remove extra leading/trailing whitespace or markdown fences)
64
+ message = response.content.strip()
65
+ if message.startswith("```"):
66
+ lines = message.splitlines()
67
+ if lines[0].startswith("```"):
68
+ lines = lines[1:]
69
+ if lines and lines[-1].startswith("```"):
70
+ lines = lines[:-1]
71
+ message = "\n".join(lines).strip()
72
+
73
+ return message
@@ -0,0 +1,115 @@
1
+ from pathlib import Path
2
+ from typing import Dict, Any, List, Tuple
3
+ from langchain_core.messages import SystemMessage, HumanMessage
4
+ from ace.core.git_ops import GitOps
5
+ from ace.utils.conflict_parser import parse_conflict_file
6
+ from ace.ai.llm_factory import get_llm
7
+ from ace.ai.prompts.conflict import CONFLICT_SYSTEM_PROMPT, USER_PROMPT_TEMPLATE
8
+ from ace.utils.json_utils import extract_json
9
+
10
+ class ConflictResolverError(Exception):
11
+ """Raised when conflict resolution fails."""
12
+ pass
13
+
14
+ class ConflictResolver:
15
+ def __init__(self, git_ops: GitOps):
16
+ self.git_ops = git_ops
17
+
18
+ def get_suggestions(self, file_path: str, offline: bool = False) -> List[Dict[str, Any]]:
19
+ """
20
+ Scan a file for conflicts and return AI resolution suggestions for each.
21
+
22
+ Returns a list of dicts:
23
+ [
24
+ {
25
+ "full_block": str,
26
+ "head": str,
27
+ "incoming": str,
28
+ "suggested_merged": str,
29
+ "explanation": str
30
+ }
31
+ ]
32
+ """
33
+ full_path = Path(self.git_ops.working_dir) / file_path
34
+ if not full_path.exists():
35
+ raise FileNotFoundError(f"File not found: {file_path}")
36
+
37
+ try:
38
+ content = full_path.read_text(encoding="utf-8")
39
+ except Exception as e:
40
+ raise ConflictResolverError(f"Failed to read file {file_path}: {e}")
41
+
42
+ blocks = parse_conflict_file(content)
43
+ if not blocks:
44
+ return []
45
+
46
+ llm = get_llm(offline_override=offline)
47
+ suggestions = []
48
+
49
+ for block in blocks:
50
+ sys_prompt = CONFLICT_SYSTEM_PROMPT.format(filename=file_path)
51
+ usr_prompt = USER_PROMPT_TEMPLATE.format(
52
+ head_content=block["head"],
53
+ incoming_content=block["incoming"]
54
+ )
55
+
56
+ messages = [
57
+ SystemMessage(content=sys_prompt),
58
+ HumanMessage(content=usr_prompt)
59
+ ]
60
+
61
+ try:
62
+ response = llm.invoke(messages)
63
+ parsed = extract_json(response.content)
64
+
65
+ suggestions.append({
66
+ "full_block": block["full_block"],
67
+ "head": block["head"],
68
+ "incoming": block["incoming"],
69
+ "suggested_merged": parsed.get("merged_content", block["head"]), # Fallback to head
70
+ "explanation": parsed.get("explanation", "AI suggested resolution.")
71
+ })
72
+ except Exception as e:
73
+ # Fallback in case of AI failures
74
+ suggestions.append({
75
+ "full_block": block["full_block"],
76
+ "head": block["head"],
77
+ "incoming": block["incoming"],
78
+ "suggested_merged": block["head"], # Keep head as fallback
79
+ "explanation": f"AI suggestion failed: {e}. Keeping local branch version as default suggestion."
80
+ })
81
+
82
+ return suggestions
83
+
84
+ def apply_resolution(self, file_path: str, block_replacements: List[Tuple[str, str]]) -> None:
85
+ """
86
+ Apply resolutions to a conflicted file.
87
+
88
+ block_replacements: List of tuples (full_conflict_block, replacement_content)
89
+ """
90
+ full_path = Path(self.git_ops.working_dir) / file_path
91
+ if not full_path.exists():
92
+ raise FileNotFoundError(f"File not found: {file_path}")
93
+
94
+ content = full_path.read_text(encoding="utf-8")
95
+
96
+ for full_block, replacement in block_replacements:
97
+ if full_block in content:
98
+ content = content.replace(full_block, replacement)
99
+ else:
100
+ # Try with normalized line endings
101
+ norm_block = full_block.replace("\r\n", "\n")
102
+ norm_content = content.replace("\r\n", "\n")
103
+ if norm_block in norm_content:
104
+ norm_content = norm_content.replace(norm_block, replacement)
105
+ # Restore Windows line endings if they were originally present
106
+ if "\r\n" in content:
107
+ content = norm_content.replace("\n", "\r\n")
108
+ else:
109
+ content = norm_content
110
+ else:
111
+ raise ConflictResolverError(
112
+ "Conflict block not found in file. Has it been edited already?"
113
+ )
114
+
115
+ full_path.write_text(content, encoding="utf-8")