Module: RailsAiBridge::Registry

Defined in:
lib/rails_ai_bridge/registry.rb,
lib/rails_ai_bridge/registry/resolver.rb,
lib/rails_ai_bridge/registry/truncatable.rb,
lib/rails_ai_bridge/registry/pack_detector.rb,
lib/rails_ai_bridge/registry/pack_resolver.rb,
lib/rails_ai_bridge/registry/source_parser.rb,
lib/rails_ai_bridge/registry/tile_manifest.rb,
lib/rails_ai_bridge/registry/rake_presenter.rb,
lib/rails_ai_bridge/registry/resolver_cache.rb,
lib/rails_ai_bridge/registry/pack_definition.rb,
lib/rails_ai_bridge/registry/registry_manifest.rb,
lib/rails_ai_bridge/registry/frontmatter_parser.rb,
lib/rails_ai_bridge/registry/skill_source_resolver.rb

Overview

Registry resolution system for skill packs.

Provides priority-based loading of skill packs from git repositories, deprecation redirect handling, and framework auto-detection.

Defined Under Namespace

Modules: GitRunner, SourceParser, Truncatable Classes: AgentEntry, DefaultGitRunner, DeprecatedEntry, DetectedFramework, FrontmatterParser, LoadedPack, PackDefinition, PackDetector, PackResolver, RakePresenter, RegistryManifest, ResolvedSkill, Resolver, ResolverCache, SkillEntry, SkillSourceResolver, SkillSummary, TileManifest

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Attribute Details

#agentsHash{String => AgentEntry} (readonly)

Returns:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/rails_ai_bridge/registry/tile_manifest.rb', line 55

TileManifest = Data.define(:name, :version, :summary, :depends_on, :skills, :agents, :deprecated_skills) do
  # Builds a {TileManifest} from a parsed JSON hash.
  #
  # @param hash [Hash] parsed JSON object
  # @return [TileManifest]
  def self.from_json(hash)
    new(
      name: hash.fetch('name'),
      version: hash.fetch('version'),
      summary: hash['summary'],
      depends_on: hash.fetch('depends_on', []),
      skills: parse_skills(hash['skills'] || {}),
      agents: parse_agents(hash['agents'] || {}),
      deprecated_skills: parse_deprecated(hash['deprecated_skills'] || {})
    )
  rescue KeyError => error
    raise ArgumentError, "Tile manifest missing required field: #{error.key}"
  end

  # Loads and parses a tile manifest from a JSON file on disk.
  #
  # @param path [String] path to the tile JSON file
  # @return [TileManifest]
  # @raise [ArgumentError] if the file does not exist, cannot be read, or contains malformed JSON
  def self.from_file(path)
    from_json(JSON.parse(File.read(path)))
  rescue JSON::ParserError => error
    raise ArgumentError, "Tile manifest at '#{path}' contains invalid JSON: #{error.message}"
  rescue SystemCallError => error
    raise ArgumentError, "Tile manifest at '#{path}' could not be read: #{error.message}"
  end

  # @api private
  def self.parse_skills(skills_hash)
    skills_hash.transform_values do |skill_data|
      SkillEntry.new(
        path: skill_data.fetch('path'),
        description: skill_data['description'],
        tags: skill_data.fetch('tags', [])
      )
    rescue KeyError => error
      raise ArgumentError, "Skill entry missing required field: #{error.key}"
    end
  end

  # @api private
  def self.parse_agents(agents_hash)
    agents_hash.transform_values do |agent_data|
      AgentEntry.new(
        path: agent_data.fetch('path'),
        description: agent_data['description'],
        depends_on: agent_data.fetch('depends_on', [])
      )
    rescue KeyError => error
      raise ArgumentError, "Agent entry missing required field: #{error.key}"
    end
  end

  # @api private
  def self.parse_deprecated(deprecated_hash)
    deprecated_hash.transform_values do |entry_data|
      DeprecatedEntry.new(
        moved_to: entry_data.fetch('moved_to'),
        message: entry_data.fetch('message'),
        removed_in: entry_data['removed_in']
      )
    rescue KeyError => error
      raise ArgumentError, "Deprecated skill entry missing required field: #{error.key}"
    end
  end

  private_class_method :parse_skills, :parse_agents, :parse_deprecated
end

#always_loadedBoolean (readonly)

Returns whether this pack is unconditionally loaded.

