rubyn-code 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +269 -467
  3. data/db/migrations/009_create_teams.sql +6 -6
  4. data/db/migrations/011_fix_mailbox_messages_columns.rb +35 -0
  5. data/db/migrations/012_expand_mailbox_message_types.rb +37 -0
  6. data/exe/rubyn-code +1 -1
  7. data/lib/rubyn_code/agent/RUBYN.md +17 -0
  8. data/lib/rubyn_code/agent/conversation.rb +68 -19
  9. data/lib/rubyn_code/agent/loop.rb +312 -54
  10. data/lib/rubyn_code/agent/loop_detector.rb +6 -6
  11. data/lib/rubyn_code/auth/RUBYN.md +19 -0
  12. data/lib/rubyn_code/auth/oauth.rb +40 -35
  13. data/lib/rubyn_code/auth/server.rb +16 -12
  14. data/lib/rubyn_code/auth/token_store.rb +22 -22
  15. data/lib/rubyn_code/autonomous/RUBYN.md +14 -0
  16. data/lib/rubyn_code/autonomous/daemon.rb +115 -79
  17. data/lib/rubyn_code/autonomous/idle_poller.rb +4 -8
  18. data/lib/rubyn_code/autonomous/task_claimer.rb +11 -11
  19. data/lib/rubyn_code/background/RUBYN.md +13 -0
  20. data/lib/rubyn_code/background/notifier.rb +0 -2
  21. data/lib/rubyn_code/background/worker.rb +60 -15
  22. data/lib/rubyn_code/cli/RUBYN.md +30 -0
  23. data/lib/rubyn_code/cli/app.rb +85 -9
  24. data/lib/rubyn_code/cli/commands/RUBYN.md +133 -0
  25. data/lib/rubyn_code/cli/commands/base.rb +53 -0
  26. data/lib/rubyn_code/cli/commands/budget.rb +24 -0
  27. data/lib/rubyn_code/cli/commands/clear.rb +16 -0
  28. data/lib/rubyn_code/cli/commands/compact.rb +21 -0
  29. data/lib/rubyn_code/cli/commands/context.rb +44 -0
  30. data/lib/rubyn_code/cli/commands/context_info.rb +56 -0
  31. data/lib/rubyn_code/cli/commands/cost.rb +23 -0
  32. data/lib/rubyn_code/cli/commands/diff.rb +30 -0
  33. data/lib/rubyn_code/cli/commands/doctor.rb +112 -0
  34. data/lib/rubyn_code/cli/commands/help.rb +41 -0
  35. data/lib/rubyn_code/cli/commands/model.rb +37 -0
  36. data/lib/rubyn_code/cli/commands/plan.rb +22 -0
  37. data/lib/rubyn_code/cli/commands/quit.rb +17 -0
  38. data/lib/rubyn_code/cli/commands/registry.rb +64 -0
  39. data/lib/rubyn_code/cli/commands/resume.rb +51 -0
  40. data/lib/rubyn_code/cli/commands/review.rb +26 -0
  41. data/lib/rubyn_code/cli/commands/skill.rb +32 -0
  42. data/lib/rubyn_code/cli/commands/spawn.rb +24 -0
  43. data/lib/rubyn_code/cli/commands/tasks.rb +32 -0
  44. data/lib/rubyn_code/cli/commands/tokens.rb +76 -0
  45. data/lib/rubyn_code/cli/commands/undo.rb +17 -0
  46. data/lib/rubyn_code/cli/commands/version.rb +16 -0
  47. data/lib/rubyn_code/cli/daemon_runner.rb +129 -0
  48. data/lib/rubyn_code/cli/input_handler.rb +20 -23
  49. data/lib/rubyn_code/cli/renderer.rb +25 -27
  50. data/lib/rubyn_code/cli/repl.rb +161 -194
  51. data/lib/rubyn_code/cli/setup.rb +117 -0
  52. data/lib/rubyn_code/cli/spinner.rb +40 -40
  53. data/lib/rubyn_code/cli/stream_formatter.rb +29 -28
  54. data/lib/rubyn_code/cli/version_check.rb +94 -0
  55. data/lib/rubyn_code/config/RUBYN.md +14 -0
  56. data/lib/rubyn_code/config/defaults.rb +28 -19
  57. data/lib/rubyn_code/config/project_config.rb +7 -9
  58. data/lib/rubyn_code/config/settings.rb +3 -3
  59. data/lib/rubyn_code/context/RUBYN.md +20 -0
  60. data/lib/rubyn_code/context/auto_compact.rb +7 -7
  61. data/lib/rubyn_code/context/compactor.rb +2 -2
  62. data/lib/rubyn_code/context/context_collapse.rb +45 -0
  63. data/lib/rubyn_code/context/manager.rb +20 -3
  64. data/lib/rubyn_code/context/manual_compact.rb +7 -7
  65. data/lib/rubyn_code/context/micro_compact.rb +12 -12
  66. data/lib/rubyn_code/db/RUBYN.md +40 -0
  67. data/lib/rubyn_code/db/connection.rb +13 -13
  68. data/lib/rubyn_code/db/migrator.rb +67 -27
  69. data/lib/rubyn_code/db/schema.rb +6 -6
  70. data/lib/rubyn_code/debug.rb +74 -0
  71. data/lib/rubyn_code/hooks/RUBYN.md +17 -0
  72. data/lib/rubyn_code/hooks/built_in.rb +9 -9
  73. data/lib/rubyn_code/hooks/registry.rb +5 -5
  74. data/lib/rubyn_code/hooks/runner.rb +1 -1
  75. data/lib/rubyn_code/hooks/user_hooks.rb +16 -16
  76. data/lib/rubyn_code/learning/RUBYN.md +16 -0
  77. data/lib/rubyn_code/learning/extractor.rb +22 -22
  78. data/lib/rubyn_code/learning/injector.rb +17 -18
  79. data/lib/rubyn_code/learning/instinct.rb +18 -14
  80. data/lib/rubyn_code/llm/RUBYN.md +15 -0
  81. data/lib/rubyn_code/llm/client.rb +121 -55
  82. data/lib/rubyn_code/llm/message_builder.rb +19 -15
  83. data/lib/rubyn_code/llm/streaming.rb +80 -50
  84. data/lib/rubyn_code/mcp/RUBYN.md +21 -0
  85. data/lib/rubyn_code/mcp/client.rb +25 -24
  86. data/lib/rubyn_code/mcp/config.rb +7 -7
  87. data/lib/rubyn_code/mcp/sse_transport.rb +27 -26
  88. data/lib/rubyn_code/mcp/stdio_transport.rb +22 -19
  89. data/lib/rubyn_code/mcp/tool_bridge.rb +32 -32
  90. data/lib/rubyn_code/memory/RUBYN.md +17 -0
  91. data/lib/rubyn_code/memory/models.rb +3 -3
  92. data/lib/rubyn_code/memory/search.rb +17 -17
  93. data/lib/rubyn_code/memory/session_persistence.rb +49 -34
  94. data/lib/rubyn_code/memory/store.rb +17 -17
  95. data/lib/rubyn_code/observability/RUBYN.md +19 -0
  96. data/lib/rubyn_code/observability/budget_enforcer.rb +16 -15
  97. data/lib/rubyn_code/observability/cost_calculator.rb +3 -3
  98. data/lib/rubyn_code/observability/token_counter.rb +1 -1
  99. data/lib/rubyn_code/observability/usage_reporter.rb +35 -35
  100. data/lib/rubyn_code/output/RUBYN.md +11 -0
  101. data/lib/rubyn_code/output/diff_renderer.rb +6 -6
  102. data/lib/rubyn_code/output/formatter.rb +4 -4
  103. data/lib/rubyn_code/permissions/RUBYN.md +17 -0
  104. data/lib/rubyn_code/permissions/prompter.rb +8 -8
  105. data/lib/rubyn_code/protocols/RUBYN.md +14 -0
  106. data/lib/rubyn_code/protocols/interrupt_handler.rb +1 -1
  107. data/lib/rubyn_code/protocols/plan_approval.rb +9 -9
  108. data/lib/rubyn_code/protocols/shutdown_handshake.rb +9 -11
  109. data/lib/rubyn_code/skills/RUBYN.md +19 -0
  110. data/lib/rubyn_code/skills/catalog.rb +7 -7
  111. data/lib/rubyn_code/skills/document.rb +15 -15
  112. data/lib/rubyn_code/skills/loader.rb +6 -8
  113. data/lib/rubyn_code/sub_agents/RUBYN.md +12 -0
  114. data/lib/rubyn_code/sub_agents/runner.rb +15 -15
  115. data/lib/rubyn_code/sub_agents/summarizer.rb +1 -1
  116. data/lib/rubyn_code/tasks/RUBYN.md +13 -0
  117. data/lib/rubyn_code/tasks/dag.rb +12 -16
  118. data/lib/rubyn_code/tasks/manager.rb +24 -24
  119. data/lib/rubyn_code/tasks/models.rb +4 -4
  120. data/lib/rubyn_code/teams/RUBYN.md +14 -0
  121. data/lib/rubyn_code/teams/mailbox.rb +38 -18
  122. data/lib/rubyn_code/teams/manager.rb +19 -19
  123. data/lib/rubyn_code/teams/teammate.rb +3 -4
  124. data/lib/rubyn_code/tools/RUBYN.md +38 -0
  125. data/lib/rubyn_code/tools/background_run.rb +9 -11
  126. data/lib/rubyn_code/tools/base.rb +54 -3
  127. data/lib/rubyn_code/tools/bash.rb +16 -34
  128. data/lib/rubyn_code/tools/bundle_add.rb +10 -12
  129. data/lib/rubyn_code/tools/bundle_install.rb +9 -11
  130. data/lib/rubyn_code/tools/compact.rb +10 -9
  131. data/lib/rubyn_code/tools/db_migrate.rb +17 -15
  132. data/lib/rubyn_code/tools/edit_file.rb +12 -12
  133. data/lib/rubyn_code/tools/executor.rb +9 -4
  134. data/lib/rubyn_code/tools/git_commit.rb +29 -34
  135. data/lib/rubyn_code/tools/git_diff.rb +17 -18
  136. data/lib/rubyn_code/tools/git_log.rb +17 -19
  137. data/lib/rubyn_code/tools/git_status.rb +18 -20
  138. data/lib/rubyn_code/tools/glob.rb +7 -9
  139. data/lib/rubyn_code/tools/grep.rb +11 -9
  140. data/lib/rubyn_code/tools/load_skill.rb +7 -7
  141. data/lib/rubyn_code/tools/memory_search.rb +13 -12
  142. data/lib/rubyn_code/tools/memory_write.rb +14 -12
  143. data/lib/rubyn_code/tools/rails_generate.rb +16 -16
  144. data/lib/rubyn_code/tools/read_file.rb +8 -7
  145. data/lib/rubyn_code/tools/read_inbox.rb +5 -5
  146. data/lib/rubyn_code/tools/registry.rb +2 -2
  147. data/lib/rubyn_code/tools/review_pr.rb +55 -55
  148. data/lib/rubyn_code/tools/run_specs.rb +20 -19
  149. data/lib/rubyn_code/tools/schema.rb +9 -11
  150. data/lib/rubyn_code/tools/send_message.rb +10 -10
  151. data/lib/rubyn_code/tools/spawn_agent.rb +51 -23
  152. data/lib/rubyn_code/tools/spawn_teammate.rb +21 -21
  153. data/lib/rubyn_code/tools/task.rb +28 -28
  154. data/lib/rubyn_code/tools/web_fetch.rb +46 -31
  155. data/lib/rubyn_code/tools/web_search.rb +64 -66
  156. data/lib/rubyn_code/tools/write_file.rb +7 -6
  157. data/lib/rubyn_code/version.rb +1 -1
  158. data/lib/rubyn_code.rb +136 -105
  159. metadata +94 -21
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module RubynCode
6
+ module CLI
7
+ # Boots all dependencies and starts the GOLEM daemon from the CLI.
8
+ # Handles authentication, database setup, and lifecycle output.
9
+ #
10
+ # Usage:
11
+ # rubyn-code daemon --name golem-1 --max-runs 50 --max-cost 5.0
12
+ class DaemonRunner
13
+ def initialize(options)
14
+ @options = options
15
+ @daemon_opts = options.fetch(:daemon, {})
16
+ @renderer = Renderer.new
17
+ @project_root = Dir.pwd
18
+ end
19
+
20
+ def run
21
+ ensure_home_dir!
22
+ ensure_auth!
23
+ setup_database!
24
+ display_banner!
25
+
26
+ daemon = build_daemon
27
+ daemon.start!
28
+
29
+ display_shutdown_summary(daemon)
30
+ rescue Interrupt
31
+ @renderer.info("\nShutting down daemon...")
32
+ display_shutdown_summary(daemon) if daemon
33
+ rescue StandardError => e
34
+ @renderer.error("Daemon failed: #{e.message}")
35
+ RubynCode::Debug.warn(e.backtrace&.first(5)&.join("\n")) if @options[:debug]
36
+ exit(1)
37
+ end
38
+
39
+ private
40
+
41
+ def build_daemon
42
+ Autonomous::Daemon.new(
43
+ agent_name: @daemon_opts[:agent_name],
44
+ role: @daemon_opts[:role],
45
+ llm_client: @llm_client,
46
+ project_root: @project_root,
47
+ task_manager: @task_manager,
48
+ mailbox: @mailbox,
49
+ max_runs: @daemon_opts[:max_runs],
50
+ max_cost: @daemon_opts[:max_cost],
51
+ poll_interval: @daemon_opts[:poll_interval],
52
+ idle_timeout: @daemon_opts[:idle_timeout],
53
+ on_state_change: method(:on_state_change),
54
+ on_task_complete: method(:on_task_complete),
55
+ on_task_error: method(:on_task_error)
56
+ )
57
+ end
58
+
59
+ # ── Callbacks ────────────────────────────────────────────────
60
+
61
+ def on_state_change(old_state, new_state)
62
+ @renderer.info(" [#{@daemon_opts[:agent_name]}] #{old_state} → #{new_state}")
63
+ end
64
+
65
+ def on_task_complete(task, result)
66
+ summary = result.to_s[0..200]
67
+ @renderer.success(" ✓ Completed: #{task.title} — #{summary}")
68
+ end
69
+
70
+ def on_task_error(task, error)
71
+ @renderer.error(" ✗ Failed: #{task.title} — #{error.message}")
72
+ end
73
+
74
+ # ── Setup ────────────────────────────────────────────────────
75
+
76
+ def ensure_home_dir!
77
+ dir = Config::Defaults::HOME_DIR
78
+ FileUtils.mkdir_p(dir)
79
+ end
80
+
81
+ def ensure_auth!
82
+ unless Auth::TokenStore.valid?
83
+ @renderer.error('No valid authentication found.')
84
+ @renderer.info('Run `rubyn-code --auth` or set ANTHROPIC_API_KEY first.')
85
+ exit(1)
86
+ end
87
+
88
+ @llm_client = LLM::Client.new
89
+ end
90
+
91
+ def setup_database!
92
+ @db = DB::Connection.instance
93
+ DB::Migrator.new(@db).migrate!
94
+ @task_manager = Tasks::Manager.new(@db)
95
+ @mailbox = Teams::Mailbox.new(@db)
96
+ end
97
+
98
+ def display_banner!
99
+ @renderer.info('╔══════════════════════════════════════╗')
100
+ @renderer.info('║ GOLEM Daemon Starting ║')
101
+ @renderer.info('╚══════════════════════════════════════╝')
102
+ @renderer.info(" Agent: #{@daemon_opts[:agent_name]}")
103
+ @renderer.info(" Role: #{@daemon_opts[:role]}")
104
+ @renderer.info(" Project: #{@project_root}")
105
+ @renderer.info(" Max runs: #{@daemon_opts[:max_runs]}")
106
+ @renderer.info(" Max cost: $#{@daemon_opts[:max_cost]}")
107
+ @renderer.info(" Idle timeout: #{@daemon_opts[:idle_timeout]}s")
108
+ @renderer.info(" Poll interval: #{@daemon_opts[:poll_interval]}s")
109
+ @renderer.info('')
110
+ @renderer.info('Waiting for tasks... (Ctrl-C to stop)')
111
+ @renderer.info('Seed tasks via the REPL: /tasks or the task tool.')
112
+ @renderer.info('')
113
+ end
114
+
115
+ def display_shutdown_summary(daemon)
116
+ return unless daemon
117
+
118
+ status = daemon.status
119
+ @renderer.info('')
120
+ @renderer.info('╔══════════════════════════════════════╗')
121
+ @renderer.info('║ GOLEM Daemon Stopped ║')
122
+ @renderer.info('╚══════════════════════════════════════╝')
123
+ @renderer.info(" Final state: #{status[:state]}")
124
+ @renderer.info(" Tasks completed: #{status[:runs_completed]}")
125
+ @renderer.info(" Total cost: $#{'%.4f' % status[:total_cost]}")
126
+ end
127
+ end
128
+ end
129
+ end
@@ -3,33 +3,28 @@
3
3
  module RubynCode
