ace-git-copilot 0.3.3__tar.gz → 0.3.5__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.5}/PKG-INFO +10 -1
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/README.md +9 -0
- ace_git_copilot-0.3.5/ace/__init__.py +1 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/cli.py +205 -4
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/pyproject.toml +1 -1
- ace_git_copilot-0.3.5/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.5}/.agents/AGENTS.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/e2e_testing_track/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/e2e_testing_track/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/e2e_testing_track/SCOPE.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/e2e_testing_track/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/emojis_list.txt +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/find_unused_modules.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/handoff.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/measure_lazy_startup.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/measure_startup.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/profile_imports.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/run_importtime.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/search_banner.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/search_emojis.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/search_git_usages.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/search_usages.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/test_import_profiler.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/explorer_init/test_mocked_sys.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/handoff.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/implementation_track/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/implementation_track/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/implementation_track/explorer_initial_report.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/implementation_track/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/orchestrator/.gitkeep +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/orchestrator/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/orchestrator/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/orchestrator/PROJECT.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/orchestrator/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/teamwork_preview_explorer_e2e_explore/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/teamwork_preview_explorer_e2e_explore/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/teamwork_preview_explorer_e2e_explore/handoff.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/teamwork_preview_explorer_e2e_explore/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/worker_e2e_testing/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/worker_e2e_testing/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/worker_e2e_testing/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/worker_m1_startup/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/worker_m1_startup/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.agents/worker_m1_startup/progress.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.env.example +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.github/workflows/tests.yml +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/.gitignore +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/CODE_OF_CONDUCT.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/CONTRIBUTING.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/LICENSE +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/PROJECT.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/SECURITY.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/SUPPORT.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/TEST_INFRA.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/TEST_READY.md +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/__main__.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/changelog_generator.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/code_reviewer.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/commit_generator.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/conflict_resolver.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/gitignore_generator.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/history_analyzer.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/intent_parser.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/llm_factory.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/pr_drafter.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/prompts/changelog.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/prompts/commit.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/prompts/conflict.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/prompts/doctor.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/prompts/explain.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/prompts/ignore.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/prompts/intent.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/prompts/pr.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/prompts/rebase.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/prompts/review.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/prompts/search.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/prompts/undo.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ai/rebase_helper.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/core/config.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/core/context.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/core/diagnostics.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/core/git_ops.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/core/hooks.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/core/safety.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ui/banner.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ui/dashboard.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ui/display.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ui/prompts.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/ui/themes.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/utils/conflict_parser.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/utils/diff_parser.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/ace/utils/json_utils.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/conftest.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/e2e/conftest.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/e2e/test_tier1_features.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/e2e/test_tier2_boundaries.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/e2e/test_tier3_combinations.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/e2e/test_tier4_workloads.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_changelog_generator.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_code_reviewer.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_config_atomic_write.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_conflict_resolver.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_conflict_resolver_backup.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_context_gitdir_detection.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_diagnostics.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_diff_trimmer.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_git_ops.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_git_ops_execute.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_help.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_history_analyzer.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_hooks.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_ignore.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_intent_parser.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_llm_factory.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_llm_factory_ollama.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_pr_drafter.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_rebase_helper.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_safety.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/tests/test_search.py +0 -0
- {ace_git_copilot-0.3.3 → ace_git_copilot-0.3.5}/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.5
|
|
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,14 @@ Distributed under the MIT License. See [LICENSE](LICENSE) for more details.
|
|
|
198
199
|
|
|
199
200
|
## Changelog
|
|
200
201
|
|
|
202
|
+
### v0.3.5 — Empty Diff Handling (2026-07-11)
|
|
203
|
+
* Improved warning panel message and title when committing empty files or mode changes to prevent confusing users when files are already staged.
|
|
204
|
+
|
|
205
|
+
### v0.3.4 — Workspace Management (2026-07-11)
|
|
206
|
+
* Added new `workspace` command (alias `ws`) to scan, monitor, and navigate multiple repositories.
|
|
207
|
+
* Shows active branch, staged/unstaged/untracked file counts, and local upstream sync offsets in a Rich Table.
|
|
208
|
+
* Supports interactive actions to open the TUI dashboard, spawn a nested shell, or run copy-paste commands inside any chosen repository.
|
|
209
|
+
|
|
201
210
|
### v0.3.3 — Stability & Resilience (2026-07-04)
|
|
202
211
|
* Implemented streaming progress and download percentages for Ollama model pulls to prevent thread hangs.
|
|
203
212
|
* 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,14 @@ Distributed under the MIT License. See [LICENSE](LICENSE) for more details.
|
|
|
165
166
|
|
|
166
167
|
## Changelog
|
|
167
168
|
|
|
169
|
+
### v0.3.5 — Empty Diff Handling (2026-07-11)
|
|
170
|
+
* Improved warning panel message and title when committing empty files or mode changes to prevent confusing users when files are already staged.
|
|
171
|
+
|
|
172
|
+
### v0.3.4 — Workspace Management (2026-07-11)
|
|
173
|
+
* Added new `workspace` command (alias `ws`) to scan, monitor, and navigate multiple repositories.
|
|
174
|
+
* Shows active branch, staged/unstaged/untracked file counts, and local upstream sync offsets in a Rich Table.
|
|
175
|
+
* Supports interactive actions to open the TUI dashboard, spawn a nested shell, or run copy-paste commands inside any chosen repository.
|
|
176
|
+
|
|
168
177
|
### v0.3.3 — Stability & Resilience (2026-07-04)
|
|
169
178
|
* Implemented streaming progress and download percentages for Ollama model pulls to prevent thread hangs.
|
|
170
179
|
* 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.5"
|
|
@@ -299,10 +299,22 @@ def commit_cmd(
|
|
|
299
299
|
with spinner("Analyzing changes and generating commit message..."):
|
|
300
300
|
msg = generator.generate_message(format_type=format_type, offline=offline)
|
|
301
301
|
except NoStagedChangesError as e:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
302
|
+
staged_files = []
|
|
303
|
+
try:
|
|
304
|
+
staged_files = git_ops.get_status().get("staged", [])
|
|
305
|
+
except Exception:
|
|
306
|
+
pass
|
|
307
|
+
|
|
308
|
+
if staged_files:
|
|
309
|
+
show_warning_panel(
|
|
310
|
+
f"{str(e)}\n\n[bold]Tip:[/bold] Use [command]git commit -m \"message\"[/command] to commit these changes manually, or add content to the files.",
|
|
311
|
+
"Empty Staged Diff"
|
|
312
|
+
)
|
|
313
|
+
else:
|
|
314
|
+
show_warning_panel(
|
|
315
|
+
f"{str(e)}\n\n[bold]Tip:[/bold] Stage your files first: [command]git add <files>[/command]",
|
|
316
|
+
"No Staged Changes"
|
|
317
|
+
)
|
|
306
318
|
raise typer.Exit(code=0)
|
|
307
319
|
except LLMConfigurationError as e:
|
|
308
320
|
show_error_panel(f"{str(e)}\n\nRun [bold]ace setup[/bold] to configure your AI credentials.", "Configuration Error")
|
|
@@ -1686,6 +1698,195 @@ def hook_cmd(
|
|
|
1686
1698
|
show_error_panel("Invalid action. Use 'install' or 'uninstall'.", "Usage Error")
|
|
1687
1699
|
raise typer.Exit(code=1)
|
|
1688
1700
|
|
|
1701
|
+
@app.command(name="workspace", help="Monitor status of multiple repositories and navigate between them.")
|
|
1702
|
+
def workspace_cmd(
|
|
1703
|
+
path: Optional[str] = typer.Argument(
|
|
1704
|
+
None, help="Custom directory to scan (defaults to sibling projects scan)"
|
|
1705
|
+
),
|
|
1706
|
+
):
|
|
1707
|
+
from pathlib import Path
|
|
1708
|
+
import os
|
|
1709
|
+
import sys
|
|
1710
|
+
import subprocess
|
|
1711
|
+
from rich.table import Table
|
|
1712
|
+
from ace.core.git_ops import GitOps, NotAGitRepositoryError
|
|
1713
|
+
from ace.ui.display import console, print_info, print_success, print_warning, print_error
|
|
1714
|
+
from ace.ui.prompts import prompt_select, prompt_action
|
|
1715
|
+
|
|
1716
|
+
# 1. Determine target directory to scan
|
|
1717
|
+
if path:
|
|
1718
|
+
scan_dir = Path(path).resolve()
|
|
1719
|
+
else:
|
|
1720
|
+
# Smart default
|
|
1721
|
+
cwd = Path(os.getcwd()).resolve()
|
|
1722
|
+
if (cwd / ".git").exists():
|
|
1723
|
+
scan_dir = cwd.parent
|
|
1724
|
+
else:
|
|
1725
|
+
scan_dir = cwd
|
|
1726
|
+
|
|
1727
|
+
if not scan_dir.exists() or not scan_dir.is_dir():
|
|
1728
|
+
print_error(f"Directory not found: {scan_dir}")
|
|
1729
|
+
raise typer.Exit(code=1)
|
|
1730
|
+
|
|
1731
|
+
print_info(f"Scanning workspace directory: [bold]{scan_dir}[/bold]")
|
|
1732
|
+
|
|
1733
|
+
# 2. Find git repositories
|
|
1734
|
+
repos_found = []
|
|
1735
|
+
try:
|
|
1736
|
+
for child in sorted(scan_dir.iterdir()):
|
|
1737
|
+
if not child.is_dir():
|
|
1738
|
+
continue
|
|
1739
|
+
# Basic validation that it's a git repo
|
|
1740
|
+
if (child / ".git").exists() or (child / "gitdir").exists() or child.name.endswith(".git"):
|
|
1741
|
+
try:
|
|
1742
|
+
git_ops = GitOps(str(child))
|
|
1743
|
+
repos_found.append((child, git_ops))
|
|
1744
|
+
except Exception:
|
|
1745
|
+
pass
|
|
1746
|
+
except Exception as e:
|
|
1747
|
+
print_error(f"Failed to scan directory: {e}")
|
|
1748
|
+
raise typer.Exit(code=1)
|
|
1749
|
+
|
|
1750
|
+
if not repos_found:
|
|
1751
|
+
print_warning(f"No active Git repositories found in: {scan_dir}")
|
|
1752
|
+
return
|
|
1753
|
+
|
|
1754
|
+
# 3. Build Status Table
|
|
1755
|
+
table = Table(
|
|
1756
|
+
title=f"[bold white]Workspace Summary: {scan_dir.name}[/bold white]",
|
|
1757
|
+
title_style="bold",
|
|
1758
|
+
border_style="#FF6D00",
|
|
1759
|
+
expand=True,
|
|
1760
|
+
)
|
|
1761
|
+
table.add_column("[bold #00D5FF]#[/bold #00D5FF]", justify="right", width=4)
|
|
1762
|
+
table.add_column("Repository", style="bold white")
|
|
1763
|
+
table.add_column("Branch", style="bold cyan")
|
|
1764
|
+
table.add_column("Status", justify="center")
|
|
1765
|
+
table.add_column("Sync", justify="center")
|
|
1766
|
+
|
|
1767
|
+
display_options = []
|
|
1768
|
+
for idx, (repo_path, git_ops) in enumerate(repos_found, 1):
|
|
1769
|
+
repo_name = repo_path.name
|
|
1770
|
+
|
|
1771
|
+
# Branch
|
|
1772
|
+
branch_name = git_ops.get_current_branch() or "Detached HEAD"
|
|
1773
|
+
|
|
1774
|
+
# Status counts
|
|
1775
|
+
status = git_ops.get_status()
|
|
1776
|
+
staged = len(status.get("staged", []))
|
|
1777
|
+
unstaged = len(status.get("unstaged", []))
|
|
1778
|
+
untracked = len(status.get("untracked", []))
|
|
1779
|
+
|
|
1780
|
+
if staged == 0 and unstaged == 0 and untracked == 0:
|
|
1781
|
+
status_desc = "[success]Clean[/success]"
|
|
1782
|
+
else:
|
|
1783
|
+
parts = []
|
|
1784
|
+
if staged > 0:
|
|
1785
|
+
parts.append(f"[bold #00E676]+{staged}[/bold #00E676]")
|
|
1786
|
+
if unstaged > 0:
|
|
1787
|
+
parts.append(f"[bold #FFD600]~{unstaged}[/bold #FFD600]")
|
|
1788
|
+
if untracked > 0:
|
|
1789
|
+
parts.append(f"[bold #FF1744]?{untracked}[/bold #FF1744]")
|
|
1790
|
+
status_desc = " ".join(parts)
|
|
1791
|
+
|
|
1792
|
+
# Sync offsets
|
|
1793
|
+
ab = git_ops.get_ahead_behind()
|
|
1794
|
+
ahead = ab.get("ahead", 0)
|
|
1795
|
+
behind = ab.get("behind", 0)
|
|
1796
|
+
tracking = git_ops.get_upstream_tracking()
|
|
1797
|
+
|
|
1798
|
+
if not tracking:
|
|
1799
|
+
sync_desc = "[#666666]No Upstream[/#666666]"
|
|
1800
|
+
elif ahead == 0 and behind == 0:
|
|
1801
|
+
sync_desc = "[success]Up-to-date[/success]"
|
|
1802
|
+
elif ahead > 0 and behind == 0:
|
|
1803
|
+
sync_desc = f"[bold #00D5FF]Ahead {ahead}[/bold #00D5FF]"
|
|
1804
|
+
elif behind > 0 and ahead == 0:
|
|
1805
|
+
sync_desc = f"[bold #FFD600]Behind {behind}[/bold #FFD600]"
|
|
1806
|
+
else:
|
|
1807
|
+
sync_desc = f"[bold #FF1744]Diverged (A{ahead}, B{behind})[/bold #FF1744]"
|
|
1808
|
+
|
|
1809
|
+
table.add_row(
|
|
1810
|
+
str(idx),
|
|
1811
|
+
repo_name,
|
|
1812
|
+
branch_name,
|
|
1813
|
+
status_desc,
|
|
1814
|
+
sync_desc
|
|
1815
|
+
)
|
|
1816
|
+
display_options.append(repo_name)
|
|
1817
|
+
|
|
1818
|
+
console.print()
|
|
1819
|
+
console.print(table)
|
|
1820
|
+
console.print()
|
|
1821
|
+
|
|
1822
|
+
# 4. Prompt selection
|
|
1823
|
+
selected_idx = prompt_select(
|
|
1824
|
+
display_options,
|
|
1825
|
+
prompt_text=" Select repository number to manage (or 'q' to quit)",
|
|
1826
|
+
default="q"
|
|
1827
|
+
)
|
|
1828
|
+
|
|
1829
|
+
if selected_idx < 0:
|
|
1830
|
+
print_info("Exit workspace monitor.")
|
|
1831
|
+
return
|
|
1832
|
+
|
|
1833
|
+
selected_path, _ = repos_found[selected_idx]
|
|
1834
|
+
|
|
1835
|
+
# 5. Prompt action choice
|
|
1836
|
+
console.print(f"\n[bold white] Choose action for [bold cyan]{selected_path.name}[/bold cyan]:[/bold white]")
|
|
1837
|
+
action_options = {
|
|
1838
|
+
"d": ("Dashboard", "Open interactive TUI dashboard"),
|
|
1839
|
+
"s": ("Shell", "Spawn an interactive shell in project directory"),
|
|
1840
|
+
"c": ("Command", "Execute an ace natural-language command"),
|
|
1841
|
+
"q": ("Quit", "Cancel action and exit")
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
action = prompt_action(action_options, default_key="d")
|
|
1845
|
+
|
|
1846
|
+
if action == "d":
|
|
1847
|
+
print_success(f"Launching dashboard in {selected_path.name}...")
|
|
1848
|
+
console.print()
|
|
1849
|
+
try:
|
|
1850
|
+
subprocess.run([sys.executable, "-m", "ace", "dash"], cwd=str(selected_path))
|
|
1851
|
+
except Exception as e:
|
|
1852
|
+
print_error(f"Failed to launch dashboard: {e}")
|
|
1853
|
+
|
|
1854
|
+
elif action == "s":
|
|
1855
|
+
shell = os.environ.get("SHELL")
|
|
1856
|
+
if not shell:
|
|
1857
|
+
if sys.platform == "win32":
|
|
1858
|
+
shell = "powershell.exe"
|
|
1859
|
+
else:
|
|
1860
|
+
shell = "/bin/bash"
|
|
1861
|
+
|
|
1862
|
+
print_success(f"Opening interactive shell in {selected_path.name}...")
|
|
1863
|
+
print_info("Type 'exit' to return to Ace workspace monitor.")
|
|
1864
|
+
console.print()
|
|
1865
|
+
|
|
1866
|
+
try:
|
|
1867
|
+
subprocess.run([shell], cwd=str(selected_path))
|
|
1868
|
+
print_success("Returned to Ace workspace.")
|
|
1869
|
+
except Exception as e:
|
|
1870
|
+
print_error(f"Failed to open shell: {e}")
|
|
1871
|
+
|
|
1872
|
+
elif action == "c":
|
|
1873
|
+
query = typer.prompt(" Enter command for this repo")
|
|
1874
|
+
if query.strip():
|
|
1875
|
+
print_success(f"Running command: {query}")
|
|
1876
|
+
console.print()
|
|
1877
|
+
try:
|
|
1878
|
+
subprocess.run([sys.executable, "-m", "ace", query], cwd=str(selected_path))
|
|
1879
|
+
except Exception as e:
|
|
1880
|
+
print_error(f"Failed to run command: {e}")
|
|
1881
|
+
|
|
1882
|
+
@app.command(name="ws", help="Alias for 'workspace' command.")
|
|
1883
|
+
def ws_cmd(
|
|
1884
|
+
path: Optional[str] = typer.Argument(
|
|
1885
|
+
None, help="Custom directory to scan"
|
|
1886
|
+
),
|
|
1887
|
+
):
|
|
1888
|
+
workspace_cmd(path)
|
|
1889
|
+
|
|
1689
1890
|
if __name__ == "__main__":
|
|
1690
1891
|
app()
|
|
1691
1892
|
|
|
@@ -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.5}/.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.5}/.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.5}/.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.5}/.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.5}/.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.5}/.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.5}/.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
|