Module: Ask::Skills

Defined in:
lib/ask/skills.rb,
lib/ask/skills/skill.rb,
lib/ask/skills/version.rb,
lib/ask/skills/registry.rb,
lib/ask/skills/formatter.rb,
lib/ask/skills/validator.rb,
lib/ask/skills/sources/base.rb,
lib/ask/skills/sources/gems.rb,
lib/ask/skills/sources/filesystem.rb

Defined Under Namespace

Modules: Source Classes: Error, Formatter, Registry, Skill, Validator

Constant Summary collapse

VERSION =
"0.2.1"

Class Method Summary collapse

Class Method Details

.builtin_skills_dirObject



33
34
35
36
37
38
# File 'lib/ask/skills.rb', line 33

def builtin_skills_dir
  # Skills live in lib/ask/skills/<skill_name>/SKILL.md
  # __dir__ in this file (lib/ask/skills.rb) is lib/ask/
  # The skill directories are in lib/ask/skills/
  File.join(__dir__, "skills")
end

.default_sourcesObject



23
24
25
26
27
28
29
30
31
# File 'lib/ask/skills.rb', line 23

def default_sources
  [
    # Highest priority first — first source wins in Registry
    Source::Filesystem.new(project_dir: ".agents/skills"),
    Source::Filesystem.new(user_dir: "~/.config/ask/skills"),
    Source::Gems.new,
    Source::Filesystem.new(dir: builtin_skills_dir),
  ]
end

.discover(sources: nil) ⇒ Object



19
20
21
# File 'lib/ask/skills.rb', line 19

def discover(sources: nil)
  Registry.new(sources || default_sources)
end

.extract_body(content) ⇒ Object

Extract the markdown body from a file with frontmatter.



82
83
84
85
86
87
88
# File 'lib/ask/skills.rb', line 82

def extract_body(content)
  return content unless content.start_with?("---\n")
  end_idx = content.index("\n---\n", 4)
  return content unless end_idx
  body = content[(end_idx + 5)..] || ""
  body.sub(/\A\n/, "").strip
end

.load_file(path) ⇒ Skill

Load a skill from an arbitrary markdown file path. Parses frontmatter if present, otherwise uses the filename as the skill name.

Parameters:

  • path (String)

    absolute or relative path to a markdown (.md) file

Returns:

  • (Skill)

    a skill with the file’s content as instructions

Raises:

  • (Errno::ENOENT)

    if the file does not exist



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/ask/skills.rb', line 46

def load_file(path)
  path = File.expand_path(path)
  content = File.read(path)
  frontmatter = parse_frontmatter(content)
  body = extract_body(content)

  name = frontmatter["name"] || File.basename(path, ".md")
  description = frontmatter["description"] || "Ad-hoc skill loaded from #{File.basename(path)}"

  Skill.new(
    name: name,
    description: description,
    instructions: body.empty? ? content : body,
    source: path
  )
end

.parse_frontmatter(content) ⇒ Object

Simple frontmatter parsing for skill files. Returns a hash of key-value pairs from between — markers.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/ask/skills.rb', line 65

def parse_frontmatter(content)
  return {} unless content.start_with?("---\n")
  end_idx = content.index("\n---\n", 4)
  return {} unless end_idx
  yaml_str = content[4...end_idx]
  yaml = {}
  yaml_str.split("\n").each do |line|
    if (m = line.match(/\A(\w+):\s*(.+)\z/))
      value = m[2].strip
      value = value.gsub(/\A"|"\z/, "").gsub(/\A'|'\z/, "")
      yaml[m[1]] = value
    end
  end
  yaml
end