lite-kits 0.3.1__tar.gz → 0.3.3__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 (41) hide show
  1. {lite_kits-0.3.1 → lite_kits-0.3.3}/PKG-INFO +2 -2
  2. {lite_kits-0.3.1 → lite_kits-0.3.3}/README.md +1 -1
  3. {lite_kits-0.3.1 → lite_kits-0.3.3}/pyproject.toml +1 -1
  4. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/__init__.py +1 -1
  5. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/cli.py +102 -19
  6. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/core/detector.py +55 -18
  7. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/core/installer.py +17 -11
  8. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/core/validator.py +39 -2
  9. {lite_kits-0.3.1 → lite_kits-0.3.3}/.gitignore +0 -0
  10. {lite_kits-0.3.1 → lite_kits-0.3.3}/LICENSE +0 -0
  11. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/core/__init__.py +0 -0
  12. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/core/banner.py +0 -0
  13. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/core/conflict_checker.py +0 -0
  14. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/core/manifest.py +0 -0
  15. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/README.md +0 -0
  16. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/README.md +0 -0
  17. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.claude/audit.md +0 -0
  18. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.claude/cleanup.md +0 -0
  19. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.claude/commit.md +0 -0
  20. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.claude/orient.md +0 -0
  21. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.claude/pr.md +0 -0
  22. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.claude/review.md +0 -0
  23. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.claude/stats.md +0 -0
  24. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.github/audit.prompt.md +0 -0
  25. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.github/cleanup.prompt.md +0 -0
  26. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.github/commit.prompt.md +0 -0
  27. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.github/orient.prompt.md +0 -0
  28. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.github/pr.prompt.md +0 -0
  29. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.github/review.prompt.md +0 -0
  30. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/dev/commands/.github/stats.prompt.md +0 -0
  31. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/kits.yaml +0 -0
  32. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/multiagent/README.md +0 -0
  33. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/multiagent/commands/.claude/sync.md +0 -0
  34. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/multiagent/commands/.github/sync.prompt.md +0 -0
  35. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/multiagent/memory/git-worktrees-protocol.md +0 -0
  36. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/multiagent/memory/parallel-work-protocol.md +0 -0
  37. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/multiagent/memory/pr-workflow-guide.md +0 -0
  38. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/multiagent/templates/collaboration-structure/README.md +0 -0
  39. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/multiagent/templates/decision.md +0 -0
  40. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/multiagent/templates/handoff.md +0 -0
  41. {lite_kits-0.3.1 → lite_kits-0.3.3}/src/lite_kits/kits/multiagent/templates/session-log.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lite-kits
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: Lightweight enhancement kits for vanilla dev tools (spec-kit, etc.)
5
5
  Project-URL: Homepage, https://github.com/tmorgan181/lite-kits
6
6
  Project-URL: Documentation, https://github.com/tmorgan181/lite-kits#readme
@@ -29,7 +29,7 @@ Description-Content-Type: text/markdown
29
29
 
30
30
  # 🌈 LITE-KITS 🎒
31
31
 
