Class: RubynCode::Skills::PackContext
- Inherits:
-
Object
- Object
- RubynCode::Skills::PackContext
- Defined in:
- lib/rubyn_code/skills/pack_context.rb
Overview
Builds additional skill context for PR reviews based on detected gems.
On GitHub App runs (or any run where a Gemfile is present in the repo):
-
Parse the Gemfile for gem names
-
Fetch matching packs from the registry API
-
Format pack skills into a context block for the review agent
The GitHub App has access to ALL packs as a premium differentiator. No local ‘/install-skills` required — everything is fetched transparently.
Usage:
context = PackContext.for_repo(
project_root: '/path/to/repo',
registry_url: 'https://rubyn.ai'
)
review_prompt = context.build_review_context(diff_content)
Constant Summary collapse
- SKILL_NAME_OVERRIDES =
{ 'stripe' => 'stripe/webhooks' }.freeze
Instance Attribute Summary collapse
-
#gems ⇒ Object
readonly
Returns the value of attribute gems.
Class Method Summary collapse
-
.for_packs(pack_names:, registry_client:) ⇒ String
Build context for an external/GitHub App context where we need to fetch the full pack content (not just local skills).
-
.for_repo(project_root:, registry_url: nil) ⇒ PackContext
Factory: build a PackContext for a given repo.
Instance Method Summary collapse
-
#build_context_block ⇒ String
Build a context block listing all detected packs and their skills.
-
#fetch_pack(pack_name) ⇒ Hash?
Fetch and cache pack content from registry.
-
#initialize(gems:, registry_client:) ⇒ PackContext
constructor
A new instance of PackContext.
-
#matched_packs ⇒ Array<String>
Returns the list of packs that matched detected gems.
Constructor Details
#initialize(gems:, registry_client:) ⇒ PackContext
Returns a new instance of PackContext.
55 56 57 58 59 |
# File 'lib/rubyn_code/skills/pack_context.rb', line 55 def initialize(gems:, registry_client:) @gems = gems @registry_client = registry_client @cache = {} end |
Instance Attribute Details
#gems ⇒ Object (readonly)
Returns the value of attribute gems.
53 54 55 |
# File 'lib/rubyn_code/skills/pack_context.rb', line 53 def gems @gems end |
Class Method Details
.for_packs(pack_names:, registry_client:) ⇒ String
Build context for an external/GitHub App context where we need to fetch the full pack content (not just local skills).
49 50 51 |
# File 'lib/rubyn_code/skills/pack_context.rb', line 49 def self.for_packs(pack_names:, registry_client:) new(gems: pack_names, registry_client: registry_client).build_context_block end |
.for_repo(project_root:, registry_url: nil) ⇒ PackContext
Factory: build a PackContext for a given repo.
36 37 38 39 40 41 42 |
# File 'lib/rubyn_code/skills/pack_context.rb', line 36 def self.for_repo(project_root:, registry_url: nil) gemfile_path = File.join(project_root, 'Gemfile') content = File.read(gemfile_path, encoding: 'UTF-8') if File.exist?(gemfile_path) gems = content ? GemfileParser.gems(content) : [] client = RegistryClient.new(base_url: registry_url) new(gems: gems, registry_client: client) end |
Instance Method Details
#build_context_block ⇒ String
Build a context block listing all detected packs and their skills. This is prepended to the review prompt so the agent can apply them.
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 |
# File 'lib/rubyn_code/skills/pack_context.rb', line 100 def build_context_block return '' if matched_packs.empty? lines = [] lines << "\n## Pack-Informed Review Context" intro = 'The following skill packs were detected from the Gemfile and ' \ 'are available for this review (via GitHub App access):' lines << intro lines << '' matched_packs.each do |pack_name| pack = fetch_pack(pack_name) if pack.nil? lines << "- **[#{pack_name}]** (pack not found in registry — skipped)" next end lines << "### Pack: #{pack_name}" lines << pack_description(pack) lines << pack_skills(pack) lines << '' rescue StandardError lines << "- **[#{pack_name}]** (failed to load — skipped)" end lines.join("\n") end |
#fetch_pack(pack_name) ⇒ Hash?
Fetch and cache pack content from registry.
RegistryClient#fetch_pack returns a { data:, etag:, not_modified: } wrapper. We cache and return only the :data payload so callers work with pack attributes directly (e.g. :description, :files) rather than the transport envelope.
87 88 89 90 91 92 93 94 |
# File 'lib/rubyn_code/skills/pack_context.rb', line 87 def fetch_pack(pack_name) return @cache[pack_name] if @cache.key?(pack_name) result = @registry_client.fetch_pack(pack_name) @cache[pack_name] = result[:data] rescue RegistryError @cache[pack_name] = nil end |
#matched_packs ⇒ Array<String>
Returns the list of packs that matched detected gems. Some gems map to pack names (e.g. stripe → stripe/webhooks).
Tradeoff: every detected gem that doesn’t appear in SKILL_NAME_OVERRIDES is queried against the registry as a potential pack name. For a typical Rails app with 40-80 gems this means up to 80 sequential registry calls, each returning a 404 for unknown packs. This is intentional for now:
- Responses are cached in @cache so repeated calls within a session are free
- The GitHub App context is latency-tolerant (async review runs)
- A future batch endpoint on the registry API can reduce this to one call
If latency becomes a problem, add a KNOWN_PACKS allowlist and skip gems that are not in it before fetching.
75 76 77 |
# File 'lib/rubyn_code/skills/pack_context.rb', line 75 def matched_packs @matched_packs ||= gems.filter_map { |gem| pack_name_for(gem) }.uniq end |