devsync 0.9.0__tar.gz → 0.11.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- devsync-0.11.0/PKG-INFO +108 -0
- devsync-0.11.0/README.md +70 -0
- devsync-0.11.0/devsync/ai_tools/aider.py +215 -0
- devsync-0.11.0/devsync/ai_tools/amazonq.py +67 -0
- devsync-0.11.0/devsync/ai_tools/amp.py +213 -0
- devsync-0.11.0/devsync/ai_tools/anteroom.py +216 -0
- devsync-0.11.0/devsync/ai_tools/augment.py +67 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/capability_registry.py +229 -0
- devsync-0.11.0/devsync/ai_tools/continuedev.py +64 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/detector.py +39 -0
- devsync-0.11.0/devsync/ai_tools/jetbrains.py +67 -0
- devsync-0.11.0/devsync/ai_tools/junie.py +216 -0
- devsync-0.11.0/devsync/ai_tools/opencode.py +215 -0
- devsync-0.11.0/devsync/ai_tools/openhands.py +64 -0
- devsync-0.11.0/devsync/ai_tools/tabnine.py +64 -0
- devsync-0.11.0/devsync/ai_tools/trae.py +64 -0
- devsync-0.11.0/devsync/ai_tools/translator.py +937 -0
- devsync-0.11.0/devsync/ai_tools/zed.py +213 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/component_detector.py +1 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/models.py +26 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/paths.py +8 -7
- devsync-0.11.0/devsync.egg-info/PKG-INFO +108 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync.egg-info/SOURCES.txt +13 -0
- {devsync-0.9.0 → devsync-0.11.0}/pyproject.toml +2 -2
- devsync-0.9.0/PKG-INFO +0 -479
- devsync-0.9.0/README.md +0 -441
- devsync-0.9.0/devsync/ai_tools/translator.py +0 -471
- devsync-0.9.0/devsync.egg-info/PKG-INFO +0 -479
- {devsync-0.9.0 → devsync-0.11.0}/LICENSE +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/__init__.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/__main__.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/__init__.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/antigravity.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/base.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/claude.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/claude_desktop.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/cline.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/codex.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/copilot.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/cursor.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/gemini.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/kiro.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/mcp_syncer.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/roo.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/winsurf.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/__init__.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/delete.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/download.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/install.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/install_new.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/list.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/main.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/mcp_configure.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/mcp_install.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/mcp_sync.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/package.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/package_create.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/package_install.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_backup.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_init.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_install.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_list.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_uninstall.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_update.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_validate.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/tools.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/uninstall.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/update.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/__init__.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/checksum.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/conflict_resolution.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/git_operations.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/mcp/__init__.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/mcp/credentials.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/mcp/manager.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/mcp/set_manager.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/mcp/validator.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/package_creator.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/package_manifest.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/repository.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/secret_detector.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/template_manifest.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/core/version.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/__init__.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/library.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/mcp_tracker.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/package_tracker.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/template_library.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/template_tracker.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/tracker.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/tui/__init__.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/tui/installer.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/__init__.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/atomic_write.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/backup.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/dotenv.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/git_helpers.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/logging.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/namespace.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/project.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/streaming.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/ui.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/validation.py +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync.egg-info/dependency_links.txt +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync.egg-info/entry_points.txt +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync.egg-info/requires.txt +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/devsync.egg-info/top_level.txt +0 -0
- {devsync-0.9.0 → devsync-0.11.0}/setup.cfg +0 -0
devsync-0.11.0/PKG-INFO
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: devsync
|
|
3
|
+
Version: 0.11.0
|
|
4
|
+
Summary: Distribute and sync dev tool configurations across teams
|
|
5
|
+
Author-email: Troy Larson <troy@calvinware.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
Project-URL: Homepage, https://github.com/troylar/devsync
|
|
8
|
+
Project-URL: Documentation, https://devsync.readthedocs.io
|
|
9
|
+
Project-URL: Repository, https://github.com/troylar/devsync
|
|
10
|
+
Project-URL: Issues, https://github.com/troylar/devsync/issues
|
|
11
|
+
Keywords: cli,ai,config,mcp,cursor,copilot,claude,cline,codex,kiro,roo,windsurf
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: typer[all]>=0.15.0
|
|
23
|
+
Requires-Dist: rich>=14.0.0
|
|
24
|
+
Requires-Dist: pyyaml>=6.0
|
|
25
|
+
Requires-Dist: textual>=6.0.0
|
|
26
|
+
Requires-Dist: GitPython>=3.1.45
|
|
27
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: black==24.10.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.10.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.9.0; extra == "dev"
|
|
34
|
+
Requires-Dist: invoke>=2.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: build>=1.2.1; extra == "dev"
|
|
36
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: types-PyYAML>=6.0.12.20240808; extra == "dev"
|
|
38
|
+
|
|
39
|
+
<div align="center">
|
|
40
|
+
|
|
41
|
+
# DevSync
|
|
42
|
+
|
|
43
|
+
**Distribute and sync AI coding assistant configurations across your team**
|
|
44
|
+
|
|
45
|
+
[](https://github.com/troylar/devsync/actions/workflows/ci.yml)
|
|
46
|
+
[](https://devsync.readthedocs.io)
|
|
47
|
+
[](https://pypi.org/project/devsync/)
|
|
48
|
+
[](https://codecov.io/gh/troylar/devsync)
|
|
49
|
+
[](https://www.python.org/downloads/)
|
|
50
|
+
[](https://opensource.org/licenses/MIT)
|
|
51
|
+
|
|
52
|
+
**Works with:** Aider | Amazon Q | Amp | Antigravity | Augment | Claude Code | Claude Desktop | Cline | Codex CLI | Continue.dev | Cursor | Gemini CLI | GitHub Copilot | JetBrains AI | Junie | Kiro | OpenCode | OpenHands | Roo Code | Tabnine | Trae | Windsurf | Zed
|
|
53
|
+
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
DevSync is a CLI tool for managing AI coding assistant instructions, MCP servers, and configuration packages across 22+ IDEs. Download shared configs from Git repos, install them to any tool, and keep your team aligned.
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install devsync
|
|
64
|
+
|
|
65
|
+
# Check detected AI tools
|
|
66
|
+
devsync tools
|
|
67
|
+
|
|
68
|
+
# Download instructions from a Git repo
|
|
69
|
+
devsync download --from github.com/company/standards --as company
|
|
70
|
+
|
|
71
|
+
# Install interactively
|
|
72
|
+
devsync install
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Features
|
|
76
|
+
|
|
77
|
+
- **Instructions** -- share coding standards, style guides, and AI prompts from Git repos
|
|
78
|
+
- **MCP Servers** -- distribute Model Context Protocol configs with secure credential management
|
|
79
|
+
- **Packages** -- bundle instructions, MCP servers, hooks, commands, and resources together
|
|
80
|
+
- **23 IDE integrations** -- Claude Code, Cursor, Windsurf, GitHub Copilot, and 19 more
|
|
81
|
+
- **Templates** -- IDE-targeted content with slash commands, hooks, and backups
|
|
82
|
+
- **Conflict resolution** -- skip, overwrite, or rename when files already exist
|
|
83
|
+
|
|
84
|
+
## Documentation
|
|
85
|
+
|
|
86
|
+
Full documentation at **[devsync.readthedocs.io](https://devsync.readthedocs.io)**:
|
|
87
|
+
|
|
88
|
+
- [Getting Started](https://devsync.readthedocs.io/getting-started/installation/) -- installation, quickstart, core concepts
|
|
89
|
+
- [CLI Reference](https://devsync.readthedocs.io/cli/) -- all commands with examples
|
|
90
|
+
- [IDE Integrations](https://devsync.readthedocs.io/ide-integrations/) -- setup guides for each AI tool
|
|
91
|
+
- [Packages](https://devsync.readthedocs.io/packages/) -- creating and installing config packages
|
|
92
|
+
- [MCP Server](https://devsync.readthedocs.io/mcp-server/) -- MCP configuration management
|
|
93
|
+
- [Tutorials](https://devsync.readthedocs.io/tutorials/team-config-repo/) -- step-by-step walkthroughs
|
|
94
|
+
|
|
95
|
+
## Contributing
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
git clone https://github.com/troylar/devsync.git
|
|
99
|
+
cd devsync
|
|
100
|
+
pip install -e .[dev]
|
|
101
|
+
invoke test
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
See the [contributing guide](https://devsync.readthedocs.io/advanced/contributing/) for details.
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT -- see [LICENSE](LICENSE).
|
devsync-0.11.0/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# DevSync
|
|
4
|
+
|
|
5
|
+
**Distribute and sync AI coding assistant configurations across your team**
|
|
6
|
+
|
|
7
|
+
[](https://github.com/troylar/devsync/actions/workflows/ci.yml)
|
|
8
|
+
[](https://devsync.readthedocs.io)
|
|
9
|
+
[](https://pypi.org/project/devsync/)
|
|
10
|
+
[](https://codecov.io/gh/troylar/devsync)
|
|
11
|
+
[](https://www.python.org/downloads/)
|
|
12
|
+
[](https://opensource.org/licenses/MIT)
|
|
13
|
+
|
|
14
|
+
**Works with:** Aider | Amazon Q | Amp | Antigravity | Augment | Claude Code | Claude Desktop | Cline | Codex CLI | Continue.dev | Cursor | Gemini CLI | GitHub Copilot | JetBrains AI | Junie | Kiro | OpenCode | OpenHands | Roo Code | Tabnine | Trae | Windsurf | Zed
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
DevSync is a CLI tool for managing AI coding assistant instructions, MCP servers, and configuration packages across 22+ IDEs. Download shared configs from Git repos, install them to any tool, and keep your team aligned.
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install devsync
|
|
26
|
+
|
|
27
|
+
# Check detected AI tools
|
|
28
|
+
devsync tools
|
|
29
|
+
|
|
30
|
+
# Download instructions from a Git repo
|
|
31
|
+
devsync download --from github.com/company/standards --as company
|
|
32
|
+
|
|
33
|
+
# Install interactively
|
|
34
|
+
devsync install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Instructions** -- share coding standards, style guides, and AI prompts from Git repos
|
|
40
|
+
- **MCP Servers** -- distribute Model Context Protocol configs with secure credential management
|
|
41
|
+
- **Packages** -- bundle instructions, MCP servers, hooks, commands, and resources together
|
|
42
|
+
- **23 IDE integrations** -- Claude Code, Cursor, Windsurf, GitHub Copilot, and 19 more
|
|
43
|
+
- **Templates** -- IDE-targeted content with slash commands, hooks, and backups
|
|
44
|
+
- **Conflict resolution** -- skip, overwrite, or rename when files already exist
|
|
45
|
+
|
|
46
|
+
## Documentation
|
|
47
|
+
|
|
48
|
+
Full documentation at **[devsync.readthedocs.io](https://devsync.readthedocs.io)**:
|
|
49
|
+
|
|
50
|
+
- [Getting Started](https://devsync.readthedocs.io/getting-started/installation/) -- installation, quickstart, core concepts
|
|
51
|
+
- [CLI Reference](https://devsync.readthedocs.io/cli/) -- all commands with examples
|
|
52
|
+
- [IDE Integrations](https://devsync.readthedocs.io/ide-integrations/) -- setup guides for each AI tool
|
|
53
|
+
- [Packages](https://devsync.readthedocs.io/packages/) -- creating and installing config packages
|
|
54
|
+
- [MCP Server](https://devsync.readthedocs.io/mcp-server/) -- MCP configuration management
|
|
55
|
+
- [Tutorials](https://devsync.readthedocs.io/tutorials/team-config-repo/) -- step-by-step walkthroughs
|
|
56
|
+
|
|
57
|
+
## Contributing
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/troylar/devsync.git
|
|
61
|
+
cd devsync
|
|
62
|
+
pip install -e .[dev]
|
|
63
|
+
invoke test
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
See the [contributing guide](https://devsync.readthedocs.io/advanced/contributing/) for details.
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
MIT -- see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Aider AI tool integration."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from devsync.ai_tools.base import AITool
|
|
9
|
+
from devsync.core.models import AIToolType, InstallationScope, Instruction
|
|
10
|
+
|
|
11
|
+
START_MARKER = "<!-- devsync:start:{name} -->"
|
|
12
|
+
END_MARKER = "<!-- devsync:end:{name} -->"
|
|
13
|
+
SECTION_PATTERN = r"<!-- devsync:start:{name} -->\n.*?\n<!-- devsync:end:{name} -->"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AiderTool(AITool):
|
|
17
|
+
"""Integration for Aider.
|
|
18
|
+
|
|
19
|
+
Aider reads a single CONVENTIONS.md file at the project root.
|
|
20
|
+
DevSync manages individual instruction sections using HTML comment markers:
|
|
21
|
+
|
|
22
|
+
<!-- devsync:start:instruction-name -->
|
|
23
|
+
... instruction content ...
|
|
24
|
+
<!-- devsync:end:instruction-name -->
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def tool_type(self) -> AIToolType:
|
|
29
|
+
"""Return the AI tool type identifier."""
|
|
30
|
+
return AIToolType.AIDER
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def tool_name(self) -> str:
|
|
34
|
+
"""Return human-readable tool name."""
|
|
35
|
+
return "Aider"
|
|
36
|
+
|
|
37
|
+
def is_installed(self) -> bool:
|
|
38
|
+
"""Check if Aider is installed on the system.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
True if aider binary is found on PATH or .aider.conf.yml exists
|
|
42
|
+
"""
|
|
43
|
+
if shutil.which("aider") is not None:
|
|
44
|
+
return True
|
|
45
|
+
return (Path.home() / ".aider.conf.yml").exists()
|
|
46
|
+
|
|
47
|
+
def get_instructions_directory(self) -> Path:
|
|
48
|
+
"""Get the directory where instructions should be installed.
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
NotImplementedError: Aider only supports project-level installation
|
|
52
|
+
"""
|
|
53
|
+
raise NotImplementedError(
|
|
54
|
+
f"{self.tool_name} global installation is not supported. "
|
|
55
|
+
"Aider uses project-level CONVENTIONS.md only. "
|
|
56
|
+
"Please use project-level installation instead (--scope project)."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def get_instruction_file_extension(self) -> str:
|
|
60
|
+
"""Get the file extension for Aider instructions.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
File extension including the dot
|
|
64
|
+
"""
|
|
65
|
+
return ".md"
|
|
66
|
+
|
|
67
|
+
def get_project_instructions_directory(self, project_root: Path) -> Path:
|
|
68
|
+
"""Get the directory for project-specific Aider instructions.
|
|
69
|
+
|
|
70
|
+
CONVENTIONS.md lives at the project root.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
project_root: Path to the project root directory
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Path to project root
|
|
77
|
+
"""
|
|
78
|
+
return project_root
|
|
79
|
+
|
|
80
|
+
def get_instruction_path(
|
|
81
|
+
self,
|
|
82
|
+
instruction_name: str,
|
|
83
|
+
scope: InstallationScope = InstallationScope.GLOBAL,
|
|
84
|
+
project_root: Optional[Path] = None,
|
|
85
|
+
) -> Path:
|
|
86
|
+
"""Get the path to CONVENTIONS.md.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
instruction_name: Name of the instruction (unused for path)
|
|
90
|
+
scope: Installation scope (must be PROJECT)
|
|
91
|
+
project_root: Project root path
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Path to CONVENTIONS.md
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
ValueError: If scope is PROJECT but project_root is None
|
|
98
|
+
NotImplementedError: If scope is GLOBAL
|
|
99
|
+
"""
|
|
100
|
+
if scope == InstallationScope.GLOBAL:
|
|
101
|
+
raise NotImplementedError(
|
|
102
|
+
f"{self.tool_name} global installation is not supported. "
|
|
103
|
+
"Please use project-level installation instead (--scope project)."
|
|
104
|
+
)
|
|
105
|
+
if project_root is None:
|
|
106
|
+
raise ValueError("project_root is required for PROJECT scope")
|
|
107
|
+
return project_root / "CONVENTIONS.md"
|
|
108
|
+
|
|
109
|
+
def instruction_exists(
|
|
110
|
+
self,
|
|
111
|
+
instruction_name: str,
|
|
112
|
+
scope: InstallationScope = InstallationScope.GLOBAL,
|
|
113
|
+
project_root: Optional[Path] = None,
|
|
114
|
+
) -> bool:
|
|
115
|
+
"""Check if an instruction section exists in CONVENTIONS.md.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
instruction_name: Name of the instruction
|
|
119
|
+
scope: Installation scope
|
|
120
|
+
project_root: Project root path
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
True if the instruction's section markers exist in CONVENTIONS.md
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
path = self.get_instruction_path(instruction_name, scope, project_root)
|
|
127
|
+
if not path.exists():
|
|
128
|
+
return False
|
|
129
|
+
content = path.read_text(encoding="utf-8")
|
|
130
|
+
start = START_MARKER.format(name=instruction_name)
|
|
131
|
+
return start in content
|
|
132
|
+
except (FileNotFoundError, ValueError, NotImplementedError):
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
def install_instruction(
|
|
136
|
+
self,
|
|
137
|
+
instruction: Instruction,
|
|
138
|
+
overwrite: bool = False,
|
|
139
|
+
scope: InstallationScope = InstallationScope.GLOBAL,
|
|
140
|
+
project_root: Optional[Path] = None,
|
|
141
|
+
) -> Path:
|
|
142
|
+
"""Install an instruction as a section in CONVENTIONS.md.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
instruction: Instruction to install
|
|
146
|
+
overwrite: Whether to overwrite existing section
|
|
147
|
+
scope: Installation scope
|
|
148
|
+
project_root: Project root path
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Path to CONVENTIONS.md
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
FileExistsError: If instruction section exists and overwrite=False
|
|
155
|
+
"""
|
|
156
|
+
path = self.get_instruction_path(instruction.name, scope, project_root)
|
|
157
|
+
|
|
158
|
+
start = START_MARKER.format(name=instruction.name)
|
|
159
|
+
end = END_MARKER.format(name=instruction.name)
|
|
160
|
+
section = f"{start}\n{instruction.content}\n{end}"
|
|
161
|
+
|
|
162
|
+
if path.exists():
|
|
163
|
+
content = path.read_text(encoding="utf-8")
|
|
164
|
+
if start in content:
|
|
165
|
+
if not overwrite:
|
|
166
|
+
raise FileExistsError(f"Instruction already exists in CONVENTIONS.md: {instruction.name}")
|
|
167
|
+
pattern = SECTION_PATTERN.format(name=re.escape(instruction.name))
|
|
168
|
+
content = re.sub(pattern, section, content, flags=re.DOTALL)
|
|
169
|
+
path.write_text(content, encoding="utf-8")
|
|
170
|
+
return path
|
|
171
|
+
if content and not content.endswith("\n"):
|
|
172
|
+
content += "\n"
|
|
173
|
+
content += "\n" + section + "\n"
|
|
174
|
+
path.write_text(content, encoding="utf-8")
|
|
175
|
+
else:
|
|
176
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
177
|
+
path.write_text(section + "\n", encoding="utf-8")
|
|
178
|
+
|
|
179
|
+
return path
|
|
180
|
+
|
|
181
|
+
def uninstall_instruction(
|
|
182
|
+
self,
|
|
183
|
+
instruction_name: str,
|
|
184
|
+
scope: InstallationScope = InstallationScope.GLOBAL,
|
|
185
|
+
project_root: Optional[Path] = None,
|
|
186
|
+
) -> bool:
|
|
187
|
+
"""Remove an instruction section from CONVENTIONS.md.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
instruction_name: Name of instruction to remove
|
|
191
|
+
scope: Installation scope
|
|
192
|
+
project_root: Project root path
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
True if section was removed, False if it didn't exist
|
|
196
|
+
"""
|
|
197
|
+
try:
|
|
198
|
+
path = self.get_instruction_path(instruction_name, scope, project_root)
|
|
199
|
+
if not path.exists():
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
content = path.read_text(encoding="utf-8")
|
|
203
|
+
start = START_MARKER.format(name=instruction_name)
|
|
204
|
+
if start not in content:
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
pattern = SECTION_PATTERN.format(name=re.escape(instruction_name))
|
|
208
|
+
new_content = re.sub(pattern, "", content, flags=re.DOTALL)
|
|
209
|
+
new_content = re.sub(r"\n{3,}", "\n\n", new_content).strip()
|
|
210
|
+
if new_content:
|
|
211
|
+
new_content += "\n"
|
|
212
|
+
path.write_text(new_content, encoding="utf-8")
|
|
213
|
+
return True
|
|
214
|
+
except (FileNotFoundError, ValueError, NotImplementedError):
|
|
215
|
+
return False
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Amazon Q AI tool integration."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from devsync.ai_tools.base import AITool
|
|
7
|
+
from devsync.core.models import AIToolType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AmazonQTool(AITool):
|
|
11
|
+
"""Integration for Amazon Q Developer.
|
|
12
|
+
|
|
13
|
+
Amazon Q uses .amazonq/rules/*.md for project-level instructions.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def tool_type(self) -> AIToolType:
|
|
18
|
+
"""Return the AI tool type identifier."""
|
|
19
|
+
return AIToolType.AMAZONQ
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def tool_name(self) -> str:
|
|
23
|
+
"""Return human-readable tool name."""
|
|
24
|
+
return "Amazon Q"
|
|
25
|
+
|
|
26
|
+
def is_installed(self) -> bool:
|
|
27
|
+
"""Check if Amazon Q is installed on the system.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
True if q binary is found on PATH or .amazonq/ directory exists
|
|
31
|
+
"""
|
|
32
|
+
if shutil.which("q") is not None:
|
|
33
|
+
return True
|
|
34
|
+
amazonq_dir = Path.home() / ".amazonq"
|
|
35
|
+
return amazonq_dir.exists()
|
|
36
|
+
|
|
37
|
+
def get_instructions_directory(self) -> Path:
|
|
38
|
+
"""Get the directory where instructions should be installed.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
NotImplementedError: Amazon Q only supports project-level installation
|
|
42
|
+
"""
|
|
43
|
+
raise NotImplementedError(
|
|
44
|
+
f"{self.tool_name} global installation is not supported. "
|
|
45
|
+
"Please use project-level installation instead (--scope project)."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def get_instruction_file_extension(self) -> str:
|
|
49
|
+
"""Get the file extension for Amazon Q instructions.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
File extension including the dot
|
|
53
|
+
"""
|
|
54
|
+
return ".md"
|
|
55
|
+
|
|
56
|
+
def get_project_instructions_directory(self, project_root: Path) -> Path:
|
|
57
|
+
"""Get the directory for project-specific Amazon Q instructions.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
project_root: Path to the project root directory
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Path to project instructions directory (.amazonq/rules/)
|
|
64
|
+
"""
|
|
65
|
+
instructions_dir = project_root / ".amazonq" / "rules"
|
|
66
|
+
instructions_dir.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
return instructions_dir
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Amp AI tool integration."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from devsync.ai_tools.base import AITool
|
|
9
|
+
from devsync.core.models import AIToolType, InstallationScope, Instruction
|
|
10
|
+
|
|
11
|
+
START_MARKER = "<!-- devsync:start:{name} -->"
|
|
12
|
+
END_MARKER = "<!-- devsync:end:{name} -->"
|
|
13
|
+
SECTION_PATTERN = r"<!-- devsync:start:{name} -->\n.*?\n<!-- devsync:end:{name} -->"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AmpTool(AITool):
|
|
17
|
+
"""Integration for Amp.
|
|
18
|
+
|
|
19
|
+
Amp reads a single AGENTS.md file at the project root.
|
|
20
|
+
DevSync manages individual instruction sections using HTML comment markers:
|
|
21
|
+
|
|
22
|
+
<!-- devsync:start:instruction-name -->
|
|
23
|
+
... instruction content ...
|
|
24
|
+
<!-- devsync:end:instruction-name -->
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def tool_type(self) -> AIToolType:
|
|
29
|
+
"""Return the AI tool type identifier."""
|
|
30
|
+
return AIToolType.AMP
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def tool_name(self) -> str:
|
|
34
|
+
"""Return human-readable tool name."""
|
|
35
|
+
return "Amp"
|
|
36
|
+
|
|
37
|
+
def is_installed(self) -> bool:
|
|
38
|
+
"""Check if Amp is installed on the system.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
True if amp binary is found on PATH
|
|
42
|
+
"""
|
|
43
|
+
return shutil.which("amp") is not None
|
|
44
|
+
|
|
45
|
+
def get_instructions_directory(self) -> Path:
|
|
46
|
+
"""Get the directory where instructions should be installed.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
NotImplementedError: Amp only supports project-level installation
|
|
50
|
+
"""
|
|
51
|
+
raise NotImplementedError(
|
|
52
|
+
f"{self.tool_name} global installation is not supported. "
|
|
53
|
+
"Amp uses project-level AGENTS.md only. "
|
|
54
|
+
"Please use project-level installation instead (--scope project)."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def get_instruction_file_extension(self) -> str:
|
|
58
|
+
"""Get the file extension for Amp instructions.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
File extension including the dot
|
|
62
|
+
"""
|
|
63
|
+
return ".md"
|
|
64
|
+
|
|
65
|
+
def get_project_instructions_directory(self, project_root: Path) -> Path:
|
|
66
|
+
"""Get the directory for project-specific Amp instructions.
|
|
67
|
+
|
|
68
|
+
AGENTS.md lives at the project root.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
project_root: Path to the project root directory
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Path to project root
|
|
75
|
+
"""
|
|
76
|
+
return project_root
|
|
77
|
+
|
|
78
|
+
def get_instruction_path(
|
|
79
|
+
self,
|
|
80
|
+
instruction_name: str,
|
|
81
|
+
scope: InstallationScope = InstallationScope.GLOBAL,
|
|
82
|
+
project_root: Optional[Path] = None,
|
|
83
|
+
) -> Path:
|
|
84
|
+
"""Get the path to AGENTS.md.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
instruction_name: Name of the instruction (unused for path)
|
|
88
|
+
scope: Installation scope (must be PROJECT)
|
|
89
|
+
project_root: Project root path
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Path to AGENTS.md
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
ValueError: If scope is PROJECT but project_root is None
|
|
96
|
+
NotImplementedError: If scope is GLOBAL
|
|
97
|
+
"""
|
|
98
|
+
if scope == InstallationScope.GLOBAL:
|
|
99
|
+
raise NotImplementedError(
|
|
100
|
+
f"{self.tool_name} global installation is not supported. "
|
|
101
|
+
"Please use project-level installation instead (--scope project)."
|
|
102
|
+
)
|
|
103
|
+
if project_root is None:
|
|
104
|
+
raise ValueError("project_root is required for PROJECT scope")
|
|
105
|
+
return project_root / "AGENTS.md"
|
|
106
|
+
|
|
107
|
+
def instruction_exists(
|
|
108
|
+
self,
|
|
109
|
+
instruction_name: str,
|
|
110
|
+
scope: InstallationScope = InstallationScope.GLOBAL,
|
|
111
|
+
project_root: Optional[Path] = None,
|
|
112
|
+
) -> bool:
|
|
113
|
+
"""Check if an instruction section exists in AGENTS.md.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
instruction_name: Name of the instruction
|
|
117
|
+
scope: Installation scope
|
|
118
|
+
project_root: Project root path
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if the instruction's section markers exist in AGENTS.md
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
path = self.get_instruction_path(instruction_name, scope, project_root)
|
|
125
|
+
if not path.exists():
|
|
126
|
+
return False
|
|
127
|
+
content = path.read_text(encoding="utf-8")
|
|
128
|
+
start = START_MARKER.format(name=instruction_name)
|
|
129
|
+
return start in content
|
|
130
|
+
except (FileNotFoundError, ValueError, NotImplementedError):
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def install_instruction(
|
|
134
|
+
self,
|
|
135
|
+
instruction: Instruction,
|
|
136
|
+
overwrite: bool = False,
|
|
137
|
+
scope: InstallationScope = InstallationScope.GLOBAL,
|
|
138
|
+
project_root: Optional[Path] = None,
|
|
139
|
+
) -> Path:
|
|
140
|
+
"""Install an instruction as a section in AGENTS.md.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
instruction: Instruction to install
|
|
144
|
+
overwrite: Whether to overwrite existing section
|
|
145
|
+
scope: Installation scope
|
|
146
|
+
project_root: Project root path
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Path to AGENTS.md
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
FileExistsError: If instruction section exists and overwrite=False
|
|
153
|
+
"""
|
|
154
|
+
path = self.get_instruction_path(instruction.name, scope, project_root)
|
|
155
|
+
|
|
156
|
+
start = START_MARKER.format(name=instruction.name)
|
|
157
|
+
end = END_MARKER.format(name=instruction.name)
|
|
158
|
+
section = f"{start}\n{instruction.content}\n{end}"
|
|
159
|
+
|
|
160
|
+
if path.exists():
|
|
161
|
+
content = path.read_text(encoding="utf-8")
|
|
162
|
+
if start in content:
|
|
163
|
+
if not overwrite:
|
|
164
|
+
raise FileExistsError(f"Instruction already exists in AGENTS.md: {instruction.name}")
|
|
165
|
+
pattern = SECTION_PATTERN.format(name=re.escape(instruction.name))
|
|
166
|
+
content = re.sub(pattern, section, content, flags=re.DOTALL)
|
|
167
|
+
path.write_text(content, encoding="utf-8")
|
|
168
|
+
return path
|
|
169
|
+
if content and not content.endswith("\n"):
|
|
170
|
+
content += "\n"
|
|
171
|
+
content += "\n" + section + "\n"
|
|
172
|
+
path.write_text(content, encoding="utf-8")
|
|
173
|
+
else:
|
|
174
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
175
|
+
path.write_text(section + "\n", encoding="utf-8")
|
|
176
|
+
|
|
177
|
+
return path
|
|
178
|
+
|
|
179
|
+
def uninstall_instruction(
|
|
180
|
+
self,
|
|
181
|
+
instruction_name: str,
|
|
182
|
+
scope: InstallationScope = InstallationScope.GLOBAL,
|
|
183
|
+
project_root: Optional[Path] = None,
|
|
184
|
+
) -> bool:
|
|
185
|
+
"""Remove an instruction section from AGENTS.md.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
instruction_name: Name of instruction to remove
|
|
189
|
+
scope: Installation scope
|
|
190
|
+
project_root: Project root path
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
True if section was removed, False if it didn't exist
|
|
194
|
+
"""
|
|
195
|
+
try:
|
|
196
|
+
path = self.get_instruction_path(instruction_name, scope, project_root)
|
|
197
|
+
if not path.exists():
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
content = path.read_text(encoding="utf-8")
|
|
201
|
+
start = START_MARKER.format(name=instruction_name)
|
|
202
|
+
if start not in content:
|
|
203
|
+
return False
|
|
204
|
+
|
|
205
|
+
pattern = SECTION_PATTERN.format(name=re.escape(instruction_name))
|
|
206
|
+
new_content = re.sub(pattern, "", content, flags=re.DOTALL)
|
|
207
|
+
new_content = re.sub(r"\n{3,}", "\n\n", new_content).strip()
|
|
208
|
+
if new_content:
|
|
209
|
+
new_content += "\n"
|
|
210
|
+
path.write_text(new_content, encoding="utf-8")
|
|
211
|
+
return True
|
|
212
|
+
except (FileNotFoundError, ValueError, NotImplementedError):
|
|
213
|
+
return False
|