4
4
  module CLI
5
5
  class InputHandler
6
+ # Legacy mapping for backward compatibility when no registry is provided.
7
+ # New code should use the command registry instead.
6
8
  SLASH_COMMANDS = {
7
- "/quit" => :quit,
8
- "/exit" => :quit,
9
- "/q" => :quit,
10
- "/compact" => :compact,
11
- "/cost" => :cost,
12
- "/clear" => :clear,
13
- "/undo" => :undo,
14
- "/help" => :help,
15
- "/tasks" => :tasks,
16
- "/budget" => :budget,
17
- "/resume" => :resume,
18
- "/skill" => :skill,
19
- "/version" => :version,
20
- "/review" => :review,
21
- "/spawn" => :spawn_teammate
9
+ '/quit' => :quit,
10
+ '/exit' => :quit,
11
+ '/q' => :quit
22
12
  }.freeze
23
13
 
24
14
  Command = Data.define(:action, :args)
25
15
 
16
+ # @param command_registry [CLI::Commands::Registry, nil]
17
+ def initialize(command_registry: nil)
18
+ @command_registry = command_registry
19
+ end
20
+
26
21
  def parse(input)
27
22
  return Command.new(action: :quit, args: []) if input.nil?
28
23
 
29
24
  stripped = input.strip
