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.
Files changed (125) hide show
  1. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/PKG-INFO +30 -1
  2. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/README.md +29 -0
  3. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/cli.py +10 -4
  4. ace_git_copilot-0.3.1/ace/ui/dashboard.py +380 -0
  5. ace_git_copilot-0.3.1/ace/ui/display.py +256 -0
  6. ace_git_copilot-0.3.1/ace/ui/prompts.py +103 -0
  7. ace_git_copilot-0.3.1/ace/ui/themes.py +33 -0
  8. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/pyproject.toml +7 -1
  9. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/e2e/conftest.py +0 -1
  10. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/e2e/test_tier1_features.py +0 -3
  11. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/e2e/test_tier2_boundaries.py +0 -1
  12. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/e2e/test_tier3_combinations.py +0 -3
  13. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/e2e/test_tier4_workloads.py +0 -3
  14. ace_git_copilot-0.2.9/ace/ui/dashboard.py +0 -294
  15. ace_git_copilot-0.2.9/ace/ui/display.py +0 -180
  16. ace_git_copilot-0.2.9/ace/ui/prompts.py +0 -89
  17. ace_git_copilot-0.2.9/ace/ui/themes.py +0 -24
  18. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/AGENTS.md +0 -0
  19. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/BRIEFING.md +0 -0
  20. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/ORIGINAL_REQUEST.md +0 -0
  21. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/e2e_testing_track/BRIEFING.md +0 -0
  22. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/e2e_testing_track/ORIGINAL_REQUEST.md +0 -0
  23. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/e2e_testing_track/SCOPE.md +0 -0
  24. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/e2e_testing_track/progress.md +0 -0
  25. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/BRIEFING.md +0 -0
  26. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/ORIGINAL_REQUEST.md +0 -0
  27. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/emojis_list.txt +0 -0
  28. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/find_unused_modules.py +0 -0
  29. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/handoff.md +0 -0
  30. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/measure_lazy_startup.py +0 -0
  31. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/measure_startup.py +0 -0
  32. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/profile_imports.py +0 -0
  33. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/progress.md +0 -0
  34. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/run_importtime.py +0 -0
  35. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/search_banner.py +0 -0
  36. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/search_emojis.py +0 -0
  37. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/search_git_usages.py +0 -0
  38. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/search_usages.py +0 -0
  39. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/test_import_profiler.py +0 -0
  40. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/explorer_init/test_mocked_sys.py +0 -0
  41. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/handoff.md +0 -0
  42. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/implementation_track/BRIEFING.md +0 -0
  43. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/implementation_track/ORIGINAL_REQUEST.md +0 -0
  44. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/implementation_track/explorer_initial_report.md +0 -0
  45. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/implementation_track/progress.md +0 -0
  46. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/orchestrator/.gitkeep +0 -0
  47. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/orchestrator/BRIEFING.md +0 -0
  48. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/orchestrator/ORIGINAL_REQUEST.md +0 -0
  49. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/orchestrator/PROJECT.md +0 -0
  50. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/orchestrator/progress.md +0 -0
  51. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/teamwork_preview_explorer_e2e_explore/BRIEFING.md +0 -0
  52. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/teamwork_preview_explorer_e2e_explore/ORIGINAL_REQUEST.md +0 -0
  53. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/teamwork_preview_explorer_e2e_explore/handoff.md +0 -0
  54. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/teamwork_preview_explorer_e2e_explore/progress.md +0 -0
  55. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/worker_e2e_testing/BRIEFING.md +0 -0
  56. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/worker_e2e_testing/ORIGINAL_REQUEST.md +0 -0
  57. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/worker_e2e_testing/progress.md +0 -0
  58. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/worker_m1_startup/BRIEFING.md +0 -0
  59. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/worker_m1_startup/ORIGINAL_REQUEST.md +0 -0
  60. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.agents/worker_m1_startup/progress.md +0 -0
  61. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.env.example +0 -0
  62. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.github/workflows/tests.yml +0 -0
  63. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/.gitignore +0 -0
  64. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/CODE_OF_CONDUCT.md +0 -0
  65. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/CONTRIBUTING.md +0 -0
  66. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/LICENSE +0 -0
  67. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/PROJECT.md +0 -0
  68. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/SECURITY.md +0 -0
  69. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/SUPPORT.md +0 -0
  70. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/TEST_INFRA.md +0 -0
  71. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/TEST_READY.md +0 -0
  72. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/__init__.py +0 -0
  73. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/__main__.py +0 -0
  74. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/changelog_generator.py +0 -0
  75. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/code_reviewer.py +0 -0
  76. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/commit_generator.py +0 -0
  77. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/conflict_resolver.py +0 -0
  78. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/gitignore_generator.py +0 -0
  79. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/history_analyzer.py +0 -0
  80. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/intent_parser.py +0 -0
  81. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/llm_factory.py +0 -0
  82. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/pr_drafter.py +0 -0
  83. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/changelog.py +0 -0
  84. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/commit.py +0 -0
  85. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/conflict.py +0 -0
  86. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/doctor.py +0 -0
  87. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/explain.py +0 -0
  88. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/ignore.py +0 -0
  89. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/intent.py +0 -0
  90. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/pr.py +0 -0
  91. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/rebase.py +0 -0
  92. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/review.py +0 -0
  93. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/search.py +0 -0
  94. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/prompts/undo.py +0 -0
  95. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ai/rebase_helper.py +0 -0
  96. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/core/config.py +0 -0
  97. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/core/context.py +0 -0
  98. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/core/diagnostics.py +0 -0
  99. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/core/git_ops.py +0 -0
  100. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/core/hooks.py +0 -0
  101. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/core/safety.py +0 -0
  102. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/ui/banner.py +0 -0
  103. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/utils/conflict_parser.py +0 -0
  104. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/utils/diff_parser.py +0 -0
  105. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/ace/utils/json_utils.py +0 -0
  106. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/importtime.txt +0 -0
  107. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/importtime_optimized.txt +0 -0
  108. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/conftest.py +0 -0
  109. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_changelog_generator.py +0 -0
  110. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_code_reviewer.py +0 -0
  111. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_conflict_resolver.py +0 -0
  112. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_diagnostics.py +0 -0
  113. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_diff_trimmer.py +0 -0
  114. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_git_ops.py +0 -0
  115. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_help.py +0 -0
  116. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_history_analyzer.py +0 -0
  117. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_hooks.py +0 -0
  118. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_ignore.py +0 -0
  119. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_intent_parser.py +0 -0
  120. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_llm_factory.py +0 -0
  121. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_pr_drafter.py +0 -0
  122. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_rebase_helper.py +0 -0
  123. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_safety.py +0 -0
  124. {ace_git_copilot-0.2.9 → ace_git_copilot-0.3.1}/tests/test_search.py +0 -0
  125. {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.2.9
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
- console.print(" [bold cyan][d][/bold cyan] -> View diff of this commit")
1339
- console.print(" [bold cyan][c][/bold cyan] -> Checkout this commit (detached HEAD)")
1340
- console.print(" [bold cyan][b][/bold cyan] -> Create and switch to new branch at this commit")
1341
- console.print(" [bold cyan][s][/bold cyan] -> Skip/Quit")
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]")