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,330 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gsd/plugins/manager'
4
+ require 'gsd/plugins/installer'
5
+ require 'gsd/plugins/marketplace'
6
+ require 'gsd/plugins/search'
7
+
8
+ module Gsd
9
+ module Plugins
10
+ # Plugin CLI - Interface de linha de comando para plugins
11
+ #
12
+ # Comandos:
13
+ # gsd plugins list
14
+ # gsd plugins install <name|url>
15
+ # gsd plugins uninstall <name>
16
+ # gsd plugins update <name>
17
+ # gsd plugins search <query>
18
+ # gsd plugins info <name>
19
+ # gsd plugins enable <name>
20
+ # gsd plugins disable <name>
21
+ class CLI
22
+ attr_reader :manager, :installer, :marketplace, :search
23
+
24
+ # Inicializa o CLI
25
+ #
26
+ # @param args [Array] Argumentos de linha de comando
27
+ def initialize(args = [])
28
+ @args = args
29
+ @manager = Manager.new
30
+ @installer = Installer.new
31
+ @marketplace = MarketplaceClient.new
32
+ @search = Search.new(client: @marketplace)
33
+ end
34
+
35
+ # Executa o comando
36
+ #
37
+ # @return [Integer] Exit code
38
+ def run
39
+ command = @args.first
40
+
41
+ case command
42
+ when 'list'
43
+ cmd_list
44
+ when 'install'
45
+ cmd_install
46
+ when 'uninstall'
47
+ cmd_uninstall
48
+ when 'update'
49
+ cmd_update
50
+ when 'search'
51
+ cmd_search
52
+ when 'info'
53
+ cmd_info
54
+ when 'enable'
55
+ cmd_enable
56
+ when 'disable'
57
+ cmd_disable
58
+ when 'help', '--help', '-h', nil
59
+ print_help
60
+ 0
61
+ else
62
+ warn "Unknown command: #{command}"
63
+ print_help
64
+ 1
65
+ end
66
+ rescue => e
67
+ warn "Error: #{e.message}"
68
+ 1
69
+ end
70
+
71
+ private
72
+
73
+ # Comando: list
74
+ #
75
+ # @return [Integer] Exit code
76
+ def cmd_list
77
+ plugins = @manager.list
78
+
79
+ if plugins.empty?
80
+ puts "No plugins installed."
81
+ puts "\nInstall plugins with:"
82
+ puts " gsd plugins install <name|url>"
83
+ return 0
84
+ end
85
+
86
+ puts "Installed plugins:"
87
+ puts "=" * 60
88
+
89
+ plugins.each do |plugin|
90
+ status = plugin[:enabled] ? '✓' : '✗'
91
+ puts "#{status} #{plugin[:name]} v#{plugin[:version]}"
92
+ puts " #{plugin[:description]}"
93
+ puts " Author: #{plugin[:author]}" if plugin[:author]
94
+ puts
95
+ end
96
+
97
+ stats = @manager.stats
98
+ puts "=" * 60
99
+ puts "Total: #{stats[:total]} | Enabled: #{stats[:enabled]} | Disabled: #{stats[:disabled]}"
100
+
101
+ 0
102
+ end
103
+
104
+ # Comando: install
105
+ #
106
+ # @return [Integer] Exit code
107
+ def cmd_install
108
+ source = @args[1]
109
+
110
+ unless source
111
+ warn "Usage: gsd plugins install <name|url>"
112
+ return 1
113
+ end
114
+
115
+ puts "Installing plugin: #{source}..."
116
+
117
+ if @installer.install(source)
118
+ puts "✓ Plugin installed successfully!"
119
+ 0
120
+ else
121
+ warn "✗ Failed to install plugin"
122
+ 1
123
+ end
124
+ end
125
+
126
+ # Comando: uninstall
127
+ #
128
+ # @return [Integer] Exit code
129
+ def cmd_uninstall
130
+ name = @args[1]
131
+
132
+ unless name
133
+ warn "Usage: gsd plugins uninstall <name>"
134
+ return 1
135
+ end
136
+
137
+ unless @installer.installed?(name)
138
+ warn "Plugin not installed: #{name}"
139
+ return 1
140
+ end
141
+
142
+ print "Uninstalling plugin #{name}... "
143
+
144
+ if @installer.uninstall(name)
145
+ puts "✓"
146
+ 0
147
+ else
148
+ puts "✗"
149
+ 1
150
+ end
151
+ end
152
+
153
+ # Comando: update
154
+ #
155
+ # @return [Integer] Exit code
156
+ def cmd_update
157
+ name = @args[1]
158
+
159
+ unless name
160
+ warn "Usage: gsd plugins update <name>"
161
+ return 1
162
+ end
163
+
164
+ puts "Updating plugin: #{name}..."
165
+
166
+ if @installer.update(name)
167
+ puts "✓ Plugin updated successfully!"
168
+ 0
169
+ else
170
+ warn "✗ Failed to update plugin"
171
+ 1
172
+ end
173
+ end
174
+
175
+ # Comando: search
176
+ #
177
+ # @return [Integer] Exit code
178
+ def cmd_search
179
+ query = @args[1]
180
+
181
+ unless query
182
+ warn "Usage: gsd plugins search <query>"
183
+ return 1
184
+ end
185
+
186
+ puts "Searching for: #{query}..."
187
+ puts "=" * 60
188
+
189
+ begin
190
+ results = @search.search(query)
191
+
192
+ if results.empty?
193
+ puts "No plugins found."
194
+ return 0
195
+ end
196
+
197
+ results.each do |plugin|
198
+ puts "#{plugin['name']} v#{plugin['version']}"
199
+ puts " #{plugin['description']}"
200
+ puts " Rating: #{plugin['rating']} ⭐ | Downloads: #{plugin['downloads']}"
201
+ puts
202
+ end
203
+
204
+ puts "=" * 60
205
+ puts "Found #{results.count} plugin(s)"
206
+
207
+ rescue => e
208
+ warn "Search error: #{e.message}"
209
+ return 1
210
+ end
211
+
212
+ 0
213
+ end
214
+
215
+ # Comando: info
216
+ #
217
+ # @return [Integer] Exit code
218
+ def cmd_info
219
+ name = @args[1]
220
+
221
+ unless name
222
+ warn "Usage: gsd plugins info <name>"
223
+ return 1
224
+ end
225
+
226
+ begin
227
+ plugin = @marketplace.get_plugin(name)
228
+
229
+ unless plugin
230
+ warn "Plugin not found: #{name}"
231
+ return 1
232
+ end
233
+
234
+ puts "Plugin: #{plugin['name']} v#{plugin['version']}"
235
+ puts "=" * 60
236
+ puts "Description: #{plugin['description']}"
237
+ puts "Author: #{plugin['author']}" if plugin['author']
238
+ puts "Category: #{plugin['category']}" if plugin['category']
239
+ puts "Tags: #{plugin['tags'].join(', ')}" if plugin['tags']
240
+ puts "Rating: #{plugin['rating']} ⭐ (#{plugin['downloads']} downloads)"
241
+ puts "Source: #{plugin['source_url']}" if plugin['source_url']
242
+ puts "Updated: #{plugin['updated_at']}" if plugin['updated_at']
243
+ puts "=" * 60
244
+
245
+ rescue => e
246
+ warn "Error: #{e.message}"
247
+ return 1
248
+ end
249
+
250
+ 0
251
+ end
252
+
253
+ # Comando: enable
254
+ #
255
+ # @return [Integer] Exit code
256
+ def cmd_enable
257
+ name = @args[1]
258
+
259
+ unless name
260
+ warn "Usage: gsd plugins enable <name>"
261
+ return 1
262
+ end
263
+
264
+ if @manager.enable(name)
265
+ puts "✓ Plugin #{name} enabled"
266
+ 0
267
+ else
268
+ warn "✗ Failed to enable plugin #{name}"
269
+ 1
270
+ end
271
+ end
272
+
273
+ # Comando: disable
274
+ #
275
+ # @return [Integer] Exit code
276
+ def cmd_disable
277
+ name = @args[1]
278
+
279
+ unless name
280
+ warn "Usage: gsd plugins disable <name>"
281
+ return 1
282
+ end
283
+
284
+ if @manager.disable(name)
285
+ puts "✓ Plugin #{name} disabled"
286
+ 0
287
+ else
288
+ warn "✗ Failed to disable plugin #{name}"
289
+ 1
290
+ end
291
+ end
292
+
293
+ # Imprime ajuda
294
+ #
295
+ # @return [void]
296
+ def print_help
297
+ puts <<~HELP
298
+
299
+ GSD Plugins CLI
300
+
301
+ USAGE:
302
+ gsd plugins <command> [args]
303
+
304
+ COMMANDS:
305
+ list List installed plugins
306
+ install <name|url> Install plugin
307
+ uninstall <name> Uninstall plugin
308
+ update <name> Update plugin
309
+ search <query> Search marketplace
310
+ info <name> Plugin information
311
+ enable <name> Enable plugin
312
+ disable <name> Disable plugin
313
+ help Show this help
314
+
315
+ EXAMPLES:
316
+ gsd plugins list
317
+ gsd plugins install github-plugin
318
+ gsd plugins install https://github.com/user/plugin.zip
319
+ gsd plugins uninstall github-plugin
320
+ gsd plugins update github-plugin
321
+ gsd plugins search github
322
+ gsd plugins info github-plugin
323
+ gsd plugins enable github-plugin
324
+ gsd plugins disable github-plugin
325
+
326
+ HELP
327
+ end
328
+ end
329
+ end
330
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'fileutils'
5
+
6
+ module Gsd
7
+ module Plugins
8
+ # Plugin Config - Gerencia configurações de plugins
9
+ #
10
+ # Responsável por:
11
+ # - Armazenar configurações por plugin
12
+ # - Carregar configurações salvas
13
+ # - Validar configurações
14
+ class Config
15
+ attr_reader :config_dir
16
+
17
+ # Inicializa o Config
18
+ #
19
+ # @param config_dir [String] Diretório de configurações
20
+ def initialize(config_dir: nil)
21
+ @config_dir = config_dir || File.join(Dir.home, '.gsd', 'plugins', 'config')
22
+ ensure_config_dir
23
+ end
24
+
25
+ # Obtém configuração de um plugin
26
+ #
27
+ # @param plugin_name [String] Nome do plugin
28
+ # @param key [String, nil] Chave específica
29
+ # @return [Hash, Object] Configuração ou valor
30
+ def get(plugin_name, key = nil)
31
+ config = load_config(plugin_name)
32
+ key ? config[key] : config
33
+ end
34
+
35
+ # Define configuração de um plugin
36
+ #
37
+ # @param plugin_name [String] Nome do plugin
38
+ # @param key [String, Hash] Chave ou hash de configurações
39
+ # @param value [Object] Valor (se key for string)
40
+ # @return [void]
41
+ def set(plugin_name, key, value = nil)
42
+ config = load_config(plugin_name)
43
+
44
+ if key.is_a?(Hash)
45
+ config.merge!(key)
46
+ else
47
+ config[key] = value
48
+ end
49
+
50
+ save_config(plugin_name, config)
51
+ end
52
+
53
+ # Remove configuração de um plugin
54
+ #
55
+ # @param plugin_name [String] Nome do plugin
56
+ # @param key [String, nil] Chave específica
57
+ # @return [void]
58
+ def delete(plugin_name, key = nil)
59
+ return unless config_exists?(plugin_name)
60
+
61
+ if key
62
+ config = load_config(plugin_name)
63
+ config.delete(key)
64
+ save_config(plugin_name, config)
65
+ else
66
+ remove_config(plugin_name)
67
+ end
68
+ end
69
+
70
+ # Verifica se plugin tem configuração
71
+ #
72
+ # @param plugin_name [String] Nome do plugin
73
+ # @return [Boolean] true se existe
74
+ def config_exists?(plugin_name)
75
+ File.exist?(config_file(plugin_name))
76
+ end
77
+
78
+ # Lista todos os plugins com configuração
79
+ #
80
+ # @return [Array<String>] Nomes dos plugins
81
+ def list_plugins
82
+ Dir.children(@config_dir).select do |child|
83
+ File.file?(File.join(@config_dir, child)) && child.end_with?('.json')
84
+ end.map { |f| f.gsub('.json', '') }
85
+ end
86
+
87
+ # Reseta configuração de um plugin
88
+ #
89
+ # @param plugin_name [String] Nome do plugin
90
+ # @return [void]
91
+ def reset(plugin_name)
92
+ remove_config(plugin_name)
93
+ end
94
+
95
+ # Exporta configurações
96
+ #
97
+ # @return [Hash] Todas as configurações
98
+ def export
99
+ result = {}
100
+ list_plugins.each do |plugin_name|
101
+ result[plugin_name] = load_config(plugin_name)
102
+ end
103
+ result
104
+ end
105
+
106
+ # Importa configurações
107
+ #
108
+ # @param configs [Hash] Configurações para importar
109
+ # @return [void]
110
+ def import(configs)
111
+ configs.each do |plugin_name, config|
112
+ save_config(plugin_name, config)
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ # Garante que diretório existe
119
+ #
120
+ # @return [void]
121
+ def ensure_config_dir
122
+ FileUtils.mkdir_p(@config_dir) unless File.directory?(@config_dir)
123
+ end
124
+
125
+ # Caminho do arquivo de configuração
126
+ #
127
+ # @param plugin_name [String] Nome do plugin
128
+ # @return [String] Caminho do arquivo
129
+ def config_file(plugin_name)
130
+ File.join(@config_dir, "#{plugin_name}.json")
131
+ end
132
+
133
+ # Carrega configuração
134
+ #
135
+ # @param plugin_name [String] Nome do plugin
136
+ # @return [Hash] Configuração
137
+ def load_config(plugin_name)
138
+ return {} unless config_exists?(plugin_name)
139
+
140
+ JSON.parse(File.read(config_file(plugin_name)))
141
+ rescue => e
142
+ warn "[Plugin Config] Error loading config for #{plugin_name}: #{e.message}"
143
+ {}
144
+ end
145
+
146
+ # Salva configuração
147
+ #
148
+ # @param plugin_name [String] Nome do plugin
149
+ # @param config [Hash] Configuração
150
+ # @return [void]
151
+ def save_config(plugin_name, config)
152
+ File.write(config_file(plugin_name), JSON.pretty_generate(config))
153
+ end
154
+
155
+ # Remove configuração
156
+ #
157
+ # @param plugin_name [String] Nome do plugin
158
+ # @return [void]
159
+ def remove_config(plugin_name)
160
+ File.delete(config_file(plugin_name)) if config_exists?(plugin_name)
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gsd
4
+ module Plugins
5
+ # Plugin Hooks - Sistema de hooks para plugins
6
+ #
7
+ # Responsável por:
8
+ # - Registrar hooks de plugins
9
+ # - Executar hooks em eventos
10
+ # - Gerenciar prioridade de hooks
11
+ class Hooks
12
+ attr_reader :hooks
13
+
14
+ # Hook types
15
+ HOOK_TYPES = [
16
+ :pre_command,
17
+ :post_command,
18
+ :pre_tool,
19
+ :post_tool,
20
+ :pre_commit,
21
+ :post_phase,
22
+ :on_enable,
23
+ :on_disable
24
+ ].freeze
25
+
26
+ # Inicializa o Hooks
27
+ def initialize
28
+ @hooks = {}
29
+ HOOK_TYPES.each { |type| @hooks[type] = [] }
30
+ end
31
+
32
+ # Registra um hook
33
+ #
34
+ # @param type [Symbol] Tipo do hook
35
+ # @param plugin [String] Nome do plugin
36
+ # @param callback [Proc] Callback para executar
37
+ # @param priority [Integer] Prioridade (maior = primeiro)
38
+ # @return [void]
39
+ def register(type, plugin, callback, priority: 0)
40
+ unless HOOK_TYPES.include?(type)
41
+ raise ArgumentError, "Invalid hook type: #{type}"
42
+ end
43
+
44
+ @hooks[type] << {
45
+ plugin: plugin,
46
+ callback: callback,
47
+ priority: priority,
48
+ registered_at: Time.now
49
+ }
50
+
51
+ # Ordena por prioridade
52
+ @hooks[type].sort_by! { |h| -h[:priority] }
53
+ end
54
+
55
+ # Remove hook de um plugin
56
+ #
57
+ # @param plugin [String] Nome do plugin
58
+ # @return [void]
59
+ def unregister(plugin)
60
+ @hooks.each do |type, hook_list|
61
+ @hooks[type] = hook_list.reject { |h| h[:plugin] == plugin }
62
+ end
63
+ end
64
+
65
+ # Executa hooks de um tipo
66
+ #
67
+ # @param type [Symbol] Tipo do hook
68
+ # @param context [Hash] Contexto para o hook
69
+ # @return [Array] Resultados dos hooks
70
+ def run(type, context = {})
71
+ unless HOOK_TYPES.include?(type)
72
+ raise ArgumentError, "Invalid hook type: #{type}"
73
+ end
74
+
75
+ results = []
76
+ @hooks[type].each do |hook|
77
+ begin
78
+ result = hook[:callback].call(context)
79
+ results << {
80
+ plugin: hook[:plugin],
81
+ result: result,
82
+ success: true
83
+ }
84
+ rescue => e
85
+ results << {
86
+ plugin: hook[:plugin],
87
+ error: e.message,
88
+ success: false
89
+ }
90
+ warn "[Hooks] Error in #{type} hook (#{hook[:plugin]}): #{e.message}"
91
+ end
92
+ end
93
+
94
+ results
95
+ end
96
+
97
+ # Lista hooks registrados
98
+ #
99
+ # @param type [Symbol, nil] Tipo específico
100
+ # @return [Array<Hash>] Lista de hooks
101
+ def list(type = nil)
102
+ if type
103
+ @hooks[type] || []
104
+ else
105
+ @hooks
106
+ end
107
+ end
108
+
109
+ # Conta hooks por tipo
110
+ #
111
+ # @return [Hash] Contagem por tipo
112
+ def count
113
+ @hooks.transform_values(&:count)
114
+ end
115
+
116
+ # Limpa todos os hooks
117
+ #
118
+ # @return [void]
119
+ def clear
120
+ HOOK_TYPES.each { |type| @hooks[type] = [] }
121
+ end
122
+
123
+ # Limpa hooks de um tipo
124
+ #
125
+ # @param type [Symbol] Tipo do hook
126
+ # @return [void]
127
+ def clear_type(type)
128
+ @hooks[type] = [] if HOOK_TYPES.include?(type)
129
+ end
130
+ end
131
+ end
132
+ end