30
25
  return Command.new(action: :empty, args: []) if stripped.empty?
31
26
 
32
- if stripped.start_with?("/")
27
+ if stripped.start_with?('/')
33
28
  parse_slash_command(stripped)
34
29
  else
35
30
  Command.new(action: :message, args: [process_file_references(stripped)])
@@ -37,26 +32,28 @@ module RubynCode
37
32
  end
38
33
 
39
34
  def multiline?(line)
40
- line&.end_with?("\\")
35
+ line&.end_with?('\\')
41
36
  end
42
37
 
43
38
  def strip_continuation(line)
44
- line.chomp("\\")
39
+ line.chomp('\\')
45
40
  end
46
41
 
47
42
  private
48
43
 
49
44
  def parse_slash_command(input)
50
- return Command.new(action: :list_commands, args: []) if input.strip == "/"
45
+ return Command.new(action: :list_commands, args: []) if input.strip == '/'
51
46
 
52
47
  parts = input.split(/\s+/, 2)
53
48
  cmd = parts[0].downcase
54
49
  args = parts[1]&.split(/\s+/) || []
55
50
 
56
- action = SLASH_COMMANDS[cmd]
51
+ # Quick exit for /quit and friends
52
+ return Command.new(action: :quit, args: []) if SLASH_COMMANDS[cmd] == :quit
57
53
 
