Skip to content

Commit 8c3a03a

Browse files
committed
Refactor InstallGenerator to support interactive guide selection and enhance configuration options. Added default guide selection and improved handling of external guides. Introduced RuboCop and Solargraph configuration templates for editor rules.
1 parent 01100be commit 8c3a03a

File tree

5 files changed

+161
-11
lines changed

5 files changed

+161
-11
lines changed

lib/generators/memory_bank/install/install_generator.rb

Lines changed: 104 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
# frozen_string_literal: true
22

33
require "rails/generators"
4+
require "fileutils"
5+
require_relative "../../../memory_bank_rails/config"
46

57
module MemoryBank
68
module Generators
79
class InstallGenerator < Rails::Generators::Base
810
source_root File.expand_path("templates", __dir__)
911

10-
class_option :guide, type: :string, default: "rails_web", desc: "Which guide to install"
11-
class_option :with_rules, type: :boolean, default: false, desc: "Copy .cursorrules if available"
12+
class_option :guide, type: :string, default: "select", desc: "Which guide to install (or 'select' to choose interactively)"
13+
class_option :with_rules, type: :boolean, default: false, desc: "Copy editor rules (.cursorrules, RuboCop, Solargraph) if available"
1214

1315
def create_config
1416
template "config/memory_bank.yml.erb", "config/memory_bank.yml"
@@ -20,18 +22,112 @@ def ensure_directories
2022
end
2123

2224
def copy_guide
23-
guide = options["guide"]
24-
guide_dir = File.join(self.class.source_root, "guides", guide)
25-
unless Dir.exist?(guide_dir)
25+
config = MemoryBankRails::Config.load(destination_root)
26+
guides_path = config.dig("memory_bank", "guides_path")
27+
guide = options["guide"].to_s
28+
29+
if guide == "select"
30+
guide, source_kind, external_full_path = select_guide_interactively(guides_path)
31+
else
32+
source_kind, external_full_path = resolve_guide_source(guide, guides_path)
33+
end
34+
35+
case source_kind
36+
when :builtin
37+
copy_file File.join("guides", guide, "developmentGuide.md"), ".memory_bank/developmentGuide.md"
38+
if options["with_rules"] && File.exist?(File.join(self.class.source_root, "guides", guide, ".cursorrules"))
39+
copy_file File.join("guides", guide, ".cursorrules"), ".cursorrules"
40+
end
41+
when :external
42+
copy_external_guide_files(external_full_path)
43+
else
2644
say "Unknown guide: #{guide}", :red
2745
return
2846
end
2947