Returns:

  • (Boolean)

    whether this pack is unconditionally loaded



18
19
20
21
# File 'lib/rails_ai_bridge/registry/pack_definition.rb', line 18

PackDefinition = Data.define(:source, :tile, :always_loaded, :depends_on, :ref) do
  # @return [Boolean]
  def always_loaded? = always_loaded
end

#base_pathString (readonly)

Returns local filesystem path where the pack is located.

Returns:

  • (String)

    local filesystem path where the pack is located



17
# File 'lib/rails_ai_bridge/registry/resolver.rb', line 17

LoadedPack = Data.define(:name, :tile, :base_path, :priority)

#contentString (readonly)

Returns complete text content of the markdown file.

Returns:

  • (String)

    complete text content of the markdown file



29
# File 'lib/rails_ai_bridge/registry/resolver.rb', line 29

ResolvedSkill = Data.define(:name, :pack, :path, :content)

#default_stackArray<String> (readonly)

Returns pack names loaded when no framework is detected.

Returns:

  • (Array<String>)

    pack names loaded when no framework is detected



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/rails_ai_bridge/registry/registry_manifest.rb', line 13

RegistryManifest = Data.define(:version, :packs, :default_stack) do
  # Builds a {RegistryManifest} from a parsed JSON hash.
  #
  # @param hash [Hash] parsed JSON object
  # @return [RegistryManifest]
  def self.from_json(hash)
    packs = (hash['packs'] || {}).transform_values do |pack_hash|
      PackDefinition.new(
        source: pack_hash.fetch('source'),
        tile: pack_hash.fetch('tile', 'directory.json'),
        always_loaded: pack_hash.fetch('always_loaded', false),
        depends_on: pack_hash.fetch('depends_on', []),
        ref: pack_hash.fetch('ref', nil)
      )
    end

    new(
      version: hash.fetch('version'),
      packs: packs,
      default_stack: hash.fetch('default_stack', [])
    )
  rescue KeyError => error
    raise ArgumentError, "Registry manifest missing required field: #{error.key}"
  end

  # Loads and parses a registry manifest from a JSON file on disk.
  #
  # @param path [String] absolute or relative path to the registry JSON file
  # @return [RegistryManifest]
  # @raise [ArgumentError] if the file does not exist, cannot be read, or contains malformed JSON
  def self.from_file(path)
    from_json(JSON.parse(File.read(path)))
  rescue JSON::ParserError => error
    raise ArgumentError, "Registry manifest at '#{path}' contains invalid JSON: #{error.message}"
  rescue SystemCallError => error
    raise ArgumentError, "Registry manifest at '#{path}' could not be read: #{error.message}"
  end
end

#depends_onArray<String> (readonly)

Returns names of packs this pack depends on.

Returns:

  • (Array<String>)

    names of packs this pack depends on



23
# File 'lib/rails_ai_bridge/registry/tile_manifest.rb', line 23

AgentEntry = Data.define(:path, :description, :depends_on)

#deprecated_skillsHash{String => DeprecatedEntry} (readonly)

Returns:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/rails_ai_bridge/registry/tile_manifest.rb', line 55

