devsync 0.11.0__tar.gz → 0.13.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 → devsync-0.13.0}/PKG-INFO +42 -13
- devsync-0.13.0/README.md +98 -0
- devsync-0.13.0/devsync/cli/extract.py +232 -0
- devsync-0.13.0/devsync/cli/install_v2.py +360 -0
- devsync-0.13.0/devsync/cli/list_v2.py +92 -0
- devsync-0.13.0/devsync/cli/main.py +260 -0
- devsync-0.13.0/devsync/cli/setup.py +69 -0
- devsync-0.13.0/devsync/core/adapter.py +181 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/component_detector.py +29 -0
- devsync-0.13.0/devsync/core/extractor.py +179 -0
- devsync-0.13.0/devsync/core/mcp_credential_prompter.py +129 -0
- devsync-0.13.0/devsync/core/package_manifest_v2.py +240 -0
- devsync-0.13.0/devsync/core/pip_utils.py +314 -0
- devsync-0.13.0/devsync/core/practice.py +183 -0
- devsync-0.13.0/devsync/llm/__init__.py +13 -0
- devsync-0.13.0/devsync/llm/anthropic.py +97 -0
- devsync-0.13.0/devsync/llm/config.py +91 -0
- devsync-0.13.0/devsync/llm/openai_provider.py +98 -0
- devsync-0.13.0/devsync/llm/openrouter.py +100 -0
- devsync-0.13.0/devsync/llm/prompts.py +120 -0
- devsync-0.13.0/devsync/llm/provider.py +137 -0
- devsync-0.13.0/devsync/llm/response_models.py +186 -0
- devsync-0.13.0/devsync/tui/__init__.py +1 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync.egg-info/PKG-INFO +42 -13
- {devsync-0.11.0 → devsync-0.13.0}/devsync.egg-info/SOURCES.txt +18 -25
- {devsync-0.11.0 → devsync-0.13.0}/devsync.egg-info/requires.txt +1 -0
- {devsync-0.11.0 → devsync-0.13.0}/pyproject.toml +2 -1
- devsync-0.11.0/README.md +0 -70
- devsync-0.11.0/devsync/cli/delete.py +0 -118
- devsync-0.11.0/devsync/cli/download.py +0 -274
- devsync-0.11.0/devsync/cli/install.py +0 -237
- devsync-0.11.0/devsync/cli/install_new.py +0 -937
- devsync-0.11.0/devsync/cli/list.py +0 -275
- devsync-0.11.0/devsync/cli/main.py +0 -454
- devsync-0.11.0/devsync/cli/mcp_configure.py +0 -233
- devsync-0.11.0/devsync/cli/mcp_install.py +0 -167
- devsync-0.11.0/devsync/cli/mcp_sync.py +0 -166
- devsync-0.11.0/devsync/cli/package.py +0 -386
- devsync-0.11.0/devsync/cli/package_create.py +0 -323
- devsync-0.11.0/devsync/cli/package_install.py +0 -474
- devsync-0.11.0/devsync/cli/template.py +0 -19
- devsync-0.11.0/devsync/cli/template_backup.py +0 -262
- devsync-0.11.0/devsync/cli/template_init.py +0 -499
- devsync-0.11.0/devsync/cli/template_install.py +0 -263
- devsync-0.11.0/devsync/cli/template_list.py +0 -172
- devsync-0.11.0/devsync/cli/template_uninstall.py +0 -146
- devsync-0.11.0/devsync/cli/template_update.py +0 -225
- devsync-0.11.0/devsync/cli/template_validate.py +0 -234
- devsync-0.11.0/devsync/cli/update.py +0 -309
- devsync-0.11.0/devsync/core/template_manifest.py +0 -283
- devsync-0.11.0/devsync/storage/library.py +0 -429
- devsync-0.11.0/devsync/storage/template_library.py +0 -231
- devsync-0.11.0/devsync/storage/template_tracker.py +0 -297
- devsync-0.11.0/devsync/tui/__init__.py +0 -5
- devsync-0.11.0/devsync/tui/installer.py +0 -511
- {devsync-0.11.0 → devsync-0.13.0}/LICENSE +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/__init__.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/__main__.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/__init__.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/aider.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/amazonq.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/amp.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/anteroom.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/antigravity.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/augment.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/base.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/capability_registry.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/claude.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/claude_desktop.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/cline.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/codex.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/continuedev.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/copilot.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/cursor.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/detector.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/gemini.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/jetbrains.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/junie.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/kiro.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/mcp_syncer.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/opencode.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/openhands.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/roo.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/tabnine.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/trae.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/translator.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/winsurf.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/zed.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/cli/__init__.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/cli/tools.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/cli/uninstall.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/__init__.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/checksum.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/conflict_resolution.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/git_operations.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/mcp/__init__.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/mcp/credentials.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/mcp/manager.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/mcp/set_manager.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/mcp/validator.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/models.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/package_creator.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/package_manifest.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/repository.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/secret_detector.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/core/version.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/storage/__init__.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/storage/mcp_tracker.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/storage/package_tracker.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/storage/tracker.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/__init__.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/atomic_write.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/backup.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/dotenv.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/git_helpers.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/logging.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/namespace.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/paths.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/project.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/streaming.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/ui.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/validation.py +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync.egg-info/dependency_links.txt +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync.egg-info/entry_points.txt +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/devsync.egg-info/top_level.txt +0 -0
- {devsync-0.11.0 → devsync-0.13.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devsync
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Distribute and sync dev tool configurations across teams
|
|
5
5
|
Author-email: Troy Larson <troy@calvinware.com>
|
|
6
6
|
License: MIT License
|
|
@@ -25,6 +25,7 @@ Requires-Dist: pyyaml>=6.0
|
|
|
25
25
|
Requires-Dist: textual>=6.0.0
|
|
26
26
|
Requires-Dist: GitPython>=3.1.45
|
|
27
27
|
Requires-Dist: python-dotenv>=1.0.0
|
|
28
|
+
Requires-Dist: httpx>=0.27.0
|
|
28
29
|
Provides-Extra: dev
|
|
29
30
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
30
31
|
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
@@ -40,7 +41,7 @@ Requires-Dist: types-PyYAML>=6.0.12.20240808; extra == "dev"
|
|
|
40
41
|
|
|
41
42
|
# DevSync
|
|
42
43
|
|
|
43
|
-
**
|
|
44
|
+
**AI-powered config distribution for AI coding assistants**
|
|
44
45
|
|
|
45
46
|
[](https://github.com/troylar/devsync/actions/workflows/ci.yml)
|
|
46
47
|
[](https://devsync.readthedocs.io)
|
|
@@ -55,31 +56,59 @@ Requires-Dist: types-PyYAML>=6.0.12.20240808; extra == "dev"
|
|
|
55
56
|
|
|
56
57
|
---
|
|
57
58
|
|
|
58
|
-
DevSync
|
|
59
|
+
DevSync uses LLM intelligence to extract coding practices from projects and adapt them to recipients' existing setups -- across 23+ AI coding assistants. Two commands: `extract` and `install`.
|
|
59
60
|
|
|
60
61
|
## Quick Start
|
|
61
62
|
|
|
62
63
|
```bash
|
|
63
64
|
pip install devsync
|
|
64
65
|
|
|
66
|
+
# One-time: configure your LLM provider
|
|
67
|
+
devsync setup
|
|
68
|
+
|
|
65
69
|
# Check detected AI tools
|
|
66
70
|
devsync tools
|
|
67
71
|
|
|
68
|
-
#
|
|
69
|
-
devsync
|
|
72
|
+
# Extract practices from a project
|
|
73
|
+
devsync extract
|
|
74
|
+
|
|
75
|
+
# Install a package into another project
|
|
76
|
+
devsync install ./team-standards
|
|
70
77
|
|
|
71
|
-
# Install
|
|
72
|
-
devsync install
|
|
78
|
+
# Install from Git
|
|
79
|
+
devsync install https://github.com/company/standards
|
|
73
80
|
```
|
|
74
81
|
|
|
82
|
+
No API key? DevSync works without one -- it falls back to file-copy mode. Add `--no-ai` to any command to force this.
|
|
83
|
+
|
|
75
84
|
## Features
|
|
76
85
|
|
|
77
|
-
- **
|
|
78
|
-
- **
|
|
79
|
-
- **
|
|
80
|
-
- **
|
|
81
|
-
- **
|
|
82
|
-
- **
|
|
86
|
+
- **AI-powered extraction** -- LLM reads your project's rules, MCP configs, and commands to produce abstract practice declarations
|
|
87
|
+
- **AI-powered installation** -- LLM adapts incoming practices to your existing setup with intelligent merging
|
|
88
|
+
- **23+ AI tool integrations** -- Claude Code, Cursor, Windsurf, GitHub Copilot, Kiro, Roo Code, Cline, Codex, and more
|
|
89
|
+
- **MCP server dependencies** -- auto-detects pip-installable MCP servers and prompts to install them (`--skip-pip` to skip)
|
|
90
|
+
- **MCP credential handling** -- prompts for credentials at install time, never stores them in repos
|
|
91
|
+
- **v1 backward compatibility** -- old `ai-config-kit-package.yaml` packages still install via file-copy
|
|
92
|
+
- **Graceful degradation** -- works without an API key, `--no-ai` flag for explicit file-copy mode
|
|
93
|
+
|
|
94
|
+
## Commands
|
|
95
|
+
|
|
96
|
+
| Command | Description |
|
|
97
|
+
|---------|-------------|
|
|
98
|
+
| `devsync setup` | Configure LLM provider (Anthropic, OpenAI, OpenRouter) |
|
|
99
|
+
| `devsync tools` | Detect installed AI coding tools |
|
|
100
|
+
| `devsync extract` | Extract practices from current project into a shareable package |
|
|
101
|
+
| `devsync install <source>` | Install a package with AI-powered adaptation |
|
|
102
|
+
| `devsync list` | Show installed packages |
|
|
103
|
+
| `devsync uninstall <name>` | Remove an installed package |
|
|
104
|
+
|
|
105
|
+
## Migrating from v1
|
|
106
|
+
|
|
107
|
+
If you have v1 packages (`ai-config-kit-package.yaml`), they still work with `devsync install`. To upgrade them to v2 format:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
devsync extract --upgrade ./old-package
|
|
111
|
+
```
|
|
83
112
|
|
|
84
113
|
## Documentation
|
|
85
114
|
|
devsync-0.13.0/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# DevSync
|
|
4
|
+
|
|
5
|
+
**AI-powered config distribution for AI coding assistants**
|
|
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 uses LLM intelligence to extract coding practices from projects and adapt them to recipients' existing setups -- across 23+ AI coding assistants. Two commands: `extract` and `install`.
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install devsync
|
|
26
|
+
|
|
27
|
+
# One-time: configure your LLM provider
|
|
28
|
+
devsync setup
|
|
29
|
+
|
|
30
|
+
# Check detected AI tools
|
|
31
|
+
devsync tools
|
|
32
|
+
|
|
33
|
+
# Extract practices from a project
|
|
34
|
+
devsync extract
|
|
35
|
+
|
|
36
|
+
# Install a package into another project
|
|
37
|
+
devsync install ./team-standards
|
|
38
|
+
|
|
39
|
+
# Install from Git
|
|
40
|
+
devsync install https://github.com/company/standards
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
No API key? DevSync works without one -- it falls back to file-copy mode. Add `--no-ai` to any command to force this.
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- **AI-powered extraction** -- LLM reads your project's rules, MCP configs, and commands to produce abstract practice declarations
|
|
48
|
+
- **AI-powered installation** -- LLM adapts incoming practices to your existing setup with intelligent merging
|
|
49
|
+
- **23+ AI tool integrations** -- Claude Code, Cursor, Windsurf, GitHub Copilot, Kiro, Roo Code, Cline, Codex, and more
|
|
50
|
+
- **MCP server dependencies** -- auto-detects pip-installable MCP servers and prompts to install them (`--skip-pip` to skip)
|
|
51
|
+
- **MCP credential handling** -- prompts for credentials at install time, never stores them in repos
|
|
52
|
+
- **v1 backward compatibility** -- old `ai-config-kit-package.yaml` packages still install via file-copy
|
|
53
|
+
- **Graceful degradation** -- works without an API key, `--no-ai` flag for explicit file-copy mode
|
|
54
|
+
|
|
55
|
+
## Commands
|
|
56
|
+
|
|
57
|
+
| Command | Description |
|
|
58
|
+
|---------|-------------|
|
|
59
|
+
| `devsync setup` | Configure LLM provider (Anthropic, OpenAI, OpenRouter) |
|
|
60
|
+
| `devsync tools` | Detect installed AI coding tools |
|
|
61
|
+
| `devsync extract` | Extract practices from current project into a shareable package |
|
|
62
|
+
| `devsync install <source>` | Install a package with AI-powered adaptation |
|
|
63
|
+
| `devsync list` | Show installed packages |
|
|
64
|
+
| `devsync uninstall <name>` | Remove an installed package |
|
|
65
|
+
|
|
66
|
+
## Migrating from v1
|
|
67
|
+
|
|
68
|
+
If you have v1 packages (`ai-config-kit-package.yaml`), they still work with `devsync install`. To upgrade them to v2 format:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
devsync extract --upgrade ./old-package
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Documentation
|
|
75
|
+
|
|
76
|
+
Full documentation at **[devsync.readthedocs.io](https://devsync.readthedocs.io)**:
|
|
77
|
+
|
|
78
|
+
- [Getting Started](https://devsync.readthedocs.io/getting-started/installation/) -- installation, quickstart, core concepts
|
|
79
|
+
- [CLI Reference](https://devsync.readthedocs.io/cli/) -- all commands with examples
|
|
80
|
+
- [IDE Integrations](https://devsync.readthedocs.io/ide-integrations/) -- setup guides for each AI tool
|
|
81
|
+
- [Packages](https://devsync.readthedocs.io/packages/) -- creating and installing config packages
|
|
82
|
+
- [MCP Server](https://devsync.readthedocs.io/mcp-server/) -- MCP configuration management
|
|
83
|
+
- [Tutorials](https://devsync.readthedocs.io/tutorials/team-config-repo/) -- step-by-step walkthroughs
|
|
84
|
+
|
|
85
|
+
## Contributing
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git clone https://github.com/troylar/devsync.git
|
|
89
|
+
cd devsync
|
|
90
|
+
pip install -e .[dev]
|
|
91
|
+
invoke test
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
See the [contributing guide](https://devsync.readthedocs.io/advanced/contributing/) for details.
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
MIT -- see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Extract command — reads project configs and produces a shareable package."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
9
|
+
|
|
10
|
+
from devsync.core.extractor import PracticeExtractor
|
|
11
|
+
from devsync.core.package_manifest_v2 import PackageManifestV2, detect_manifest_format, parse_manifest
|
|
12
|
+
from devsync.llm.config import load_config
|
|
13
|
+
from devsync.llm.provider import resolve_provider
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def extract_command(
|
|
19
|
+
output: Optional[str] = None,
|
|
20
|
+
name: Optional[str] = None,
|
|
21
|
+
no_ai: bool = False,
|
|
22
|
+
project_dir: Optional[str] = None,
|
|
23
|
+
upgrade: Optional[str] = None,
|
|
24
|
+
) -> int:
|
|
25
|
+
"""Extract practices from the current project into a shareable package.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
output: Output directory for the package. Defaults to './devsync-package/'.
|
|
29
|
+
name: Package name. Defaults to project directory name.
|
|
30
|
+
no_ai: Force file-copy mode (no LLM calls).
|
|
31
|
+
project_dir: Project directory to extract from. Defaults to cwd.
|
|
32
|
+
upgrade: Path to a v1 package to convert to v2 format.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Exit code (0 = success).
|
|
36
|
+
"""
|
|
37
|
+
if upgrade:
|
|
38
|
+
return _upgrade_v1_package(upgrade, output=output, name=name, no_ai=no_ai)
|
|
39
|
+
|
|
40
|
+
project_path = Path(project_dir) if project_dir else Path.cwd()
|
|
41
|
+
if not project_path.is_dir():
|
|
42
|
+
console.print(f"[red]Not a directory: {project_path}[/red]")
|
|
43
|
+
return 1
|
|
44
|
+
|
|
45
|
+
package_name = name or project_path.name
|
|
46
|
+
output_path = Path(output) if output else project_path / "devsync-package"
|
|
47
|
+
|
|
48
|
+
llm = None
|
|
49
|
+
if not no_ai:
|
|
50
|
+
config = load_config()
|
|
51
|
+
llm = resolve_provider(
|
|
52
|
+
preferred_provider=config.provider,
|
|
53
|
+
preferred_model=config.model,
|
|
54
|
+
)
|
|
55
|
+
if not llm:
|
|
56
|
+
console.print("[yellow]No LLM API key found. Using file-copy mode.[/yellow]")
|
|
57
|
+
console.print("Run [cyan]devsync setup[/cyan] to configure AI features.\n")
|
|
58
|
+
|
|
59
|
+
extractor = PracticeExtractor(llm_provider=llm)
|
|
60
|
+
|
|
61
|
+
with Progress(
|
|
62
|
+
SpinnerColumn(),
|
|
63
|
+
TextColumn("[progress.description]{task.description}"),
|
|
64
|
+
console=console,
|
|
65
|
+
) as progress:
|
|
66
|
+
task = progress.add_task("Scanning project...", total=None)
|
|
67
|
+
result = extractor.extract(project_path)
|
|
68
|
+
progress.update(task, description="Building package...")
|
|
69
|
+
|
|
70
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
|
|
72
|
+
manifest = PackageManifestV2(
|
|
73
|
+
format_version="2.0",
|
|
74
|
+
name=package_name,
|
|
75
|
+
version="1.0.0",
|
|
76
|
+
description=f"Extracted from {project_path.name}",
|
|
77
|
+
practices=result.practices,
|
|
78
|
+
mcp_servers=result.mcp_servers,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if not result.ai_powered:
|
|
82
|
+
_copy_source_files(project_path, output_path, result.source_files)
|
|
83
|
+
components: dict = {}
|
|
84
|
+
if result.source_files:
|
|
85
|
+
from devsync.core.package_manifest_v2 import ComponentRef
|
|
86
|
+
|
|
87
|
+
components["instructions"] = [
|
|
88
|
+
ComponentRef(
|
|
89
|
+
name=Path(f).stem,
|
|
90
|
+
file=f"instructions/{Path(f).name}",
|
|
91
|
+
)
|
|
92
|
+
for f in result.source_files
|
|
93
|
+
]
|
|
94
|
+
manifest.components = components
|
|
95
|
+
|
|
96
|
+
manifest_path = output_path / "devsync-package.yaml"
|
|
97
|
+
manifest_path.write_text(manifest.to_yaml())
|
|
98
|
+
|
|
99
|
+
mode = "[green]AI-powered[/green]" if result.ai_powered else "[yellow]file-copy[/yellow]"
|
|
100
|
+
console.print(f"\nExtracted ({mode}):")
|
|
101
|
+
console.print(f" Practices: {len(result.practices)}")
|
|
102
|
+
console.print(f" MCP servers: {len(result.mcp_servers)}")
|
|
103
|
+
console.print(f" Source files: {len(result.source_files)}")
|
|
104
|
+
console.print(f"\nPackage written to: [cyan]{output_path}[/cyan]")
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _upgrade_v1_package(
|
|
109
|
+
v1_path: str,
|
|
110
|
+
output: Optional[str] = None,
|
|
111
|
+
name: Optional[str] = None,
|
|
112
|
+
no_ai: bool = False,
|
|
113
|
+
) -> int:
|
|
114
|
+
"""Convert a v1 package to v2 format.
|
|
115
|
+
|
|
116
|
+
Reads the v1 manifest, extracts instruction files, and produces a v2
|
|
117
|
+
package with practice declarations (AI-powered) or literal content (no-AI).
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
v1_path: Path to the v1 package directory.
|
|
121
|
+
output: Output directory for the v2 package.
|
|
122
|
+
name: Package name override.
|
|
123
|
+
no_ai: Disable AI-powered conversion.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Exit code (0 = success).
|
|
127
|
+
"""
|
|
128
|
+
package_path = Path(v1_path).expanduser()
|
|
129
|
+
if not package_path.is_dir():
|
|
130
|
+
console.print(f"[red]Not a directory: {package_path}[/red]")
|
|
131
|
+
return 1
|
|
132
|
+
|
|
133
|
+
fmt = detect_manifest_format(package_path)
|
|
134
|
+
if not fmt:
|
|
135
|
+
console.print(f"[red]No manifest found in {package_path}[/red]")
|
|
136
|
+
console.print("Expected: ai-config-kit-package.yaml or devsync-package.yaml")
|
|
137
|
+
return 1
|
|
138
|
+
|
|
139
|
+
if fmt == "v2":
|
|
140
|
+
console.print("[yellow]Package is already v2 format. No upgrade needed.[/yellow]")
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
v1_manifest = parse_manifest(package_path)
|
|
144
|
+
console.print(f"\n[bold]Upgrading v1 package: {v1_manifest.name} v{v1_manifest.version}[/bold]")
|
|
145
|
+
|
|
146
|
+
instruction_files: dict[str, str] = {}
|
|
147
|
+
for comp_type, refs in v1_manifest.components.items():
|
|
148
|
+
if comp_type != "instructions":
|
|
149
|
+
continue
|
|
150
|
+
for ref in refs:
|
|
151
|
+
src_file = (package_path / ref.file).resolve()
|
|
152
|
+
try:
|
|
153
|
+
src_file.relative_to(package_path.resolve())
|
|
154
|
+
except ValueError:
|
|
155
|
+
console.print(f" [red]Rejected (path traversal): {ref.file}[/red]")
|
|
156
|
+
continue
|
|
157
|
+
if src_file.exists() and src_file.stat().st_size < 100_000:
|
|
158
|
+
try:
|
|
159
|
+
content = src_file.read_text(encoding="utf-8")
|
|
160
|
+
instruction_files[ref.file] = content
|
|
161
|
+
except (OSError, UnicodeDecodeError):
|
|
162
|
+
console.print(f" [yellow]Could not read: {ref.file}[/yellow]")
|
|
163
|
+
|
|
164
|
+
if not instruction_files:
|
|
165
|
+
console.print("[red]No instruction files found in v1 package.[/red]")
|
|
166
|
+
return 1
|
|
167
|
+
|
|
168
|
+
llm = None
|
|
169
|
+
if not no_ai:
|
|
170
|
+
config = load_config()
|
|
171
|
+
llm = resolve_provider(preferred_provider=config.provider, preferred_model=config.model)
|
|
172
|
+
if not llm:
|
|
173
|
+
console.print("[yellow]No LLM API key found. Using file-copy mode.[/yellow]")
|
|
174
|
+
|
|
175
|
+
extractor = PracticeExtractor(llm_provider=llm)
|
|
176
|
+
|
|
177
|
+
with Progress(
|
|
178
|
+
SpinnerColumn(),
|
|
179
|
+
TextColumn("[progress.description]{task.description}"),
|
|
180
|
+
console=console,
|
|
181
|
+
) as progress:
|
|
182
|
+
task = progress.add_task("Converting to v2...", total=None)
|
|
183
|
+
if llm:
|
|
184
|
+
result = extractor._extract_with_ai(instruction_files, [])
|
|
185
|
+
else:
|
|
186
|
+
result = extractor._extract_without_ai(instruction_files, [])
|
|
187
|
+
progress.update(task, description="Building v2 package...")
|
|
188
|
+
|
|
189
|
+
output_path = Path(output) if output else package_path.parent / f"{package_path.name}-v2"
|
|
190
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
|
|
192
|
+
package_name = name or v1_manifest.name
|
|
193
|
+
|
|
194
|
+
v2_manifest = PackageManifestV2(
|
|
195
|
+
format_version="2.0",
|
|
196
|
+
name=package_name,
|
|
197
|
+
version=v1_manifest.version,
|
|
198
|
+
description=v1_manifest.description or f"Upgraded from v1: {v1_manifest.name}",
|
|
199
|
+
practices=result.practices,
|
|
200
|
+
mcp_servers=result.mcp_servers,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if not result.ai_powered:
|
|
204
|
+
_copy_source_files(package_path, output_path, list(instruction_files.keys()))
|
|
205
|
+
from devsync.core.package_manifest_v2 import ComponentRef
|
|
206
|
+
|
|
207
|
+
v2_manifest.components = {
|
|
208
|
+
"instructions": [
|
|
209
|
+
ComponentRef(name=Path(f).stem, file=f"instructions/{Path(f).name}") for f in instruction_files
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
manifest_path = output_path / "devsync-package.yaml"
|
|
214
|
+
manifest_path.write_text(v2_manifest.to_yaml())
|
|
215
|
+
|
|
216
|
+
mode = "[green]AI-powered[/green]" if result.ai_powered else "[yellow]file-copy[/yellow]"
|
|
217
|
+
console.print(f"\nUpgraded ({mode}):")
|
|
218
|
+
console.print(f" Practices: {len(result.practices)}")
|
|
219
|
+
console.print(f" Source files: {len(instruction_files)}")
|
|
220
|
+
console.print(f"\nv2 package written to: [cyan]{output_path}[/cyan]")
|
|
221
|
+
return 0
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _copy_source_files(project_path: Path, output_path: Path, source_files: list[str]) -> None:
|
|
225
|
+
"""Copy source instruction files to the output package directory."""
|
|
226
|
+
instructions_dir = output_path / "instructions"
|
|
227
|
+
instructions_dir.mkdir(parents=True, exist_ok=True)
|
|
228
|
+
for rel_path in source_files:
|
|
229
|
+
src = project_path / rel_path
|
|
230
|
+
if src.exists():
|
|
231
|
+
dest = instructions_dir / Path(rel_path).name
|
|
232
|
+
shutil.copy2(str(src), str(dest))
|