Class: KairosMcp::Skillset

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

Overview

Represents a single SkillSet plugin installed in .kairos/skillsets/#name/

A SkillSet is an independent package containing tools, libraries, knowledge, and configuration. The layer declaration determines governance policy (blockchain recording level, approval requirements, RAG indexing).

Constant Summary collapse

REQUIRED_FIELDS =
%w[name version].freeze
VALID_LAYERS =
%i[L0 L1 L2].freeze
EXECUTABLE_EXTENSIONS =
%w[.rb .py .sh .js .ts .pl .lua .exe .so .dylib .dll .class .jar .wasm].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ Skillset

Returns a new instance of Skillset.



19
20
21
22
23
24
# File 'lib/kairos_mcp/skillset.rb', line 19

def initialize(path)
  @path = File.expand_path(path)
  @metadata = 
  @name = @metadata['name']
  @loaded = false
end

Instance Attribute Details

#metadataObject (readonly)

Returns the value of attribute metadata.



17
18
19
# File 'lib/kairos_mcp/skillset.rb', line 17

def 
  @metadata
end

#nameObject (readonly)

Returns the value of attribute name.



17
18
19
# File 'lib/kairos_mcp/skillset.rb', line 17

def name
  @name
end

#pathObject (readonly)

Returns the value of attribute path.



17
18
19
# File 'lib/kairos_mcp/skillset.rb', line 17

def path
  @path
end

Instance Method Details

#all_file_hashesObject

Hash of each file for full blockchain recording (L0)



148
149
150
151
152
153
154
155
# File 'lib/kairos_mcp/skillset.rb', line 148

def all_file_hashes
  hashes = {}
  Dir[File.join(@path, '**', '*')].select { |f| File.file?(f) }.sort.each do |file|
    relative = file.sub("#{@path}/", '')
    hashes[relative] = Digest::SHA256.hexdigest(File.read(file))
  end
  hashes
end

#authorObject



51
52
53
# File 'lib/kairos_mcp/skillset.rb', line 51

def author
  @metadata['author'] || ''
end

#config_filesObject



73
74
75
# File 'lib/kairos_mcp/skillset.rb', line 73

def config_files
  @metadata['config_files'] || []
end

#content_hashObject

Compute content hash of all files in the SkillSet



143
144
145
# File 'lib/kairos_mcp/skillset.rb', line 143

def content_hash
  Digest::SHA256.hexdigest(all_file_hashes.to_json)
end

#default_layerObject



38
39
40
41
# File 'lib/kairos_mcp/skillset.rb', line 38

def default_layer
  raw = @metadata['layer'] || 'L1'
  raw.to_sym
end

#depends_onObject



55
56
57
# File 'lib/kairos_mcp/skillset.rb', line 55

def depends_on
  parsed_depends_on.map { |d| d[:name] }
end

#depends_on_with_versionsObject

Detailed dependency info with version constraints Each element: { name: String, version: String|nil }



61
62
63
# File 'lib/kairos_mcp/skillset.rb', line 61

def depends_on_with_versions
  parsed_depends_on
end

#descriptionObject



47
48
49
# File 'lib/kairos_mcp/skillset.rb', line 47

def description
  @metadata['description'] || ''
end

#exchangeable?Boolean

Only knowledge-only SkillSets are safe to exchange over the network

Returns:

  • (Boolean)


174
175
176
# File 'lib/kairos_mcp/skillset.rb', line 174

def exchangeable?
  knowledge_only? && valid?
end

#file_listObject

Sorted list of relative file paths within the SkillSet



179
180
181
182
183
184
# File 'lib/kairos_mcp/skillset.rb', line 179

def file_list
  Dir[File.join(@path, '**', '*')]
    .select { |f| File.file?(f) }
    .map { |f| f.sub("#{@path}/", '') }
    .sort
end

#has_knowledge?Boolean

Returns:

  • (Boolean)


127
128
129
# File 'lib/kairos_mcp/skillset.rb', line 127

def has_knowledge?
  !knowledge_dirs.empty?
end

#has_plugin?Boolean

Returns:

  • (Boolean)


136
137
138
139
140
# File 'lib/kairos_mcp/skillset.rb', line 136

def has_plugin?
  return false unless plugin_config
  plugin_dir = File.join(@path, 'plugin')
  Dir.exist?(plugin_dir)
end

#index_knowledge?Boolean

Returns:

  • (Boolean)


87
88
89
90
91
92
# File 'lib/kairos_mcp/skillset.rb', line 87

def index_knowledge?
  return @metadata['index_knowledge'] == true if @metadata.key?('index_knowledge')

  # Default: L0 and L1 are indexed, L2 is not
  %i[L0 L1].include?(layer)
end

#knowledge_dir_namesObject



77
78
79
# File 'lib/kairos_mcp/skillset.rb', line 77

def knowledge_dir_names
  @metadata['knowledge_dirs'] || []
end

#knowledge_dirsObject

Full path to knowledge directories