TileManifest = Data.define(:name, :version, :summary, :depends_on, :skills, :agents, :deprecated_skills) do
  # Builds a {TileManifest} from a parsed JSON hash.
  #
  # @param hash [Hash] parsed JSON object
  # @return [TileManifest]
  def self.from_json(hash)
    new(
      name: hash.fetch('name'),
      version: hash.fetch('version'),
      summary: hash['summary'],
      depends_on: hash.fetch('depends_on', []),
      skills: parse_skills(hash['skills'] || {}),
      agents: parse_agents(hash['agents'] || {}),
      deprecated_skills: parse_deprecated(hash['deprecated_skills'] || {})
    )
  rescue KeyError => error
    raise ArgumentError, "Tile manifest missing required field: #{error.key}"
  end

  # Loads and parses a tile manifest from a JSON file on disk.
  #
  # @param path [String] path to the tile JSON file
  # @return [TileManifest]
  # @raise [ArgumentError] if the file does not exist, cannot be read, or contains malformed JSON
  def self.from_file(path)
    from_json(JSON.parse(File.read(path)))
  rescue JSON::ParserError => error
    raise ArgumentError, "Tile manifest at '#{path}' contains invalid JSON: #{error.message}"
  rescue SystemCallError => error
    raise ArgumentError, "Tile manifest at '#{path}' could not be read: #{error.message}"
  end

  # @api private
  def self.parse_skills(skills_hash)
    skills_hash.transform_values do |skill_data|
      SkillEntry.new(
        path: skill_data.fetch('path'),
        description: skill_data['description'],
        tags: skill_data.fetch('tags', [])
      )
    rescue KeyError => error
      raise ArgumentError, "Skill entry missing required field: #{error.key}"
    end
  end

  # @api private
  def self.parse_agents(agents_hash)
    agents_hash.transform_values do |agent_data|
      AgentEntry.new(
        path: agent_data.fetch('path'),
        description: agent_data['description'],
        depends_on: agent_data.fetch('depends_on', [])
      )
    rescue KeyError => error
      raise ArgumentError, "Agent entry missing required field: #{error.key}"
    end
  end

  # @api private
  def self.parse_deprecated(deprecated_hash)
    deprecated_hash.transform_values do |entry_data|
      DeprecatedEntry.new(
        moved_to: entry_data.fetch('moved_to'),
        message: entry_data.fetch('message'),
        removed_in: entry_data['removed_in']
      )
    rescue KeyError => error
      raise ArgumentError, "Deprecated skill entry missing required field: #{error.key}"
    end
  end

  private_class_method :parse_skills, :parse_agents, :parse_deprecated
end

#descriptionString? (readonly)

Returns optional description.

Returns:

  • (String, nil)

    optional description



39
# File 'lib/rails_ai_bridge/registry/resolver.rb', line 39

SkillSummary = Data.define(:name, :pack, :description)

#messageString (readonly)

Returns human-readable deprecation message.

Returns:

  • (String)

    human-readable deprecation message



33
34
35
36
37
# File 'lib/rails_ai_bridge/registry/tile_manifest.rb', line 33

DeprecatedEntry = Data.define(:moved_to, :message, :removed_in) do
  # @return [Boolean]
  # :reek:NilCheck
  def removed_in? = !removed_in.nil?
end

#moved_toString (readonly)

Returns name of the skill this has been moved to.

Returns:

  • (String)

    name of the skill this has been moved to



33
34
35
36
37
# File 'lib/rails_ai_bridge/registry/tile_manifest.rb', line 33

DeprecatedEntry = Data.define(:moved_to, :message, :removed_in) do
  # @return [Boolean]
  # :reek:NilCheck
  def removed_in? = !removed_in.nil?
end

#nameString (readonly)

Returns unique pack name, e.g. "ruby-core-skills".

Returns:

  • (String)

    unique pack name, e.g. "ruby-core-skills"



17
# File 'lib/rails_ai_bridge/registry/resolver.rb', line 17

LoadedPack = Data.define(:name, :tile, :base_path, :priority)

#packString (readonly)

Returns source pack name.

Returns:

  • (String)

    source pack name



29
# File 'lib/rails_ai_bridge/registry/resolver.rb', line 29

ResolvedSkill = Data.define(:name, :pack, :path, :content)

#packsHash{String => PackDefinition} (readonly)

Returns map of pack name to definition.

Returns:



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/rails_ai_bridge/registry/registry_manifest.rb', line 13

RegistryManifest = Data.define(:version, :packs, :default_stack) do
  # Builds a {RegistryManifest} from a parsed JSON hash.
  #
  # @param hash [Hash] parsed JSON object
  # @return [RegistryManifest]
  def self.from_json(hash)
    packs = (hash['packs'] || {}).transform_values do |pack_hash|
      PackDefinition.new(
        source: pack_hash.fetch('source'),
        tile: pack_hash.fetch('tile', 'directory.json'),
        always_loaded: pack_hash.fetch('always_loaded', false),
        depends_on: pack_hash.fetch('depends_on', []),
        ref: pack_hash.fetch('ref', nil)
      )
    end

    new(
      version: hash.fetch('version'),
      packs: packs,
      default_stack: hash.fetch('default_stack', [])
    )
  rescue KeyError => error
    raise ArgumentError, "Registry manifest missing required field: #{error.key}"
  end

  # Loads and parses a registry manifest from a JSON file on disk.
  #
  # @param path [String] absolute or relative path to the registry JSON file
  # @return [RegistryManifest]
  # @raise [ArgumentError] if the file does not exist, cannot be read, or contains malformed JSON
  def self.from_file(path)
    from_json(JSON.parse(File.read(path)))
  rescue JSON::ParserError => error
    raise ArgumentError, "Registry manifest at '#{path}' contains invalid JSON: #{error.message}"
  rescue SystemCallError => error
    raise ArgumentError, "Registry manifest at '#{path}' could not be read: #{error.message}"
  end