58
- if action
59
- Command.new(action: action, args: args)
54
+ # Dispatch through registry if available
55
+ if @command_registry&.known?(cmd)
56
+ Command.new(action: :slash_command, args: [cmd, *args])
60
57
  else
61
58
  Command.new(action: :unknown_command, args: [cmd])
62
59
  end
@@ -66,7 +63,7 @@ module RubynCode
66
63
  input.gsub(/@(\S+)/) do |match|
67
64
  path = Regexp.last_match(1)
68
65
  if File.exist?(path)
69
- content = File.read(path, encoding: "utf-8")
66
+ content = File.read(path, encoding: 'utf-8')
70
67
  truncated = content.length > 50_000 ? "#{content[0...50_000]}\n[truncated]" : content
71
68
  "\n<file path=\"#{path}\">\n#{truncated}\n</file>\n"
72
69
  else
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pastel"
4
- require "rouge"
3
+ require 'pastel'
4
+ require 'rouge'
5
5
 
6
6
  module RubynCode
7
7
  module CLI
@@ -33,7 +33,7 @@ module RubynCode
33
33
  puts @pastel.cyan(" > #{name}: #{format_params(params)}")
34
34
  end
35
35
 
36
- def tool_result(name, output)
36
+ def tool_result(_name, output)
37
37
  truncated = output.to_s[0...300]
