ace-git-copilot 0.2.9__tar.gz → 0.3.1__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.
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/PKG-INFO +30 -1
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/README.md +29 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/cli.py +10 -4
- ace_git_copilot-0.3.1/ace/ui/dashboard.py +380 -0
- ace_git_copilot-0.3.1/ace/ui/display.py +256 -0
- ace_git_copilot-0.3.1/ace/ui/prompts.py +103 -0
- ace_git_copilot-0.3.1/ace/ui/themes.py +33 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/pyproject.toml +7 -1
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/e2e/conftest.py +0 -1
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/e2e/test_tier1_features.py +0 -3
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/e2e/test_tier2_boundaries.py +0 -1
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/e2e/test_tier3_combinations.py +0 -3
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/e2e/test_tier4_workloads.py +0 -3
- ace_git_copilot-0.2.9/ace/ui/dashboard.py +0 -294
- ace_git_copilot-0.2.9/ace/ui/display.py +0 -180
- ace_git_copilot-0.2.9/ace/ui/prompts.py +0 -89
- ace_git_copilot-0.2.9/ace/ui/themes.py +0 -24
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/AGENTS.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/BRIEFING.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/e2e_testing_track/BRIEFING.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/e2e_testing_track/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/e2e_testing_track/SCOPE.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/e2e_testing_track/progress.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/BRIEFING.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/emojis_list.txt +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/find_unused_modules.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/handoff.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/measure_lazy_startup.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/measure_startup.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/profile_imports.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/progress.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/run_importtime.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/search_banner.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/search_emojis.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/search_git_usages.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/search_usages.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/test_import_profiler.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/test_mocked_sys.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/handoff.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/implementation_track/BRIEFING.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/implementation_track/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/implementation_track/explorer_initial_report.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/implementation_track/progress.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/orchestrator/.gitkeep +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/orchestrator/BRIEFING.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/orchestrator/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/orchestrator/PROJECT.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/orchestrator/progress.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/teamwork_preview_explorer_e2e_explore/BRIEFING.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/teamwork_preview_explorer_e2e_explore/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/teamwork_preview_explorer_e2e_explore/handoff.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/teamwork_preview_explorer_e2e_explore/progress.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/worker_e2e_testing/BRIEFING.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/worker_e2e_testing/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/worker_e2e_testing/progress.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/worker_m1_startup/BRIEFING.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/worker_m1_startup/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/worker_m1_startup/progress.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.env.example +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.github/workflows/tests.yml +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.gitignore +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/CODE_OF_CONDUCT.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/CONTRIBUTING.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/LICENSE +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/PROJECT.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/SECURITY.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/SUPPORT.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/TEST_INFRA.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/TEST_READY.md +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/__init__.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/__main__.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/changelog_generator.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/code_reviewer.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/commit_generator.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/conflict_resolver.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/gitignore_generator.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/history_analyzer.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/intent_parser.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/llm_factory.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/pr_drafter.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/changelog.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/commit.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/conflict.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/doctor.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/explain.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/ignore.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/intent.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/pr.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/rebase.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/review.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/search.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/undo.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/rebase_helper.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/core/config.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/core/context.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/core/diagnostics.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/core/git_ops.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/core/hooks.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/core/safety.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ui/banner.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/utils/conflict_parser.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/utils/diff_parser.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/utils/json_utils.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/importtime.txt +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/importtime_optimized.txt +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/conftest.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_changelog_generator.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_code_reviewer.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_conflict_resolver.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_diagnostics.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_diff_trimmer.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_git_ops.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_help.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_history_analyzer.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_hooks.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_ignore.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_intent_parser.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_llm_factory.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_pr_drafter.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_rebase_helper.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_safety.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_search.py +0 -0
- {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_undo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ace-git-copilot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: AI-powered Git copilot — talk to Git in plain English
|
|
5
5
|
Project-URL: Homepage, https://github.com/jachinsamuel/Ace
|
|
6
6
|
Project-URL: Documentation, https://github.com/jachinsamuel/Ace#readme
|
|
@@ -193,3 +193,32 @@ The dashboard features:
|
|
|
193
193
|
## License
|
|
194
194
|
|
|
195
195
|
Distributed under the MIT License. See [LICENSE](LICENSE) for more details.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Changelog
|
|
200
|
+
|
|
201
|
+
### v0.3.1 — Patch (2026-06-30)
|
|
202
|
+
* Fixed invisible key labels (`[c]`, `[r]`, etc.) in the dashboard and search menus caused by Rich markup tag conflicts.
|
|
203
|
+
* Fixed `[Y/n]` confirmation prompt rendering invisibly.
|
|
204
|
+
|
|
205
|
+
### v0.3.0 — UI Polish (2026-06-30)
|
|
206
|
+
* Rewrote all UI output modules (`display.py`, `prompts.py`, `dashboard.py`, `themes.py`) for a polished, professional look.
|
|
207
|
+
* Removed all unprofessional emojis; replaced with clean ASCII status symbols (`>>`, `**`, `!!`, `EE`).
|
|
208
|
+
* Dashboard panels now use consistent `ROUNDED` borders, colour-coded file lists (`+` staged, `~` unstaged, `?` untracked), and a styled commit history table.
|
|
209
|
+
* Execution plan table uses `SIMPLE_HEAD` style with colour-highlighted `git` and `ace` command prefixes.
|
|
210
|
+
* Commit message panel shows character count badge coloured green/amber/red relative to the 72-char limit.
|
|
211
|
+
* AI code review score rendered as a styled badge based on score range.
|
|
212
|
+
* Added `[tool.ruff]` configuration to exclude `.agents/` scratch folder from lint checks.
|
|
213
|
+
|
|
214
|
+
### v0.2.9 — Metadata & Build Fix (2026-06-30)
|
|
215
|
+
* Fixed broken `pyproject.toml` TOML syntax (invalid inline `urls` table).
|
|
216
|
+
* Added full `authors` and `[project.urls]` metadata visible on PyPI.
|
|
217
|
+
|
|
218
|
+
### v0.2.6 — Startup Optimisation (2026-06-21)
|
|
219
|
+
* Implemented lazy-loading for all heavy LangChain imports — CLI startup under 200 ms.
|
|
220
|
+
* Added `ace switch` command for switching between sibling repositories from the dashboard.
|
|
221
|
+
|
|
222
|
+
### v0.2.3 — Feature Expansion (2026-06-09)
|
|
223
|
+
* Added `ace doctor`, `ace hook`, and `ace squash` commands.
|
|
224
|
+
* Comprehensive E2E test suite added under `tests/e2e/`.
|
|
@@ -160,3 +160,32 @@ The dashboard features:
|
|
|
160
160
|
## License
|
|
161
161
|
|
|
162
162
|
Distributed under the MIT License. See [LICENSE](LICENSE) for more details.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Changelog
|
|
167
|
+
|
|
168
|
+
### v0.3.1 — Patch (2026-06-30)
|
|
169
|
+
* Fixed invisible key labels (`[c]`, `[r]`, etc.) in the dashboard and search menus caused by Rich markup tag conflicts.
|
|
170
|
+
* Fixed `[Y/n]` confirmation prompt rendering invisibly.
|
|
171
|
+
|
|
172
|
+
### v0.3.0 — UI Polish (2026-06-30)
|
|
173
|
+
* Rewrote all UI output modules (`display.py`, `prompts.py`, `dashboard.py`, `themes.py`) for a polished, professional look.
|
|
174
|
+
* Removed all unprofessional emojis; replaced with clean ASCII status symbols (`>>`, `**`, `!!`, `EE`).
|
|
175
|
+
* Dashboard panels now use consistent `ROUNDED` borders, colour-coded file lists (`+` staged, `~` unstaged, `?` untracked), and a styled commit history table.
|
|
176
|
+
* Execution plan table uses `SIMPLE_HEAD` style with colour-highlighted `git` and `ace` command prefixes.
|
|
177
|
+
* Commit message panel shows character count badge coloured green/amber/red relative to the 72-char limit.
|
|
178
|
+
* AI code review score rendered as a styled badge based on score range.
|
|
179
|
+
* Added `[tool.ruff]` configuration to exclude `.agents/` scratch folder from lint checks.
|
|
180
|
+
|
|
181
|
+
### v0.2.9 — Metadata & Build Fix (2026-06-30)
|
|
182
|
+
* Fixed broken `pyproject.toml` TOML syntax (invalid inline `urls` table).
|
|
183
|
+
* Added full `authors` and `[project.urls]` metadata visible on PyPI.
|
|
184
|
+
|
|
185
|
+
### v0.2.6 — Startup Optimisation (2026-06-21)
|
|
186
|
+
* Implemented lazy-loading for all heavy LangChain imports — CLI startup under 200 ms.
|
|
187
|
+
* Added `ace switch` command for switching between sibling repositories from the dashboard.
|
|
188
|
+
|
|
189
|
+
### v0.2.3 — Feature Expansion (2026-06-09)
|
|
190
|
+
* Added `ace doctor`, `ace hook`, and `ace squash` commands.
|
|
191
|
+
* Comprehensive E2E test suite added under `tests/e2e/`.
|
|
@@ -1335,10 +1335,16 @@ def search_cmd(
|
|
|
1335
1335
|
|
|
1336
1336
|
# Show options
|
|
1337
1337
|
console.print("\n[bold]Select action:[/bold]")
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1338
|
+
def _key(k: str, desc: str) -> None:
|
|
1339
|
+
from rich.text import Text
|
|
1340
|
+
console.print(Text.assemble(
|
|
1341
|
+
(" ", ""), (f"[{k}]", "bold #00D5FF"), (f" {desc}", "#BDBDBD")
|
|
1342
|
+
))
|
|
1343
|
+
_key("d", "View diff of this commit")
|
|
1344
|
+
_key("c", "Checkout this commit (detached HEAD)")
|
|
1345
|
+
_key("b", "Create and switch to a new branch here")
|
|
1346
|
+
_key("s", "Skip / Quit")
|
|
1347
|
+
|
|
1342
1348
|
|
|
1343
1349
|
action = click.getchar().lower().strip()
|
|
1344
1350
|
console.print(action)
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import typer
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.columns import Columns
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
from rich import box
|
|
9
|
+
from ace.ui.display import console, spinner, show_warning_panel, print_success, print_warning
|
|
10
|
+
from ace.core.git_ops import GitOps
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
def _branch_label(branch: str) -> Text:
|
|
16
|
+
"""Render the current branch name with a small indicator."""
|
|
17
|
+
return Text.assemble((" ", ""), (branch, "bold #00E676"))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _sync_label(ahead: int, behind: int) -> Text:
|
|
21
|
+
if not ahead and not behind:
|
|
22
|
+
return Text("Up to date", style="#00E676")
|
|
23
|
+
parts: list = []
|
|
24
|
+
if ahead:
|
|
25
|
+
parts.append((f"+{ahead} ahead", "bold #00E676"))
|
|
26
|
+
if ahead and behind:
|
|
27
|
+
parts.append((" / ", "dim #555555"))
|
|
28
|
+
if behind:
|
|
29
|
+
parts.append((f"-{behind} behind", "bold #FF1744"))
|
|
30
|
+
return Text.assemble(*parts)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _file_count_label(n: int, colour: str) -> Text:
|
|
34
|
+
if n == 0:
|
|
35
|
+
return Text("none", style="dim #666666")
|
|
36
|
+
return Text(str(n), style=f"bold {colour}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ─── Dashboard entry point ───────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
def show_dashboard(git_ops: GitOps, offline: bool = False):
|
|
42
|
+
"""
|
|
43
|
+
Renders an interactive terminal dashboard displaying repository state,
|
|
44
|
+
workspace changes, and a menu to quickly run Ace operations.
|
|
45
|
+
"""
|
|
46
|
+
from ace.ui.banner import animate_fire_banner, get_fire_banner_static
|
|
47
|
+
|
|
48
|
+
click.clear()
|
|
49
|
+
try:
|
|
50
|
+
animate_fire_banner(duration_seconds=1.2)
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
while True:
|
|
55
|
+
click.clear()
|
|
56
|
+
|
|
57
|
+
# ── Header ──────────────────────────────────────────────────────────
|
|
58
|
+
console.print(get_fire_banner_static())
|
|
59
|
+
console.print(
|
|
60
|
+
Text.assemble(
|
|
61
|
+
(" Ace", "bold #FF6D00"),
|
|
62
|
+
(" AI Git Copilot", "bold white"),
|
|
63
|
+
(" · Interactive Dashboard", "dim #9E9E9E"),
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
console.print()
|
|
67
|
+
|
|
68
|
+
# ── Fetch repo state ────────────────────────────────────────────────
|
|
69
|
+
try:
|
|
70
|
+
current_branch = git_ops.get_current_branch() or "Detached HEAD"
|
|
71
|
+
tracking = git_ops.get_upstream_tracking() or "No remote tracking"
|
|
72
|
+
ab = git_ops.get_ahead_behind()
|
|
73
|
+
status = git_ops.get_status()
|
|
74
|
+
commits = git_ops.get_log(n=5)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
console.print(f"[error]Failed to read repository: {e}[/error]")
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
# ── Status panel ────────────────────────────────────────────────────
|
|
80
|
+
st = Table.grid(padding=(0, 2))
|
|
81
|
+
st.add_column(style="label", justify="right", min_width=12)
|
|
82
|
+
st.add_column()
|
|
83
|
+
st.add_row("Branch", _branch_label(current_branch))
|
|
84
|
+
st.add_row("Remote", Text(tracking, style="#9E9E9E"))
|
|
85
|
+
st.add_row("Sync", _sync_label(ab["ahead"], ab["behind"]))
|
|
86
|
+
status_panel = Panel(
|
|
87
|
+
st,
|
|
88
|
+
title="[bold white]Repository[/bold white]",
|
|
89
|
+
border_style="#00D5FF",
|
|
90
|
+
box=box.ROUNDED,
|
|
91
|
+
expand=False,
|
|
92
|
+
padding=(0, 1),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# ── Changes panel ───────────────────────────────────────────────────
|
|
96
|
+
ct = Table.grid(padding=(0, 2))
|
|
97
|
+
ct.add_column(style="label", justify="right", min_width=12)
|
|
98
|
+
ct.add_column()
|
|
99
|
+
ct.add_row("Staged", _file_count_label(len(status["staged"]), "#00E676"))
|
|
100
|
+
ct.add_row("Unstaged", _file_count_label(len(status["unstaged"]), "#FFD600"))
|
|
101
|
+
ct.add_row("Untracked", _file_count_label(len(status["untracked"]), "#9E9E9E"))
|
|
102
|
+
changes_panel = Panel(
|
|
103
|
+
ct,
|
|
104
|
+
title="[bold white]Workspace[/bold white]",
|
|
105
|
+
border_style="#FFD600",
|
|
106
|
+
box=box.ROUNDED,
|
|
107
|
+
expand=False,
|
|
108
|
+
padding=(0, 1),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# ── Sibling repos panel ─────────────────────────────────────────────
|
|
112
|
+
parent_dir = Path(git_ops.working_dir).parent
|
|
113
|
+
sibling_repos: list[str] = []
|
|
114
|
+
try:
|
|
115
|
+
for p in parent_dir.iterdir():
|
|
116
|
+
if p.is_dir() and p != Path(git_ops.working_dir) and (p / ".git").exists():
|
|
117
|
+
sibling_repos.append(p.name)
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
sibling_panel = None
|
|
122
|
+
if sibling_repos:
|
|
123
|
+
rt = Table.grid(padding=(0, 2))
|
|
124
|
+
rt.add_column(style="bold #B388FF", min_width=14)
|
|
125
|
+
rt.add_column(style="dim #9E9E9E")
|
|
126
|
+
for r_name in sibling_repos[:5]:
|
|
127
|
+
sib_branch = "?"
|
|
128
|
+
try:
|
|
129
|
+
import git as _git
|
|
130
|
+
sib_branch = _git.Repo(parent_dir / r_name).active_branch.name
|
|
131
|
+
except Exception:
|
|
132
|
+
pass
|
|
133
|
+
rt.add_row(r_name, sib_branch)
|
|
134
|
+
if len(sibling_repos) > 5:
|
|
135
|
+
rt.add_row(f" +{len(sibling_repos) - 5} more", "")
|
|
136
|
+
sibling_panel = Panel(
|
|
137
|
+
rt,
|
|
138
|
+
title="[bold white]Workspace Repos[/bold white]",
|
|
139
|
+
border_style="#B388FF",
|
|
140
|
+
box=box.ROUNDED,
|
|
141
|
+
expand=False,
|
|
142
|
+
padding=(0, 1),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
panels = [status_panel, changes_panel]
|
|
146
|
+
if sibling_panel:
|
|
147
|
+
panels.append(sibling_panel)
|
|
148
|
+
console.print(Columns(panels))
|
|
149
|
+
console.print()
|
|
150
|
+
|
|
151
|
+
# ── Staged / Unstaged file lists ────────────────────────────────────
|
|
152
|
+
if status["staged"]:
|
|
153
|
+
t = Table(show_header=False, box=None, padding=(0, 2))
|
|
154
|
+
t.add_column()
|
|
155
|
+
for f in status["staged"]:
|
|
156
|
+
t.add_row(Text.assemble(("+ ", "bold #00E676"), (f, "#BDBDBD")))
|
|
157
|
+
console.print(Panel(t, title="[bold #00E676]Staged[/bold #00E676]",
|
|
158
|
+
border_style="#00E676", box=box.SIMPLE, expand=False))
|
|
159
|
+
console.print()
|
|
160
|
+
|
|
161
|
+
if status["unstaged"]:
|
|
162
|
+
t = Table(show_header=False, box=None, padding=(0, 2))
|
|
163
|
+
t.add_column()
|
|
164
|
+
for f in status["unstaged"]:
|
|
165
|
+
t.add_row(Text.assemble(("~ ", "bold #FFD600"), (f, "#BDBDBD")))
|
|
166
|
+
console.print(Panel(t, title="[bold #FFD600]Unstaged[/bold #FFD600]",
|
|
167
|
+
border_style="#FFD600", box=box.SIMPLE, expand=False))
|
|
168
|
+
console.print()
|
|
169
|
+
|
|
170
|
+
if status["untracked"]:
|
|
171
|
+
t = Table(show_header=False, box=None, padding=(0, 2))
|
|
172
|
+
t.add_column()
|
|
173
|
+
for f in status["untracked"]:
|
|
174
|
+
t.add_row(Text.assemble(("? ", "dim #9E9E9E"), (f, "dim #9E9E9E")))
|
|
175
|
+
console.print(Panel(t, title="[dim]Untracked[/dim]",
|
|
176
|
+
border_style="#555555", box=box.SIMPLE, expand=False))
|
|
177
|
+
console.print()
|
|
178
|
+
|
|
179
|
+
# ── Recent commits ──────────────────────────────────────────────────
|
|
180
|
+
if commits:
|
|
181
|
+
commit_table = Table(
|
|
182
|
+
show_header=True,
|
|
183
|
+
header_style="bold #9E9E9E",
|
|
184
|
+
box=box.SIMPLE_HEAD,
|
|
185
|
+
show_edge=False,
|
|
186
|
+
padding=(0, 2),
|
|
187
|
+
)
|
|
188
|
+
commit_table.add_column("Hash", style="#666666", width=8, no_wrap=True)
|
|
189
|
+
commit_table.add_column("Message", style="white", ratio=4)
|
|
190
|
+
commit_table.add_column("Author", style="#B388FF", ratio=2)
|
|
191
|
+
for c in commits:
|
|
192
|
+
commit_table.add_row(c["hexsha"][:7], c["summary"], c["author"])
|
|
193
|
+
console.print(
|
|
194
|
+
Panel(commit_table, title="[bold white]Recent Commits[/bold white]",
|
|
195
|
+
border_style="#444444", box=box.ROUNDED, expand=False)
|
|
196
|
+
)
|
|
197
|
+
else:
|
|
198
|
+
console.print("[dim] No commit history yet.[/dim]")
|
|
199
|
+
console.print()
|
|
200
|
+
|
|
201
|
+
# ── Action menu ─────────────────────────────────────────────────────
|
|
202
|
+
menu = Table(show_header=False, box=None, padding=(0, 4), expand=False)
|
|
203
|
+
menu.add_column(min_width=22)
|
|
204
|
+
menu.add_column(min_width=22)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _row(key: str, label: str) -> Text:
|
|
208
|
+
return Text.assemble((f"[{key}]", "bold #00D5FF"), (f" {label}", "#BDBDBD"))
|
|
209
|
+
|
|
210
|
+
menu.add_row(_row("c", "AI Commit"), _row("r", "AI Code Review"))
|
|
211
|
+
menu.add_row(_row("u", "Smart Undo"), _row("p", "Plan Command (AI)"))
|
|
212
|
+
menu.add_row(_row("s", "Repo Stats"), _row("w", "Switch Repo"))
|
|
213
|
+
menu.add_row(_row("q", "Quit"), Text(""))
|
|
214
|
+
|
|
215
|
+
console.print(
|
|
216
|
+
Panel(menu, title="[bold white]Actions[/bold white]",
|
|
217
|
+
border_style="#FF6D00", box=box.ROUNDED, expand=False)
|
|
218
|
+
)
|
|
219
|
+
console.print()
|
|
220
|
+
console.print("[bold #FF6D00] Press a key ...[/bold #FF6D00] ", end="")
|
|
221
|
+
|
|
222
|
+
# ── Key input ───────────────────────────────────────────────────────
|
|
223
|
+
while True:
|
|
224
|
+
choice = click.getchar().lower().strip()
|
|
225
|
+
if choice in ("\r", "\n", ""):
|
|
226
|
+
choice = "q"
|
|
227
|
+
break
|
|
228
|
+
if choice in ("c", "r", "u", "p", "s", "w", "q"):
|
|
229
|
+
break
|
|
230
|
+
|
|
231
|
+
console.print(f"[dim]{choice}[/dim]")
|
|
232
|
+
console.print()
|
|
233
|
+
|
|
234
|
+
# ── Handle choice ───────────────────────────────────────────────────
|
|
235
|
+
if choice == "q":
|
|
236
|
+
console.print("[dim] Exiting dashboard.[/dim]")
|
|
237
|
+
break
|
|
238
|
+
|
|
239
|
+
elif choice == "c":
|
|
240
|
+
from ace.cli import commit_cmd
|
|
241
|
+
try:
|
|
242
|
+
commit_cmd(offline=offline)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
console.print(f"[error] Error: {e}[/error]")
|
|
245
|
+
|
|
246
|
+
elif choice == "r":
|
|
247
|
+
from ace.cli import review_cmd
|
|
248
|
+
try:
|
|
249
|
+
review_cmd(all_changes=True, offline=offline)
|
|
250
|
+
except Exception as e:
|
|
251
|
+
console.print(f"[error] Error: {e}[/error]")
|
|
252
|
+
|
|
253
|
+
elif choice == "u":
|
|
254
|
+
from ace.cli import undo_cmd
|
|
255
|
+
try:
|
|
256
|
+
undo_cmd(offline=offline)
|
|
257
|
+
except Exception as e:
|
|
258
|
+
console.print(f"[error] Error: {e}[/error]")
|
|
259
|
+
|
|
260
|
+
elif choice == "s":
|
|
261
|
+
from ace.cli import stats_cmd
|
|
262
|
+
try:
|
|
263
|
+
stats_cmd()
|
|
264
|
+
except Exception as e:
|
|
265
|
+
console.print(f"[error] Error: {e}[/error]")
|
|
266
|
+
|
|
267
|
+
elif choice == "w":
|
|
268
|
+
_handle_switch_repo(git_ops, parent_dir)
|
|
269
|
+
|
|
270
|
+
elif choice == "p":
|
|
271
|
+
_handle_plan_command(git_ops, offline)
|
|
272
|
+
|
|
273
|
+
console.print()
|
|
274
|
+
console.print("[dim] Press any key to return ...[/dim] ", end="")
|
|
275
|
+
click.getchar()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# ─── Action handlers ──────────────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
def _handle_switch_repo(git_ops: GitOps, parent_dir: Path) -> None:
|
|
281
|
+
"""Interactive repository switcher."""
|
|
282
|
+
from ace.ui.prompts import prompt_select
|
|
283
|
+
|
|
284
|
+
all_repos: list[str] = []
|
|
285
|
+
try:
|
|
286
|
+
all_repos = sorted(
|
|
287
|
+
p.name for p in parent_dir.iterdir()
|
|
288
|
+
if p.is_dir() and (p / ".git").exists()
|
|
289
|
+
)
|
|
290
|
+
except Exception:
|
|
291
|
+
pass
|
|
292
|
+
|
|
293
|
+
if not all_repos:
|
|
294
|
+
print_warning("No other repositories found in the parent directory.")
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
current_name = Path(git_ops.working_dir).name
|
|
298
|
+
display_options = [
|
|
299
|
+
f"{name} [bold #00E676](current)[/bold #00E676]" if name == current_name else name
|
|
300
|
+
for name in all_repos
|
|
301
|
+
]
|
|
302
|
+
|
|
303
|
+
console.print("[bold white] Repositories in workspace:[/bold white]")
|
|
304
|
+
sel_idx = prompt_select(display_options, prompt_text=" Repository number", default="s")
|
|
305
|
+
if sel_idx < 0:
|
|
306
|
+
console.print("[dim] Switch cancelled.[/dim]")
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
selected = all_repos[sel_idx]
|
|
310
|
+
new_path = parent_dir / selected
|
|
311
|
+
try:
|
|
312
|
+
from ace.core.git_ops import GitOps as _GitOps
|
|
313
|
+
git_ops.__dict__.update(_GitOps(str(new_path)).__dict__)
|
|
314
|
+
print_success(f"Switched to {selected}")
|
|
315
|
+
except Exception as e:
|
|
316
|
+
console.print(f"[error] Failed to switch: {e}[/error]")
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _handle_plan_command(git_ops: GitOps, offline: bool) -> None:
|
|
320
|
+
"""AI natural-language command planner."""
|
|
321
|
+
from ace.ai.intent_parser import IntentParser
|
|
322
|
+
from ace.core.safety import SafetyChecker
|
|
323
|
+
from ace.ui.display import show_plan
|
|
324
|
+
from ace.ui.prompts import confirm as ui_confirm
|
|
325
|
+
|
|
326
|
+
query = typer.prompt(" What do you want to do with Git?")
|
|
327
|
+
if not query.strip():
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
parser = IntentParser(git_ops)
|
|
331
|
+
try:
|
|
332
|
+
with spinner("Planning commands..."):
|
|
333
|
+
parsed = parser.parse_intent(query, offline=offline)
|
|
334
|
+
|
|
335
|
+
commands = parsed.get("commands", [])
|
|
336
|
+
explanation = parsed.get("explanation", "")
|
|
337
|
+
|
|
338
|
+
if not commands:
|
|
339
|
+
console.print(f"[dim] No commands planned.[/dim] {explanation}")
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
show_plan(commands, [explanation] + [""] * (len(commands) - 1))
|
|
343
|
+
|
|
344
|
+
# Safety classification
|
|
345
|
+
highest_risk = "safe"
|
|
346
|
+
risk_details: list[str] = []
|
|
347
|
+
for cmd in commands:
|
|
348
|
+
r_level, r_desc, _ = SafetyChecker.analyze_command(cmd)
|
|
349
|
+
if r_level == "destructive":
|
|
350
|
+
highest_risk = "destructive"
|
|
351
|
+
risk_details.append(f"[bold]{cmd}[/bold]\n{r_desc}")
|
|
352
|
+
elif r_level == "moderate" and highest_risk != "destructive":
|
|
353
|
+
highest_risk = "moderate"
|
|
354
|
+
|
|
355
|
+
execute = True
|
|
356
|
+
if highest_risk == "destructive":
|
|
357
|
+
show_warning_panel("\n\n".join(risk_details), "Destructive Operation Detected")
|
|
358
|
+
execute = ui_confirm("Execute these destructive commands?", default=False)
|
|
359
|
+
elif highest_risk == "moderate":
|
|
360
|
+
execute = ui_confirm("Execute this plan?", default=True)
|
|
361
|
+
|
|
362
|
+
if execute:
|
|
363
|
+
for cmd in commands:
|
|
364
|
+
console.print(
|
|
365
|
+
Text.assemble(
|
|
366
|
+
(" › ", "bold #00D5FF"),
|
|
367
|
+
("Running ", "#9E9E9E"),
|
|
368
|
+
(cmd, "bold white"),
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
git_args = cmd[4:] if cmd.startswith("git ") else cmd
|
|
372
|
+
result = git_ops.execute(git_args)
|
|
373
|
+
if result.strip():
|
|
374
|
+
console.print(f"[dim]{result}[/dim]")
|
|
375
|
+
print_success("Plan executed successfully.")
|
|
376
|
+
else:
|
|
377
|
+
console.print("[dim] Plan aborted.[/dim]")
|
|
378
|
+
|
|
379
|
+
except Exception as e:
|
|
380
|
+
console.print(f"[error] Error: {e}[/error]")
|