Class: KairosMcp::PluginProjector

Inherits:
Object
  • Object
show all
Defined in:
lib/kairos_mcp/plugin_projector.rb

Overview

Projects SkillSet plugin artifacts to Claude Code plugin/project structure.

Dual-mode:

:project (default) — writes to .claude/skills/, .claude/agents/, .claude/settings.json
:plugin            — writes to plugin root skills/, agents/, hooks/hooks.json

Design: log/skillset_plugin_projection_design_v2.2_20260404.md

Constant Summary collapse

SEED_SKILLS =
%w[kairos-chain].freeze
PROJECTED_BY =
'kairos-chain'
SAFE_NAME_PATTERN =
/\A[a-zA-Z0-9][a-zA-Z0-9_-]*\z/
ALLOWED_HOOK_COMMANDS =
/\Akairos-/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project_root, mode: :auto) ⇒ PluginProjector

Returns a new instance of PluginProjector.



25
26
27
28
29
30
# File 'lib/kairos_mcp/plugin_projector.rb', line 25

def initialize(project_root, mode: :auto)
  @project_root = project_root
  @mode = resolve_mode(mode)
  @output_root = @mode == :plugin ? project_root : File.join(project_root, '.claude')
  @manifest_path = File.join(project_root, '.kairos', 'projection_manifest.json')
end

Instance Attribute Details

#modeObject (readonly)

Returns the value of attribute mode.



23
24
25
# File 'lib/kairos_mcp/plugin_projector.rb', line 23

def mode
  @mode
end

#output_rootObject (readonly)

Returns the value of attribute output_root.



23
24
25
# File 'lib/kairos_mcp/plugin_projector.rb', line 23

def output_root
  @output_root
end

#project_rootObject (readonly)

Returns the value of attribute project_root.



23
24
25
# File 'lib/kairos_mcp/plugin_projector.rb', line 23

def project_root
  @project_root
end

Instance Method Details

#project!(enabled_skillsets, knowledge_entries: []) ⇒ Object

Main entry: project all SkillSet plugin artifacts + L1 knowledge meta skill



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/kairos_mcp/plugin_projector.rb', line 33

def project!(enabled_skillsets, knowledge_entries: [])
  previous_manifest = load_manifest
  current_outputs = {}
  merged_hooks = @mode == :plugin ? load_seed_hooks : { 'hooks' => {} }

  enabled_skillsets.each do |ss|
    next unless ss.has_plugin?

    plugin_dir = File.join(ss.path, 'plugin')
    project_skill!(ss, plugin_dir, current_outputs)
    project_agents!(ss, plugin_dir, current_outputs)
    collect_hooks!(ss, plugin_dir, merged_hooks)
  end

  project_knowledge_meta_skill!(knowledge_entries, current_outputs)
  write_merged_hooks!(merged_hooks, current_outputs)
  cleanup_stale!(previous_manifest, current_outputs)
  save_manifest(current_outputs, enabled_skillsets, knowledge_entries)
end

#project_if_changed!(enabled_skillsets, knowledge_entries: []) ⇒ Object

Digest-based no-op: skip projection if nothing changed



54
55
56
57
58
59
# File 'lib/kairos_mcp/plugin_projector.rb', line 54

def project_if_changed!(enabled_skillsets, knowledge_entries: [])
  digest = compute_source_digest(enabled_skillsets, knowledge_entries)
  return false if digest == load_manifest.dig('source_digest')
  project!(enabled_skillsets, knowledge_entries: knowledge_entries)
  true
end

#statusObject

Status summary for MCP tool



62
63
64
65
66
67
68
69
70
71
# File 'lib/kairos_mcp/plugin_projector.rb', line 62

def status
  manifest = load_manifest
  {
    mode: @mode,
    output_root: @output_root,
    projected_at: manifest['projected_at'],
    source_digest: manifest['source_digest'],
    output_count: manifest.fetch('outputs', {}).size
  }
end

#verifyObject

Verify projected files match manifest



74
75
76
77
78
79
80
# File 'lib/kairos_mcp/plugin_projector.rb', line 74

def verify
  manifest = load_manifest
  outputs = manifest.fetch('outputs', {})
  missing = outputs.keys.reject { |f| File.exist?(f) }
  orphaned = find_orphaned_files(outputs)
  { valid: missing.empty? && orphaned.empty?, missing: missing, orphaned: orphaned }
end