end

#pathString (readonly)

Returns relative path to the agent markdown file.

Returns:

  • (String)

    relative path to the agent markdown file



29
# File 'lib/rails_ai_bridge/registry/resolver.rb', line 29

ResolvedSkill = Data.define(:name, :pack, :path, :content)

#priorityInteger (readonly)

Returns priority level (lower value is higher priority).

Returns:

  • (Integer)

    priority level (lower value is higher priority)



17
# File 'lib/rails_ai_bridge/registry/resolver.rb', line 17

LoadedPack = Data.define(:name, :tile, :base_path, :priority)

#refString? (readonly)

Returns optional git ref (branch, tag, or SHA) to pin the pack version; nil means use the default branch (HEAD).

Returns:

  • (String, nil)

    optional git ref (branch, tag, or SHA) to pin the pack version; nil means use the default branch (HEAD)



18
19
20
21
# File 'lib/rails_ai_bridge/registry/pack_definition.rb', line 18

PackDefinition = Data.define(:source, :tile, :always_loaded, :depends_on, :ref) do
  # @return [Boolean]
  def always_loaded? = always_loaded
end

#removed_inString? (readonly)

Returns version in which this alias will be removed.

Returns:

  • (String, nil)

    version in which this alias will be removed



33
34
35
36
37
# File 'lib/rails_ai_bridge/registry/tile_manifest.rb', line 33

DeprecatedEntry = Data.define(:moved_to, :message, :removed_in) do
  # @return [Boolean]
  # :reek:NilCheck
  def removed_in? = !removed_in.nil?
end

#skillsHash{String => SkillEntry} (readonly)

Returns:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/rails_ai_bridge/registry/tile_manifest.rb', line 55

TileManifest = Data.define(:name, :version, :summary, :depends_on, :skills, :agents, :deprecated_skills) do
  # Builds a {TileManifest} from a parsed JSON hash.
  #
  # @param hash [Hash] parsed JSON object
  # @return [TileManifest]
  def self.from_json(hash)
    new(
      name: hash.fetch('name'),
      version: hash.fetch('version'),
      summary: hash['summary'],
      depends_on: hash.fetch('depends_on', []),
      skills: parse_skills(hash['skills'] || {}),
      agents: parse_agents(hash['agents'] || {}),
      deprecated_skills: parse_deprecated(hash['deprecated_skills'] || {})
    )
  rescue KeyError => error
    raise ArgumentError, "Tile manifest missing required field: #{error.key}"
  end

  # Loads and parses a tile manifest from a JSON file on disk.
  #
  # @param path [String] path to the tile JSON file
  # @return [TileManifest]
  # @raise [ArgumentError] if the file does not exist, cannot be read, or contains malformed JSON
  def self.from_file(path)
    from_json(JSON.parse(File.read(path)))
  rescue JSON::ParserError => error
    raise ArgumentError, "Tile manifest at '#{path}' contains invalid JSON: #{error.message}"
  rescue SystemCallError => error
    raise ArgumentError, "Tile manifest at '#{path}' could not be read: #{error.message}"
  end

  # @api private
  def self.parse_skills(skills_hash)
    skills_hash.transform_values do |skill_data|
      SkillEntry.new(
        path: skill_data.fetch('path'),
        description: skill_data['description'],
        tags: skill_data.fetch('tags', [])
      )
    rescue KeyError => error
      raise ArgumentError, "Skill entry missing required field: #{error.key}"
    end
  end

  # @api private
  def self.parse_agents(agents_hash)
    agents_hash.transform_values do |agent_data|
      AgentEntry.new(
        path: agent_data.fetch('path'),
        description: agent_data['description'],
        depends_on: agent_data.fetch('depends_on', [])
      )
    rescue KeyError => error
      raise ArgumentError, "Agent entry missing required field: #{error.key}"
    end
  end

  # @api private
  def self.parse_deprecated(deprecated_hash)
    deprecated_hash.transform_values do |entry_data|
      DeprecatedEntry.new(
        moved_to: entry_data.fetch('moved_to'),
        message: entry_data.fetch('message'),
        removed_in: entry_data['removed_in']
      )
    rescue KeyError => error
      raise ArgumentError, "Deprecated skill entry missing required field: #{error.key}"
    end
  end

  private_class_method :parse_skills, :parse_agents, :parse_deprecated