30-
copy_file File.join("guides", guide, "developmentGuide.md"), ".memory_bank/developmentGuide.md"
31-
if options["with_rules"] && File.exist?(File.join(guide_dir, ".cursorrules"))
32-
copy_file File.join("guides", guide, ".cursorrules"), ".cursorrules"
48+
install_editor_rules if options["with_rules"]
49+
end
50+
51+
private
52+
53+
def resolve_guide_source(guide, guides_path)
54+
builtin_dir = File.join(self.class.source_root, "guides", guide)
55+
return [:builtin, nil] if Dir.exist?(builtin_dir)
56+
57+
if guides_path
58+
expanded = File.expand_path(guides_path)
59+
external_dir = File.join(expanded, guide)
60+
return [:external, external_dir] if Dir.exist?(external_dir)
61+
end
62+
63+
[:unknown, nil]
64+
end
65+
66+
def select_guide_interactively(guides_path)
67+
builtin = available_builtin_guides
68+
external = available_external_guides(guides_path)
69+
options = []
70+
builtin.each { |g| options << ["📦 #{g} (builtin)", :builtin, g, nil] }
71+
external.each { |g, path| options << ["🔧 #{g} (external)", :external, g, path] }
72+
73+
if options.empty?
74+
say "No guides available. Falling back to builtin 'rails_web'", :yellow
75+
return ["rails_web", :builtin, nil]
76+
end
77+
78+
say "\n🚀 Memory Bank Initializer (Rails)", :green
79+
say "=================================\n"
80+
options.each_with_index do |(label, _kind, _slug, _path), idx|
81+
say format("%2d) %s", idx + 1, label)
82+
end
83+
choice = ask("\n? What type of memory bank would you like to install? (1-#{options.size})").to_i
84+
choice = 1 if choice < 1 || choice > options.size
85+
label, kind, slug, full_path = options[choice - 1]
86+
[slug, kind, full_path]
87+
end
88+
89+
def available_builtin_guides
90+
Dir.children(File.join(self.class.source_root, "guides")).select do |entry|
91+
File.exist?(File.join(self.class.source_root, "guides", entry, "developmentGuide.md"))
92+
end.sort
93+
end
94+
95+
def available_external_guides(guides_path)
96+
return [] unless guides_path
97+
expanded = File.expand_path(guides_path)
98+
return [] unless Dir.exist?(expanded)
99+
Dir.children(expanded).filter_map do |entry|
100+
full = File.join(expanded, entry)
101+
guide_md = File.join(full, "developmentGuide.md")
102+
File.directory?(full) && File.exist?(guide_md) ? [entry, full] : nil
103+
end.sort_by(&:first)
104+
end
105+
106+
def copy_external_guide_files(external_dir)
107+
source_md = File.join(external_dir, "developmentGuide.md")
108+
unless File.exist?(source_md)
109+
say "External guide is missing developmentGuide.md: #{external_dir}", :red
110+
return
111+
end
112+
FileUtils.cp(source_md, File.join(destination_root, ".memory_bank", "developmentGuide.md"))
113+
114+
rules = File.join(external_dir, ".cursorrules")
115+
if options["with_rules"] && File.exist?(rules)
116+
FileUtils.cp(rules, File.join(destination_root, ".cursorrules"))
33117
end
34118
end
119+
120+
def install_editor_rules
121+
# Copy base RuboCop and Solargraph configs if they don't exist
122+
rubocop_template = File.join("editor", ".rubocop.yml")
123+
solargraph_template = File.join("editor", ".solargraph.yml")
124+
125+
rubocop_target = File.join(destination_root, ".rubocop.yml")
126+
solargraph_target = File.join(destination_root, ".solargraph.yml")
127+
128+
copy_file(rubocop_template, ".rubocop.yml") unless File.exist?(rubocop_target)
129+
copy_file(solargraph_template, ".solargraph.yml") unless File.exist?(solargraph_target)
130+
end
35131
end
36132
end
37133
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
AllCops:
2+
NewCops: enable
3+
TargetRubyVersion: 3.1
4+
5+
Layout/LineLength:
6+
Max: 120
7+
8+
Metrics/BlockLength:
9+
ExcludedMethods: ['describe', 'context', 'feature']
10+
11+
Style/Documentation:
12+
Enabled: false
13+
14+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
include:
3+
- "app/**/*.rb"
4+
- "lib/**/*.rb"
5+
require:
6+
- rubocop
7+
domains: []
8+
reporters:
9+
- rubocop
10+
plugins: []
11+
12+

lib/memory_bank_rails/cli.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,13 @@ def self.load_rake_tasks
3535
end
3636
end
3737

38+
# Backwards compatible alias used in README one-off example
39+
module MemoryBank
40+
class CLI
41+
def self.run(command)
42+
MemoryBankRails::CLI.run(command)
43+
end
44+
end
45+
end
46+
3847

lib/tasks/memory_bank_rails.rake

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require File.expand_path("../memory_bank_rails/services/report", __dir__)
1010
namespace :memory_bank do
1111
desc "Install Memory Bank (non-interactive). Options: GUIDE=rails_web WITH_RULES=true"
1212
task :init do
13-
guide = ENV["GUIDE"] || "rails_web"
13+
guide = ENV["GUIDE"] || "select"
1414
with_rules = ENV["WITH_RULES"] == "true"
1515

1616
if defined?(Rails::Generators)
@@ -62,8 +62,27 @@ namespace :memory_bank do
6262
current = File.exist?(config_path) ? YAML.safe_load(File.read(config_path)) : {}
6363
current ||= {}
6464
current["memory_bank"] ||= {}
65-
current["memory_bank"]["guides_path"] = ENV["GUIDES_PATH"] if ENV["GUIDES_PATH"]
66-
current["memory_bank"]["default_guide"] = ENV["DEFAULT_GUIDE"] if ENV["DEFAULT_GUIDE"]
65+
guides_path = ENV["GUIDES_PATH"]
66+
default_guide = ENV["DEFAULT_GUIDE"]
67+
68+
unless guides_path && default_guide
69+
# interactive fallback
70+
puts "Configure Memory Bank"
71+
puts "====================="
72+
guides_path ||= begin
73+
print "Guides path (folder containing your custom guides) [leave blank to keep current]: "
74+
input = $stdin.gets&.strip
75+
input unless input.to_s.empty?
76+
end
77+
default_guide ||= begin
78+
print "Default guide slug (e.g., company-rails) [leave blank to keep current]: "
79+
input = $stdin.gets&.strip
80+
input unless input.to_s.empty?
81+
end
82+
end
83+
84+
current["memory_bank"]["guides_path"] = guides_path if guides_path
85+
current["memory_bank"]["default_guide"] = default_guide if default_guide
6786

6887
File.write(config_path, current.to_yaml)
6988
puts "Updated #{config_path}"

0 commit comments

Comments
 (0)