devsync 0.5.5__py3-none-any.whl

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 (84) hide show
  1. aiconfigkit/__init__.py +0 -0
  2. aiconfigkit/__main__.py +6 -0
  3. aiconfigkit/ai_tools/__init__.py +0 -0
  4. aiconfigkit/ai_tools/base.py +236 -0
  5. aiconfigkit/ai_tools/capability_registry.py +262 -0
  6. aiconfigkit/ai_tools/claude.py +91 -0
  7. aiconfigkit/ai_tools/claude_desktop.py +97 -0
  8. aiconfigkit/ai_tools/cline.py +92 -0
  9. aiconfigkit/ai_tools/copilot.py +92 -0
  10. aiconfigkit/ai_tools/cursor.py +109 -0
  11. aiconfigkit/ai_tools/detector.py +169 -0
  12. aiconfigkit/ai_tools/kiro.py +85 -0
  13. aiconfigkit/ai_tools/mcp_syncer.py +291 -0
  14. aiconfigkit/ai_tools/roo.py +110 -0
  15. aiconfigkit/ai_tools/translator.py +390 -0
  16. aiconfigkit/ai_tools/winsurf.py +102 -0
  17. aiconfigkit/cli/__init__.py +0 -0
  18. aiconfigkit/cli/delete.py +118 -0
  19. aiconfigkit/cli/download.py +274 -0
  20. aiconfigkit/cli/install.py +237 -0
  21. aiconfigkit/cli/install_new.py +937 -0
  22. aiconfigkit/cli/list.py +275 -0
  23. aiconfigkit/cli/main.py +454 -0
  24. aiconfigkit/cli/mcp_configure.py +232 -0
  25. aiconfigkit/cli/mcp_install.py +166 -0
  26. aiconfigkit/cli/mcp_sync.py +165 -0
  27. aiconfigkit/cli/package.py +383 -0
  28. aiconfigkit/cli/package_create.py +323 -0
  29. aiconfigkit/cli/package_install.py +472 -0
  30. aiconfigkit/cli/template.py +19 -0
  31. aiconfigkit/cli/template_backup.py +261 -0
  32. aiconfigkit/cli/template_init.py +499 -0
  33. aiconfigkit/cli/template_install.py +261 -0
  34. aiconfigkit/cli/template_list.py +172 -0
  35. aiconfigkit/cli/template_uninstall.py +146 -0
  36. aiconfigkit/cli/template_update.py +225 -0
  37. aiconfigkit/cli/template_validate.py +234 -0
  38. aiconfigkit/cli/tools.py +47 -0
  39. aiconfigkit/cli/uninstall.py +125 -0
  40. aiconfigkit/cli/update.py +309 -0
  41. aiconfigkit/core/__init__.py +0 -0
  42. aiconfigkit/core/checksum.py +211 -0
  43. aiconfigkit/core/component_detector.py +905 -0
  44. aiconfigkit/core/conflict_resolution.py +329 -0
  45. aiconfigkit/core/git_operations.py +539 -0
  46. aiconfigkit/core/mcp/__init__.py +1 -0
  47. aiconfigkit/core/mcp/credentials.py +279 -0
  48. aiconfigkit/core/mcp/manager.py +308 -0
  49. aiconfigkit/core/mcp/set_manager.py +1 -0
  50. aiconfigkit/core/mcp/validator.py +1 -0
  51. aiconfigkit/core/models.py +1661 -0
  52. aiconfigkit/core/package_creator.py +743 -0
  53. aiconfigkit/core/package_manifest.py +248 -0
  54. aiconfigkit/core/repository.py +298 -0
  55. aiconfigkit/core/secret_detector.py +438 -0
  56. aiconfigkit/core/template_manifest.py +283 -0
  57. aiconfigkit/core/version.py +201 -0
  58. aiconfigkit/storage/__init__.py +0 -0
  59. aiconfigkit/storage/library.py +429 -0
  60. aiconfigkit/storage/mcp_tracker.py +1 -0
  61. aiconfigkit/storage/package_tracker.py +234 -0
  62. aiconfigkit/storage/template_library.py +229 -0
  63. aiconfigkit/storage/template_tracker.py +296 -0
  64. aiconfigkit/storage/tracker.py +416 -0
  65. aiconfigkit/tui/__init__.py +5 -0
  66. aiconfigkit/tui/installer.py +511 -0
  67. aiconfigkit/utils/__init__.py +0 -0
  68. aiconfigkit/utils/atomic_write.py +90 -0
  69. aiconfigkit/utils/backup.py +169 -0
  70. aiconfigkit/utils/dotenv.py +128 -0
  71. aiconfigkit/utils/git_helpers.py +187 -0
  72. aiconfigkit/utils/logging.py +60 -0
  73. aiconfigkit/utils/namespace.py +134 -0
  74. aiconfigkit/utils/paths.py +205 -0
  75. aiconfigkit/utils/project.py +109 -0
  76. aiconfigkit/utils/streaming.py +216 -0
  77. aiconfigkit/utils/ui.py +194 -0
  78. aiconfigkit/utils/validation.py +187 -0
  79. devsync-0.5.5.dist-info/LICENSE +21 -0
  80. devsync-0.5.5.dist-info/METADATA +477 -0
  81. devsync-0.5.5.dist-info/RECORD +84 -0
  82. devsync-0.5.5.dist-info/WHEEL +5 -0
  83. devsync-0.5.5.dist-info/entry_points.txt +2 -0
  84. devsync-0.5.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,329 @@
