Class: RailsAiBridge::Registry::SkillSourceResolver
- Inherits:
-
Object
- Object
- RailsAiBridge::Registry::SkillSourceResolver
- Defined in:
- lib/rails_ai_bridge/registry/skill_source_resolver.rb
Overview
Resolves remote git skill pack sources by cloning or pulling them into a local cache directory.
Manages a cache of git repositories based on source strings, computing a unique cache key for each source. If the repository is not cached, it clones it; if already cached and the pull TTL window has elapsed, it pulls updates. Otherwise the cached copy is used as-is.
Defined Under Namespace
Classes: ResolutionError
Constant Summary collapse
- PULL_TRACKER_MAX =
Maximum number of distinct cache paths tracked in @last_pulled. Oldest entries are evicted once this limit is exceeded, preventing unbounded growth in long-running MCP server processes.
500
Class Method Summary collapse
-
.compute_cache_key(source, ref = nil) ⇒ String
Computes a cache key for a given source string and optional ref.
-
.default_cache_dir ⇒ String
Resolves the default cache directory, checking RAILS_AI_BRIDGE_CACHE_DIR then HOME.
Instance Method Summary collapse
-
#initialize(cache_dir, git_runner = DefaultGitRunner.new, pull_ttl: 86_400) ⇒ SkillSourceResolver
constructor
Creates a new SkillSourceResolver with the given cache directory and git runner.
-
#resolve(source, ref: nil) ⇒ String
Resolves a source to a local path.
Constructor Details
#initialize(cache_dir, git_runner = DefaultGitRunner.new, pull_ttl: 86_400) ⇒ SkillSourceResolver
Creates a new SkillSourceResolver with the given cache directory and git runner.
153 154 155 156 157 158 159 |
# File 'lib/rails_ai_bridge/registry/skill_source_resolver.rb', line 153 def initialize(cache_dir, git_runner = DefaultGitRunner.new, pull_ttl: 86_400) @cache_dir = validate_cache_dir(cache_dir) @git_runner = git_runner @pull_ttl = pull_ttl @last_pulled = {} # cache_path => Float (monotonic seconds) — in-memory freshness tracking @pull_mutex = Mutex.new end |
Class Method Details
.compute_cache_key(source, ref = nil) ⇒ String
Computes a cache key for a given source string and optional ref.
When a ref is provided the key includes it so that different refs for the same source produce isolated cache directories, preventing cross-ref contamination. Sanitizes non-alphanumeric characters to underscores and appends a SHA256 hash suffix to ensure uniqueness.
183 184 185 186 187 188 |
# File 'lib/rails_ai_bridge/registry/skill_source_resolver.rb', line 183 def self.compute_cache_key(source, ref = nil) identity = ref ? "#{source}@#{ref}" : source sanitized = identity.gsub(/[^a-zA-Z0-9]/, '_') hash = Digest::SHA256.hexdigest(identity)[0..15] "#{sanitized}_#{hash}" end |
.default_cache_dir ⇒ String
Resolves the default cache directory, checking RAILS_AI_BRIDGE_CACHE_DIR then HOME.
165 166 167 168 169 170 171 |
# File 'lib/rails_ai_bridge/registry/skill_source_resolver.rb', line 165 def self.default_cache_dir dir = ENV.fetch('RAILS_AI_BRIDGE_CACHE_DIR', nil) return dir if dir && !dir.strip.empty? home = Dir.home File.join(home, '.rails-ai-bridge', 'cache') end |
Instance Method Details
#resolve(source, ref: nil) ⇒ String
Resolves a source to a local path.
Delegates format detection to RailsAiBridge::Registry::SourceParser. Local paths are returned immediately without any git operations. For git sources:
- When +ref+ is nil (floating branch), a pull is attempted if the pack's TTL window has elapsed, keeping the default branch up-to-date.
- When +ref+ is set (pinned tag, SHA, or named branch), the pull is skipped entirely. A pinned ref is deterministic — there is no reason to pull, and doing so on a detached HEAD (after checkout) fails with "You are not currently on a branch".
Each (source, ref) pair gets its own cache directory so a repo checked out at two different refs coexists without interference.
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/rails_ai_bridge/registry/skill_source_resolver.rb', line 210 def resolve(source, ref: nil) parsed = SourceParser.parse(source) return parsed.resolved_url if parsed.type == :local_path cache_key = self.class.compute_cache_key(source, ref) cache_path = File.join(@cache_dir, cache_key) if File.exist?(cache_path) # Only pull when no ref is pinned. A pinned ref is deterministic and # a previous checkout may have left the repo in detached HEAD, which # causes git pull to fail with "not currently on a branch". perform_pull(source, cache_path) if ref.nil? && pull_stale?(cache_path) else perform_clone(source, cache_path, parsed.resolved_url) end perform_checkout_ref(source, cache_path, ref) if ref cache_path end |