Module: Rails::Hyperdrive::InstallLayout

Defined in:
lib/rails/hyperdrive/install_layout.rb

Overview

The registry every artifact kind declares itself in, and the one place install paths and artifact names are built and parsed. Must stay require-free: the MCP server loads it and must not drag in the install machinery.

Defined Under Namespace

Classes: Dest, Kind

Constant Summary collapse

SKILLS_DIR =
".claude/skills".freeze
HYPERDRIVE_DIR =
".claude/hyperdrive".freeze
AGENTS_DIR =
".claude/agents".freeze
COMMANDS_DIR =
".claude/commands".freeze
INDEX_PATH =
"#{HYPERDRIVE_DIR}/index.md".freeze
LOCK_PATH =
".hyperdrive/lock.yml".freeze
DEFAULT_TARGET =
:claude
KINDS =
{
  skill: Kind.new(
    type: :skill,
    shape: :dir,
    section: "skills",
    key_label: "skill relpath",
    shipped_label: "skill directory",
    dir_key: "skills_dir",
    prefix_key: nil,
    convention_roots: ->(gem_name) { [File.join("lib", gem_name, "hyperdrive", "skills"), "skills"] },
    identity: :frontmatter,
    frontmatter: :required,
    install_body: :verbatim,
    erb: true,
    collision_rewrites_name: true,
    eager: false,
    name_from_dest: ->(dest) { File.basename(File.dirname(dest)) },
    dests: { claude: Dest.new(root: SKILLS_DIR, path: ->(name) { "#{SKILLS_DIR}/#{name}/SKILL.md" }) }
  ),
  skill_support: Kind.new(
    type: :skill_support,
    shape: nil,
    install_body: :verbatim,
    collision_rewrites_name: false,
    eager: false,
    name_from_dest: ->(dest) { dest.split("/")[2] }, # .claude/skills/<name>/<relpath>
    dests: {}
  ),
  guideline: Kind.new(
    type: :guideline,
    shape: :flat,
    section: "guidelines",
    key_label: "guideline filename",
    shipped_label: "guideline",
    dir_key: nil,
    prefix_key: nil,
    convention_roots: ->(gem_name) { [File.join("lib", gem_name, "hyperdrive", "guidelines")] },
    identity: :frontmatter,
    frontmatter: :required,
    install_body: :strip_frontmatter,
    erb: true,
    collision_rewrites_name: false,
    eager: true,
    name_from_dest: ->(dest) { File.basename(dest, ".md") },
    dests: {
      claude: Dest.new(
        root: HYPERDRIVE_DIR,
        path: ->(name) { "#{HYPERDRIVE_DIR}/guidelines/#{name}.md" }
      )
    }
  ),
  agent: Kind.new(
    type: :agent,
    shape: :flat,
    section: "agents",
    key_label: "agent filename",
    shipped_label: "agent",
    dir_key: "agents_dir",
    prefix_key: nil,
    convention_roots: ->(_gem_name) { ["agents"] },
    identity: :frontmatter,
    frontmatter: :required,
    install_body: :verbatim,
    erb: true,
    collision_rewrites_name: true,
    eager: false,
    name_from_dest: ->(dest) { File.basename(dest, ".md") },
    dests: { claude: Dest.new(root: AGENTS_DIR, path: ->(name) { "#{AGENTS_DIR}/#{name}.md" }) }
  ),
  command: Kind.new(
    type: :command,
    shape: :flat,
    section: "commands",
    key_label: "command filename",
    shipped_label: "command",
    dir_key: "commands_dir",
    prefix_key: "command_prefix",
    convention_roots: ->(_gem_name) { ["commands"] },
    identity: :filename_stem,
    frontmatter: :optional,
    install_body: :verbatim,
    erb: true,
    collision_rewrites_name: false,
    eager: false,
    name_from_dest: ->(dest) { File.basename(dest, ".md") },
    dests: { claude: Dest.new(root: COMMANDS_DIR, path: ->(name) { "#{COMMANDS_DIR}/#{name}.md" }) }
  )
}.freeze
ARTIFACT_TYPES =
KINDS.each_with_object({}) { |(type, kind), h| h[kind.lock_kind] = type }.freeze

Class Method Summary collapse

Class Method Details

.base_name(name) ⇒ Object



184
185
186
# File 'lib/rails/hyperdrive/install_layout.rb', line 184

def base_name(name)
  name.split("--").first.to_s
end

.content_kindsObject

The kinds a companion gem ships, in install order.



151
152
153
# File 'lib/rails/hyperdrive/install_layout.rb', line 151

def content_kinds
  KINDS.values.select(&:section)
end

.dest_for(type, final_name, target: DEFAULT_TARGET) ⇒ Object



163
164
165
# File 'lib/rails/hyperdrive/install_layout.rb', line 163

def dest_for(type, final_name, target: DEFAULT_TARGET)
  KINDS[type]&.dest_for(final_name, target: target)
end

.dest_roots(target: DEFAULT_TARGET) ⇒ Object



159
160
161
# File 'lib/rails/hyperdrive/install_layout.rb', line 159

def dest_roots(target: DEFAULT_TARGET)
  content_kinds.filter_map { |kind| kind.dests[target]&.root }.uniq
end

.dir_keysObject



155
156
157
# File 'lib/rails/hyperdrive/install_layout.rb', line 155

def dir_keys
  content_kinds.filter_map(&:dir_key)
end

.installed_name(type, dest) ⇒ Object



171
172
173
# File 'lib/rails/hyperdrive/install_layout.rb', line 171

def installed_name(type, dest)
  KINDS[type]&.installed_name(dest)
end

.kind(type) ⇒ Object



146
147
148
# File 'lib/rails/hyperdrive/install_layout.rb', line 146

def kind(type)
  KINDS[type]
end

.postfixed_name(name, source_gem) ⇒ Object



180
181
182
# File 'lib/rails/hyperdrive/install_layout.rb', line 180

def postfixed_name(name, source_gem)
  "#{name}--#{source_gem}"
end

.sidecar_path(dest) ⇒ Object



167
168
169
# File 'lib/rails/hyperdrive/install_layout.rb', line 167

def sidecar_path(dest)
  "#{dest}.new"
end

.skill_dir_of(dest) ⇒ Object



175
176
177
178
# File 'lib/rails/hyperdrive/install_layout.rb', line 175

def skill_dir_of(dest)
  segments = dest.split("/")
  File.join(*segments[0, 3]) # .claude/skills/<name>
end