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.
Files changed (109) hide show
  1. devsync-0.11.0/PKG-INFO +108 -0
  2. devsync-0.11.0/README.md +70 -0
  3. devsync-0.11.0/devsync/ai_tools/aider.py +215 -0
  4. devsync-0.11.0/devsync/ai_tools/amazonq.py +67 -0
  5. devsync-0.11.0/devsync/ai_tools/amp.py +213 -0
  6. devsync-0.11.0/devsync/ai_tools/anteroom.py +216 -0
  7. devsync-0.11.0/devsync/ai_tools/augment.py +67 -0
  8. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/capability_registry.py +229 -0
  9. devsync-0.11.0/devsync/ai_tools/continuedev.py +64 -0
  10. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/detector.py +39 -0
  11. devsync-0.11.0/devsync/ai_tools/jetbrains.py +67 -0
  12. devsync-0.11.0/devsync/ai_tools/junie.py +216 -0
  13. devsync-0.11.0/devsync/ai_tools/opencode.py +215 -0
  14. devsync-0.11.0/devsync/ai_tools/openhands.py +64 -0
  15. devsync-0.11.0/devsync/ai_tools/tabnine.py +64 -0
  16. devsync-0.11.0/devsync/ai_tools/trae.py +64 -0
  17. devsync-0.11.0/devsync/ai_tools/translator.py +937 -0
  18. devsync-0.11.0/devsync/ai_tools/zed.py +213 -0
  19. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/component_detector.py +1 -0
  20. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/models.py +26 -0
  21. {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/paths.py +8 -7
  22. devsync-0.11.0/devsync.egg-info/PKG-INFO +108 -0
  23. {devsync-0.9.0 → devsync-0.11.0}/devsync.egg-info/SOURCES.txt +13 -0
  24. {devsync-0.9.0 → devsync-0.11.0}/pyproject.toml +2 -2
  25. devsync-0.9.0/PKG-INFO +0 -479
  26. devsync-0.9.0/README.md +0 -441
  27. devsync-0.9.0/devsync/ai_tools/translator.py +0 -471
  28. devsync-0.9.0/devsync.egg-info/PKG-INFO +0 -479
  29. {devsync-0.9.0 → devsync-0.11.0}/LICENSE +0 -0
  30. {devsync-0.9.0 → devsync-0.11.0}/devsync/__init__.py +0 -0
  31. {devsync-0.9.0 → devsync-0.11.0}/devsync/__main__.py +0 -0
  32. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/__init__.py +0 -0
  33. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/antigravity.py +0 -0
  34. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/base.py +0 -0
  35. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/claude.py +0 -0
  36. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/claude_desktop.py +0 -0
  37. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/cline.py +0 -0
  38. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/codex.py +0 -0
  39. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/copilot.py +0 -0
  40. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/cursor.py +0 -0
  41. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/gemini.py +0 -0
  42. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/kiro.py +0 -0
  43. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/mcp_syncer.py +0 -0
  44. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/roo.py +0 -0
  45. {devsync-0.9.0 → devsync-0.11.0}/devsync/ai_tools/winsurf.py +0 -0
  46. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/__init__.py +0 -0
  47. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/delete.py +0 -0
  48. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/download.py +0 -0
  49. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/install.py +0 -0
  50. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/install_new.py +0 -0
  51. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/list.py +0 -0
  52. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/main.py +0 -0
  53. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/mcp_configure.py +0 -0
  54. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/mcp_install.py +0 -0
  55. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/mcp_sync.py +0 -0
  56. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/package.py +0 -0
  57. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/package_create.py +0 -0
  58. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/package_install.py +0 -0
  59. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template.py +0 -0
  60. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_backup.py +0 -0
  61. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_init.py +0 -0
  62. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_install.py +0 -0
  63. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_list.py +0 -0
  64. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_uninstall.py +0 -0
  65. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_update.py +0 -0
  66. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/template_validate.py +0 -0
  67. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/tools.py +0 -0
  68. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/uninstall.py +0 -0
  69. {devsync-0.9.0 → devsync-0.11.0}/devsync/cli/update.py +0 -0
  70. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/__init__.py +0 -0
  71. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/checksum.py +0 -0
  72. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/conflict_resolution.py +0 -0
  73. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/git_operations.py +0 -0
  74. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/mcp/__init__.py +0 -0
  75. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/mcp/credentials.py +0 -0
  76. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/mcp/manager.py +0 -0
  77. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/mcp/set_manager.py +0 -0
  78. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/mcp/validator.py +0 -0
  79. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/package_creator.py +0 -0
  80. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/package_manifest.py +0 -0
  81. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/repository.py +0 -0
  82. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/secret_detector.py +0 -0
  83. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/template_manifest.py +0 -0
  84. {devsync-0.9.0 → devsync-0.11.0}/devsync/core/version.py +0 -0
  85. {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/__init__.py +0 -0
  86. {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/library.py +0 -0
  87. {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/mcp_tracker.py +0 -0
  88. {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/package_tracker.py +0 -0
  89. {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/template_library.py +0 -0
  90. {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/template_tracker.py +0 -0
  91. {devsync-0.9.0 → devsync-0.11.0}/devsync/storage/tracker.py +0 -0
  92. {devsync-0.9.0 → devsync-0.11.0}/devsync/tui/__init__.py +0 -0
  93. {devsync-0.9.0 → devsync-0.11.0}/devsync/tui/installer.py +0 -0
  94. {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/__init__.py +0 -0
  95. {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/atomic_write.py +0 -0
  96. {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/backup.py +0 -0
  97. {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/dotenv.py +0 -0
  98. {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/git_helpers.py +0 -0
  99. {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/logging.py +0 -0
  100. {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/namespace.py +0 -0
  101. {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/project.py +0 -0
  102. {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/streaming.py +0 -0
  103. {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/ui.py +0 -0
  104. {devsync-0.9.0 → devsync-0.11.0}/devsync/utils/validation.py +0 -0
  105. {devsync-0.9.0 → devsync-0.11.0}/devsync.egg-info/dependency_links.txt +0 -0
  106. {devsync-0.9.0 → devsync-0.11.0}/devsync.egg-info/entry_points.txt +0 -0
  107. {devsync-0.9.0 → devsync-0.11.0}/devsync.egg-info/requires.txt +0 -0
  108. {devsync-0.9.0 → devsync-0.11.0}/devsync.egg-info/top_level.txt +0 -0
  109. {devsync-0.9.0 → devsync-0.11.0}/setup.cfg +0 -0
@@ -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
+ [![CI](https://github.com/troylar/devsync/actions/workflows/ci.yml/badge.svg)](https://github.com/troylar/devsync/actions/workflows/ci.yml)
46
+ [![Docs](https://readthedocs.org/projects/devsync/badge/?version=latest)](https://devsync.readthedocs.io)
47
+ [![PyPI version](https://img.shields.io/pypi/v/devsync.svg)](https://pypi.org/project/devsync/)
48
+ [![Coverage](https://codecov.io/gh/troylar/devsync/branch/main/graph/badge.svg)](https://codecov.io/gh/troylar/devsync)
49
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
50
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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).
@@ -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
+ [![CI](https://github.com/troylar/devsync/actions/workflows/ci.yml/badge.svg)](https://github.com/troylar/devsync/actions/workflows/ci.yml)
8
+ [![Docs](https://readthedocs.org/projects/devsync/badge/?version=latest)](https://devsync.readthedocs.io)
9
+ [![PyPI version](https://img.shields.io/pypi/v/devsync.svg)](https://pypi.org/project/devsync/)
10
+ [![Coverage](https://codecov.io/gh/troylar/devsync/branch/main/graph/badge.svg)](https://codecov.io/gh/troylar/devsync)
11
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
12
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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