38
38
  lines = truncated.lines
39
39
  if lines.length > 6
@@ -69,17 +69,17 @@ module RubynCode
69
69
  end
70
70
 
71
71
  def welcome
72
- puts @pastel.bold.cyan("╔══════════════════════════════════════╗")
72
+ puts @pastel.bold.cyan('╔══════════════════════════════════════╗')
73
73
  puts @pastel.bold.cyan("║ Rubyn Code v#{RubynCode::VERSION} ║")
74
- puts @pastel.bold.cyan("║ Ruby & Rails Agentic Assistant ║")
75
- puts @pastel.bold.cyan("╚══════════════════════════════════════╝")
74
+ puts @pastel.bold.cyan('║ Ruby & Rails Agentic Assistant ║')
75
+ puts @pastel.bold.cyan('╚══════════════════════════════════════╝')
76
76
  puts
77
- puts @pastel.dim("Type /help for commands, /quit to exit")
77
+ puts @pastel.dim('Type /help for commands, /quit to exit')
78
78
  puts
79
79
  end
80
80
 
81
81
  def cost_summary(session_cost:, daily_cost:, tokens:)
82
- puts @pastel.bold("Cost Summary:")
82
+ puts @pastel.bold('Cost Summary:')
83
83
  puts " Session: $#{'%.4f' % session_cost}"