32
- [![Version](https://img.shields.io/badge/version-0.3.0-blue.svg)](https://github.com/tmorgan181/lite-kits/releases/tag/v0.3.0)
32
+ [![PyPI version](https://img.shields.io/pypi/v/lite-kits.svg)](https://pypi.org/project/lite-kits/)
33
33
  [![Python](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
34
34
  [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
35
35
  [![Spec-Kit](https://img.shields.io/badge/spec--kit-compatible-purple.svg)](https://github.com/github/spec-kit)
@@ -1,6 +1,6 @@
1
1
  # 🌈 LITE-KITS 🎒
2
2
 
3
- [![Version](https://img.shields.io/badge/version-0.3.0-blue.svg)](https://github.com/tmorgan181/lite-kits/releases/tag/v0.3.0)
3
+ [![PyPI version](https://img.shields.io/pypi/v/lite-kits.svg)](https://pypi.org/project/lite-kits/)
4
4
  [![Python](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
5
5
  [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
6
6
  [![Spec-Kit](https://img.shields.io/badge/spec--kit-compatible-purple.svg)](https://github.com/github/spec-kit)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "lite-kits"
7
- version = "0.3.1"
7
+ version = "0.3.3"
8
8
  description = "Lightweight enhancement kits for vanilla dev tools (spec-kit, etc.)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -6,7 +6,7 @@ without forking or replacing any core files.
6
6
  """
7
7
 
8
8
  # Version
9
- __version__ = "0.3.1"
9
+ __version__ = "0.3.3"
10
10
 
11
11
  # Package metadata
12
12
  APP_NAME = "lite-kits"
@@ -51,7 +51,7 @@ def print_help_hint():
51
51
  def print_version_info():
52
52
  """Print version information."""
53
53
  console.print(f"[bold]Version:[/bold]")
54
- console.print(f" [bold cyan]{APP_NAME} version {__version__}[/bold cyan]\n")
54
+ console.print(f" [bold cyan]{APP_NAME} version {__version__}[/bold cyan]")
55
55
 
56
56
  def print_quick_start():
57
57
  console.print("[bold]Quick Start:[/bold]")
@@ -80,15 +80,68 @@ def print_spec_kit_error():
80
80
  console.print(" 4. More info: https://github.com/github/spec-kit\n")
81
81
  console.print()
82
82
 
83
+ def _build_kit_breakdown_table(target_dir: Path, kits: list[str]) -> Table:
84
+ """Build agent/shell breakdown table for kits.
85
+
86
+ Args:
87
+ target_dir: Target project directory
88
+ kits: List of kit names (e.g., ["dev", "multiagent"])
89
+
90
+ Returns:
91
+ Rich Table with agent/shell breakdown
92
+ """
93
+ from rich.box import ROUNDED
94
+
95
+ # Detect which agents/shells have files
96
+ agent_dirs = {
97
+ "Claude Code": target_dir / ".claude" / "commands",
98
+ "GitHub Copilot": target_dir / ".github" / "prompts"
99
+ }
100
+
101
+ shell_dirs = {
102
+ "Bash": target_dir / ".specify" / "scripts" / "bash",
103
+ "PowerShell": target_dir / ".specify" / "scripts" / "powershell"
104
+ }
105
+
106
+ # Build table
107
+ table = Table(show_header=True, header_style="bold cyan", box=ROUNDED, title=f"[bold magenta]Kit Breakdown[/bold magenta]")
108
+ table.add_column("Kit", style="cyan")
109
+ table.add_column("Agents", style="green")
110
+ table.add_column("Shells", style="white")
111
+
112
+ for kit in kits:
113
+ # Check which agents have this kit's files
114
+ agents_with_kit = []
115
+ for agent_name, agent_dir in agent_dirs.items():
116
+ if agent_dir.exists() and any(agent_dir.glob("*.md")):
117
+ # Check for at least one non-spec-kit file (not speckit.*)
118
+ non_speckit_files = [f for f in agent_dir.glob("*.md") if not f.stem.startswith("speckit.")]
119
+ if non_speckit_files:
120
+ agents_with_kit.append(agent_name)
121
+
122
+ # Check which shells have scripts
123
+ shells_with_kit = []
124
+ for shell_name, shell_dir in shell_dirs.items():
125
+ if shell_dir.exists() and (any(shell_dir.glob("*.sh")) or any(shell_dir.glob("*.ps1"))):
126
+ shells_with_kit.append(shell_name)
127
+
128
+ # Format output
129
+ agents_display = ", ".join(agents_with_kit) if agents_with_kit else "[dim]none[/dim]"
130
+ shells_display = ", ".join(shells_with_kit) if shells_with_kit else "[dim]none[/dim]"
131
+
132
+ # Use consistent kit naming format: "kit-name" not "kit-name-kit"
133
+ table.add_row(kit, agents_display, shells_display)
134
+
135
+ return table
136
+
83
137
  def print_kit_info(target_dir: Path, is_spec_kit: bool, installed_kits: list):
84
- """Print kit installation info."""
138
+ """Print kit installation info with agent/shell breakdown."""
85
139
  console.print()
86
140
  if is_spec_kit:
87
141
  console.print(f"[bold green][OK] Spec-kit project detected in {target_dir}.[/bold green]\n")
88
142
  if installed_kits:
89
- console.print("Installed kits:", style="bold")
90
- for kit in installed_kits:
91
- console.print(f" [green]+[/green] {kit}-kit")
143
+ table = _build_kit_breakdown_table(target_dir, installed_kits)
144
+ console.print(table)
92
145
  else:
93
146
  console.print("No kits installed.", style="dim yellow")
94
147
  else:
@@ -221,13 +274,17 @@ def add_kits(
221
274
  kits = [k.strip() for k in kit.split(',')]
222
275
  # else: kits=None will use default from manifest
223
276
 
277
+ # Parse comma-separated agents and shells
278
+ agents = [a.strip() for a in agent.split(',')] if agent else None
279
+ shells = [s.strip() for s in shell.split(',')] if shell else None
280
+
224
281
  try:
225
282
  installer = Installer(
226
283
  target_dir,
227
284
  kits=kits,
228
285
  force=force,
229
- agent=agent,
230
- shell=shell
286
+ agents=agents,
287
+ shells=shells
231
288
  )
232
289
  except ValueError as e:
233
290
  console.print()
@@ -259,10 +316,16 @@ def add_kits(
259
316
 
260
317
  # Always show preview unless --force flag was used
261
318
  if not skip_preview:
262
- console.print(f"\n[bold magenta]Previewing changes for:[/bold magenta]\n[bold yellow]{target_dir}[/bold yellow]\n")
263
- preview = installer.preview_installation()
319
+ try:
320
+ preview = installer.preview_installation()
321
+ except ValueError as e:
322
+ console.print()
323
+ console.print(f"[red]Error:[/red] {e}", style="bold")
324
+ console.print()
325
+ raise typer.Exit(1)
326
+
264
327
  normalized_preview = _normalize_preview_for_display(preview, operation="install")
265
- _display_changes(normalized_preview, verbose=verbose)
328
+ _display_changes(normalized_preview, target_dir, verbose=verbose)
266
329
 
267
330
  # Show warnings/conflicts
268
331
  if preview.get("warnings"):
@@ -288,8 +351,8 @@ def add_kits(
288
351
  target_dir,
289
352
  kits=kits,
290
353
  force=True, # Skip conflict checks after user confirmed
291
- agent=agent,
292
- shell=shell
354
+ agents=agents,
355
+ shells=shells
293
356
  )
294
357
 
295
358
  # Install
@@ -385,7 +448,6 @@ def remove(
385
448
  # Show preview and confirmation unless --force is used
386
449
  if not force:
387
450
  # Show preview of files to be removed
388
- console.print(f"\n[bold magenta]Previewing changes for:[/bold magenta]\n[bold yellow]{target_dir}[/bold yellow]\n")
389
451
  preview = installer.preview_removal()
390
452
 
391
453
  if preview["total_files"] == 0:
@@ -395,7 +457,7 @@ def remove(
395
457
 
396
458
  # Normalize removal preview to standard format for DRY display
397
459
  normalized_preview = _normalize_preview_for_display(preview, operation="remove")
398
- _display_changes(normalized_preview, verbose=verbose)
460
+ _display_changes(normalized_preview, target_dir, verbose=verbose)
399
461
 
400
462
  # Confirm removal
401
463
  if not typer.confirm("Continue with removal?"):
@@ -543,15 +605,19 @@ def _normalize_preview_for_display(preview: dict, operation: str = "install") ->
543
605
  else:
544
606
  raise ValueError(f"Unknown operation: {operation}")
545
607
 
546
- def _display_changes(changes: dict, verbose: bool = False):
608
+ def _display_changes(changes: dict, target_dir: Path, verbose: bool = False):
547
609
  """Display preview of changes.
548
610
 
549
611
  Args:
550
612
  changes: Normalized preview dict with file/directory changes
613
+ target_dir: Target directory being modified
551
614
  verbose: If True, show detailed file listings; if False, show only tables
552
615
  """
553
616
  from collections import defaultdict
554
617
 
618
+ # Show preview header
619
+ console.print(f"\n[bold magenta]Previewing changes for:[/bold magenta]\n[bold yellow]{target_dir}[/bold yellow]\n")
620
+
555
621
  # Collect stats for each kit
556
622
  kit_stats = {}
557
623
  total_stats = defaultdict(int)
@@ -859,13 +925,18 @@ def _display_removal_summary(result: dict, verbose: bool = False):
859
925
  console.print(f"\nRemoved {len(all_removed)} files")
860
926
 
861
927
  def _display_validation_results(validation_result: dict):
862
- """Display validation results in a user-friendly format.
928
+ """Display validation results with per-kit status and breakdown table.
929
+
930
+ Shows validation-specific information (file checks, missing files, integrity issues)
931
+ followed by the agent/shell breakdown table for kits that passed validation.
863
932
 
864
933
  Args:
865
- validation_result: Dict with 'valid' (bool) and 'checks' (dict of kit results)
934
+ validation_result: Dict with 'valid' (bool), 'checks' (dict of kit results), and 'target_dir' (Path)
866
935
  """
867
936
  checks = validation_result.get("checks", {})
937
+ target_dir = validation_result.get("target_dir", Path.cwd())
868
938
 
939
+ # Show per-kit validation status with detailed issues
869
940
  for kit_name, result in checks.items():
870
941
  status = result.get("status", "unknown")
871
942
 
@@ -874,13 +945,25 @@ def _display_validation_results(validation_result: dict):
874
945
  elif status == "not_installed":
875
946
  console.print(f"[dim][-] {kit_name} (not installed)[/dim]")
876
947
  elif status == "partial":
877
- console.print(f"[yellow][!] {kit_name} (partial - some files missing)[/yellow]")
948
+ console.print(f"[yellow][!] {kit_name} (partial - issues found)[/yellow]")
949
+ # Show detailed issues
878
950
  missing = result.get("missing_files", [])
951
+ corrupted = result.get("corrupted_files", [])
879
952
  if missing:
880
- console.print(f"[dim] Missing files: {', '.join(missing[:3])}" + (" ..." if len(missing) > 3 else "") + "[/dim]")
953
+ console.print(f"[dim] Missing: {', '.join(missing[:3])}" + (" ..." if len(missing) > 3 else "") + "[/dim]")
954
+ if corrupted:
955
+ console.print(f"[dim] Corrupted: {', '.join(corrupted[:3])}" + (" ..." if len(corrupted) > 3 else "") + "[/dim]")
881
956
  else:
882
957
  console.print(f"[red][X] {kit_name} ({status})[/red]")
883
958
 
959
+ # Show agent/shell breakdown table for validated kits
960
+ validated_kits = [kit_name for kit_name, result in checks.items() if result.get("status") == "installed"]
961
+
962
+ if validated_kits:
963
+ console.print()
964
+ table = _build_kit_breakdown_table(target_dir, validated_kits)
965
+ console.print(table)
966
+
884
967
  def _cleanup_empty_directories(target_dir: Path):
885
968
  """Clean up empty directories created by lite-kits."""
886
969
  directories_to_check = [
@@ -24,24 +24,37 @@ class Detector:
24
24
  self.target_dir = target_dir
25
25
  self.manifest = manifest
26
26
 
27
- def detect_agents(self, preferred: Optional[str] = None) -> List[str]:
27
+ def detect_agents(self, preferred: Optional[List[str]] = None) -> List[str]:
28
28
  """
29
29
  Auto-detect which AI agents are present.
30
30
 
31
31
  Args:
32
- preferred: Explicit agent preference (overrides auto-detection)
32
+ preferred: List of explicit agent preferences (overrides auto-detection)
33
33
 
34
34
  Returns:
35
35
  List of agent names sorted by priority
36
36
  """
37
- # If explicit preference, validate and return
37
+ # If explicit preferences, validate and return
38
38
  if preferred:
39
- config = self.manifest.get_agent_config(preferred)
40
- if not config:
41
- raise ValueError(f"Unknown agent: {preferred}")
42
- if not config.get('supported', False):
43
- raise ValueError(f"Agent not supported: {preferred}")
44
- return [preferred]
39
+ validated = []
40
+ for agent_name in preferred:
41
+ config = self.manifest.get_agent_config(agent_name)
42
+ if not config:
43
+ # Build helpful error message with valid options
44
+ agents = self.manifest.manifest.get('agents', {})
45
+ valid_agents = [
46
+ name for name, cfg in agents.items()
47
+ if cfg.get('supported', False)
48
+ ]
49
+ valid_list = ', '.join(valid_agents)
50
+ raise ValueError(
51
+ f"Unknown agent: '{agent_name}'\n"
52
+ f"Valid options: {valid_list}"
53
+ )
54
+ if not config.get('supported', False):
55
+ raise ValueError(f"Agent not supported: {agent_name}")
56
+ validated.append(agent_name)
57
+ return validated
45
58
 
46
59
  # Auto-detect from manifest
47
60
  detected = []
@@ -65,24 +78,48 @@ class Detector:
65
78
  detected.sort(key=lambda x: x['priority'])
66
79
  return [agent['name'] for agent in detected]
67
80
 
68
- def detect_shells(self, preferred: Optional[str] = None) -> List[str]:
81
+ def detect_shells(self, preferred: Optional[List[str]] = None) -> List[str]:
69
82
  """
70
83
  Determine which shells to install for.
71
84
 
72
85
  Args:
73
- preferred: Explicit shell preference (overrides auto-detection)
86
+ preferred: List of explicit shell preferences (overrides auto-detection)
74
87
 
75
88
  Returns:
76
89
  List of shell names
77
90
  """
78
- # If explicit preference, validate and return
91
+ # Shell aliases for common shorthands
92
+ shell_aliases = {
93
+ 'ps': 'powershell',
94
+ 'pwsh': 'powershell',
95
+ 'sh': 'bash',
96
+ }
97
+
98
+ # If explicit preferences, validate and return
79
99
  if preferred:
80
- config = self.manifest.manifest.get('shells', {}).get(preferred)
81
- if not config:
82
- raise ValueError(f"Unknown shell: {preferred}")
83
- if not config.get('supported', False):
84
- raise ValueError(f"Shell not supported: {preferred}")
85
- return [preferred]
100
+ validated = []
101
+ for shell_name in preferred:
102
+ # Normalize shell name using aliases
103
+ normalized = shell_aliases.get(shell_name.lower(), shell_name.lower())
104
+
105
+ config = self.manifest.manifest.get('shells', {}).get(normalized)
106
+ if not config:
107
+ # Build helpful error message with valid options
108
+ shells_config = self.manifest.manifest.get('shells', {})
109
+ valid_shells = [
110
+ name for name, cfg in shells_config.items()
111
+ if cfg.get('supported', False)
112
+ ]
113
+ valid_list = ', '.join(valid_shells)
114
+ raise ValueError(
115
+ f"Unknown shell: '{shell_name}'\n"
116
+ f"Valid options: {valid_list}\n"
117
+ f"Aliases: ps/pwsh->powershell, sh->bash"
118
+ )
119
+ if not config.get('supported', False):
120
+ raise ValueError(f"Shell not supported: {normalized}")
121
+ validated.append(normalized)
122
+ return validated
86
123
 
87
124
  # Check if shell detection is enabled
88
125
  options = self.manifest.manifest.get('options', {})
@@ -23,8 +23,8 @@ class Installer:
23
23
  target_dir: Path,
24
24
  kits: Optional[List[str]] = None,
25
25
  force: bool = False,
26
- agent: Optional[str] = None,
27
- shell: Optional[str] = None,
26
+ agents: Optional[List[str]] = None,
27
+ shells: Optional[List[str]] = None,
28
28
  ):
29
29
  """
30
30
  Initialize installer.
@@ -33,8 +33,8 @@ class Installer:
33
33
  target_dir: Target spec-kit project directory
34
34
  kits: List of kits to install (None = use default from manifest)
35
35
  force: Skip confirmations and overwrite existing files
36
- agent: Explicit agent preference (None = auto-detect)
37
- shell: Explicit shell preference (None = auto-detect)
36
+ agents: List of explicit agent preferences (None = auto-detect)
37
+ shells: List of explicit shell preferences (None = auto-detect)
38
38
  """
39
39
  self.target_dir = Path(target_dir).resolve()
40
40
  self.kits_dir = Path(__file__).parent.parent / "kits"
@@ -54,9 +54,15 @@ class Installer:
54
54
  # Operational modes
55
55
  self.force = force
56
56
 
57
- # Preferences
58
- self.preferred_agent = agent
59
- self.preferred_shell = shell
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)
60
66
 
61
67
  # Kits to install
62
68
  self.kits = kits or [self.manifest.get_default_kit()]
@@ -82,8 +88,8 @@ class Installer:
82
88
 
83
89
  def preview_installation(self) -> Dict:
84
90
  """Preview installation without making changes."""
85
- agents = self.detector.detect_agents(self.preferred_agent)
86
- shells = self.detector.detect_shells(self.preferred_shell)
91
+ agents = self.detector.detect_agents(self.preferred_agents)
92
+ shells = self.detector.detect_shells(self.preferred_shells)
87
93
 
88
94
  preview = {
89
95
  "kits": [],
@@ -169,8 +175,8 @@ class Installer:
169
175
  }
170
176
 
171
177
  try:
172
- agents = self.detector.detect_agents(self.preferred_agent)
173
- shells = self.detector.detect_shells(self.preferred_shell)
178
+ agents = self.detector.detect_agents(self.preferred_agents)
179
+ shells = self.detector.detect_shells(self.preferred_shells)
174
180
 
175
181
  if not agents:
176
182
  supported = [
@@ -8,6 +8,7 @@ from pathlib import Path
8
8
  from typing import Dict
9
9
 
10
10
  from .manifest import KitManifest
11
+ from .detector import Detector
11
12
 
12
13
 
13
14
  class Validator:
@@ -23,6 +24,7 @@ class Validator:
23
24
  """
24
25
  self.target_dir = target_dir
25
26
  self.manifest = manifest
27
+ self.detector = Detector(target_dir, manifest)
26
28
 
27
29
  def validate_all(self) -> Dict:
28
30
  """
@@ -46,6 +48,7 @@ class Validator:
46
48
  return {
47
49
  "valid": any_installed,
48
50
  "checks": checks,
51
+ "target_dir": self.target_dir,
49
52
  }
50
53
 
51
54
  def validate_kit(self, kit_name: str, options: Dict) -> Dict:
@@ -75,14 +78,48 @@ class Validator:
75
78
  "message": f"{kit_info['name']}: not installed",
76
79
  }
77
80
 
78
- # Kit is installed, validate files
81
+ # Detect which agents are actually present in the project
82
+ detected_agents = self.detector.detect_agents()
83
+ detected_shells = self.detector.detect_shells()
84
+
85
+ # Kit is installed, validate only files for detected agents/shells that actually have kit files
86
+ files_to_validate = []
87
+
88
+ # Add agent-specific files (commands/prompts) - only if at least one kit file exists for that agent
89
+ for agent in detected_agents:
90
+ agent_files = self.manifest.get_kit_files(kit_name, agent=agent)
91
+ # Check if any of this kit's files exist for this agent
92
+ has_kit_files = any(
93
+ (self.target_dir / f['path']).exists()
94
+ for f in agent_files
95
+ if f.get('status') != 'planned'
96
+ )
97
+ if has_kit_files:
98
+ files_to_validate.extend(agent_files)
99
+
100
+ # Add shell-specific files (scripts) - only if at least one kit file exists for that shell
101
+ for shell in detected_shells:
102
+ shell_files = self.manifest.get_kit_files(kit_name, agent=shell)
103
+ has_kit_files = any(
104
+ (self.target_dir / f['path']).exists()
105
+ for f in shell_files
106
+ if f.get('status') != 'planned'
107
+ )
108
+ if has_kit_files:
109
+ files_to_validate.extend(shell_files)
110
+
111
+ # Add agent/shell-agnostic files (memory, templates, etc.)
79
112
  all_files = self.manifest.get_kit_files(kit_name, agent=None)
113
+ for file_info in all_files:
114
+ # Only add files that aren't agent/shell-specific
115
+ if file_info.get('type') not in ['command', 'prompt', 'script']:
116
+ files_to_validate.append(file_info)
80
117
 
81
118
  missing = []
82
119
  corrupted = []
83
120
  outdated = []
84
121
 
85
- for file_info in all_files:
122
+ for file_info in files_to_validate:
86
123
  # Skip non-required files
87
124
  if not file_info.get('required', True):
88
125
  continue
File without changes
File without changes