Class: Kward::Skills::Registry
- Inherits:
-
Object
- Object
- Kward::Skills::Registry
- Defined in:
- lib/kward/skills/registry.rb
Overview
Discovers Agent Skills and reads their bounded instruction resources.
Project sources are included only when the caller marks them trusted. If duplicate skill names exist, the first source in documented precedence order wins.
Defined Under Namespace
Classes: SkillCandidate, SkillSource
Instance Method Summary collapse
-
#initialize(config_dir:, workspace_root:, project_skills_trusted:, skill_class:, max_file_bytes:, markdown_parser:, inside_directory:, warning_sink: nil, project_skill_paths: nil) ⇒ Registry
constructor
A new instance of Registry.
-
#project_skill_candidates ⇒ Array<SkillCandidate>
Returns project skill files without activating their instructions.
-
#read_skill_file(name, relative_path = nil) ⇒ String
Reads a skill's main instructions or a bounded relative resource.
-
#skills ⇒ Array<ConfigFiles::Skill>
Returns discovered, validated skills in precedence order.
Constructor Details
#initialize(config_dir:, workspace_root:, project_skills_trusted:, skill_class:, max_file_bytes:, markdown_parser:, inside_directory:, warning_sink: nil, project_skill_paths: nil) ⇒ Registry
Returns a new instance of Registry.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/kward/skills/registry.rb', line 19 def initialize(config_dir:, workspace_root:, project_skills_trusted:, skill_class:, max_file_bytes:, markdown_parser:, inside_directory:, warning_sink: nil, project_skill_paths: nil) @config_dir = config_dir @workspace_root = workspace_root @project_skills_trusted = project_skills_trusted @skill_class = skill_class @max_file_bytes = max_file_bytes @markdown_parser = markdown_parser @inside_directory = inside_directory @warning_sink = warning_sink @project_skill_paths = project_skill_paths&.map do |path| File.realpath(path) rescue SystemCallError File.(path) end end |
Instance Method Details
#project_skill_candidates ⇒ Array<SkillCandidate>
Returns project skill files without activating their instructions.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/kward/skills/registry.rb', line 39 def project_skill_candidates skill_sources.select { |source| source.scope == :project }.flat_map do |source| discover_source(source).filter_map do |path| SkillCandidate.new( path: path, root: source.root, label: source.label, scope: source.scope, digest: skill_digest(path) ) rescue StandardError => e emit_warning "Warning: skipping Kward skill #{path}: #{e.}" nil end end end |
#read_skill_file(name, relative_path = nil) ⇒ String
Reads a skill's main instructions or a bounded relative resource.
Absolute paths, paths escaping the skill folder, missing files, and files above the configured size limit return user-facing error strings.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/kward/skills/registry.rb', line 91 def read_skill_file(name, relative_path = nil) skill = skills.find { |candidate| candidate.name == name.to_s } return "Error: unknown skill: #{name}" unless skill path = relative_path.to_s.empty? ? "SKILL.md" : relative_path.to_s return "Error: skill path must be relative" if Pathname.new(path).absolute? base = File.realpath(skill.folder) target = File.(path, base) real_target = File.realpath(target) unless @inside_directory.call(real_target, base) return "Error: skill path outside skill folder: #{path}" end return "Error: skill path is not a file: #{path}" unless File.file?(real_target) size = File.size(real_target) return "Error: skill file too large: #{path} (#{size} bytes)" if size > @max_file_bytes content = File.read(real_target) relative_path.to_s.empty? ? skill_content(skill, content) : content rescue Errno::ENOENT "Error: skill file not found: #{path}" rescue StandardError => e "Error: could not read skill file #{path}: #{e.}" end |
#skills ⇒ Array<ConfigFiles::Skill>
Returns discovered, validated skills in precedence order.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/kward/skills/registry.rb', line 60 def skills seen = {} skill_sources.flat_map do |source| scan_source(source).filter_map do |path| skill = parse_skill(path) next unless skill if seen[skill.name] emit_warning "Warning: skipping duplicate Kward skill #{skill.name.inspect}: #{path}" next end seen[skill.name] = true skill end end rescue StandardError => e emit_warning "Warning: skipping Kward skills: #{e.}" [] end |