84
84
  puts " Today: $#{'%.4f' % daily_cost}"
85
85
  puts " Tokens: #{tokens[:input]} in / #{tokens[:output]} out"
@@ -87,9 +87,9 @@ module RubynCode
87
87
 
88
88
  def prompt
89
89
  if @yolo
90
- @pastel.bold.green("rubyn ") + @pastel.bold.red("YOLO") + @pastel.bold.green(" > ")
90
+ @pastel.bold.green('rubyn ') + @pastel.bold.red('YOLO') + @pastel.bold.green(' > ')
91
91
  else
92
- @pastel.bold.green("rubyn > ")
92
+ @pastel.bold.green('rubyn > ')
93
93
  end
94
94
  end
95
95
 
@@ -113,7 +113,7 @@ module RubynCode
113
113
  else
114
114
  in_code_block = true
115
115
  code_lang = line.strip.match(/\A```(\w*)/)[1]
116
- code_lang = "ruby" if code_lang.empty?
116
+ code_lang = 'ruby' if code_lang.empty?
117
117
  end
118
118
  elsif in_code_block
119
119
  code_buffer << line
@@ -123,9 +123,7 @@ module RubynCode
123
123
  end
124
124
 
125
125
  # Flush any unclosed code block
126
- unless code_buffer.empty?
127
- result << render_code_block(code_buffer.join, code_lang || "ruby")
128
- end
126
+ result << render_code_block(code_buffer.join, code_lang || 'ruby') unless code_buffer.empty?
129
127
 
130
128
  result.join
131
129
  end
@@ -133,9 +131,9 @@ module RubynCode
133
131
  def render_code_block(code, lang)
134
132
  lexer = Rouge::Lexer.find(lang) || Rouge::Lexers::PlainText.new
135
133
  highlighted = @rouge_formatter.format(lexer.lex(code))
136
- border = @pastel.dim("")
134
+ border = @pastel.dim('')
137
135
  formatted = highlighted.lines.map { |l| "#{border}#{l}" }.join
138
- "\n#{@pastel.dim(" ┌─ #{lang}")}\n#{formatted}#{@pastel.dim(" └─")}\n"
136
+ "\n#{@pastel.dim(" ┌─ #{lang}")}\n#{formatted}#{@pastel.dim(' └─')}\n"
139
137
  rescue StandardError
140
138
  "\n#{code}\n"
141
139
  end
@@ -144,7 +142,7 @@ module RubynCode
144
142
  # Headers
145
143
  if line.match?(/\A\s*\#{1,6}\s/)
146
144
  level = line.match(/\A\s*(\#{1,6})\s/)[1].length
147
- text = line.sub(/\A\s*\#{1,6}\s+/, "").rstrip
145
+ text = line.sub(/\A\s*\#{1,6}\s+/, '').rstrip
148
146
  case level
149
147
  when 1 then "#{@pastel.bold.underline(text)}\n"
150
148
  when 2 then "#{@pastel.bold(text)}\n"
@@ -153,19 +151,19 @@ module RubynCode
153
151
  # Bullet lists
154
152
  elsif line.match?(/\A\s*[-*]\s/)
155
153
  indent = line.match(/\A(\s*)/)[1]
156
- content = line.sub(/\A\s*[-*]\s+/, "")
157
- "#{indent} #{@pastel.cyan("")} #{render_inline(content)}"
154
+ content = line.sub(/\A\s*[-*]\s+/, '')
155
+ "#{indent} #{@pastel.cyan('')} #{render_inline(content)}"
158
156
  # Numbered lists
159
157
  elsif line.match?(/\A\s*\d+\.\s/)
160
158
  indent = line.match(/\A(\s*)/)[1]
161
159
  num = line.match(/(\d+)\./)[1]
162
- content = line.sub(/\A\s*\d+\.\s+/, "")
163
- "#{indent} #{@pastel.cyan(num + ".")} #{render_inline(content)}"
160
+ content = line.sub(/\A\s*\d+\.\s+/, '')
161
+ "#{indent} #{@pastel.cyan("#{num}.")} #{render_inline(content)}"
164
162
  # Horizontal rules
165
163
  elsif line.strip.match?(/\A-{3,}\z/)
166
- "#{@pastel.dim("" * [terminal_width - 4, 40].min)}\n"
164
+ "#{@pastel.dim('' * [terminal_width - 4, 40].min)}\n"
167
165
  # Table rows
168
- elsif line.include?("|")
166
+ elsif line.include?('|')
169
167
  render_table_row(line)
170
168
  else
171
169
  render_inline(line)
@@ -180,16 +178,16 @@ module RubynCode
180
178
  end
181
179
 
182
180
  def render_table_row(line)
183
- return "" if line.strip.match?(/\A\|[\s\-:|]+\|\z/) # separator row
181
+ return '' if line.strip.match?(/\A\|[\s\-:|]+\|\z/) # separator row
184
182
 
185
- cells = line.split("|").map(&:strip).reject(&:empty?)
186
- " #{cells.map { |c| render_inline(c) }.join("")}\n"
183
+ cells = line.split('|').map(&:strip).reject(&:empty?)
184
+ " #{cells.map { |c| render_inline(c) }.join('')}\n"
187
185
  end
188
186
 
189
187
  def format_params(params)
190
188
  case params
191
189
  when Hash
192
- params.map { |k, v| "#{k}=#{v.to_s[0...80]}" }.join(", ")
190
+ params.map { |k, v| "#{k}=#{v.to_s[0...80]}" }.join(', ')
193
191
  else
194
192
  params.to_s[0...200]
195
193
  end