123
124
125
# File 'lib/kairos_mcp/skillset.rb', line 123

def knowledge_dirs
  knowledge_dir_names.map { |d| File.join(@path, d) }.select { |d| File.directory?(d) }
end

#knowledge_only?Boolean

True if the SkillSet contains no executable code (tools/ or lib/) Checks for executable extensions (.rb, .py, .sh, etc.) and shebang lines

Returns:

  • (Boolean)


159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/kairos_mcp/skillset.rb', line 159

def knowledge_only?
  tools_dir = File.join(@path, 'tools')
  lib_dir = File.join(@path, 'lib')
  no_tools = !File.directory?(tools_dir) ||
    Dir[File.join(tools_dir, '**', '*')].none? { |f|
      File.file?(f) && (executable_extension?(f) || has_shebang?(f))
    }
  no_lib = !File.directory?(lib_dir) ||
    Dir[File.join(lib_dir, '**', '*')].none? { |f|
      File.file?(f) && (executable_extension?(f) || has_shebang?(f))
    }
  no_tools && no_lib
end

#layerObject

Layer as symbol (:L0, :L1, :L2), with override support



27
28
29
# File 'lib/kairos_mcp/skillset.rb', line 27

def layer
  @layer_override || default_layer
end

#layer=(sym) ⇒ Object

Raises:

  • (ArgumentError)


31
32
33
34
35
36
# File 'lib/kairos_mcp/skillset.rb', line 31

def layer=(sym)
  sym = sym.to_sym
  raise ArgumentError, "Invalid layer: #{sym}. Valid: #{VALID_LAYERS}" unless VALID_LAYERS.include?(sym)

  @layer_override = sym
end

#load!Object

Load the SkillSet code (require lib/ and tools/)



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/kairos_mcp/skillset.rb', line 95

def load!
  return if @loaded
  return false unless version_compatible?

  lib_dir = File.join(@path, 'lib')
  $LOAD_PATH.unshift(lib_dir) if File.directory?(lib_dir) && !$LOAD_PATH.include?(lib_dir)

  # Require lib entry point if it exists
  entry_point = Dir[File.join(lib_dir, '*.rb')].first
  require entry_point if entry_point

  # Ensure BaseTool is available before loading SkillSet tools
  require_relative 'tools/base_tool'

  # Require all tool files
  tools_dir = File.join(@path, 'tools')
  if File.directory?(tools_dir)
    Dir[File.join(tools_dir, '*.rb')].sort.each { |f| require f }
  end

  @loaded = true
end

#loaded?Boolean

Returns:

  • (Boolean)


118
119
120
# File 'lib/kairos_mcp/skillset.rb', line 118

def loaded?
  @loaded
end

#place_extensionsObject

Place extension declarations for PlaceRouter integration. Returns Array of Hashes, each with ‘class’, ‘require’, ‘route_actions’.



83
84
85
# File 'lib/kairos_mcp/skillset.rb', line 83

def place_extensions
  @metadata['place_extensions'] || []
end

#plugin_configObject

Plugin projection support: Claude Code plugin artifacts in plugin/ directory



132
133
134
# File 'lib/kairos_mcp/skillset.rb', line 132

def plugin_config
  @metadata['plugin']
end

#providesObject



65
66
67
# File 'lib/kairos_mcp/skillset.rb', line 65

def provides
  @metadata['provides'] || []
end

#to_hObject



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/kairos_mcp/skillset.rb', line 186

def to_h
  {
    name: @name,
    version: version,
    description: description,
    author: author,
    layer: layer,
    depends_on: depends_on,
    provides: provides,
    tool_classes: tool_class_names,
    knowledge_only: knowledge_only?,
    exchangeable: exchangeable?,
    path: @path,
    loaded: @loaded
  }
end

#tool_class_namesObject



69
70
71
# File 'lib/kairos_mcp/skillset.rb', line 69

def tool_class_names
  @metadata['tool_classes'] || []
end

#valid?Boolean

Returns:

  • (Boolean)


203
204
205
206
207
# File 'lib/kairos_mcp/skillset.rb', line 203

def valid?
  REQUIRED_FIELDS.all? { |f| @metadata[f] && !@metadata[f].to_s.strip.empty? }
rescue StandardError
  false
end

#versionObject



43
44
45
# File 'lib/kairos_mcp/skillset.rb', line 43

def version
  @metadata['version']
end

#version_compatible?Boolean

Check if the SkillSet is compatible with the current core version

Returns:

  • (Boolean)


210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/kairos_mcp/skillset.rb', line 210

def version_compatible?
  min_version = @metadata['min_core_version']
  return true unless min_version

  current = KairosMcp::VERSION
  if Gem::Version.new(current) < Gem::Version.new(min_version)
    warn "[SkillSet] '#{@name}' requires core >= #{min_version}, current: #{current} — skipping"
    return false
  end
  true
rescue ArgumentError
  warn "[SkillSet] '#{@name}' has invalid min_core_version: #{min_version} — ignoring"
  true
end