ace-git-copilot 0.3.3__tar.gz → 0.3.4__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.3.3 → ace_git_copilot-0.3.4}/PKG-INFO +7 -1
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/README.md +6 -0
- ace_git_copilot-0.3.4/ace/__init__.py +1 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/cli.py +189 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/pyproject.toml +1 -1
- ace_git_copilot-0.3.4/tests/test_workspace.py +89 -0
- ace_git_copilot-0.3.3/ace/__init__.py +0 -1
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/AGENTS.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/e2e_testing_track/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/e2e_testing_track/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/e2e_testing_track/SCOPE.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/e2e_testing_track/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/emojis_list.txt +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/find_unused_modules.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/handoff.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/measure_lazy_startup.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/measure_startup.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/profile_imports.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/run_importtime.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/search_banner.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/search_emojis.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/search_git_usages.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/search_usages.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/test_import_profiler.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/test_mocked_sys.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/handoff.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/implementation_track/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/implementation_track/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/implementation_track/explorer_initial_report.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/implementation_track/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/orchestrator/.gitkeep +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/orchestrator/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/orchestrator/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/orchestrator/PROJECT.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/orchestrator/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/teamwork_preview_explorer_e2e_explore/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/teamwork_preview_explorer_e2e_explore/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/teamwork_preview_explorer_e2e_explore/handoff.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/teamwork_preview_explorer_e2e_explore/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/worker_e2e_testing/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/worker_e2e_testing/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/worker_e2e_testing/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/worker_m1_startup/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/worker_m1_startup/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/worker_m1_startup/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.env.example +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.github/workflows/tests.yml +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.gitignore +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/CODE_OF_CONDUCT.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/CONTRIBUTING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/LICENSE +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/PROJECT.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/SECURITY.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/SUPPORT.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/TEST_INFRA.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/TEST_READY.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/__main__.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/changelog_generator.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/code_reviewer.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/commit_generator.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/conflict_resolver.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/gitignore_generator.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/history_analyzer.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/intent_parser.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/llm_factory.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/pr_drafter.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/prompts/changelog.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/prompts/commit.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/prompts/conflict.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/prompts/doctor.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/prompts/explain.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/prompts/ignore.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/prompts/intent.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/prompts/pr.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/prompts/rebase.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/prompts/review.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/prompts/search.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/prompts/undo.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ai/rebase_helper.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/core/config.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/core/context.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/core/diagnostics.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/core/git_ops.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/core/hooks.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/core/safety.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ui/banner.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ui/dashboard.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ui/display.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ui/prompts.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/ui/themes.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/utils/conflict_parser.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/utils/diff_parser.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/ace/utils/json_utils.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/conftest.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/e2e/conftest.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/e2e/test_tier1_features.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/e2e/test_tier2_boundaries.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/e2e/test_tier3_combinations.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/e2e/test_tier4_workloads.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_changelog_generator.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_code_reviewer.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_config_atomic_write.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_conflict_resolver.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_conflict_resolver_backup.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_context_gitdir_detection.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_diagnostics.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_diff_trimmer.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_git_ops.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_git_ops_execute.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_help.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_history_analyzer.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_hooks.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_ignore.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_intent_parser.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_llm_factory.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_llm_factory_ollama.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_pr_drafter.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_rebase_helper.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_safety.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/tests/test_search.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/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.
|
|
3
|
+
Version: 0.3.4
|
|
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
|
|
@@ -170,6 +170,7 @@ Ace has dedicated subcommands for specific tasks. Here is the complete command l
|
|
|
170
170
|
| `ace squash` | — | Automatically analyze commit history and run AI-guided interactive rebases. |
|
|
171
171
|
| `ace ignore <rule>` | — | Generate and append standard templates to `.gitignore`. |
|
|
172
172
|
| `ace undo` | — | Safely revert the last action after checking repository status. |
|
|
173
|
+
| `ace workspace` | `ace ws` | Monitor status of multiple repositories and navigate between them. |
|
|
173
174
|
| `ace dash` | — | Launch the interactive repository management console (TUI). |
|
|
174
175
|
| `ace config` | — | Print out active configuration settings. |
|
|
175
176
|
| `ace help` | — | Show user guide and help information on how to use Ace. |
|
|
@@ -198,6 +199,11 @@ Distributed under the MIT License. See [LICENSE](LICENSE) for more details.
|
|
|
198
199
|
|
|
199
200
|
## Changelog
|
|
200
201
|
|
|
202
|
+
### v0.3.4 — Workspace Management (2026-07-11)
|
|
203
|
+
* Added new `workspace` command (alias `ws`) to scan, monitor, and navigate multiple repositories.
|
|
204
|
+
* Shows active branch, staged/unstaged/untracked file counts, and local upstream sync offsets in a Rich Table.
|
|
205
|
+
* Supports interactive actions to open the TUI dashboard, spawn a nested shell, or run copy-paste commands inside any chosen repository.
|
|
206
|
+
|
|
201
207
|
### v0.3.3 — Stability & Resilience (2026-07-04)
|
|
202
208
|
* Implemented streaming progress and download percentages for Ollama model pulls to prevent thread hangs.
|
|
203
209
|
* Added atomic file saves and rollback backups for merge conflict resolutions and config modifications to prevent data corruption.
|
|
@@ -137,6 +137,7 @@ Ace has dedicated subcommands for specific tasks. Here is the complete command l
|
|
|
137
137
|
| `ace squash` | — | Automatically analyze commit history and run AI-guided interactive rebases. |
|
|
138
138
|
| `ace ignore <rule>` | — | Generate and append standard templates to `.gitignore`. |
|
|
139
139
|
| `ace undo` | — | Safely revert the last action after checking repository status. |
|
|
140
|
+
| `ace workspace` | `ace ws` | Monitor status of multiple repositories and navigate between them. |
|
|
140
141
|
| `ace dash` | — | Launch the interactive repository management console (TUI). |
|
|
141
142
|
| `ace config` | — | Print out active configuration settings. |
|
|
142
143
|
| `ace help` | — | Show user guide and help information on how to use Ace. |
|
|
@@ -165,6 +166,11 @@ Distributed under the MIT License. See [LICENSE](LICENSE) for more details.
|
|
|
165
166
|
|
|
166
167
|
## Changelog
|
|
167
168
|
|
|
169
|
+
### v0.3.4 — Workspace Management (2026-07-11)
|
|
170
|
+
* Added new `workspace` command (alias `ws`) to scan, monitor, and navigate multiple repositories.
|
|
171
|
+
* Shows active branch, staged/unstaged/untracked file counts, and local upstream sync offsets in a Rich Table.
|
|
172
|
+
* Supports interactive actions to open the TUI dashboard, spawn a nested shell, or run copy-paste commands inside any chosen repository.
|
|
173
|
+
|
|
168
174
|
### v0.3.3 — Stability & Resilience (2026-07-04)
|
|
169
175
|
* Implemented streaming progress and download percentages for Ollama model pulls to prevent thread hangs.
|
|
170
176
|
* Added atomic file saves and rollback backups for merge conflict resolutions and config modifications to prevent data corruption.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.4"
|
|
@@ -1686,6 +1686,195 @@ def hook_cmd(
|
|
|
1686
1686
|
show_error_panel("Invalid action. Use 'install' or 'uninstall'.", "Usage Error")
|
|
1687
1687
|
raise typer.Exit(code=1)
|
|
1688
1688
|
|
|
1689
|
+
@app.command(name="workspace", help="Monitor status of multiple repositories and navigate between them.")
|
|
1690
|
+
def workspace_cmd(
|
|
1691
|
+
path: Optional[str] = typer.Argument(
|
|
1692
|
+
None, help="Custom directory to scan (defaults to sibling projects scan)"
|
|
1693
|
+
),
|
|
1694
|
+
):
|
|
1695
|
+
from pathlib import Path
|
|
1696
|
+
import os
|
|
1697
|
+
import sys
|
|
1698
|
+
import subprocess
|
|
1699
|
+
from rich.table import Table
|
|
1700
|
+
from ace.core.git_ops import GitOps, NotAGitRepositoryError
|
|
1701
|
+
from ace.ui.display import console, print_info, print_success, print_warning, print_error
|
|
1702
|
+
from ace.ui.prompts import prompt_select, prompt_action
|
|
1703
|
+
|
|
1704
|
+
# 1. Determine target directory to scan
|
|
1705
|
+
if path:
|
|
1706
|
+
scan_dir = Path(path).resolve()
|
|
1707
|
+
else:
|
|
1708
|
+
# Smart default
|
|
1709
|
+
cwd = Path(os.getcwd()).resolve()
|
|
1710
|
+
if (cwd / ".git").exists():
|
|
1711
|
+
scan_dir = cwd.parent
|
|
1712
|
+
else:
|
|
1713
|
+
scan_dir = cwd
|
|
1714
|
+
|
|
1715
|
+
if not scan_dir.exists() or not scan_dir.is_dir():
|
|
1716
|
+
print_error(f"Directory not found: {scan_dir}")
|
|
1717
|
+
raise typer.Exit(code=1)
|
|
1718
|
+
|
|
1719
|
+
print_info(f"Scanning workspace directory: [bold]{scan_dir}[/bold]")
|
|
1720
|
+
|
|
1721
|
+
# 2. Find git repositories
|
|
1722
|
+
repos_found = []
|
|
1723
|
+
try:
|
|
1724
|
+
for child in sorted(scan_dir.iterdir()):
|
|
1725
|
+
if not child.is_dir():
|
|
1726
|
+
continue
|
|
1727
|
+
# Basic validation that it's a git repo
|
|
1728
|
+
if (child / ".git").exists() or (child / "gitdir").exists() or child.name.endswith(".git"):
|
|
1729
|
+
try:
|
|
1730
|
+
git_ops = GitOps(str(child))
|
|
1731
|
+
repos_found.append((child, git_ops))
|
|
1732
|
+
except Exception:
|
|
1733
|
+
pass
|
|
1734
|
+
except Exception as e:
|
|
1735
|
+
print_error(f"Failed to scan directory: {e}")
|
|
1736
|
+
raise typer.Exit(code=1)
|
|
1737
|
+
|
|
1738
|
+
if not repos_found:
|
|
1739
|
+
print_warning(f"No active Git repositories found in: {scan_dir}")
|
|
1740
|
+
return
|
|
1741
|
+
|
|
1742
|
+
# 3. Build Status Table
|
|
1743
|
+
table = Table(
|
|
1744
|
+
title=f"[bold white]Workspace Summary: {scan_dir.name}[/bold white]",
|
|
1745
|
+
title_style="bold",
|
|
1746
|
+
border_style="#FF6D00",
|
|
1747
|
+
expand=True,
|
|
1748
|
+
)
|
|
1749
|
+
table.add_column("[bold #00D5FF]#[/bold #00D5FF]", justify="right", width=4)
|
|
1750
|
+
table.add_column("Repository", style="bold white")
|
|
1751
|
+
table.add_column("Branch", style="bold cyan")
|
|
1752
|
+
table.add_column("Status", justify="center")
|
|
1753
|
+
table.add_column("Sync", justify="center")
|
|
1754
|
+
|
|
1755
|
+
display_options = []
|
|
1756
|
+
for idx, (repo_path, git_ops) in enumerate(repos_found, 1):
|
|
1757
|
+
repo_name = repo_path.name
|
|
1758
|
+
|
|
1759
|
+
# Branch
|
|
1760
|
+
branch_name = git_ops.get_current_branch() or "Detached HEAD"
|
|
1761
|
+
|
|
1762
|
+
# Status counts
|
|
1763
|
+
status = git_ops.get_status()
|
|
1764
|
+
staged = len(status.get("staged", []))
|
|
1765
|
+
unstaged = len(status.get("unstaged", []))
|
|
1766
|
+
untracked = len(status.get("untracked", []))
|
|
1767
|
+
|
|
1768
|
+
if staged == 0 and unstaged == 0 and untracked == 0:
|
|
1769
|
+
status_desc = "[success]Clean[/success]"
|
|
1770
|
+
else:
|
|
1771
|
+
parts = []
|
|
1772
|
+
if staged > 0:
|
|
1773
|
+
parts.append(f"[bold #00E676]+{staged}[/bold #00E676]")
|
|
1774
|
+
if unstaged > 0:
|
|
1775
|
+
parts.append(f"[bold #FFD600]~{unstaged}[/bold #FFD600]")
|
|
1776
|
+
if untracked > 0:
|
|
1777
|
+
parts.append(f"[bold #FF1744]?{untracked}[/bold #FF1744]")
|
|
1778
|
+
status_desc = " ".join(parts)
|
|
1779
|
+
|
|
1780
|
+
# Sync offsets
|
|
1781
|
+
ab = git_ops.get_ahead_behind()
|
|
1782
|
+
ahead = ab.get("ahead", 0)
|
|
1783
|
+
behind = ab.get("behind", 0)
|
|
1784
|
+
tracking = git_ops.get_upstream_tracking()
|
|
1785
|
+
|
|
1786
|
+
if not tracking:
|
|
1787
|
+
sync_desc = "[#666666]No Upstream[/#666666]"
|
|
1788
|
+
elif ahead == 0 and behind == 0:
|
|
1789
|
+
sync_desc = "[success]Up-to-date[/success]"
|
|
1790
|
+
elif ahead > 0 and behind == 0:
|
|
1791
|
+
sync_desc = f"[bold #00D5FF]Ahead {ahead}[/bold #00D5FF]"
|
|
1792
|
+
elif behind > 0 and ahead == 0:
|
|
1793
|
+
sync_desc = f"[bold #FFD600]Behind {behind}[/bold #FFD600]"
|
|
1794
|
+
else:
|
|
1795
|
+
sync_desc = f"[bold #FF1744]Diverged (A{ahead}, B{behind})[/bold #FF1744]"
|
|
1796
|
+
|
|
1797
|
+
table.add_row(
|
|
1798
|
+
str(idx),
|
|
1799
|
+
repo_name,
|
|
1800
|
+
branch_name,
|
|
1801
|
+
status_desc,
|
|
1802
|
+
sync_desc
|
|
1803
|
+
)
|
|
1804
|
+
display_options.append(repo_name)
|
|
1805
|
+
|
|
1806
|
+
console.print()
|
|
1807
|
+
console.print(table)
|
|
1808
|
+
console.print()
|
|
1809
|
+
|
|
1810
|
+
# 4. Prompt selection
|
|
1811
|
+
selected_idx = prompt_select(
|
|
1812
|
+
display_options,
|
|
1813
|
+
prompt_text=" Select repository number to manage (or 'q' to quit)",
|
|
1814
|
+
default="q"
|
|
1815
|
+
)
|
|
1816
|
+
|
|
1817
|
+
if selected_idx < 0:
|
|
1818
|
+
print_info("Exit workspace monitor.")
|
|
1819
|
+
return
|
|
1820
|
+
|
|
1821
|
+
selected_path, _ = repos_found[selected_idx]
|
|
1822
|
+
|
|
1823
|
+
# 5. Prompt action choice
|
|
1824
|
+
console.print(f"\n[bold white] Choose action for [bold cyan]{selected_path.name}[/bold cyan]:[/bold white]")
|
|
1825
|
+
action_options = {
|
|
1826
|
+
"d": ("Dashboard", "Open interactive TUI dashboard"),
|
|
1827
|
+
"s": ("Shell", "Spawn an interactive shell in project directory"),
|
|
1828
|
+
"c": ("Command", "Execute an ace natural-language command"),
|
|
1829
|
+
"q": ("Quit", "Cancel action and exit")
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
action = prompt_action(action_options, default_key="d")
|
|
1833
|
+
|
|
1834
|
+
if action == "d":
|
|
1835
|
+
print_success(f"Launching dashboard in {selected_path.name}...")
|
|
1836
|
+
console.print()
|
|
1837
|
+
try:
|
|
1838
|
+
subprocess.run([sys.executable, "-m", "ace", "dash"], cwd=str(selected_path))
|
|
1839
|
+
except Exception as e:
|
|
1840
|
+
print_error(f"Failed to launch dashboard: {e}")
|
|
1841
|
+
|
|
1842
|
+
elif action == "s":
|
|
1843
|
+
shell = os.environ.get("SHELL")
|
|
1844
|
+
if not shell:
|
|
1845
|
+
if sys.platform == "win32":
|
|
1846
|
+
shell = "powershell.exe"
|
|
1847
|
+
else:
|
|
1848
|
+
shell = "/bin/bash"
|
|
1849
|
+
|
|
1850
|
+
print_success(f"Opening interactive shell in {selected_path.name}...")
|
|
1851
|
+
print_info("Type 'exit' to return to Ace workspace monitor.")
|
|
1852
|
+
console.print()
|
|
1853
|
+
|
|
1854
|
+
try:
|
|
1855
|
+
subprocess.run([shell], cwd=str(selected_path))
|
|
1856
|
+
print_success("Returned to Ace workspace.")
|
|
1857
|
+
except Exception as e:
|
|
1858
|
+
print_error(f"Failed to open shell: {e}")
|
|
1859
|
+
|
|
1860
|
+
elif action == "c":
|
|
1861
|
+
query = typer.prompt(" Enter command for this repo")
|
|
1862
|
+
if query.strip():
|
|
1863
|
+
print_success(f"Running command: {query}")
|
|
1864
|
+
console.print()
|
|
1865
|
+
try:
|
|
1866
|
+
subprocess.run([sys.executable, "-m", "ace", query], cwd=str(selected_path))
|
|
1867
|
+
except Exception as e:
|
|
1868
|
+
print_error(f"Failed to run command: {e}")
|
|
1869
|
+
|
|
1870
|
+
@app.command(name="ws", help="Alias for 'workspace' command.")
|
|
1871
|
+
def ws_cmd(
|
|
1872
|
+
path: Optional[str] = typer.Argument(
|
|
1873
|
+
None, help="Custom directory to scan"
|
|
1874
|
+
),
|
|
1875
|
+
):
|
|
1876
|
+
workspace_cmd(path)
|
|
1877
|
+
|
|
1689
1878
|
if __name__ == "__main__":
|
|
1690
1879
|
app()
|
|
1691
1880
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import patch, MagicMock
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from ace.cli import workspace_cmd
|
|
5
|
+
|
|
6
|
+
@patch("ace.core.git_ops.GitOps")
|
|
7
|
+
@patch("pathlib.Path.iterdir")
|
|
8
|
+
@patch("pathlib.Path.is_dir")
|
|
9
|
+
@patch("pathlib.Path.exists")
|
|
10
|
+
@patch("ace.ui.prompts.prompt_select")
|
|
11
|
+
@patch("ace.ui.prompts.prompt_action")
|
|
12
|
+
@patch("subprocess.run")
|
|
13
|
+
def test_workspace_cmd_select_dashboard(
|
|
14
|
+
mock_sub_run,
|
|
15
|
+
mock_action,
|
|
16
|
+
mock_select,
|
|
17
|
+
mock_exists,
|
|
18
|
+
mock_is_dir,
|
|
19
|
+
mock_iterdir,
|
|
20
|
+
mock_gitops
|
|
21
|
+
):
|
|
22
|
+
# Setup directories
|
|
23
|
+
repo_path = MagicMock(spec=Path)
|
|
24
|
+
repo_path.name = "my-test-project"
|
|
25
|
+
repo_path.__str__.return_value = "/mock/workspace/my-test-project"
|
|
26
|
+
|
|
27
|
+
# Mock checks
|
|
28
|
+
mock_is_dir.return_value = True
|
|
29
|
+
# mock exists to simulate that (child / ".git") exists
|
|
30
|
+
mock_exists.side_effect = lambda: True
|
|
31
|
+
mock_iterdir.return_value = [repo_path]
|
|
32
|
+
|
|
33
|
+
# Mock GitOps response
|
|
34
|
+
mock_git = MagicMock()
|
|
35
|
+
mock_git.get_current_branch.return_value = "main"
|
|
36
|
+
mock_git.get_status.return_value = {"staged": ["file1.txt"], "unstaged": [], "untracked": []}
|
|
37
|
+
mock_git.get_ahead_behind.return_value = {"ahead": 2, "behind": 0}
|
|
38
|
+
mock_git.get_upstream_tracking.return_value = "origin/main"
|
|
39
|
+
mock_gitops.return_value = mock_git
|
|
40
|
+
|
|
41
|
+
# Selection: idx 0 (my-test-project)
|
|
42
|
+
mock_select.return_value = 0
|
|
43
|
+
# Action: 'd' (Dashboard)
|
|
44
|
+
mock_action.return_value = "d"
|
|
45
|
+
|
|
46
|
+
# Run
|
|
47
|
+
workspace_cmd("/mock/workspace")
|
|
48
|
+
|
|
49
|
+
# Assert dashboard was spawned in the subdirectory via subprocess
|
|
50
|
+
mock_sub_run.assert_called_once()
|
|
51
|
+
args, kwargs = mock_sub_run.call_args
|
|
52
|
+
assert "dash" in args[0]
|
|
53
|
+
assert kwargs["cwd"] == "/mock/workspace/my-test-project"
|
|
54
|
+
|
|
55
|
+
@patch("ace.core.git_ops.GitOps")
|
|
56
|
+
@patch("pathlib.Path.iterdir")
|
|
57
|
+
@patch("pathlib.Path.is_dir")
|
|
58
|
+
@patch("pathlib.Path.exists")
|
|
59
|
+
@patch("ace.ui.prompts.prompt_select")
|
|
60
|
+
def test_workspace_cmd_quit_early(
|
|
61
|
+
mock_select,
|
|
62
|
+
mock_exists,
|
|
63
|
+
mock_is_dir,
|
|
64
|
+
mock_iterdir,
|
|
65
|
+
mock_gitops
|
|
66
|
+
):
|
|
67
|
+
repo_path = MagicMock(spec=Path)
|
|
68
|
+
repo_path.name = "my-test-project"
|
|
69
|
+
repo_path.__str__.return_value = "/mock/workspace/my-test-project"
|
|
70
|
+
|
|
71
|
+
mock_is_dir.return_value = True
|
|
72
|
+
mock_exists.side_effect = lambda: True
|
|
73
|
+
mock_iterdir.return_value = [repo_path]
|
|
74
|
+
|
|
75
|
+
mock_git = MagicMock()
|
|
76
|
+
mock_git.get_current_branch.return_value = "main"
|
|
77
|
+
mock_git.get_status.return_value = {"staged": [], "unstaged": [], "untracked": []}
|
|
78
|
+
mock_git.get_ahead_behind.return_value = {"ahead": 0, "behind": 0}
|
|
79
|
+
mock_git.get_upstream_tracking.return_value = "origin/main"
|
|
80
|
+
mock_gitops.return_value = mock_git
|
|
81
|
+
|
|
82
|
+
# Selection: -1 (Quit/Skip)
|
|
83
|
+
mock_select.return_value = -1
|
|
84
|
+
|
|
85
|
+
# Run
|
|
86
|
+
workspace_cmd("/mock/workspace")
|
|
87
|
+
|
|
88
|
+
# Verify no action prompt or subprocess was triggered
|
|
89
|
+
mock_select.assert_called_once()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/e2e_testing_track/ORIGINAL_REQUEST.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/find_unused_modules.py
RENAMED
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/measure_lazy_startup.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/explorer_init/test_import_profiler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/implementation_track/ORIGINAL_REQUEST.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/worker_e2e_testing/ORIGINAL_REQUEST.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.3 → ace_git_copilot-0.3.4}/.agents/worker_m1_startup/ORIGINAL_REQUEST.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|