Class: Pikuri::Tool::SkillCatalog

Inherits:
Object
  • Object
show all
Defined in:
lib/pikuri/tool/skill_catalog.rb

Overview

Discovery + validation of skills — Markdown files with YAML frontmatter that the agent loads on demand via Skill.

A skill is a directory containing a SKILL.md file. The frontmatter declares the skill’s name and description; everything in the directory (helper scripts, reference notes, templates) is treated as sidecar content the LLM can pull in via the regular read tool after loading the skill. Pikuri implements the Agent Skills standard leniently — invalid frontmatter is warned about but the skill still loads, except a missing description which is fatal (no description means the LLM has no signal for when to invoke it).

Catalog vs. tool

SkillCatalog owns the discovery layer: where skills live, how the frontmatter is parsed, what wins on a name collision. The Skill tool is the loading layer: it receives a catalog at construction and looks names up against it when the LLM invokes skill. The two are paired by Agent — see Agent#initialize for the auto-registration rule (non-empty catalog ⇒ Tool::Skill is appended to the tool list and the catalog’s prompt block is appended to the system prompt).

The seam

SkillCatalog is an abstract base with two bundled implementations: Empty (the EMPTY singleton, used by pikuri-chat and as the default for any caller that doesn’t have a filesystem story) and Bundled (the on-disk scanner). The seam lets future hosts (e.g. an in-editor pikuri reading skills from a virtual filesystem, or a synthetic test catalog) swap in without touching Skill or Agent.

Direct Known Subclasses

Bundled, Empty

Defined Under Namespace

Classes: Bundled, Empty, Skill

Constant Summary collapse

MAX_NAME_LENGTH =

Frontmatter name cap per the Agent Skills standard.

64
MAX_DESCRIPTION_LENGTH =

Frontmatter description cap per the Agent Skills standard.

1024
SKILL_DIR_NAMES =

The three skill directories pikuri scans, in precedence order. .pikuri first (the user’s own pikuri-specific skills), .claude second (so existing Claude Code skills travel along for free), .agents last (the cross-harness convention PI also honors). Same names are used for both global roots (under ~/) and project roots (relative to CWD, walked up).

['.pikuri/skills', '.claude/skills', '.agents/skills'].freeze

Instance Method Summary collapse

Instance Method Details

#empty?Boolean

Returns true when #list is empty. Agent keys its auto-wiring (system-prompt augmentation + Tool::Skill registration) off this — empty catalog ⇒ no surface change.

Returns:

  • (Boolean)

    true when #list is empty. Agent keys its auto-wiring (system-prompt augmentation + Tool::Skill registration) off this — empty catalog ⇒ no surface change.



93
94
95
# File 'lib/pikuri/tool/skill_catalog.rb', line 93

def empty?
  list.empty?
end

#format_for_promptString

System-prompt block advertising every loaded skill to the LLM. Follows the Agent Skills standard’s XML shape (PI uses the same one) so a skill folder authored against any compliant harness renders consistently here.

Returns “” for an empty catalog so callers can unconditionally concatenate without an if. The leading nn separates the block from whatever the caller’s static system prompt ends with.

Returns:

  • (String)


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/pikuri/tool/skill_catalog.rb', line 107

def format_for_prompt
  return '' if empty?

  lines = [
    '',
    '',
    'The following skills provide specialized instructions for specific tasks.',
    "Use the `skill` tool with the skill's name to load its full instructions.",
    '',
    '<available_skills>'
  ]
  list.each do |skill|
    lines << '  <skill>'
    lines << "    <name>#{escape_xml(skill.name)}</name>"
    lines << "    <description>#{escape_xml(skill.description)}</description>"
    lines << "    <location>#{escape_xml(skill.location)}</location>"
    lines << '  </skill>'
  end
  lines << '</available_skills>'
  lines.join("\n")
end

#get(name) ⇒ Skill?

Look up a skill by name.

Parameters:

  • name (String)

Returns:

Raises:

  • (NotImplementedError)

    in the abstract base



86
87
88
# File 'lib/pikuri/tool/skill_catalog.rb', line 86

def get(name)
  raise NotImplementedError, "#{self.class}#get must be implemented"
end

#listArray<Skill>

Skills available to the LLM, in stable insertion order (which equals the order they were discovered, which equals precedence).

Returns:

Raises:

  • (NotImplementedError)

    in the abstract base



77
78
79
# File 'lib/pikuri/tool/skill_catalog.rb', line 77

def list
  raise NotImplementedError, "#{self.class}#list must be implemented"
end