Class: Rubino::Skills::Skill

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/skills/skill.rb

Overview

Represents a single skill. Two layouts are supported:

* flat file  — <dir>/<name>.md (the skill name is the basename)
* directory  — <dir>/<name>/SKILL.md (the skill name is the dir name,
  plus bundled files under references/ scripts/ assets/ etc.)

In both cases ‘path` points at the markdown body that carries the name/description frontmatter. Directory skills also expose `linked_files` (relative paths of bundled files) and can read a specific bundled file sandboxed to the skill’s own directory.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path:) ⇒ Skill

Returns a new instance of Skill.



29
30
31
32
33
34
35
36
37
# File 'lib/rubino/skills/skill.rb', line 29

def initialize(path:)
  @path = path
  @metadata = {}
  @content = nil
  @linked_files = []
  @directory = directory_skill? ? File.dirname(path) : nil
  discover_linked_files! if directory?
  parse_frontmatter!
end

Instance Attribute Details

#descriptionObject (readonly)

Returns the value of attribute description.



17
18
19
# File 'lib/rubino/skills/skill.rb', line 17

def description
  @description
end

#linked_filesObject (readonly)

Returns the value of attribute linked_files.



17
18
19
# File 'lib/rubino/skills/skill.rb', line 17

def linked_files
  @linked_files
end

#metadataObject (readonly)

Returns the value of attribute metadata.



17
18
19
# File 'lib/rubino/skills/skill.rb', line 17

def 
  @metadata
end

#nameObject (readonly)

Returns the value of attribute name.



17
18
19
# File 'lib/rubino/skills/skill.rb', line 17

def name
  @name
end

#pathObject (readonly)

Returns the value of attribute path.



17
18
19
# File 'lib/rubino/skills/skill.rb', line 17

def path
  @path
end

Instance Method Details

#contentObject

Returns the full skill content (loaded lazily)



50
51
52
# File 'lib/rubino/skills/skill.rb', line 50

def content
  @content ||= load_content
end

#current_linked_filesObject

Live relative paths of bundled files, recomputed from disk. Unlike the linked_files snapshot taken at init, this reflects the current dir state — so an error message built from it can’t list a file that #read_file just failed to find (the W3 self-contradiction). Empty for flat-file skills.



84
85
86
87
88
# File 'lib/rubino/skills/skill.rb', line 84

def current_linked_files
  return [] unless directory?

  collect_linked_files
end

#dirObject

The skill’s own directory (only for directory skills).



45
46
47
# File 'lib/rubino/skills/skill.rb', line 45

def dir
  @directory
end

#directory?Boolean

True when this skill is backed by a <name>/SKILL.md directory.

Returns:

  • (Boolean)


40
41
42
# File 'lib/rubino/skills/skill.rb', line 40

def directory?
  !@directory.nil?
end

#languagesObject

Languages this skill is scoped to (lower-cased tokens, e.g. [“ruby”]), parsed from the optional ‘languages:` frontmatter key. Empty means the skill is language-agnostic and always surfaced. A scoped skill is only auto-listed in the system-prompt catalogue when the project uses one of its languages — see Registry#summaries — so a Ruby skill no longer brands a Python project. It stays discoverable/loadable on demand.



25
26
27
# File 'lib/rubino/skills/skill.rb', line 25

def languages
  Array(@metadata["languages"]).map { |l| l.to_s.strip.downcase }.reject(&:empty?)
end

#loaded?Boolean

Returns true if the skill has been fully loaded

Returns:

  • (Boolean)


55
56
57
# File 'lib/rubino/skills/skill.rb', line 55

def loaded?
  !@content.nil?
end

#read_file(relative_path) ⇒ Object

Reads a bundled file by its relative path, sandboxed to the skill dir. Returns the file contents, or nil if the skill has no directory, the path escapes the skill dir, or the file does not exist.

Resolve and read happen back-to-back with no listing step in between, so the caller can’t observe a “present in the listing but unreadable” state from THIS method. A File::ENOENT between #file? and #read (the skill dir being torn down mid-call) is swallowed to nil rather than raised, so a concurrent teardown reads as a clean miss instead of a crash (W3).



68
69
70
71
72
73
74
75
76
77
# File 'lib/rubino/skills/skill.rb', line 68

def read_file(relative_path)
  return nil unless directory?

  resolved = resolve_within_dir(relative_path)
  return nil unless resolved && File.file?(resolved)

  File.read(resolved, encoding: "UTF-8")
rescue Errno::ENOENT, Errno::EACCES
  nil
end

#summaryObject

Returns a summary for the agent to see available skills



91
92
93
# File 'lib/rubino/skills/skill.rb', line 91

def summary
  "#{@name}: #{@description}"
end