1
+ """Conflict resolution for instruction and template installations."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from rich.console import Console
7
+ from rich.prompt import Prompt
8
+
9
+ from aiconfigkit.core.checksum import sha256_file, sha256_string
10
+ from aiconfigkit.core.models import ConflictInfo, ConflictResolution, ConflictType, TemplateInstallationRecord
11
+ from aiconfigkit.utils.paths import resolve_conflict_name
12
+
13
+
14
+ class ConflictResolver:
15
+ """Handle file conflicts during instruction installation."""
16
+
17
+ def __init__(self, default_strategy: ConflictResolution = ConflictResolution.SKIP):
18
+ """
19
+ Initialize conflict resolver.
20
+
21
+ Args:
22
+ default_strategy: Default resolution strategy
23
+ """
24
+ self.default_strategy = default_strategy
25
+
26
+ def resolve(
27
+ self, instruction_name: str, target_path: Path, strategy: Optional[ConflictResolution] = None
28
+ ) -> ConflictInfo:
29
+ """
30
+ Resolve a file conflict.
31
+
32
+ Args:
33
+ instruction_name: Name of instruction causing conflict
34
+ target_path: Path where file would be installed
35
+ strategy: Resolution strategy (uses default if None)
36
+
37
+ Returns:
38
+ ConflictInfo with resolution details
39
+ """
40
+ if strategy is None:
41
+ strategy = self.default_strategy
42
+
43
+ # Handle each strategy
44
+ if strategy == ConflictResolution.SKIP:
45
+ return ConflictInfo(
46
+ instruction_name=instruction_name,
47
+ existing_path=str(target_path),
48
+ resolution=ConflictResolution.SKIP,
49
+ new_path=None,
50
+ )
51
+
52
+ elif strategy == ConflictResolution.OVERWRITE:
53
+ return ConflictInfo(
54
+ instruction_name=instruction_name,
55
+ existing_path=str(target_path),
56
+ resolution=ConflictResolution.OVERWRITE,
57
+ new_path=str(target_path), # Same path, will be overwritten
58
+ )
59
+
60
+ elif strategy == ConflictResolution.RENAME:
61
+ # Generate new path with auto-increment
62
+ new_path = resolve_conflict_name(target_path)
63
+ return ConflictInfo(
64
+ instruction_name=instruction_name,
65
+ existing_path=str(target_path),
66
+ resolution=ConflictResolution.RENAME,
67
+ new_path=str(new_path),
68
+ )
69
+
70
+ else:
71
+ raise ValueError(f"Unknown conflict resolution strategy: {strategy}")
72
+
73
+ def should_install(self, conflict_info: ConflictInfo) -> bool:
74
+ """
75
+ Determine if instruction should be installed based on conflict resolution.
76
+
77
+ Args:
78
+ conflict_info: Conflict resolution info
79
+
80
+ Returns:
81
+ True if installation should proceed
82
+ """
83
+ return conflict_info.resolution != ConflictResolution.SKIP
84
+
85
+ def get_install_path(self, original_path: Path, conflict_info: Optional[ConflictInfo] = None) -> Path:
86
+ """
87
+ Get the actual path where file should be installed.
88
+
89
+ Args:
90
+ original_path: Original intended path
91
+ conflict_info: Conflict resolution info (if conflict occurred)
92
+
93
+ Returns:
94
+ Path where file should be installed
95
+ """
96
+ if conflict_info is None:
97
+ return original_path
98
+
99
+ if conflict_info.resolution == ConflictResolution.SKIP:
100
+ # Should not install, but return original for reference
101
+ return original_path
102
+
103
+ elif conflict_info.resolution == ConflictResolution.OVERWRITE:
104
+ return original_path
105
+
106
+ elif conflict_info.resolution == ConflictResolution.RENAME:
107
+ if conflict_info.new_path:
108
+ return Path(conflict_info.new_path)
109
+ else:
110
+ # Fallback: should not happen
111
+ return original_path
112
+
113
+ return original_path
114
+
115
+
116
+ def prompt_conflict_resolution(instruction_name: str) -> ConflictResolution:
117
+ """
118
+ Prompt user for conflict resolution choice.
119
+
120
+ Args:
121
+ instruction_name: Name of conflicting instruction
122
+
123
+ Returns:
124
+ Selected resolution strategy
125
+ """
126
+ print(f"\nConflict: Instruction '{instruction_name}' already exists.")
127
+ print("How would you like to resolve this?")
128
+ print(" 1. Skip (keep existing file)")
129
+ print(" 2. Rename (install with auto-incremented name)")
130
+ print(" 3. Overwrite (replace existing file)")
131
+
132
+ while True:
133
+ choice = input("Enter choice (1/2/3): ").strip()
134
+
135
+ if choice == "1":
136
+ return ConflictResolution.SKIP
137
+ elif choice == "2":
138
+ return ConflictResolution.RENAME
139
+ elif choice == "3":
140
+ return ConflictResolution.OVERWRITE
141
+ else:
142
+ print("Invalid choice. Please enter 1, 2, or 3.")
143
+
144
+
145
+ def check_conflicts(target_paths: list[Path]) -> dict[str, Path]:
146
+ """
147
+ Check which target paths already exist.
148
+
149
+ Args:
150
+ target_paths: List of paths to check
151
+
152
+ Returns:
153
+ Dictionary mapping instruction names to conflicting paths
154
+ """
155
+ conflicts = {}
156
+
157
+ for path in target_paths:
158
+ if path.exists():
159
+ # Extract instruction name from filename
160
+ instruction_name = path.stem
161
+ conflicts[instruction_name] = path
162
+
163
+ return conflicts
164
+
165
+
166
+ def batch_resolve_conflicts(conflicts: dict[str, Path], strategy: ConflictResolution) -> dict[str, ConflictInfo]:
167
+ """
168
+ Resolve multiple conflicts with the same strategy.
169
+
170
+ Args:
171
+ conflicts: Dictionary of instruction names to conflicting paths
172
+ strategy: Resolution strategy to apply to all
173
+
174
+ Returns:
175
+ Dictionary mapping instruction names to conflict resolutions
176
+ """
177
+ resolver = ConflictResolver(default_strategy=strategy)
178
+ resolutions = {}
179
+
180
+ for instruction_name, path in conflicts.items():
181
+ resolution = resolver.resolve(instruction_name, path)
182
+ resolutions[instruction_name] = resolution
183
+
184
+ return resolutions
185
+
186
+
187
+ # Template Sync System - Checksum-based conflict detection
188
+
189
+
190
+ def detect_conflict(
191
+ installed_file: Path, new_template_content: str, installation_record: TemplateInstallationRecord
192
+ ) -> ConflictType:
193
+ """
194
+ Detect if conflict exists between installed and new template using checksums.
195
+
196
+ Args:
197
+ installed_file: Path to currently installed file
198
+ new_template_content: Content of new template version
199
+ installation_record: Original installation record with checksum
200
+
201
+ Returns:
202
+ ConflictType indicating conflict status
203
+
204
+ Example:
205
+ >>> from pathlib import Path
206
+ >>> conflict = detect_conflict(
207
+ ... Path(".cursor/rules/acme.test.md"),
208
+ ... "new template content",
209
+ ... installation_record
210
+ ... )
211
+ >>> conflict == ConflictType.NONE
212
+ True
213
+ """
214
+ if not installed_file.exists():
215
+ # File doesn't exist, no conflict
216
+ return ConflictType.NONE
217
+
218
+ # Calculate current file checksum
219
+ current_checksum = sha256_file(installed_file)
220
+
221
+ # Get original checksum at installation
222
+ original_checksum = installation_record.checksum
223
+
224
+ # Calculate new template checksum
225
+ new_checksum = sha256_string(new_template_content)
226
+
227
+ # Decision matrix:
228
+ if current_checksum == original_checksum:
229
+ # File unchanged since installation
230
+ if new_checksum == original_checksum:
231
+ return ConflictType.NONE # No changes anywhere
232
+ else:
233
+ return ConflictType.NONE # Only remote changed, safe to update
234
+ else:
235
+ # File modified locally
236
+ if new_checksum == original_checksum:
237
+ return ConflictType.LOCAL_MODIFIED # Only local changed
238
+ else:
239
+ return ConflictType.BOTH_MODIFIED # Both changed
240
+
241
+
242
+ def prompt_conflict_resolution_template(template_name: str, conflict_type: ConflictType) -> ConflictResolution:
243
+ """
244
+ Interactive prompt for template conflict resolution using Rich.
245
+
246
+ Args:
247
+ template_name: Name of conflicting template
248
+ conflict_type: Type of conflict detected
249
+
250
+ Returns:
251
+ User's resolution choice
252
+
253
+ Example:
254
+ >>> resolution = prompt_conflict_resolution_template(
255
+ ... "test-command",
256
+ ... ConflictType.BOTH_MODIFIED
257
+ ... )
258
+ """
259
+ console = Console()
260
+
261
+ console.print(f"\n[yellow]⚠️ Conflict detected for '{template_name}'[/yellow]")
262
+
263
+ if conflict_type == ConflictType.LOCAL_MODIFIED:
264
+ console.print("Local file was modified since installation")
265
+ elif conflict_type == ConflictType.BOTH_MODIFIED:
266
+ console.print("Both local and remote versions have changes")
267
+
268
+ console.print("\nChoose action:")
269
+ console.print(" [K]eep local version (ignore remote update)")
270
+ console.print(" [O]verwrite with remote version (discard local changes)")
271
+ console.print(" [R]ename local and install remote")
272
+
273
+ choice = Prompt.ask("Your choice", choices=["k", "o", "r", "K", "O", "R"], default="k").lower()
274
+
275
+ return {"k": ConflictResolution.SKIP, "o": ConflictResolution.OVERWRITE, "r": ConflictResolution.RENAME}[choice]
276
+
277
+
278
+ def apply_resolution(template_path: Path, new_content: str, resolution: ConflictResolution) -> Path:
279
+ """
280
+ Apply conflict resolution and install template.
281
+
282
+ SAFETY: Automatically creates backups before overwriting files.
283
+ Backups stored in .instructionkit/backups/<timestamp>/
284
+
285
+ Args:
286
+ template_path: Original template path
287
+ new_content: New template content
288
+ resolution: How to resolve the conflict
289
+
290
+ Returns:
291
+ Path where template was actually installed
292
+
293
+ Raises:
294
+ ValueError: If resolution strategy is unknown
295
+ """
296
+ if resolution == ConflictResolution.SKIP:
297
+ # Keep existing file, don't install
298
+ return template_path
299
+
300
+ elif resolution == ConflictResolution.OVERWRITE:
301
+ # SAFETY: Create backup before overwriting
302
+ if template_path.exists():
303
+ from aiconfigkit.utils.backup import create_backup
304
+
305
+ backup_path = create_backup(template_path)
306
+ from rich.console import Console
307
+
308
+ console = Console()
309
+ console.print(f"[dim] Backup created: {backup_path.relative_to(backup_path.parent.parent)}[/dim]")
310
+
311
+ # Overwrite existing file
312
+ template_path.write_text(new_content, encoding="utf-8")
313
+ return template_path
314
+
315
+ elif resolution == ConflictResolution.RENAME:
316
+ # Rename local file and install new one
317
+ # Generate new path with suffix
318
+ renamed_path = resolve_conflict_name(template_path)
319
+
320
+ # Rename existing file (this preserves the original)
321
+ if template_path.exists():
322
+ template_path.rename(renamed_path)
323
+
324
+ # Install new content at original path
325
+ template_path.write_text(new_content, encoding="utf-8")
326
+ return template_path
327
+
328
+ else:
329
+ raise ValueError(f"Unknown conflict resolution strategy: {resolution}")