Class: Pikuri::Skill::Catalog::Bundled

Inherits:
Catalog
  • Object
show all
Defined in:
lib/pikuri/skill/catalog.rb

Overview

On-disk catalog over a list of search bases (project root, home dir, …). Under each base it scans .pikuri/skills (the user's own), .claude/skills (Claude Code skills ride along) and .agents/skills (PI's cross-harness convention), in that order; missing subdirs are skipped.

Precedence

Bases left to right, subdirs in the order above; the first skill seen for a name wins, later ones dropped with a warning. Pass the project root first for "project beats global".

Validation

The Agent Skills standard is enforced leniently: a missing description is the only hard failure (skill skipped). Name/dir mismatch, oversized name/description, invalid name chars, a paths: that is neither a string nor a list of them, and malformed YAML log a warning and load anyway. A file with no frontmatter is skipped silently (not meant as a skill). Note which way the paths: failure falls: a value this class cannot read leaves the skill advertised, never hidden.

Trust

Bundled.new(search_bases: [
Pikuri::Bundle::Base.new(dir: project_root, trusted: repo_sources_are_mine),
Pikuri::Bundle::Base.new(dir: Dir.home)
])

Per-base Bundle::Base#trusted unions into one #untrusted_level at scan time, because SkillTool declares its legs once: a single skill from an unvouched base takes the whole catalog to :hard. The union counts a skill at the point it wins its name, so an untrusted base costs nothing when it is empty or when its skills all lost a collision — a host may therefore name a base it knows nothing about and still report :none for the user who turns out to have no skills there.

A host that can rule out writes to a base may vouch for it without asking anyone: bin/pikuri-code does this for Dir.home, since skill roots ride in the workspace's readable: and never its writable:, so nothing but the user's own hand puts a skill there.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(search_bases:) ⇒ Bundled

Parameters:

  • search_bases (Array<Pikuri::Bundle::Base, String, Pathname>)

    directories under which to look for skill subdirs. Typical values: the project root and Dir.home. Processed left to right. A bare path means Bundle::Base.new(dir: path) — untrusted, so the unsafe value is the one you get by saying nothing.



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/pikuri/skill/catalog.rb', line 176

def initialize(search_bases:)
  super()
  @roots = []
  @skills = {}
  @untrusted = false

  search_bases.each do |entry|
    base = entry.is_a?(Bundle::Base) ? entry : Bundle::Base.new(dir: entry)
    SKILL_SUBDIRS.each do |sub|
      root = File.join(base.dir.to_s, sub)
      next unless File.directory?(root)

      @roots << root
      scan_root(root, trusted: base.trusted)
    end
  end

  @roots.freeze
  @skills.freeze
  @list = @skills.values.freeze
  freeze
end

Instance Attribute Details

#rootsArray<String> (readonly)

Returns absolute paths of the existing skill directories this catalog covered, in scan order.

Returns:

  • (Array<String>)

    absolute paths of the existing skill directories this catalog covered, in scan order.



212
213
214
# File 'lib/pikuri/skill/catalog.rb', line 212

def roots
  @roots
end

Instance Method Details

#get(name) ⇒ Skill?

Parameters:

  • name (String)

Returns:



206
207
208
# File 'lib/pikuri/skill/catalog.rb', line 206

def get(name)
  @skills[name]
end

#listArray<Skill>

Returns:



200
201
202
# File 'lib/pikuri/skill/catalog.rb', line 200

def list
  @list
end

#untrusted_levelSymbol

Returns :none when every skill this catalog stored came from a trusted base, else :hard.

Returns:

  • (Symbol)

    :none when every skill this catalog stored came from a trusted base, else :hard



216
# File 'lib/pikuri/skill/catalog.rb', line 216

def untrusted_level = @untrusted ? :hard : :none