Module: Rails::Hyperdrive::CanonicalSkillRender

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

Overview

Renders each SKILL.md.erb template in a companion gem repo to the static SKILL.md it pairs with, using the canonical fail-open binding. Companion-repo dev tooling: problems raise.

Defined Under Namespace

Classes: Rendered, Roots

Constant Summary collapse

Error =
GemspecLocator::Error
PUBLIC_SKILLS_DIR =
"skills".freeze

Class Method Summary collapse

Class Method Details

.public_erb_templates(gemspec: nil, dir: Dir.pwd, roots: nil) ⇒ Object

A *.md.erb reachable through a public skills root is copied verbatim by generic skills.sh consumers.



101
102
103
104
105
106
107
108
109
110
# File 'lib/rails/hyperdrive/canonical_skill_render.rb', line 101

def public_erb_templates(gemspec: nil, dir: Dir.pwd, roots: nil)
  roots ||= resolve_roots(gemspec: gemspec, dir: dir)
  templates_root = File.expand_path(roots.templates_root)

  [roots.public_skills_root, roots.skills_root]
    .uniq { |r| File.expand_path(r) }
    .flat_map { |root| Dir.glob(File.join(root, "**", "*.md.erb")) }
    .reject { |path| File.expand_path(path).start_with?("#{templates_root}/") }
    .uniq.sort
end

.render_all(gemspec: nil, dir: Dir.pwd, roots: nil) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/rails/hyperdrive/canonical_skill_render.rb', line 63

def render_all(gemspec: nil, dir: Dir.pwd, roots: nil)
  roots ||= resolve_roots(gemspec: gemspec, dir: dir)
  templates_root = roots.templates_root

  Dir.glob(File.join(templates_root, "**", "SKILL.md.erb")).sort.flat_map do |template|
    rel = File.dirname(template).delete_prefix(templates_root).delete_prefix("/")
    dest_dir = File.join(roots.skills_root, rel)
    # A SKILL.md written beside the SKILL.md.erb would take precedence
    # over it at discovery time, silently demoting the skill to its
    # static face.
    if File.expand_path(dest_dir) == File.expand_path(File.dirname(template))
      raise Error, "#{template}: content dir equals template dir; set " \
                   "skills_dir: in hyperdrive.yml to a separate root"
    end

    body = render_template(template)
    validate_output!(body, template)
    [Rendered.new(template: template, dest: File.join(dest_dir, "SKILL.md"), body: body)] +
      support_renders(File.dirname(template), dest_dir)
  end
end

.resolve_roots(gemspec: nil, dir: Dir.pwd) ⇒ Object

Resolved once per task so a check does not load the gemspec twice.

Raises:



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/rails/hyperdrive/canonical_skill_render.rb', line 28

def resolve_roots(gemspec: nil, dir: Dir.pwd)
  spec, gem_root = GemspecLocator.load_spec(gemspec, dir)
  path = File.join(gem_root, GemManifest.manifest_relpath(spec))
  # Rendering with default roots over a manifest the installer cannot
  # read would mask the bug this surface exists to catch.
  manifest, failure = GemManifest.read_root(path)
  raise Error, "#{path}: #{failure}" if failure

  Roots.new(
    skills_root: File.join(gem_root, manifest_dir(manifest, "skills_dir", path) || PUBLIC_SKILLS_DIR),
    templates_root: File.join(gem_root, manifest_dir(manifest, "skill_templates_dir", path) ||
      File.join("lib", spec.name, "hyperdrive", "skills")),
    public_skills_root: File.join(gem_root, PUBLIC_SKILLS_DIR)
  )
end

.stale(gemspec: nil, dir: Dir.pwd, roots: nil) ⇒ Object



57
58
59
60
61
# File 'lib/rails/hyperdrive/canonical_skill_render.rb', line 57

def stale(gemspec: nil, dir: Dir.pwd, roots: nil)
  render_all(gemspec: gemspec, dir: dir, roots: roots).reject do |r|
    File.file?(r.dest) && File.read(r.dest) == r.body
  end
end

.write(gemspec: nil, dir: Dir.pwd, roots: nil) ⇒ Object



50
51
52
53
54
55
# File 'lib/rails/hyperdrive/canonical_skill_render.rb', line 50

def write(gemspec: nil, dir: Dir.pwd, roots: nil)
  render_all(gemspec: gemspec, dir: dir, roots: roots).each do |r|
    FileUtils.mkdir_p(File.dirname(r.dest))
    File.write(r.dest, r.body)
  end
end