end

#sourceString (readonly)

Returns pack source — local path, full git URL, or "owner/repo" shorthand.

Returns:

  • (String)

    pack source — local path, full git URL, or "owner/repo" shorthand



18
19
20
21
# File 'lib/rails_ai_bridge/registry/pack_definition.rb', line 18

PackDefinition = Data.define(:source, :tile, :always_loaded, :depends_on, :ref) do
  # @return [Boolean]
  def always_loaded? = always_loaded
end

#summaryString? (readonly)

Returns optional human-readable pack description.

Returns:

  • (String, nil)

    optional human-readable pack description



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/rails_ai_bridge/registry/tile_manifest.rb', line 55

TileManifest = Data.define(:name, :version, :summary, :depends_on, :skills, :agents, :deprecated_skills) do
  # Builds a {TileManifest} from a parsed JSON hash.
  #
  # @param hash [Hash] parsed JSON object
  # @return [TileManifest]
  def self.from_json(hash)
    new(
      name: hash.fetch('name'),
      version: hash.fetch('version'),
      summary: hash['summary'],
      depends_on: hash.fetch('depends_on', []),
      skills: parse_skills(hash['skills'] || {}),
      agents: parse_agents(hash['agents'] || {}),
      deprecated_skills: parse_deprecated(hash['deprecated_skills'] || {})
    )
  rescue KeyError => error
    raise ArgumentError, "Tile manifest missing required field: #{error.key}"
  end

  # Loads and parses a tile manifest from a JSON file on disk.
  #
  # @param path [String] path to the tile JSON file
  # @return [TileManifest]
  # @raise [ArgumentError] if the file does not exist, cannot be read, or contains malformed JSON
  def self.from_file(path)
    from_json(JSON.parse(File.read(path)))
  rescue JSON::ParserError => error
    raise ArgumentError, "Tile manifest at '#{path}' contains invalid JSON: #{error.message}"
  rescue SystemCallError => error
    raise ArgumentError, "Tile manifest at '#{path}' could not be read: #{error.message}"
  end

  # @api private
  def self.parse_skills(skills_hash)
    skills_hash.transform_values do |skill_data|
      SkillEntry.new(
        path: skill_data.fetch('path'),
        description: skill_data['description'],
        tags: skill_data.fetch('tags', [])
      )
    rescue KeyError => error
      raise ArgumentError, "Skill entry missing required field: #{error.key}"
    end
  end

  # @api private
  def self.parse_agents(agents_hash)
    agents_hash.transform_values do |agent_data|
      AgentEntry.new(
        path: agent_data.fetch('path'),
        description: agent_data['description'],
        depends_on: agent_data.fetch('depends_on', [])
      )
    rescue KeyError => error
      raise ArgumentError, "Agent entry missing required field: #{error.key}"
    end
  end

  # @api private
  def self.parse_deprecated(deprecated_hash)
    deprecated_hash.transform_values do |entry_data|
      DeprecatedEntry.new(
        moved_to: entry_data.fetch('moved_to'),
        message: entry_data.fetch('message'),
        removed_in: entry_data['removed_in']
      )
    rescue KeyError => error
      raise ArgumentError, "Deprecated skill entry missing required field: #{error.key}"
    end
  end

  private_class_method :parse_skills, :parse_agents, :parse_deprecated
end

#tagsArray<String> (readonly)

Returns optional classification tags.

Returns:

  • (Array<String>)

    optional classification tags



13
# File 'lib/rails_ai_bridge/registry/tile_manifest.rb', line 13

SkillEntry = Data.define(:path, :description, :tags)

#tileString (readonly)

Returns relative path to the pack's tile manifest, usually "directory.json".

Returns:

  • (String)

    relative path to the pack's tile manifest, usually "directory.json"



17
# File 'lib/rails_ai_bridge/registry/resolver.rb', line 17

LoadedPack = Data.define(:name, :tile, :base_path, :priority)

#versionString (readonly)

