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.
Files changed (126) hide show
  1. {devsync-0.11.0 → devsync-0.13.0}/PKG-INFO +42 -13
  2. devsync-0.13.0/README.md +98 -0
  3. devsync-0.13.0/devsync/cli/extract.py +232 -0
  4. devsync-0.13.0/devsync/cli/install_v2.py +360 -0
  5. devsync-0.13.0/devsync/cli/list_v2.py +92 -0
  6. devsync-0.13.0/devsync/cli/main.py +260 -0
  7. devsync-0.13.0/devsync/cli/setup.py +69 -0
  8. devsync-0.13.0/devsync/core/adapter.py +181 -0
  9. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/component_detector.py +29 -0
  10. devsync-0.13.0/devsync/core/extractor.py +179 -0
  11. devsync-0.13.0/devsync/core/mcp_credential_prompter.py +129 -0
  12. devsync-0.13.0/devsync/core/package_manifest_v2.py +240 -0
  13. devsync-0.13.0/devsync/core/pip_utils.py +314 -0
  14. devsync-0.13.0/devsync/core/practice.py +183 -0
  15. devsync-0.13.0/devsync/llm/__init__.py +13 -0
  16. devsync-0.13.0/devsync/llm/anthropic.py +97 -0
  17. devsync-0.13.0/devsync/llm/config.py +91 -0
  18. devsync-0.13.0/devsync/llm/openai_provider.py +98 -0
  19. devsync-0.13.0/devsync/llm/openrouter.py +100 -0
  20. devsync-0.13.0/devsync/llm/prompts.py +120 -0
  21. devsync-0.13.0/devsync/llm/provider.py +137 -0
  22. devsync-0.13.0/devsync/llm/response_models.py +186 -0
  23. devsync-0.13.0/devsync/tui/__init__.py +1 -0
  24. {devsync-0.11.0 → devsync-0.13.0}/devsync.egg-info/PKG-INFO +42 -13
  25. {devsync-0.11.0 → devsync-0.13.0}/devsync.egg-info/SOURCES.txt +18 -25
  26. {devsync-0.11.0 → devsync-0.13.0}/devsync.egg-info/requires.txt +1 -0
  27. {devsync-0.11.0 → devsync-0.13.0}/pyproject.toml +2 -1
  28. devsync-0.11.0/README.md +0 -70
  29. devsync-0.11.0/devsync/cli/delete.py +0 -118
  30. devsync-0.11.0/devsync/cli/download.py +0 -274
  31. devsync-0.11.0/devsync/cli/install.py +0 -237
  32. devsync-0.11.0/devsync/cli/install_new.py +0 -937
  33. devsync-0.11.0/devsync/cli/list.py +0 -275
  34. devsync-0.11.0/devsync/cli/main.py +0 -454
  35. devsync-0.11.0/devsync/cli/mcp_configure.py +0 -233
  36. devsync-0.11.0/devsync/cli/mcp_install.py +0 -167
  37. devsync-0.11.0/devsync/cli/mcp_sync.py +0 -166
  38. devsync-0.11.0/devsync/cli/package.py +0 -386
  39. devsync-0.11.0/devsync/cli/package_create.py +0 -323
  40. devsync-0.11.0/devsync/cli/package_install.py +0 -474
  41. devsync-0.11.0/devsync/cli/template.py +0 -19
  42. devsync-0.11.0/devsync/cli/template_backup.py +0 -262
  43. devsync-0.11.0/devsync/cli/template_init.py +0 -499
  44. devsync-0.11.0/devsync/cli/template_install.py +0 -263
  45. devsync-0.11.0/devsync/cli/template_list.py +0 -172
  46. devsync-0.11.0/devsync/cli/template_uninstall.py +0 -146
  47. devsync-0.11.0/devsync/cli/template_update.py +0 -225
  48. devsync-0.11.0/devsync/cli/template_validate.py +0 -234
  49. devsync-0.11.0/devsync/cli/update.py +0 -309
  50. devsync-0.11.0/devsync/core/template_manifest.py +0 -283
  51. devsync-0.11.0/devsync/storage/library.py +0 -429
  52. devsync-0.11.0/devsync/storage/template_library.py +0 -231
  53. devsync-0.11.0/devsync/storage/template_tracker.py +0 -297
  54. devsync-0.11.0/devsync/tui/__init__.py +0 -5
  55. devsync-0.11.0/devsync/tui/installer.py +0 -511
  56. {devsync-0.11.0 → devsync-0.13.0}/LICENSE +0 -0
  57. {devsync-0.11.0 → devsync-0.13.0}/devsync/__init__.py +0 -0
  58. {devsync-0.11.0 → devsync-0.13.0}/devsync/__main__.py +0 -0
  59. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/__init__.py +0 -0
  60. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/aider.py +0 -0
  61. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/amazonq.py +0 -0
  62. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/amp.py +0 -0
  63. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/anteroom.py +0 -0
  64. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/antigravity.py +0 -0
  65. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/augment.py +0 -0
  66. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/base.py +0 -0
  67. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/capability_registry.py +0 -0
  68. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/claude.py +0 -0
  69. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/claude_desktop.py +0 -0
  70. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/cline.py +0 -0
  71. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/codex.py +0 -0
  72. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/continuedev.py +0 -0
  73. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/copilot.py +0 -0
  74. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/cursor.py +0 -0
  75. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/detector.py +0 -0
  76. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/gemini.py +0 -0
  77. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/jetbrains.py +0 -0
  78. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/junie.py +0 -0
  79. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/kiro.py +0 -0
  80. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/mcp_syncer.py +0 -0
  81. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/opencode.py +0 -0
  82. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/openhands.py +0 -0
  83. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/roo.py +0 -0
  84. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/tabnine.py +0 -0
  85. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/trae.py +0 -0
  86. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/translator.py +0 -0
  87. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/winsurf.py +0 -0
  88. {devsync-0.11.0 → devsync-0.13.0}/devsync/ai_tools/zed.py +0 -0
  89. {devsync-0.11.0 → devsync-0.13.0}/devsync/cli/__init__.py +0 -0
  90. {devsync-0.11.0 → devsync-0.13.0}/devsync/cli/tools.py +0 -0
  91. {devsync-0.11.0 → devsync-0.13.0}/devsync/cli/uninstall.py +0 -0
  92. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/__init__.py +0 -0
  93. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/checksum.py +0 -0
  94. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/conflict_resolution.py +0 -0
  95. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/git_operations.py +0 -0
  96. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/mcp/__init__.py +0 -0
  97. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/mcp/credentials.py +0 -0
  98. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/mcp/manager.py +0 -0
  99. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/mcp/set_manager.py +0 -0
  100. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/mcp/validator.py +0 -0
  101. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/models.py +0 -0
  102. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/package_creator.py +0 -0
  103. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/package_manifest.py +0 -0
  104. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/repository.py +0 -0
  105. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/secret_detector.py +0 -0
  106. {devsync-0.11.0 → devsync-0.13.0}/devsync/core/version.py +0 -0
  107. {devsync-0.11.0 → devsync-0.13.0}/devsync/storage/__init__.py +0 -0
  108. {devsync-0.11.0 → devsync-0.13.0}/devsync/storage/mcp_tracker.py +0 -0
  109. {devsync-0.11.0 → devsync-0.13.0}/devsync/storage/package_tracker.py +0 -0
  110. {devsync-0.11.0 → devsync-0.13.0}/devsync/storage/tracker.py +0 -0
  111. {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/__init__.py +0 -0
  112. {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/atomic_write.py +0 -0
  113. {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/backup.py +0 -0
  114. {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/dotenv.py +0 -0
  115. {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/git_helpers.py +0 -0
  116. {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/logging.py +0 -0
  117. {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/namespace.py +0 -0
  118. {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/paths.py +0 -0
  119. {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/project.py +0 -0
  120. {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/streaming.py +0 -0
  121. {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/ui.py +0 -0
  122. {devsync-0.11.0 → devsync-0.13.0}/devsync/utils/validation.py +0 -0
  123. {devsync-0.11.0 → devsync-0.13.0}/devsync.egg-info/dependency_links.txt +0 -0
  124. {devsync-0.11.0 → devsync-0.13.0}/devsync.egg-info/entry_points.txt +0 -0
  125. {devsync-0.11.0 → devsync-0.13.0}/devsync.egg-info/top_level.txt +0 -0
  126. {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.11.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
- **Distribute and sync AI coding assistant configurations across your team**
44
+ **AI-powered config distribution for AI coding assistants**
44
45
 
45
46
  [![CI](https://github.com/troylar/devsync/actions/workflows/ci.yml/badge.svg)](https://github.com/troylar/devsync/actions/workflows/ci.yml)
46
47
  [![Docs](https://readthedocs.org/projects/devsync/badge/?version=latest)](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 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
+ 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
- # Download instructions from a Git repo
69
- devsync download --from github.com/company/standards --as company
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 interactively
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
- - **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
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
 
@@ -0,0 +1,98 @@
1
+ <div align="center">
2
+
3
+ # DevSync
4
+
5
+ **AI-powered config distribution for AI coding assistants**
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 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))