Class: Generators::Avo::SkillsGenerator

Inherits:
BaseGenerator
  • Object
show all
Defined in:
lib/generators/avo/skills_generator.rb

Overview

Installs the Avo skills loader into the host app.

Only the loader is copied — the skills themselves stay inside the gem, which is the whole point: a copied skill tree drifts from the locked Avo version with nothing to refresh it. Re-running this generator refreshes the loader.

Deliberately its own namespace rather than part of avo:install, so it does not change behavior for apps that already ran the installer.

Constant Summary collapse

TARGETS =

Claude Code scans .claude/skills; .agents/skills is the cross-agent convention; .cursor/skills is what Cursor reads. Installing to all three is why the loader is copied rather than symlinked — a symlink into a version-named gem directory breaks on the next bundle update.

{
  "claude" => ".claude/skills/avo",
  "agents" => ".agents/skills/avo",
  "cursor" => ".cursor/skills/avo"
}.freeze
LEGACY_ROOTS =

Where a pre-gem install of avo-hq/skills materialized its catalog. npx skills add writes project-locally, so the stale copies land in the same directories this generator installs into.

[
  ".claude/skills",
  ".agents/skills",
  ".cursor/skills"
].freeze
GLOBAL_LEGACY_ROOT =

Checked, reported, and never deleted. This directory is shared by every project on the machine — a skill here may be deliberately installed for a different app, and a generator run inside one project has no business removing it. The user gets the command instead.

"~/.claude/skills"

Instance Method Summary collapse

Methods inherited from BaseGenerator

#initialize

Constructor Details

This class inherits a constructor from Generators::Avo::BaseGenerator

Instance Method Details

#clean_legacy_skillsObject

A leftover catalog sits in the same scan directories as the loader and can shadow it, silently serving instructions written for a different Avo version — which is the whole problem this feature removes. Install time is the right place to catch it: it is the one moment we know the user is present and thinking about skills.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/generators/avo/skills_generator.rb', line 69

def clean_legacy_skills
  leftovers = legacy_skill_dirs
  global = global_legacy_skill_dirs
  return if leftovers.empty? && global.empty?

  if leftovers.any?
    say "\nFound #{pluralize_skills(leftovers.length)} in this project from a previous avo-hq/skills install:"
    by_directory(leftovers).each { |dir, count| say "  #{dir}#{" " * padding(dir)}#{count}", :yellow }
    say "These are not version-pinned and can shadow the skills that ship with your Avo gem."

    if remove_legacy?
      by_directory(leftovers).each do |dir, count|
        leftovers.select { |path| display_dir(path) == dir }.each { |path| FileUtils.rm_rf(path) }
        say_status :remove, "#{dir} (#{pluralize_skills(count)})", :red
      end
    else
      report_kept(leftovers)
    end
  end

  report_global(global) if global.any?
  say "\nIf you installed the Claude Code plugin, remove it with: /plugin uninstall avo-skills"
end

#install_loaderObject



54
55
56
57
58
59
60
61
62
# File 'lib/generators/avo/skills_generator.rb', line 54

def install_loader
  return if panel_cancelled?

  # Only the loader is written. The resolver stays inside the gem, where
  # `bundle update` refreshes it and it cannot drift; a copy in the app
  # would be one more file to keep in sync, and 250 lines of shell in
  # someone's repo is a reasonable thing to be suspicious of.
  destinations.each { |destination| copy_file "skills/SKILL.md", File.join(destination, "SKILL.md") }
end