Class: KairosMcp::Skillset
- Inherits:
-
Object
- Object
- KairosMcp::Skillset
- 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
-
#metadata ⇒ Object
readonly
Returns the value of attribute metadata.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#path ⇒ Object
readonly
Returns the value of attribute path.
Instance Method Summary collapse
-
#all_file_hashes ⇒ Object
Hash of each file for full blockchain recording (L0).
- #author ⇒ Object
- #config_files ⇒ Object
-
#content_hash ⇒ Object
Compute content hash of all files in the SkillSet.
- #default_layer ⇒ Object
- #depends_on ⇒ Object
-
#depends_on_with_versions ⇒ Object
Detailed dependency info with version constraints Each element: { name: String, version: String|nil }.
- #description ⇒ Object
-
#exchangeable? ⇒ Boolean
Only knowledge-only SkillSets are safe to exchange over the network.
-
#file_list ⇒ Object
Sorted list of relative file paths within the SkillSet.
- #has_knowledge? ⇒ Boolean
- #has_plugin? ⇒ Boolean
- #index_knowledge? ⇒ Boolean
-
#initialize(path) ⇒ Skillset
constructor
A new instance of Skillset.
- #knowledge_dir_names ⇒ Object
-
#knowledge_dirs ⇒ Object
Full path to knowledge directories.
-
#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.
-
#layer ⇒ Object
Layer as symbol (:L0, :L1, :L2), with override support.
- #layer=(sym) ⇒ Object
-
#lifecycle_hooks ⇒ Object
24/7 v0.4 §2.3 — LifecycleHook declarations.
-
#load! ⇒ Object
Load the SkillSet code (require lib/ and tools/).
- #loaded? ⇒ Boolean
-
#place_extensions ⇒ Object
Place extension declarations for PlaceRouter integration.
-
#plugin_config ⇒ Object
Plugin projection support: Claude Code plugin artifacts in plugin/ directory.
- #provides ⇒ Object
- #to_h ⇒ Object
- #tool_class_names ⇒ Object
- #valid? ⇒ Boolean
- #version ⇒ Object
-
#version_compatible? ⇒ Boolean
Check if the SkillSet is compatible with the current core version.
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.(path) @metadata = @name = @metadata['name'] @loaded = false end |
Instance Attribute Details
#metadata ⇒ Object (readonly)
Returns the value of attribute metadata.
17 18 19 |
# File 'lib/kairos_mcp/skillset.rb', line 17 def @metadata end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
17 18 19 |
# File 'lib/kairos_mcp/skillset.rb', line 17 def name @name end |
#path ⇒ Object (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_hashes ⇒ Object
Hash of each file for full blockchain recording (L0)
161 162 163 164 165 166 167 168 |
# File 'lib/kairos_mcp/skillset.rb', line 161 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 |
#author ⇒ Object
51 52 53 |
# File 'lib/kairos_mcp/skillset.rb', line 51 def @metadata['author'] || '' end |
#config_files ⇒ Object
86 87 88 |
# File 'lib/kairos_mcp/skillset.rb', line 86 def config_files @metadata['config_files'] || [] end |
#content_hash ⇒ Object
Compute content hash of all files in the SkillSet
156 157 158 |
# File 'lib/kairos_mcp/skillset.rb', line 156 def content_hash Digest::SHA256.hexdigest(all_file_hashes.to_json) end |
#default_layer ⇒ Object
38 39 40 41 |
# File 'lib/kairos_mcp/skillset.rb', line 38 def default_layer raw = @metadata['layer'] || 'L1' raw.to_sym end |
#depends_on ⇒ Object
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_versions ⇒ Object
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 |
#description ⇒ Object
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
187 188 189 |
# File 'lib/kairos_mcp/skillset.rb', line 187 def exchangeable? knowledge_only? && valid? end |
#file_list ⇒ Object
Sorted list of relative file paths within the SkillSet
192 193 194 195 196 197 |
# File 'lib/kairos_mcp/skillset.rb', line 192 def file_list Dir[File.join(@path, '**', '*')] .select { |f| File.file?(f) } .map { |f| f.sub("#{@path}/", '') } .sort end |
#has_knowledge? ⇒ Boolean
140 141 142 |
# File 'lib/kairos_mcp/skillset.rb', line 140 def has_knowledge? !knowledge_dirs.empty? end |
#has_plugin? ⇒ Boolean
149 150 151 152 153 |
# File 'lib/kairos_mcp/skillset.rb', line 149 def has_plugin? return false unless plugin_config plugin_dir = File.join(@path, 'plugin') Dir.exist?(plugin_dir) end |
#index_knowledge? ⇒ Boolean
100 101 102 103 104 105 |
# File 'lib/kairos_mcp/skillset.rb', line 100 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_names ⇒ Object
90 91 92 |
# File 'lib/kairos_mcp/skillset.rb', line 90 def knowledge_dir_names @metadata['knowledge_dirs'] || [] end |
#knowledge_dirs ⇒ Object
Full path to knowledge directories
136 137 138 |
# File 'lib/kairos_mcp/skillset.rb', line 136 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
172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/kairos_mcp/skillset.rb', line 172 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 |
#layer ⇒ Object
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
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 |
#lifecycle_hooks ⇒ Object
24/7 v0.4 §2.3 — LifecycleHook declarations. Returns a Hash of { hook_name(String) => class_name(String) }. Non-Hash values and entries with non-string values are coerced away so downstream registration never encounters malformed input.
77 78 79 80 81 82 83 84 |
# File 'lib/kairos_mcp/skillset.rb', line 77 def lifecycle_hooks raw = @metadata['lifecycle_hooks'] || {} return {} unless raw.is_a?(Hash) raw.each_with_object({}) do |(k, v), acc| next unless k.is_a?(String) && v.is_a?(String) && !k.empty? && !v.empty? acc[k] = v end end |
#load! ⇒ Object
Load the SkillSet code (require lib/ and tools/)
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/kairos_mcp/skillset.rb', line 108 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
131 132 133 |
# File 'lib/kairos_mcp/skillset.rb', line 131 def loaded? @loaded end |
#place_extensions ⇒ Object
Place extension declarations for PlaceRouter integration. Returns Array of Hashes, each with ‘class’, ‘require’, ‘route_actions’.
96 97 98 |
# File 'lib/kairos_mcp/skillset.rb', line 96 def place_extensions @metadata['place_extensions'] || [] end |
#plugin_config ⇒ Object
Plugin projection support: Claude Code plugin artifacts in plugin/ directory
145 146 147 |
# File 'lib/kairos_mcp/skillset.rb', line 145 def plugin_config @metadata['plugin'] end |
#provides ⇒ Object
65 66 67 |
# File 'lib/kairos_mcp/skillset.rb', line 65 def provides @metadata['provides'] || [] end |
#to_h ⇒ Object
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/kairos_mcp/skillset.rb', line 199 def to_h { name: @name, version: version, description: description, 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_names ⇒ Object
69 70 71 |
# File 'lib/kairos_mcp/skillset.rb', line 69 def tool_class_names @metadata['tool_classes'] || [] end |
#valid? ⇒ Boolean
216 217 218 219 220 |
# File 'lib/kairos_mcp/skillset.rb', line 216 def valid? REQUIRED_FIELDS.all? { |f| @metadata[f] && !@metadata[f].to_s.strip.empty? } rescue StandardError false end |
#version ⇒ Object
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
223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/kairos_mcp/skillset.rb', line 223 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 |