lite-kits 0.1.1__py3-none-any.whl → 0.3.2__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.
- lite_kits/__init__.py +56 -4
- lite_kits/cli.py +782 -189
- lite_kits/core/__init__.py +6 -0
- lite_kits/core/banner.py +1 -1
- lite_kits/core/conflict_checker.py +115 -0
- lite_kits/core/detector.py +177 -0
- lite_kits/core/installer.py +242 -351
- lite_kits/core/manifest.py +146 -146
- lite_kits/core/validator.py +183 -0
- lite_kits/kits/README.md +6 -6
- lite_kits/kits/dev/README.md +241 -241
- lite_kits/kits/dev/{claude/commands → commands/.claude}/audit.md +143 -143
- lite_kits/kits/dev/{claude/commands → commands/.claude}/cleanup.md +2 -2
- lite_kits/kits/{git/claude/commands → dev/commands/.claude}/commit.md +2 -2
- lite_kits/kits/{project/claude/commands → dev/commands/.claude}/orient.md +3 -4
- lite_kits/kits/{git/claude/commands → dev/commands/.claude}/pr.md +1 -1
- lite_kits/kits/{git/claude/commands → dev/commands/.claude}/review.md +202 -202
- lite_kits/kits/{project/claude/commands → dev/commands/.claude}/stats.md +162 -162
- lite_kits/kits/{project/github/prompts → dev/commands/.github}/audit.prompt.md +143 -143
- lite_kits/kits/{git/github/prompts → dev/commands/.github}/cleanup.prompt.md +2 -2
- lite_kits/kits/{git/github/prompts → dev/commands/.github}/commit.prompt.md +2 -2
- lite_kits/kits/dev/{github/prompts → commands/.github}/orient.prompt.md +3 -4
- lite_kits/kits/{git/github/prompts → dev/commands/.github}/pr.prompt.md +1 -1
- lite_kits/kits/{git/github/prompts → dev/commands/.github}/review.prompt.md +202 -202
- lite_kits/kits/dev/{github/prompts → commands/.github}/stats.prompt.md +163 -163
- lite_kits/kits/kits.yaml +497 -180
- lite_kits/kits/multiagent/README.md +6 -6
- lite_kits/kits/multiagent/{claude/commands → commands/.claude}/sync.md +331 -331
- lite_kits/kits/multiagent/{github/prompts → commands/.github}/sync.prompt.md +73 -69
- lite_kits/kits/multiagent/memory/git-worktrees-protocol.md +370 -370
- lite_kits/kits/multiagent/memory/parallel-work-protocol.md +536 -536
- lite_kits/kits/multiagent/memory/pr-workflow-guide.md +275 -275
- lite_kits/kits/multiagent/templates/collaboration-structure/README.md +166 -166
- lite_kits/kits/multiagent/templates/decision.md +79 -79
- lite_kits/kits/multiagent/templates/handoff.md +95 -95
- lite_kits/kits/multiagent/templates/session-log.md +68 -68
- lite_kits-0.3.2.dist-info/METADATA +259 -0
- lite_kits-0.3.2.dist-info/RECORD +41 -0
- {lite_kits-0.1.1.dist-info → lite_kits-0.3.2.dist-info}/licenses/LICENSE +21 -21
- lite_kits/kits/dev/claude/commands/commit.md +0 -612
- lite_kits/kits/dev/claude/commands/orient.md +0 -146
- lite_kits/kits/dev/claude/commands/pr.md +0 -593
- lite_kits/kits/dev/claude/commands/review.md +0 -202
- lite_kits/kits/dev/claude/commands/stats.md +0 -162
- lite_kits/kits/dev/github/prompts/audit.prompt.md +0 -143
- lite_kits/kits/dev/github/prompts/cleanup.prompt.md +0 -382
- lite_kits/kits/dev/github/prompts/commit.prompt.md +0 -591
- lite_kits/kits/dev/github/prompts/pr.prompt.md +0 -603
- lite_kits/kits/dev/github/prompts/review.prompt.md +0 -202
- lite_kits/kits/git/README.md +0 -365
- lite_kits/kits/git/claude/commands/cleanup.md +0 -361
- lite_kits/kits/git/scripts/bash/get-git-context.sh +0 -208
- lite_kits/kits/git/scripts/powershell/Get-GitContext.ps1 +0 -242
- lite_kits/kits/project/README.md +0 -228
- lite_kits/kits/project/claude/commands/audit.md +0 -143
- lite_kits/kits/project/claude/commands/review.md +0 -112
- lite_kits/kits/project/github/prompts/orient.prompt.md +0 -150
- lite_kits/kits/project/github/prompts/review.prompt.md +0 -112
- lite_kits/kits/project/github/prompts/stats.prompt.md +0 -163
- lite_kits-0.1.1.dist-info/METADATA +0 -447
- lite_kits-0.1.1.dist-info/RECORD +0 -58
- {lite_kits-0.1.1.dist-info → lite_kits-0.3.2.dist-info}/WHEEL +0 -0
- {lite_kits-0.1.1.dist-info → lite_kits-0.3.2.dist-info}/entry_points.txt +0 -0
lite_kits/core/installer.py
CHANGED
@@ -1,433 +1,313 @@
|
|
1
1
|
"""
|
2
|
-
|
2
|
+
Manifest-driven installer for lite-kits.
|
3
3
|
|
4
|
-
|
4
|
+
Orchestrates detection, validation, and file operations.
|
5
|
+
Delegates to specialized modules for specific tasks.
|
5
6
|
"""
|
6
7
|
|
7
8
|
import shutil
|
8
9
|
from pathlib import Path
|
9
10
|
from typing import Dict, List, Optional
|
10
11
|
|
12
|
+
from .conflict_checker import ConflictChecker
|
13
|
+
from .detector import Detector
|
14
|
+
from .manifest import KitManifest
|
15
|
+
from .validator import Validator
|
11
16
|
|
12
|
-
class Installer:
|
13
|
-
"""Manages installation of multiagent features to spec-kit projects."""
|
14
17
|
|
15
|
-
|
18
|
+
class Installer:
|
19
|
+
"""Main installer orchestrator."""
|
20
|
+
|
21
|
+
def __init__(
|
22
|
+
self,
|
23
|
+
target_dir: Path,
|
24
|
+
kits: Optional[List[str]] = None,
|
25
|
+
force: bool = False,
|
26
|
+
agents: Optional[List[str]] = None,
|
27
|
+
shells: Optional[List[str]] = None,
|
28
|
+
):
|
16
29
|
"""
|
17
30
|
Initialize installer.
|
18
31
|
|
19
32
|
Args:
|
20
33
|
target_dir: Target spec-kit project directory
|
21
|
-
kits: List of kits to install (
|
34
|
+
kits: List of kits to install (None = use default from manifest)
|
35
|
+
force: Skip confirmations and overwrite existing files
|
36
|
+
agents: List of explicit agent preferences (None = auto-detect)
|
37
|
+
shells: List of explicit shell preferences (None = auto-detect)
|
22
38
|
"""
|
23
39
|
self.target_dir = Path(target_dir).resolve()
|
24
40
|
self.kits_dir = Path(__file__).parent.parent / "kits"
|
25
|
-
|
41
|
+
|
42
|
+
# Load manifest
|
43
|
+
self.manifest = KitManifest(self.kits_dir)
|
44
|
+
|
45
|
+
# Initialize specialized modules
|
46
|
+
self.detector = Detector(self.target_dir, self.manifest)
|
47
|
+
self.validator = Validator(self.target_dir, self.manifest)
|
48
|
+
self.conflict_checker = ConflictChecker(
|
49
|
+
self.target_dir,
|
50
|
+
self.kits_dir,
|
51
|
+
self.manifest
|
52
|
+
)
|
53
|
+
|
54
|
+
# Operational modes
|
55
|
+
self.force = force
|
56
|
+
|
57
|
+
# Preferences - validate immediately during init
|
58
|
+
self.preferred_agents = agents
|
59
|
+
self.preferred_shells = shells
|
60
|
+
|
61
|
+
# Validate agent/shell preferences early (raises ValueError if invalid)
|
62
|
+
if agents:
|
63
|
+
self.detector.detect_agents(agents)
|
64
|
+
if shells:
|
65
|
+
self.detector.detect_shells(shells)
|
66
|
+
|
67
|
+
# Kits to install
|
68
|
+
self.kits = kits or [self.manifest.get_default_kit()]
|
26
69
|
|
27
70
|
# Validate kit names
|
28
|
-
|
71
|
+
self._validate_kit_names()
|
72
|
+
|
73
|
+
def _validate_kit_names(self):
|
74
|
+
"""Validate kit names against manifest."""
|
75
|
+
valid_kits = set(self.manifest.get_kit_names())
|
29
76
|
invalid = set(self.kits) - valid_kits
|
30
77
|
if invalid:
|
31
|
-
|
32
|
-
|
33
|
-
# No auto-dependencies - all kits are independent
|
78
|
+
valid_list = ', '.join(sorted(valid_kits))
|
79
|
+
raise ValueError(f"Invalid kit(s): {invalid}. Valid: {valid_list}")
|
34
80
|
|
35
81
|
def is_spec_kit_project(self) -> bool:
|
36
|
-
"""
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
True if multiagent features detected
|
55
|
-
"""
|
56
|
-
# Check for kit markers
|
57
|
-
markers = {
|
58
|
-
'project': [
|
59
|
-
self.target_dir / ".claude" / "commands" / "orient.md",
|
60
|
-
self.target_dir / ".github" / "prompts" / "orient.prompt.md",
|
61
|
-
],
|
62
|
-
'git': [
|
63
|
-
self.target_dir / ".claude" / "commands" / "commit.md",
|
64
|
-
self.target_dir / ".github" / "prompts" / "commit.prompt.md",
|
65
|
-
self.target_dir / ".claude" / "commands" / "review.md",
|
66
|
-
],
|
67
|
-
'multiagent': [
|
68
|
-
self.target_dir / ".specify" / "memory" / "pr-workflow-guide.md",
|
69
|
-
],
|
82
|
+
"""Check if target is a spec-kit project."""
|
83
|
+
return self.detector.is_spec_kit_project()
|
84
|
+
|
85
|
+
def is_kit_installed(self, kit_name: str) -> bool:
|
86
|
+
"""Check if kit is installed."""
|
87
|
+
return self.validator.is_kit_installed(kit_name)
|
88
|
+
|
89
|
+
def preview_installation(self) -> Dict:
|
90
|
+
"""Preview installation without making changes."""
|
91
|
+
agents = self.detector.detect_agents(self.preferred_agents)
|
92
|
+
shells = self.detector.detect_shells(self.preferred_shells)
|
93
|
+
|
94
|
+
preview = {
|
95
|
+
"kits": [],
|
96
|
+
"conflicts": [],
|
97
|
+
"warnings": [],
|
98
|
+
"agents": agents,
|
99
|
+
"shells": shells,
|
70
100
|
}
|
71
101
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
102
|
+
if not agents:
|
103
|
+
supported = [
|
104
|
+
name for name, config in self.manifest.manifest.get('agents', {}).items()
|
105
|
+
if config.get('supported', False)
|
106
|
+
]
|
107
|
+
preview['warnings'].append(f"No AI agents detected. Supported: {', '.join(supported)}")
|
108
|
+
return preview
|
76
109
|
|
77
|
-
|
110
|
+
conflicts = self.conflict_checker.check_conflicts(self.kits, agents, shells)
|
111
|
+
preview['conflicts'] = conflicts['overwrites']
|
78
112
|
|
79
|
-
|
80
|
-
|
81
|
-
|
113
|
+
for kit_name in self.kits:
|
114
|
+
kit_preview = self._preview_kit(kit_name, agents, shells)
|
115
|
+
preview['kits'].append(kit_preview)
|
82
116
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
117
|
+
return preview
|
118
|
+
|
119
|
+
def _preview_kit(self, kit_name: str, agents: List[str], shells: List[str]) -> Dict:
|
120
|
+
"""Preview installation for a single kit."""
|
121
|
+
kit_info = self.manifest.get_kit(kit_name)
|
122
|
+
kit_preview = {
|
123
|
+
"name": kit_info['name'],
|
87
124
|
"new_files": [],
|
88
125
|
"modified_files": [],
|
89
126
|
"new_directories": [],
|
90
127
|
}
|
91
128
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
if
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
"
|
128
|
-
|
129
|
-
|
130
|
-
changes["new_directories"].append("specs/*/collaboration/ (created with new features)")
|
131
|
-
|
132
|
-
return changes
|
129
|
+
for agent in agents:
|
130
|
+
files = self.manifest.get_kit_files(kit_name, agent=agent)
|
131
|
+
self._preview_files(files, kit_preview)
|
132
|
+
|
133
|
+
for shell in shells:
|
134
|
+
files = self.manifest.get_kit_files(kit_name, agent=shell)
|
135
|
+
self._preview_files(files, kit_preview)
|
136
|
+
|
137
|
+
# Get other files (not commands\prompts\scripts - those are handled above)
|
138
|
+
all_files = self.manifest.get_kit_files(kit_name, agent=None)
|
139
|
+
for file_info in all_files:
|
140
|
+
if file_info.get('type') in ['command', 'prompt', 'script']:
|
141
|
+
continue # Already handled by agent/shell sections above
|
142
|
+
self._preview_files([file_info], kit_preview)
|
143
|
+
|
144
|
+
return kit_preview
|
145
|
+
|
146
|
+
def _preview_files(self, files: List[Dict], preview: Dict):
|
147
|
+
"""Preview a list of files."""
|
148
|
+
for file_info in files:
|
149
|
+
if file_info.get('status') == 'planned':
|
150
|
+
continue
|
151
|
+
|
152
|
+
# Normalize paths to use backslashes for Windows display
|
153
|
+
target_path = str(file_info['path']).replace("/", "\\")
|
154
|
+
target_full = self.target_dir / file_info['path']
|
155
|
+
|
156
|
+
if target_full.exists():
|
157
|
+
if target_path not in preview["modified_files"]:
|
158
|
+
preview["modified_files"].append(target_path)
|
159
|
+
else:
|
160
|
+
if target_path not in preview["new_files"]:
|
161
|
+
preview["new_files"].append(target_path)
|
162
|
+
|
163
|
+
parent_dir = str(target_full.parent.relative_to(self.target_dir))
|
164
|
+
if parent_dir not in preview["new_directories"]:
|
165
|
+
if not target_full.parent.exists():
|
166
|
+
preview["new_directories"].append(parent_dir)
|
133
167
|
|
134
168
|
def install(self) -> Dict:
|
135
|
-
"""
|
136
|
-
Install multiagent features to target project.
|
137
|
-
|
138
|
-
Returns:
|
139
|
-
Dictionary with success status and installed items
|
140
|
-
"""
|
169
|
+
"""Install kits to target project."""
|
141
170
|
result = {
|
142
171
|
"success": False,
|
143
172
|
"installed": [],
|
173
|
+
"skipped": [],
|
144
174
|
"error": None,
|
145
175
|
}
|
146
176
|
|
147
177
|
try:
|
148
|
-
|
149
|
-
|
150
|
-
has_copilot = (self.target_dir / ".github" / "prompts").exists()
|
178
|
+
agents = self.detector.detect_agents(self.preferred_agents)
|
179
|
+
shells = self.detector.detect_shells(self.preferred_shells)
|
151
180
|
|
152
|
-
if not
|
153
|
-
|
181
|
+
if not agents:
|
182
|
+
supported = [
|
183
|
+
name for name, config in self.manifest.manifest.get('agents', {}).items()
|
184
|
+
if config.get('supported', False)
|
185
|
+
]
|
186
|
+
result["error"] = (
|
187
|
+
f"No supported AI interface found. Supported: {', '.join(supported)}. "
|
188
|
+
r"To enable AI interface support, create a '.claude\' or '.github\prompts\' directory in your project."
|
189
|
+
)
|
154
190
|
return result
|
155
191
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
self._install_file('project/github/prompts/audit.prompt.md', '.github/prompts/audit.prompt.md')
|
169
|
-
self._install_file('project/github/prompts/stats.prompt.md', '.github/prompts/stats.prompt.md')
|
170
|
-
result["installed"].append("project-kit (Copilot): /orient, /review, /audit, /stats commands")
|
171
|
-
|
172
|
-
# Install git kit
|
173
|
-
if 'git' in self.kits:
|
174
|
-
if has_claude:
|
175
|
-
self._install_file('git/claude/commands/commit.md', '.claude/commands/commit.md')
|
176
|
-
self._install_file('git/claude/commands/pr.md', '.claude/commands/pr.md')
|
177
|
-
self._install_file('git/claude/commands/review.md', '.claude/commands/review.md')
|
178
|
-
self._install_file('git/claude/commands/cleanup.md', '.claude/commands/cleanup.md')
|
179
|
-
result["installed"].append("git-kit (Claude): /commit, /pr, /review, /cleanup commands")
|
180
|
-
|
181
|
-
if has_copilot:
|
182
|
-
self._install_file('git/github/prompts/commit.prompt.md', '.github/prompts/commit.prompt.md')
|
183
|
-
self._install_file('git/github/prompts/pr.prompt.md', '.github/prompts/pr.prompt.md')
|
184
|
-
self._install_file('git/github/prompts/review.prompt.md', '.github/prompts/review.prompt.md')
|
185
|
-
self._install_file('git/github/prompts/cleanup.prompt.md', '.github/prompts/cleanup.prompt.md')
|
186
|
-
result["installed"].append("git-kit (Copilot): /commit, /pr, /review, /cleanup commands")
|
187
|
-
|
188
|
-
# Install multiagent kit
|
189
|
-
if 'multiagent' in self.kits and (self.target_dir / ".specify").exists():
|
190
|
-
# Commands
|
191
|
-
if has_claude:
|
192
|
-
self._install_file('multiagent/claude/commands/sync.md', '.claude/commands/sync.md')
|
193
|
-
if has_copilot:
|
194
|
-
self._install_file('multiagent/github/prompts/sync.prompt.md', '.github/prompts/sync.prompt.md')
|
195
|
-
|
196
|
-
# Memory guides
|
197
|
-
self._install_file('multiagent/memory/pr-workflow-guide.md', '.specify/memory/pr-workflow-guide.md')
|
198
|
-
self._install_file('multiagent/memory/git-worktrees-protocol.md', '.specify/memory/git-worktrees-protocol.md')
|
199
|
-
self._install_file('multiagent/memory/parallel-work-protocol.md', '.specify/memory/parallel-work-protocol.md')
|
200
|
-
|
201
|
-
# Templates
|
202
|
-
templates_dir = self.target_dir / ".specify" / "templates"
|
203
|
-
templates_dir.mkdir(parents=True, exist_ok=True)
|
204
|
-
self._install_file('multiagent/templates/session-log.md', '.specify/templates/session-log.md')
|
205
|
-
self._install_file('multiagent/templates/handoff.md', '.specify/templates/handoff.md')
|
206
|
-
self._install_file('multiagent/templates/decision.md', '.specify/templates/decision.md')
|
207
|
-
self._install_file('multiagent/templates/collaboration-structure/README.md', '.specify/templates/collaboration-README.md')
|
208
|
-
|
209
|
-
result["installed"].append("multiagent-kit: /sync command")
|
210
|
-
result["installed"].append("multiagent-kit: Memory guides (PR workflow, git worktrees, parallel work)")
|
211
|
-
result["installed"].append("multiagent-kit: Templates (session-log, handoff, decision, collaboration)")
|
192
|
+
if not self.force:
|
193
|
+
conflicts = self.conflict_checker.check_conflicts(self.kits, agents, shells)
|
194
|
+
|
195
|
+
if conflicts['has_conflicts']:
|
196
|
+
result["conflicts"] = conflicts['overwrites']
|
197
|
+
result["error"] = f"Found {len(conflicts['conflicts'])} file conflicts. Use --force to overwrite."
|
198
|
+
return result
|
199
|
+
|
200
|
+
options = self.manifest.manifest.get('options', {})
|
201
|
+
|
202
|
+
for kit_name in self.kits:
|
203
|
+
self._install_kit(kit_name, agents, shells, options, result)
|
212
204
|
|
213
205
|
result["success"] = True
|
214
206
|
|
207
|
+
if options.get('validate_on_install', True):
|
208
|
+
result["validation"] = self.validator.validate_all()
|
209
|
+
|
215
210
|
except Exception as e:
|
216
211
|
result["error"] = str(e)
|
217
212
|
|
218
213
|
return result
|
219
214
|
|
220
|
-
def
|
221
|
-
"""
|
222
|
-
|
215
|
+
def _install_kit(self, kit_name: str, agents: List[str], shells: List[str], options: Dict, result: Dict):
|
216
|
+
"""Install a single kit."""
|
217
|
+
skip_existing = options.get('skip_existing', True)
|
223
218
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
checks = {}
|
219
|
+
for agent in agents:
|
220
|
+
files = self.manifest.get_kit_files(kit_name, agent=agent)
|
221
|
+
self._install_files(files, skip_existing, result)
|
228
222
|
|
229
|
-
|
230
|
-
|
231
|
-
|
223
|
+
for shell in shells:
|
224
|
+
files = self.manifest.get_kit_files(kit_name, agent=shell)
|
225
|
+
self._install_files(files, skip_existing, result)
|
232
226
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
}
|
227
|
+
all_files = self.manifest.get_kit_files(kit_name, agent=None)
|
228
|
+
for file_info in all_files:
|
229
|
+
if file_info.get('type') in ['command', 'prompt', 'script']:
|
230
|
+
continue
|
231
|
+
self._install_files([file_info], skip_existing, result)
|
239
232
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
git_kit_installed = claude_commit.exists() or claude_pr.exists() or claude_review.exists() or claude_cleanup.exists()
|
247
|
-
checks["git_kit"] = {
|
248
|
-
"passed": git_kit_installed,
|
249
|
-
"message": "git-kit: /commit, /pr, /review, /cleanup commands found" if git_kit_installed
|
250
|
-
else "git-kit not installed - run: lite-kits add --here --kit git",
|
251
|
-
}
|
233
|
+
def _install_files(self, files: List[Dict], skip_existing: bool, result: Dict):
|
234
|
+
"""Install a list of files."""
|
235
|
+
for file_info in files:
|
236
|
+
if file_info.get('status') == 'planned':
|
237
|
+
result["skipped"].append(f"{file_info['path']} (planned)")
|
238
|
+
continue
|
252
239
|
|
253
|
-
|
254
|
-
claude_sync = self.target_dir / ".claude" / "commands" / "sync.md"
|
255
|
-
pr_guide = self.target_dir / ".specify" / "memory" / "pr-workflow-guide.md"
|
256
|
-
worktree_guide = self.target_dir / ".specify" / "memory" / "git-worktrees-protocol.md"
|
240
|
+
target_path = self.target_dir / file_info['path']
|
257
241
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
"message": "multiagent-kit: /sync command and memory guides found" if multiagent_kit_installed
|
262
|
-
else "multiagent-kit not installed - run: lite-kits add --here --kit multiagent",
|
263
|
-
}
|
242
|
+
if skip_existing and target_path.exists() and not self.force:
|
243
|
+
result["skipped"].append(file_info['path'])
|
244
|
+
continue
|
264
245
|
|
265
|
-
|
266
|
-
|
267
|
-
all_passed = checks["project_kit"]["passed"] or checks["git_kit"]["passed"] or checks["multiagent_kit"]["passed"]
|
246
|
+
self._copy_file(file_info['source'], file_info['path'])
|
247
|
+
result["installed"].append(file_info['path'])
|
268
248
|
|
269
|
-
|
270
|
-
|
271
|
-
|
249
|
+
def validate(self) -> Dict:
|
250
|
+
"""Validate all installed kits."""
|
251
|
+
return self.validator.validate_all()
|
252
|
+
|
253
|
+
def preview_removal(self) -> Dict:
|
254
|
+
"""Preview files that would be removed."""
|
255
|
+
preview = {
|
256
|
+
"kits": [],
|
257
|
+
"total_files": 0,
|
272
258
|
}
|
273
259
|
|
274
|
-
|
260
|
+
for kit_name in self.kits:
|
261
|
+
kit_info = self.manifest.get_kit(kit_name)
|
262
|
+
files_to_remove = []
|
275
263
|
|
276
|
-
|
277
|
-
"""
|
278
|
-
Install a file from kits directory to target project.
|
264
|
+
all_files = self.manifest.get_kit_files(kit_name, agent=None)
|
279
265
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
source = self.kits_dir / kit_relative_path
|
285
|
-
target = self.target_dir / target_relative_path
|
266
|
+
for file_info in all_files:
|
267
|
+
target_path = self.target_dir / file_info['path']
|
268
|
+
if target_path.exists():
|
269
|
+
files_to_remove.append(file_info['path'])
|
286
270
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
271
|
+
if files_to_remove:
|
272
|
+
preview["kits"].append({
|
273
|
+
'name': kit_info['name'],
|
274
|
+
'files': files_to_remove
|
275
|
+
})
|
276
|
+
preview["total_files"] += len(files_to_remove)
|
292
277
|
|
293
|
-
|
294
|
-
|
295
|
-
def _merge_constitution(self):
|
296
|
-
"""
|
297
|
-
Merge multiagent sections into existing constitution.
|
298
|
-
|
299
|
-
Strategy:
|
300
|
-
1. Read existing constitution
|
301
|
-
2. Check for multiagent marker (<!-- MULTIAGENT-START -->)
|
302
|
-
3. If marker exists, replace section
|
303
|
-
4. If marker missing, append section
|
304
|
-
5. Preserve user edits outside markers
|
305
|
-
"""
|
306
|
-
# TODO: Implement smart merge logic
|
307
|
-
# - Read templates/enhancements/constitution-multiagent.md
|
308
|
-
# - Merge into .specify/memory/constitution.md
|
309
|
-
# - Use marker comments for idempotent updates
|
310
|
-
pass
|
311
|
-
|
312
|
-
def _install_collaboration_template(self):
|
313
|
-
"""
|
314
|
-
Create collaboration directory template.
|
315
|
-
|
316
|
-
Creates:
|
317
|
-
- .specify/templates/collaboration-template/
|
318
|
-
- Scripts reference this when creating new features
|
319
|
-
"""
|
320
|
-
# TODO: Create collaboration directory template
|
321
|
-
# - active/
|
322
|
-
# - archive/
|
323
|
-
# - results/
|
324
|
-
pass
|
278
|
+
return preview
|
325
279
|
|
326
280
|
def remove(self) -> Dict:
|
327
|
-
"""
|
328
|
-
Remove multiagent features from project.
|
329
|
-
|
330
|
-
Returns to vanilla spec-kit state.
|
331
|
-
|
332
|
-
Returns:
|
333
|
-
Dictionary with success status and removed items
|
334
|
-
"""
|
281
|
+
"""Remove kits from project."""
|
335
282
|
result = {
|
336
283
|
"success": False,
|
337
284
|
"removed": [],
|
285
|
+
"not_found": [],
|
338
286
|
"error": None,
|
339
287
|
}
|
340
288
|
|
341
289
|
try:
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
# Claude
|
348
|
-
for cmd in project_commands:
|
349
|
-
cmd_file = self.target_dir / ".claude" / "commands" / f"{cmd}.md"
|
350
|
-
if cmd_file.exists():
|
351
|
-
cmd_file.unlink()
|
352
|
-
removed.append(f".claude/commands/{cmd}.md")
|
353
|
-
|
354
|
-
# Copilot
|
355
|
-
for cmd in project_commands:
|
356
|
-
cmd_file = self.target_dir / ".github" / "prompts" / f"{cmd}.prompt.md"
|
357
|
-
if cmd_file.exists():
|
358
|
-
cmd_file.unlink()
|
359
|
-
removed.append(f".github/prompts/{cmd}.prompt.md")
|
360
|
-
|
361
|
-
if removed:
|
362
|
-
result["removed"].append(f"project-kit: {', '.join(removed)}")
|
363
|
-
|
364
|
-
# Remove git kit files
|
365
|
-
if 'git' in self.kits:
|
366
|
-
removed = []
|
367
|
-
git_commands = ['commit', 'pr', 'review', 'cleanup']
|
368
|
-
|
369
|
-
# Claude
|
370
|
-
for cmd in git_commands:
|
371
|
-
cmd_file = self.target_dir / ".claude" / "commands" / f"{cmd}.md"
|
372
|
-
if cmd_file.exists():
|
373
|
-
cmd_file.unlink()
|
374
|
-
removed.append(f".claude/commands/{cmd}.md")
|
375
|
-
|
376
|
-
# Copilot
|
377
|
-
for cmd in git_commands:
|
378
|
-
cmd_file = self.target_dir / ".github" / "prompts" / f"{cmd}.prompt.md"
|
379
|
-
if cmd_file.exists():
|
380
|
-
cmd_file.unlink()
|
381
|
-
removed.append(f".github/prompts/{cmd}.prompt.md")
|
382
|
-
|
383
|
-
if removed:
|
384
|
-
result["removed"].append(f"git-kit: {', '.join(removed)}")
|
385
|
-
|
386
|
-
# Remove multiagent kit files
|
387
|
-
if 'multiagent' in self.kits:
|
388
|
-
removed = []
|
389
|
-
|
390
|
-
# Sync command
|
391
|
-
sync_claude = self.target_dir / ".claude" / "commands" / "sync.md"
|
392
|
-
if sync_claude.exists():
|
393
|
-
sync_claude.unlink()
|
394
|
-
removed.append(".claude/commands/sync.md")
|
395
|
-
|
396
|
-
sync_copilot = self.target_dir / ".github" / "prompts" / "sync.prompt.md"
|
397
|
-
if sync_copilot.exists():
|
398
|
-
sync_copilot.unlink()
|
399
|
-
removed.append(".github/prompts/sync.prompt.md")
|
400
|
-
|
401
|
-
# Memory guides
|
402
|
-
memory_files = [
|
403
|
-
'pr-workflow-guide.md',
|
404
|
-
'git-worktrees-protocol.md',
|
405
|
-
'parallel-work-protocol.md',
|
406
|
-
]
|
407
|
-
for file in memory_files:
|
408
|
-
file_path = self.target_dir / ".specify" / "memory" / file
|
409
|
-
if file_path.exists():
|
410
|
-
file_path.unlink()
|
411
|
-
removed.append(f".specify/memory/{file}")
|
412
|
-
|
413
|
-
# Templates
|
414
|
-
template_files = [
|
415
|
-
'session-log.md',
|
416
|
-
'handoff.md',
|
417
|
-
'decision.md',
|
418
|
-
'collaboration-README.md',
|
419
|
-
]
|
420
|
-
for file in template_files:
|
421
|
-
file_path = self.target_dir / ".specify" / "templates" / file
|
422
|
-
if file_path.exists():
|
423
|
-
file_path.unlink()
|
424
|
-
removed.append(f".specify/templates/{file}")
|
290
|
+
for kit_name in self.kits:
|
291
|
+
kit_info = self.manifest.get_kit(kit_name)
|
292
|
+
removed_files = []
|
293
|
+
not_found_files = []
|
425
294
|
|
426
|
-
|
427
|
-
result["removed"].append(f"multiagent-kit: {', '.join(removed)}")
|
295
|
+
all_files = self.manifest.get_kit_files(kit_name, agent=None)
|
428
296
|
|
429
|
-
|
430
|
-
|
297
|
+
for file_info in all_files:
|
298
|
+
target_path = self.target_dir / file_info['path']
|
299
|
+
|
300
|
+
if target_path.exists():
|
301
|
+
target_path.unlink()
|
302
|
+
removed_files.append(file_info['path'])
|
303
|
+
else:
|
304
|
+
not_found_files.append(file_info['path'])
|
305
|
+
|
306
|
+
if removed_files:
|
307
|
+
result["removed"].append({'kit': kit_info['name'], 'files': removed_files})
|
308
|
+
|
309
|
+
if not_found_files:
|
310
|
+
result["not_found"].extend(not_found_files)
|
431
311
|
|
432
312
|
result["success"] = True
|
433
313
|
|
@@ -435,3 +315,14 @@ class Installer:
|
|
435
315
|
result["error"] = str(e)
|
436
316
|
|
437
317
|
return result
|
318
|
+
|
319
|
+
def _copy_file(self, kit_relative_path: str, target_relative_path: str):
|
320
|
+
"""Copy file from kits/ to target project."""
|
321
|
+
source = self.kits_dir / kit_relative_path
|
322
|
+
target = self.target_dir / target_relative_path
|
323
|
+
|
324
|
+
if not source.exists():
|
325
|
+
raise FileNotFoundError(f"Kit file not found: {source}")
|
326
|
+
|
327
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
328
|
+
shutil.copy2(source, target)
|