fantasy-cli 1.2.6

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 (112) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +456 -0
  4. data/bin/gsd +8 -0
  5. data/bin/gsd-core-darwin-amd64 +0 -0
  6. data/bin/gsd-core-darwin-arm64 +0 -0
  7. data/bin/gsd-core-linux-amd64 +0 -0
  8. data/bin/gsd-core-linux-arm64 +0 -0
  9. data/bin/gsd-core-windows-amd64.exe +0 -0
  10. data/bin/gsd-core-windows-arm64.exe +0 -0
  11. data/bin/gsd-core.exe +0 -0
  12. data/lib/gsd/agents/coordinator.rb +195 -0
  13. data/lib/gsd/agents/task_manager.rb +158 -0
  14. data/lib/gsd/agents/worker.rb +162 -0
  15. data/lib/gsd/agents.rb +30 -0
  16. data/lib/gsd/ai/chat.rb +486 -0
  17. data/lib/gsd/ai/cli.rb +248 -0
  18. data/lib/gsd/ai/command_parser.rb +97 -0
  19. data/lib/gsd/ai/commands/base.rb +42 -0
  20. data/lib/gsd/ai/commands/clear.rb +20 -0
  21. data/lib/gsd/ai/commands/context.rb +30 -0
  22. data/lib/gsd/ai/commands/cost.rb +30 -0
  23. data/lib/gsd/ai/commands/export.rb +42 -0
  24. data/lib/gsd/ai/commands/help.rb +61 -0
  25. data/lib/gsd/ai/commands/model.rb +67 -0
  26. data/lib/gsd/ai/commands/reset.rb +22 -0
  27. data/lib/gsd/ai/config.rb +256 -0
  28. data/lib/gsd/ai/context.rb +324 -0
  29. data/lib/gsd/ai/cost_tracker.rb +361 -0
  30. data/lib/gsd/ai/git_context.rb +169 -0
  31. data/lib/gsd/ai/history.rb +384 -0
  32. data/lib/gsd/ai/providers/anthropic.rb +429 -0
  33. data/lib/gsd/ai/providers/base.rb +282 -0
  34. data/lib/gsd/ai/providers/lmstudio.rb +279 -0
  35. data/lib/gsd/ai/providers/ollama.rb +336 -0
  36. data/lib/gsd/ai/providers/openai.rb +396 -0
  37. data/lib/gsd/ai/providers/openrouter.rb +429 -0
  38. data/lib/gsd/ai/reference_resolver.rb +225 -0
  39. data/lib/gsd/ai/repl.rb +349 -0
  40. data/lib/gsd/ai/streaming.rb +438 -0
  41. data/lib/gsd/ai/ui.rb +429 -0
  42. data/lib/gsd/buddy/cli.rb +284 -0
  43. data/lib/gsd/buddy/gacha.rb +148 -0
  44. data/lib/gsd/buddy/renderer.rb +108 -0
  45. data/lib/gsd/buddy/species.rb +190 -0
  46. data/lib/gsd/buddy/stats.rb +156 -0
  47. data/lib/gsd/buddy.rb +28 -0
  48. data/lib/gsd/cli.rb +455 -0
  49. data/lib/gsd/commands.rb +198 -0
  50. data/lib/gsd/config.rb +183 -0
  51. data/lib/gsd/error.rb +188 -0
  52. data/lib/gsd/frontmatter.rb +123 -0
  53. data/lib/gsd/go/bridge.rb +173 -0
  54. data/lib/gsd/history.rb +76 -0
  55. data/lib/gsd/milestone.rb +75 -0
  56. data/lib/gsd/output.rb +184 -0
  57. data/lib/gsd/phase.rb +102 -0
  58. data/lib/gsd/plugins/base.rb +92 -0
  59. data/lib/gsd/plugins/cli.rb +330 -0
  60. data/lib/gsd/plugins/config.rb +164 -0
  61. data/lib/gsd/plugins/hooks.rb +132 -0
  62. data/lib/gsd/plugins/installer.rb +158 -0
  63. data/lib/gsd/plugins/loader.rb +122 -0
  64. data/lib/gsd/plugins/manager.rb +187 -0
  65. data/lib/gsd/plugins/marketplace.rb +142 -0
  66. data/lib/gsd/plugins/sandbox.rb +114 -0
  67. data/lib/gsd/plugins/search.rb +131 -0
  68. data/lib/gsd/plugins/validator.rb +157 -0
  69. data/lib/gsd/plugins.rb +48 -0
  70. data/lib/gsd/profile.rb +127 -0
  71. data/lib/gsd/research.rb +85 -0
  72. data/lib/gsd/roadmap.rb +90 -0
  73. data/lib/gsd/skills/bundled/commit.md +58 -0
  74. data/lib/gsd/skills/bundled/debug.md +28 -0
  75. data/lib/gsd/skills/bundled/explain.md +41 -0
  76. data/lib/gsd/skills/bundled/plan.md +42 -0
  77. data/lib/gsd/skills/bundled/verify.md +26 -0
  78. data/lib/gsd/skills/loader.rb +189 -0
  79. data/lib/gsd/state.rb +102 -0
  80. data/lib/gsd/template.rb +106 -0
  81. data/lib/gsd/tools/ask_user_question.rb +179 -0
  82. data/lib/gsd/tools/base.rb +204 -0
  83. data/lib/gsd/tools/bash.rb +246 -0
  84. data/lib/gsd/tools/file_edit.rb +297 -0
  85. data/lib/gsd/tools/file_read.rb +199 -0
  86. data/lib/gsd/tools/file_write.rb +153 -0
  87. data/lib/gsd/tools/glob.rb +202 -0
  88. data/lib/gsd/tools/grep.rb +227 -0
  89. data/lib/gsd/tools/gsd_frontmatter.rb +165 -0
  90. data/lib/gsd/tools/gsd_phase.rb +140 -0
  91. data/lib/gsd/tools/gsd_roadmap.rb +108 -0
  92. data/lib/gsd/tools/gsd_state.rb +143 -0
  93. data/lib/gsd/tools/gsd_template.rb +157 -0
  94. data/lib/gsd/tools/gsd_verify.rb +159 -0
  95. data/lib/gsd/tools/registry.rb +103 -0
  96. data/lib/gsd/tools/task.rb +235 -0
  97. data/lib/gsd/tools/todo_write.rb +290 -0
  98. data/lib/gsd/tools/web.rb +260 -0
  99. data/lib/gsd/tui/app.rb +366 -0
  100. data/lib/gsd/tui/auto_complete.rb +79 -0
  101. data/lib/gsd/tui/colors.rb +111 -0
  102. data/lib/gsd/tui/command_palette.rb +126 -0
  103. data/lib/gsd/tui/header.rb +38 -0
  104. data/lib/gsd/tui/input_box.rb +199 -0
  105. data/lib/gsd/tui/spinner.rb +40 -0
  106. data/lib/gsd/tui/status_bar.rb +51 -0
  107. data/lib/gsd/tui.rb +17 -0
  108. data/lib/gsd/validator.rb +216 -0
  109. data/lib/gsd/verify.rb +175 -0
  110. data/lib/gsd/version.rb +5 -0
  111. data/lib/gsd/workstream.rb +91 -0
  112. metadata +231 -0
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/md5'
4
+
5
+ module Gsd
6
+ module Buddy
7
+ # Gacha - Sistema gacha para obter buddies
8
+ #
9
+ # Features:
10
+ # - Deterministic (seed = userId)
11
+ # - Rarity weights
12
+ # - Pity system (guaranteed epic+ after 50 pulls)
13
+ class Gacha
14
+ attr_reader :user_id, :pull_count, :pity_count
15
+
16
+ # Inicializa o Gacha
17
+ #
18
+ # @param user_id [String] ID do usuário (seed)
19
+ def initialize(user_id:)
20
+ @user_id = user_id
21
+ @pull_count = 0
22
+ @pity_count = 0
23
+ @seed = Digest::MD5.hexdigest(user_id).to_i(16)
24
+ end
25
+
26
+ # Faz um pull no gacha
27
+ #
28
+ # @return [Hash] Resultado do pull
29
+ def pull
30
+ @pull_count += 1
31
+ @pity_count += 1
32
+
33
+ # Pity system: guaranteed epic+ after 50 pulls
34
+ if @pity_count >= 50
35
+ result = pull_with_rarity(:epic_or_legendary)
36
+ @pity_count = 0
37
+ return result
38
+ end
39
+
40
+ # Roll for rarity
41
+ rarity = roll_rarity
42
+
43
+ # Pull species of that rarity
44
+ pull_with_rarity(rarity)
45
+ end
46
+
47
+ # Faz múltiplos pulls
48
+ #
49
+ # @param count [Integer] Número de pulls
50
+ # @return [Array<Hash>] Resultados
51
+ def pull_many(count)
52
+ count.times.map { pull }
53
+ end
54
+
55
+ # Reseta pity counter
56
+ #
57
+ # @return [void]
58
+ def reset_pity
59
+ @pity_count = 0
60
+ end
61
+
62
+ # Estatísticas do gacha
63
+ #
64
+ # @return [Hash] Estatísticas
65
+ def stats
66
+ {
67
+ user_id: @user_id,
68
+ pull_count: @pull_count,
69
+ pity_count: @pity_count,
70
+ seed: @seed
71
+ }
72
+ end
73
+
74
+ private
75
+
76
+ # Roll para determinar raridade
77
+ #
78
+ # @return [Symbol] Raridade
79
+ def roll_rarity
80
+ weights = Species.rarity_weights
81
+ total = weights.values.sum
82
+ roll = seeded_random % total
83
+
84
+ cumulative = 0
85
+ weights.each do |rarity, weight|
86
+ cumulative += weight
87
+ return rarity if roll < cumulative
88
+ end
89
+
90
+ :common
91
+ end
92
+
93
+ # Pull com raridade específica
94
+ #
95
+ # @param rarity [Symbol] Raridade
96
+ # @return [Hash] Resultado
97
+ def pull_with_rarity(rarity)
98
+ # Handle epic_or_legendary for pity
99
+ if rarity == :epic_or_legendary
100
+ rarity = seeded_random(2) == 0 ? :epic : :legendary
101
+ end
102
+
103
+ # Get species of that rarity
104
+ species_keys = Species.by_rarity(rarity)
105
+ species_key = species_keys[seeded_random(species_keys.length)]
106
+ species = Species.get(species_key)
107
+
108
+ # Generate stats with some variance
109
+ stats = generate_stats(species[:base_stats])
110
+
111
+ result = {
112
+ species: species_key,
113
+ name: species[:name],
114
+ rarity: rarity,
115
+ stats: stats,
116
+ ascii: species[:ascii],
117
+ pull_number: @pull_count
118
+ }
119
+
120
+ result
121
+ end
122
+
123
+ # Gera stats com variância
124
+ #
125
+ # @param base_stats [Hash] Stats base
126
+ # @return [Hash] Stats gerados
127
+ def generate_stats(base_stats)
128
+ stats = {}
129
+ base_stats.each do |stat, value|
130
+ # Variance: -1 to +2
131
+ variance = seeded_random(4) - 1
132
+ stats[stat] = [1, value + variance, 10].clamp
133
+ end
134
+ stats
135
+ end
136
+
137
+ # Random number generator com seed
138
+ #
139
+ # @param max [Integer] Maximum value
140
+ # @return [Integer] Random number
141
+ def seeded_random(max = 100)
142
+ # Linear congruential generator
143
+ @seed = (@seed * 1103515245 + 12345) & 0x7fffffff
144
+ @seed % max
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gsd
4
+ module Buddy
5
+ # Renderer - Renderiza buddy no terminal
6
+ #
7
+ # Features:
8
+ # - ASCII art
9
+ # - Stats display
10
+ # - Stage indicator
11
+ class Renderer
12
+ # Renderiza buddy
13
+ #
14
+ # @param buddy [Buddy] Buddy para renderizar
15
+ # @return [String] Buddy renderizado
16
+ def render(buddy)
17
+ lines = []
18
+
19
+ # ASCII art
20
+ lines << buddy.ascii
21
+ lines << ''
22
+
23
+ # Info
24
+ lines << "Name: #{buddy.name}"
25
+ lines << "Stage: #{buddy.stage}"
26
+ lines << "Level: #{buddy.level}"
27
+ lines << ''
28
+
29
+ # Stats
30
+ lines << 'Stats:'
31
+ buddy.stats.all.each do |stat, value|
32
+ bar = render_bar(value, 10)
33
+ lines << " #{stat.to_s.upcase.ljust(12)}: #{bar} #{value}/10"
34
+ end
35
+ lines << ''
36
+
37
+ # Interactions
38
+ lines << "Interactions: #{buddy.interactions}"
39
+ lines << "Total Score: #{buddy.stats.total_score}"
40
+
41
+ lines.join("\n")
42
+ end
43
+
44
+ # Renderiza lista de buddies
45
+ #
46
+ # @param buddies [Array<Buddy>] Lista de buddies
47
+ # @return [String] Lista renderizada
48
+ def render_list(buddies)
49
+ lines = []
50
+ lines << 'Your Buddies:'
51
+ lines << '=' * 60
52
+
53
+ buddies.each do |buddy|
54
+ lines << "#{buddy.ascii} #{buddy.name} (#{buddy.rarity}) - Level #{buddy.level}"
55
+ lines << " Stage: #{buddy.stage} | Score: #{buddy.stats.total_score}"
56
+ lines << ''
57
+ end
58
+
59
+ lines << '=' * 60
60
+ lines << "Total: #{buddies.count} buddy(ies)"
61
+
62
+ lines.join("\n")
63
+ end
64
+
65
+ # Renderiza barra de progresso
66
+ #
67
+ # @param current [Integer] Valor atual
68
+ # @param max [Integer] Valor máximo
69
+ # @param filled [String] Caractere preenchido
70
+ # @param empty [String] Caractere vazio
71
+ # @return [String] Barra renderizada
72
+ def render_bar(current, max, filled: '█', empty: '░')
73
+ filled_count = (current.to_f / max * 10).round
74
+ empty_count = 10 - filled_count
75
+ filled * filled_count + empty * empty_count
76
+ end
77
+
78
+ # Renderiza comparação de stats
79
+ #
80
+ # @param buddy1 [Buddy] Primeiro buddy
81
+ # @param buddy2 [Buddy] Segundo buddy
82
+ # @return [String] Comparação renderizada
83
+ def render_comparison(buddy1, buddy2)
84
+ lines = []
85
+ lines << 'Stats Comparison:'
86
+ lines << '=' * 60
87
+
88
+ Buddy::Stats::STAT_NAMES.each do |stat|
89
+ v1 = buddy1.stats.get(stat)
90
+ v2 = buddy2.stats.get(stat)
91
+
92
+ bar1 = render_bar(v1, 10)
93
+ bar2 = render_bar(v2, 10)
94
+
95
+ lines << "#{stat.to_s.upcase.ljust(12)}:"
96
+ lines << " #{buddy1.name.ljust(15)}: #{bar1} #{v1}"
97
+ lines << " #{buddy2.name.ljust(15)}: #{bar2} #{v2}"
98
+ lines << ''
99
+ end
100
+
101
+ lines << '=' * 60
102
+ lines << "Total: #{buddy1.stats.total_score} vs #{buddy2.stats.total_score}"
103
+
104
+ lines.join("\n")
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gsd
4
+ module Buddy
5
+ # Species - Definição das 18 espécies de buddies
6
+ #
7
+ # Espécies por raridade:
8
+ # - Common (50%): Pebblecrab, Codebug, Scriptling
9
+ # - Uncommon (30%): Bytebird, Loopfox, Debugdeer
10
+ # - Rare (15%): Testurtle, Refactoray, Mergebat
11
+ # - Epic (4%): Deploydragon, Pipelineon, Commitcat
12
+ # - Legendary (1%): Nebulynx, Quantumquail
13
+ module Species
14
+ # Todas as espécies
15
+ ALL = {
16
+ # Common
17
+ pebblecrab: {
18
+ name: 'Pebblecrab',
19
+ rarity: :common,
20
+ base_stats: { debugging: 3, chaos: 2, snark: 1, focus: 4, creativity: 2 },
21
+ ascii: '/ᐠ。‿。ᐟ\\',
22
+ description: 'A cute crab that lives in pebbles. Good at debugging.'
23
+ },
24
+ codebug: {
25
+ name: 'Codebug',
26
+ rarity: :common,
27
+ base_stats: { debugging: 4, chaos: 3, snark: 1, focus: 3, creativity: 1 },
28
+ ascii: '🐛',
29
+ description: 'A friendly bug that helps find other bugs.'
30
+ },
31
+ scriptling: {
32
+ name: 'Scriptling',
33
+ rarity: :common,
34
+ base_stats: { debugging: 2, chaos: 2, snark: 2, focus: 3, creativity: 3 },
35
+ ascii: '📜',
36
+ description: 'A small spirit that inhabits scripts.'
37
+ },
38
+
39
+ # Uncommon
40
+ bytebird: {
41
+ name: 'Bytebird',
42
+ rarity: :uncommon,
43
+ base_stats: { debugging: 4, chaos: 2, snark: 2, focus: 4, creativity: 3 },
44
+ ascii: '🐦',
45
+ description: 'A bird that carries bytes of information.'
46
+ },
47
+ loopfox: {
48
+ name: 'Loopfox',
49
+ rarity: :uncommon,
50
+ base_stats: { debugging: 5, chaos: 3, snark: 3, focus: 4, creativity: 2 },
51
+ ascii: '🦊',
52
+ description: 'A clever fox that loops around problems.'
53
+ },
54
+ debugdeer: {
55
+ name: 'Debugdeer',
56
+ rarity: :uncommon,
57
+ base_stats: { debugging: 6, chaos: 2, snark: 2, focus: 5, creativity: 2 },
58
+ ascii: '🦌',
59
+ description: 'A deer with antlers that point to bugs.'
60
+ },
61
+
62
+ # Rare
63
+ testurtle: {
64
+ name: 'Testurtle',
65
+ rarity: :rare,
66
+ base_stats: { debugging: 5, chaos: 1, snark: 2, focus: 6, creativity: 3 },
67
+ ascii: '🐢',
68
+ description: 'A slow but steady turtle that tests everything.'
69
+ },
70
+ refactoray: {
71
+ name: 'Refactoray',
72
+ rarity: :rare,
73
+ base_stats: { debugging: 4, chaos: 2, snark: 3, focus: 5, creativity: 5 },
74
+ ascii: '✨',
75
+ description: 'A manta ray that glides through code refactoring.'
76
+ },
77
+ mergebat: {
78
+ name: 'Mergebat',
79
+ rarity: :rare,
80
+ base_stats: { debugging: 5, chaos: 4, snark: 3, focus: 4, creativity: 3 },
81
+ ascii: '🦇',
82
+ description: 'A bat that merges code in the dark.'
83
+ },
84
+
85
+ # Epic
86
+ deploydragon: {
87
+ name: 'Deploydragon',
88
+ rarity: :epic,
89
+ base_stats: { debugging: 7, chaos: 5, snark: 4, focus: 6, creativity: 5 },
90
+ ascii: '🐉',
91
+ description: 'A mighty dragon that breathes fire on deployments.'
92
+ },
93
+ pipelineon: {
94
+ name: 'Pipelineon',
95
+ rarity: :epic,
96
+ base_stats: { debugging: 6, chaos: 3, snark: 3, focus: 8, creativity: 4 },
97
+ ascii: '🔷',
98
+ description: 'A lion that rules the CI/CD pipeline.'
99
+ },
100
+ commitcat: {
101
+ name: 'Commitcat',
102
+ rarity: :epic,
103
+ base_stats: { debugging: 6, chaos: 4, snark: 5, focus: 6, creativity: 5 },
104
+ ascii: '🐱',
105
+ description: 'A cat that commits code with style.'
106
+ },
107
+
108
+ # Legendary
109
+ nebulynx: {
110
+ name: 'Nebulynx',
111
+ rarity: :legendary,
112
+ base_stats: { debugging: 9, chaos: 4, snark: 5, focus: 8, creativity: 7 },
113
+ ascii: '🔮',
114
+ description: 'A mystical lynx from the nebula. Ultimate debugging powers.'
115
+ },
116
+ quantumquail: {
117
+ name: 'Quantumquail',
118
+ rarity: :legendary,
119
+ base_stats: { debugging: 8, chaos: 6, snark: 4, focus: 7, creativity: 9 },
120
+ ascii: '⚛️',
121
+ description: 'A quail that exists in multiple states simultaneously.'
122
+ }
123
+ }.freeze
124
+
125
+ # Retorna espécie por nome
126
+ #
127
+ # @param name [Symbol] Nome da espécie
128
+ # @return [Hash, nil] Espécie ou nil
129
+ def self.get(name)
130
+ ALL[name]
131
+ end
132
+
133
+ # Retorna espécies por raridade
134
+ #
135
+ # @param rarity [Symbol] Raridade
136
+ # @return [Array<Symbol>] Lista de espécies
137
+ def self.by_rarity(rarity)
138
+ ALL.select { |_, s| s[:rarity] == rarity }.keys
139
+ end
140
+
141
+ # Retorna todas as espécies comuns
142
+ #
143
+ # @return [Array<Symbol>] Espécies comuns
144
+ def self.common
145
+ by_rarity(:common)
146
+ end
147
+
148
+ # Retorna todas as espécies incomuns
149
+ #
150
+ # @return [Array<Symbol>] Espécies incomuns
151
+ def self.uncommon
152
+ by_rarity(:uncommon)
153
+ end
154
+
155
+ # Retorna todas as espécies raras
156
+ #
157
+ # @return [Array<Symbol>] Espécies raras
158
+ def self.rare
159
+ by_rarity(:rare)
160
+ end
161
+
162
+ # Retorna todas as espécies épicas
163
+ #
164
+ # @return [Array<Symbol>] Espécies épicas
165
+ def self.epic
166
+ by_rarity(:epic)
167
+ end
168
+
169
+ # Retorna todas as espécies lendárias
170
+ #
171
+ # @return [Array<Symbol>] Espécies lendárias
172
+ def self.legendary
173
+ by_rarity(:legendary)
174
+ end
175
+
176
+ # Pesos de raridade para gacha
177
+ #
178
+ # @return [Hash] Pesos
179
+ def self.rarity_weights
180
+ {
181
+ common: 50,
182
+ uncommon: 30,
183
+ rare: 15,
184
+ epic: 4,
185
+ legendary: 1
186
+ }
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gsd
4
+ module Buddy
5
+ # Stats - Gerenciamento de stats do buddy
6
+ #
7
+ # Stats:
8
+ # - DEBUGGING: Habilidade de debug
9
+ # - CHAOS: Tendência a causar bugs
10
+ # - SNARK: Sarcasmo nas respostas
11
+ # - FOCUS: Concentração em tarefas
12
+ # - CREATIVITY: Criatividade em soluções
13
+ class Stats
14
+ STAT_NAMES = [:debugging, :chaos, :snark, :focus, :creativity].freeze
15
+
16
+ attr_reader :stats, :interactions, :level
17
+
18
+ # Inicializa os Stats
19
+ #
20
+ # @param base_stats [Hash] Stats base da espécie
21
+ def initialize(base_stats)
22
+ @stats = base_stats.dup
23
+ @interactions = 0
24
+ @level = 1
25
+ @exp = 0
26
+ end
27
+
28
+ # Incrementa interação
29
+ #
30
+ # @return [void]
31
+ def increment_interaction
32
+ @interactions += 1
33
+ @exp += 10
34
+ check_level_up
35
+ end
36
+
37
+ # Verifica se subiu de nível
38
+ #
39
+ # @return [Boolean] true se subiu de nível
40
+ def check_level_up
41
+ exp_needed = @level * 100
42
+
43
+ if @exp >= exp_needed
44
+ @level += 1
45
+ @exp = 0
46
+ level_up
47
+ true
48
+ else
49
+ false
50
+ end
51
+ end
52
+
53
+ # Sobe de nível
54
+ #
55
+ # @return [void]
56
+ def level_up
57
+ # Increase random stat
58
+ stat = STAT_NAMES.sample
59
+ @stats[stat] = [@stats[stat] + 1, 10].min
60
+ end
61
+
62
+ # Obtém stat
63
+ #
64
+ # @param name [Symbol] Nome do stat
65
+ # @return [Integer] Valor do stat
66
+ def get(name)
67
+ @stats[name] || 0
68
+ end
69
+
70
+ # Define stat
71
+ #
72
+ # @param name [Symbol] Nome do stat
73
+ # @param value [Integer] Valor
74
+ # @return [void]
75
+ def set(name, value)
76
+ @stats[name] = value.clamp(1, 10)
77
+ end
78
+
79
+ # Retorna todos os stats
80
+ #
81
+ # @return [Hash] Stats
82
+ def all
83
+ @stats.dup
84
+ end
85
+
86
+ # Retorna stat mais alto
87
+ #
88
+ # @return [Symbol] Nome do stat
89
+ def highest_stat
90
+ @stats.max_by { |_, v| v }&.first
91
+ end
92
+
93
+ # Retorna stat mais baixo
94
+ #
95
+ # @return [Symbol] Nome do stat
96
+ def lowest_stat
97
+ @stats.min_by { |_, v| v }&.first
98
+ end
99
+
100
+ # Calcula score total
101
+ #
102
+ # @return [Integer] Score total
103
+ def total_score
104
+ @stats.values.sum
105
+ end
106
+
107
+ # Verifica se pode evoluir
108
+ #
109
+ # @param stage [Symbol] Stage atual
110
+ # @return [Boolean] true se pode evoluir
111
+ def can_evolve?(stage)
112
+ case stage
113
+ when :baby
114
+ @interactions >= 10
115
+ when :juvenile
116
+ @interactions >= 50 && @level >= 5
117
+ when :adult
118
+ @interactions >= 100 && @level >= 10 && total_score >= 40
119
+ else
120
+ false
121
+ end
122
+ end
123
+
124
+ # Retorna stage atual
125
+ #
126
+ # @return [Symbol] Stage
127
+ def stage
128
+ if @interactions < 10
129
+ :baby
130
+ elsif @interactions < 50
131
+ :juvenile
132
+ elsif @interactions < 100
133
+ :adult
134
+ else
135
+ :master
136
+ end
137
+ end
138
+
139
+ # Exporta stats para hash
140
+ #
141
+ # @return [Hash] Stats exportados
142
+ def to_h
143
+ {
144
+ stats: @stats,
145
+ interactions: @interactions,
146
+ level: @level,
147
+ exp: @exp,
148
+ stage: stage,
149
+ total_score: total_score,
150
+ highest_stat: highest_stat,
151
+ lowest_stat: lowest_stat
152
+ }
153
+ end
154
+ end
155
+ end
156
+ end
data/lib/gsd/buddy.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # GSD Buddy Module
4
+ #
5
+ # Tamagotchi-style companion system for GSD Tools
6
+ #
7
+ # Uso:
8
+ # require 'gsd/buddy'
9
+ #
10
+ # # Pull gacha
11
+ # gacha = Gsd::Buddy::Gacha.new(user_id: 'user-123')
12
+ # result = gacha.pull
13
+ #
14
+ # # CLI
15
+ # cli = Gsd::Buddy::CLI.new(['get'])
16
+ # cli.run
17
+
18
+ module Gsd
19
+ module Buddy
20
+ end
21
+ end
22
+
23
+ # Load buddy files
24
+ require 'gsd/buddy/species'
25
+ require 'gsd/buddy/gacha'
26
+ require 'gsd/buddy/stats'
27
+ require 'gsd/buddy/renderer'
28
+ require 'gsd/buddy/cli'