Returns manifest schema version.

Returns:

  • (String)

    manifest schema version



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/rails_ai_bridge/registry/tile_manifest.rb', line 55

TileManifest = Data.define(:name, :version, :summary, :depends_on, :skills, :agents, :deprecated_skills) do
  # Builds a {TileManifest} from a parsed JSON hash.
  #
  # @param hash [Hash] parsed JSON object
  # @return [TileManifest]
  def self.from_json(hash)
    new(
      name: hash.fetch('name'),
      version: hash.fetch('version'),
      summary: hash['summary'],
      depends_on: hash.fetch('depends_on', []),
      skills: parse_skills(hash['skills'] || {}),
      agents: parse_agents(hash['agents'] || {}),
      deprecated_skills: parse_deprecated(hash['deprecated_skills'] || {})
    )
  rescue KeyError => error
    raise ArgumentError, "Tile manifest missing required field: #{error.key}"
  end

  # Loads and parses a tile manifest from a JSON file on disk.
  #
  # @param path [String] path to the tile JSON file
  # @return [TileManifest]
  # @raise [ArgumentError] if the file does not exist, cannot be read, or contains malformed JSON
  def self.from_file(path)
    from_json(JSON.parse(File.read(path)))
  rescue JSON::ParserError => error
    raise ArgumentError, "Tile manifest at '#{path}' contains invalid JSON: #{error.message}"
  rescue SystemCallError => error
    raise ArgumentError, "Tile manifest at '#{path}' could not be read: #{error.message}"
  end

  # @api private
  def self.parse_skills(skills_hash)
    skills_hash.transform_values do |skill_data|
      SkillEntry.new(
        path: skill_data.fetch('path'),
        description: skill_data['description'],
        tags: skill_data.fetch('tags', [])
      )
    rescue KeyError => error
      raise ArgumentError, "Skill entry missing required field: #{error.key}"
    end
  end

  # @api private
  def self.parse_agents(agents_hash)
    agents_hash.transform_values do |agent_data|
      AgentEntry.new(
        path: agent_data.fetch('path'),
        description: agent_data['description'],
        depends_on: agent_data.fetch('depends_on', [])
      )
    rescue KeyError => error
      raise ArgumentError, "Agent entry missing required field: #{error.key}"
    end
  end

  # @api private
  def self.parse_deprecated(deprecated_hash)
    deprecated_hash.transform_values do |entry_data|
      DeprecatedEntry.new(
        moved_to: entry_data.fetch('moved_to'),
        message: entry_data.fetch('message'),
        removed_in: entry_data['removed_in']
      )
    rescue KeyError => error
      raise ArgumentError, "Deprecated skill entry missing required field: #{error.key}"
    end
  end

  private_class_method :parse_skills, :parse_agents, :parse_deprecated
end

Class Method Details

.build_resolver(config = RailsAiBridge.configuration.registry) ⇒ Resolver?

Builds (or returns a cached) Resolver from the current Config::Registry.

The first call for a given TTL window loads the manifest from disk, wires the SkillSourceResolver and PackResolver pipeline, and caches the result. Subsequent calls within the TTL window return the same resolver without I/O.

Returns +nil+ when the registry manifest file does not exist, allowing callers to surface a helpful setup message rather than raising. A nil result is never cached — the next call will retry.

Parameters:

Returns:

  • (Resolver, nil)

    wired resolver, or nil if manifest file is missing



80
81
82
# File 'lib/rails_ai_bridge/registry.rb', line 80

def self.build_resolver(config = RailsAiBridge.configuration.registry)
  resolver_cache.fetch(config) { build_resolver_uncached(config) }
end

.invalidate_resolver_cache!void

This method returns an undefined value.

Discards the cached resolver so the next build_resolver call rebuilds from disk.

Call this after clearing the git cache or modifying the registry manifest at runtime.



64
65
66
# File 'lib/rails_ai_bridge/registry.rb', line 64

def self.invalidate_resolver_cache!
  @resolver_cache_mutex.synchronize { @resolver_cache&.invalidate! }
end

.resolver_cacheObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the module-level ResolverCache instance, creating it on first call.



53
54
55
56
57
# File 'lib/rails_ai_bridge/registry.rb', line 53

def self.resolver_cache
  @resolver_cache_mutex.synchronize do
    @resolver_cache ||= ResolverCache.new
  end
end