bug_bunny 4.8.0 → 4.8.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/skills/documentation-writer/SKILL.md +45 -0
  3. data/.agents/skills/gem-release/SKILL.md +114 -0
  4. data/.agents/skills/quality-code/SKILL.md +51 -0
  5. data/.agents/skills/sentry/SKILL.md +135 -0
  6. data/.agents/skills/sentry/references/api-endpoints.md +147 -0
  7. data/.agents/skills/sentry/scripts/sentry.rb +194 -0
  8. data/.agents/skills/skill-builder/SKILL.md +232 -0
  9. data/.agents/skills/skill-manager/SKILL.md +172 -0
  10. data/.agents/skills/skill-manager/scripts/sync.rb +310 -0
  11. data/.agents/skills/yard/SKILL.md +311 -0
  12. data/.agents/skills/yard/references/tipos.md +144 -0
  13. data/CHANGELOG.md +8 -0
  14. data/CLAUDE.md +28 -231
  15. data/lib/bug_bunny/version.rb +1 -1
  16. data/skill/SKILL.md +230 -0
  17. data/skill/references/client-middleware.md +144 -0
  18. data/skill/references/consumer.md +104 -0
  19. data/skill/references/controller.md +105 -0
  20. data/skill/references/errores.md +97 -0
  21. data/skill/references/resource.md +116 -0
  22. data/skill/references/routing.md +82 -0
  23. data/skill/references/testing.md +138 -0
  24. data/skills.lock +24 -0
  25. data/skills.yml +19 -0
  26. metadata +24 -28
  27. data/.claude/commands/gem-ai-setup.md +0 -174
  28. data/.claude/commands/pr.md +0 -53
  29. data/.claude/commands/release.md +0 -52
  30. data/.claude/commands/rubocop.md +0 -22
  31. data/.claude/commands/service-ai-setup.md +0 -168
  32. data/.claude/commands/test.md +0 -28
  33. data/.claude/commands/yard.md +0 -46
  34. data/docs/_index.md +0 -50
  35. data/docs/ai/_index.md +0 -56
  36. data/docs/ai/antipatterns.md +0 -166
  37. data/docs/ai/api.md +0 -251
  38. data/docs/ai/architecture.md +0 -92
  39. data/docs/ai/errors.md +0 -158
  40. data/docs/ai/faq_external.md +0 -133
  41. data/docs/ai/faq_internal.md +0 -86
  42. data/docs/ai/glossary.md +0 -45
  43. data/docs/concepts.md +0 -140
  44. data/docs/howto/controller.md +0 -194
  45. data/docs/howto/middleware_client.md +0 -119
  46. data/docs/howto/middleware_consumer.md +0 -127
  47. data/docs/howto/rails.md +0 -214
  48. data/docs/howto/resource.md +0 -200
  49. data/docs/howto/routing.md +0 -133
  50. data/docs/howto/testing.md +0 -259
  51. data/docs/howto/tracing.md +0 -119
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'yaml'
5
+ require 'fileutils'
6
+ require 'json'
7
+ require 'net/http'
8
+ require 'uri'
9
+
10
+ module SkillsSync
11
+ SKILLS_YML = 'skills.yml'
12
+ SKILLS_LOCK = 'skills.lock'
13
+ SKILLS_DIR = File.join('.agents', 'skills')
14
+
15
+ GLOBAL_SKILL_PATHS = [
16
+ File.join(Dir.home, '.agents', 'skills'),
17
+ File.join(Dir.home, '.claude', '.agents', 'skills'),
18
+ File.join(Dir.home, '.claude', 'skills')
19
+ ].freeze
20
+
21
+ class << self
22
+ def run
23
+ config_path = File.join(Dir.pwd, SKILLS_YML)
24
+
25
+ unless File.exist?(config_path)
26
+ puts "skill-manager sync — ERROR: #{SKILLS_YML} no encontrado en #{Dir.pwd}"
27
+ exit 1
28
+ end
29
+
30
+ config = YAML.safe_load_file(config_path)
31
+ @local_skills_path = File.join(Dir.pwd, SKILLS_DIR)
32
+ FileUtils.mkdir_p(@local_skills_path)
33
+
34
+ @use_gh = !gh_available?.nil?
35
+ @token = ENV['GITHUB_TOKEN']
36
+
37
+ unless @use_gh || @token
38
+ puts "skill-manager sync — WARNING: ni 'gh' CLI ni GITHUB_TOKEN disponibles. Solo se sincronizarán gemas locales."
39
+ end
40
+
41
+ @previous_lock = load_lock
42
+ @current_lock = []
43
+
44
+ sync_gems(config['gems'] || [])
45
+ sync_services(config['services'] || [])
46
+ sync_skills(config['skills'] || [])
47
+
48
+ cleanup_removed_skills
49
+ save_lock
50
+
51
+ puts 'skill-manager sync — OK.'
52
+ end
53
+
54
+ private
55
+
56
+ # --- Lock file ---
57
+
58
+ def load_lock
59
+ lock_path = File.join(Dir.pwd, SKILLS_LOCK)
60
+ return [] unless File.exist?(lock_path)
61
+
62
+ data = YAML.safe_load_file(lock_path)
63
+ data['skills'] || []
64
+ rescue StandardError
65
+ []
66
+ end
67
+
68
+ def save_lock
69
+ lock_path = File.join(Dir.pwd, SKILLS_LOCK)
70
+ data = {
71
+ 'synced_at' => Time.now.strftime('%Y-%m-%d %H:%M:%S'),
72
+ 'skills' => @current_lock.sort_by { |s| s['name'] }
73
+ }
74
+ File.write(lock_path, YAML.dump(data))
75
+ end
76
+
77
+ def record_lock(name, scope, path)
78
+ @current_lock << { 'name' => name, 'scope' => scope || 'local', 'path' => path }
79
+ end
80
+
81
+ def cleanup_removed_skills
82
+ previous_names = @previous_lock.map { |s| s['name'] }
83
+ current_names = @current_lock.map { |s| s['name'] }
84
+ removed = previous_names - current_names
85
+
86
+ removed.each do |name|
87
+ entry = @previous_lock.find { |s| s['name'] == name }
88
+ path = entry['path']
89
+
90
+ if File.exist?(path)
91
+ puts " #{name} — eliminado (ya no está en #{SKILLS_YML})"
92
+ FileUtils.rm_rf(path)
93
+ end
94
+ end
95
+ end
96
+
97
+ # --- Scope resolution ---
98
+
99
+ def resolve_dest(name, scope)
100
+ case scope
101
+ when 'global'
102
+ global_path = find_global_path(name) || default_global_path
103
+ dest = File.join(global_path, name)
104
+ local_path = File.join(@local_skills_path, name)
105
+
106
+ # Si existe local, borrarla
107
+ if File.exist?(local_path)
108
+ puts " #{name} — eliminando copia local (scope: global)"
109
+ FileUtils.rm_rf(local_path)
110
+ end
111
+
112
+ dest
113
+ else # local o sin especificar
114
+ # Si existe global, saltear
115
+ existing_global = find_global_path(name)
116
+ if existing_global
117
+ puts " #{name} — disponible globalmente en #{File.join(existing_global, name)}. Saltando."
118
+ return nil
119
+ end
120
+
121
+ File.join(@local_skills_path, name)
122
+ end
123
+ end
124
+
125
+ def find_global_path(name)
126
+ GLOBAL_SKILL_PATHS.find do |path|
127
+ File.exist?(File.join(path, name, 'SKILL.md'))
128
+ end
129
+ end
130
+
131
+ def default_global_path
132
+ existing = GLOBAL_SKILL_PATHS.find { |p| File.directory?(p) }
133
+ return existing if existing
134
+
135
+ path = GLOBAL_SKILL_PATHS.first
136
+ FileUtils.mkdir_p(path)
137
+ path
138
+ end
139
+
140
+ # --- Sync methods ---
141
+
142
+ def sync_gems(gems)
143
+ gems.each do |gem_config|
144
+ name = gem_config['name']
145
+ scope = gem_config['scope']
146
+ dest = resolve_dest(name, scope)
147
+ next unless dest
148
+
149
+ begin
150
+ spec = Gem::Specification.find_by_name(name)
151
+ rescue Gem::MissingSpecError
152
+ puts " WARNING: gema '#{name}' no instalada. Saltando."
153
+ next
154
+ end
155
+
156
+ skill_dir = File.join(spec.gem_dir, 'skill')
157
+ skill_file = File.join(skill_dir, 'SKILL.md')
158
+
159
+ unless File.exist?(skill_file)
160
+ puts " WARNING: #{name} v#{spec.version} no incluye skill en skill/. Saltando."
161
+ next
162
+ end
163
+
164
+ scope_label = scope == 'global' ? 'global' : 'local'
165
+ puts " #{name} v#{spec.version} (#{scope_label})"
166
+ replace_dir(dest)
167
+ FileUtils.cp_r(Dir[File.join(skill_dir, '*')], dest)
168
+ record_lock(name, scope, dest)
169
+ end
170
+ end
171
+
172
+ def sync_services(services)
173
+ services.each do |service|
174
+ name = service['name']
175
+ scope = service['scope']
176
+ dest = resolve_dest(name, scope)
177
+ next unless dest
178
+
179
+ repo = service['repo']
180
+ scope_label = scope == 'global' ? 'global' : 'local'
181
+ puts " #{name} (GitHub: #{repo}, skill/) [#{scope_label}]"
182
+ replace_dir(dest)
183
+ download_github_dir(repo, 'main', 'skill', dest)
184
+ record_lock(name, scope, dest)
185
+ end
186
+ end
187
+
188
+ def sync_skills(skills)
189
+ skills.each do |skill|
190
+ name = skill['name']
191
+ scope = skill['scope']
192
+ dest = resolve_dest(name, scope)
193
+ next unless dest
194
+
195
+ repo = skill['repo']
196
+ remote_path = skill['path'] || "#{SKILLS_DIR}/#{name}"
197
+ scope_label = scope == 'global' ? 'global' : 'local'
198
+ puts " #{name} (GitHub: #{repo}, #{remote_path}/) [#{scope_label}]"
199
+ replace_dir(dest)
200
+ download_github_dir(repo, 'main', remote_path, dest)
201
+ record_lock(name, scope, dest)
202
+ end
203
+ end
204
+
205
+ # --- Helpers ---
206
+
207
+ def replace_dir(dest)
208
+ FileUtils.rm_rf(dest)
209
+ FileUtils.mkdir_p(dest)
210
+ end
211
+
212
+ def download_github_dir(repo, ref, remote_path, dest)
213
+ entries = list_github_dir(repo, ref, remote_path)
214
+
215
+ if entries.empty?
216
+ puts " WARNING: #{remote_path}/ no encontrado en #{repo}@#{ref}. Saltando."
217
+ return
218
+ end
219
+
220
+ entries.each do |entry|
221
+ next unless entry['type'] == 'file'
222
+
223
+ relative_path = entry['path'].sub(%r{^#{Regexp.escape(remote_path)}/}, '')
224
+ file_dest = File.join(dest, relative_path)
225
+ FileUtils.mkdir_p(File.dirname(file_dest))
226
+
227
+ content = if @use_gh
228
+ gh_fetch_file(repo, ref, entry['path'])
229
+ else
230
+ fetch_url(entry['download_url'])
231
+ end
232
+
233
+ if content
234
+ File.write(file_dest, content)
235
+ else
236
+ puts " WARNING: no se pudo descargar #{entry['path']} de #{repo}."
237
+ end
238
+ end
239
+ end
240
+
241
+ def list_github_dir(repo, ref, path)
242
+ response = if @use_gh
243
+ gh_api("repos/#{repo}/contents/#{path}?ref=#{ref}")
244
+ else
245
+ fetch_url("https://api.github.com/repos/#{repo}/contents/#{path}?ref=#{ref}")
246
+ end
247
+ return [] unless response
248
+
249
+ items = JSON.parse(response)
250
+ return [] unless items.is_a?(Array)
251
+
252
+ all_entries = []
253
+ items.each do |item|
254
+ if item['type'] == 'dir'
255
+ all_entries.concat(list_github_dir(repo, ref, item['path']))
256
+ else
257
+ all_entries << item
258
+ end
259
+ end
260
+ all_entries
261
+ end
262
+
263
+ # --- GitHub CLI ---
264
+
265
+ def gh_available?
266
+ `which gh 2>/dev/null`.strip
267
+ $?.success? ? true : nil
268
+ rescue StandardError
269
+ nil
270
+ end
271
+
272
+ def gh_api(endpoint)
273
+ output = `gh api "#{endpoint}" 2>/dev/null`
274
+ $?.success? ? output.force_encoding('UTF-8') : nil
275
+ rescue StandardError
276
+ nil
277
+ end
278
+
279
+ def gh_fetch_file(repo, ref, path)
280
+ output = `gh api "repos/#{repo}/contents/#{path}?ref=#{ref}" --jq '.content' 2>/dev/null`
281
+ return nil unless $?.success?
282
+
283
+ require 'base64'
284
+ Base64.decode64(output).force_encoding('UTF-8')
285
+ rescue StandardError
286
+ nil
287
+ end
288
+
289
+ # --- HTTP directo ---
290
+
291
+ def fetch_url(url)
292
+ uri = URI.parse(url)
293
+ request = Net::HTTP::Get.new(uri)
294
+ request['Authorization'] = "Bearer #{@token}" if @token
295
+ request['User-Agent'] = 'skill-manager/1.0'
296
+ request['Accept'] = 'application/vnd.github.v3+json' if url.include?('api.github.com')
297
+
298
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
299
+ http.request(request)
300
+ end
301
+
302
+ response.code == '200' ? response.body.force_encoding('UTF-8') : nil
303
+ rescue StandardError => e
304
+ puts " ERROR fetching #{url}: #{e.message}"
305
+ nil
306
+ end
307
+ end
308
+ end
309
+
310
+ SkillsSync.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,311 @@
1
+ ---
2
+ name: yard
3
+ description: Experto en documentación YARD para Ruby. Consultame para escribir documentación correcta con tags, tipos, directivas, duck types y patrones avanzados. Úsala SIEMPRE que necesites documentar código Ruby con YARD o auditar documentación existente.
4
+ ---
5
+
6
+ # YARD Expert
7
+
8
+ Skill de conocimiento completo sobre YARD (Yet Another Ruby Document). Consultame para escribir documentación correcta, auditar cobertura o resolver dudas sobre tags, tipos y directivas.
9
+
10
+ ## Glosario
11
+
12
+ **Tag** — Metadato prefijado con `@` que describe un aspecto del código (ej: `@param`, `@return`).
13
+
14
+ **Directiva** — Instrucción prefijada con `@!` que modifica el contexto de parsing (ej: `@!method`, `@!attribute`).
15
+
16
+ **Type specifier list** — Lista de tipos entre corchetes `[Type]` usada en tags como `@param` y `@return`.
17
+
18
+ **Duck type** — Tipo definido por interfaz, no por clase. Se escribe como `#method_name`.
19
+
20
+ **Reference tag** — Sintaxis `(see OBJECT)` que copia tags de otro objeto.
21
+
22
+ ## Anatomía de una documentación YARD
23
+
24
+ ```ruby
25
+ # Descripción breve del método (primera línea).
26
+ #
27
+ # Descripción extendida opcional. Puede tener múltiples párrafos,
28
+ # listas y ejemplos en markdown.
29
+ #
30
+ # @param name [Type] descripción del parámetro
31
+ # @return [Type] descripción del retorno
32
+ # @raise [ExceptionClass] cuándo se lanza
33
+ # @example Título del ejemplo
34
+ # resultado = mi_metodo("valor")
35
+ # # => "esperado"
36
+ def mi_metodo(name)
37
+ end
38
+ ```
39
+
40
+ **Reglas clave:**
41
+ - Primera línea: descripción breve, sin punto final si es corta.
42
+ - Línea vacía entre descripción y tags.
43
+ - Tags multilínea: indentar 2 espacios las líneas siguientes.
44
+ - Orden recomendado de tags: `@param` → `@option` → `@yield` → `@yieldparam` → `@yieldreturn` → `@return` → `@raise` → `@example`.
45
+
46
+ ## Sistema de Tipos
47
+
48
+ Ver catálogo completo en [references/tipos.md](references/tipos.md).
49
+
50
+ ### Tipos básicos
51
+ ```ruby
52
+ # @param name [String] un string
53
+ # @param count [Integer] un entero
54
+ # @param flag [Boolean] true o false (convención YARD, no existe en Ruby)
55
+ # @return [void] sin valor de retorno significativo
56
+ # @return [nil] retorna nil explícitamente
57
+ # @return [self] retorna self (métodos encadenables)
58
+ ```
59
+
60
+ ### Union types
61
+ ```ruby
62
+ # @param input [String, Symbol] acepta string o symbol
63
+ # @return [String, nil] puede retornar nil
64
+ ```
65
+
66
+ ### Generics (parametrized types)
67
+ ```ruby
68
+ # @param items [Array<String>] array de strings
69
+ # @param map [Hash<Symbol, Integer>] hash con keys symbol y values integer
70
+ # @return [Set<User>] set de usuarios
71
+ ```
72
+
73
+ ### Hashes con estructura
74
+ ```ruby
75
+ # @param opts [Hash{Symbol => String}] opciones con keys symbol
76
+ # @param data [Hash{String => Array<Integer>}] hash complejo
77
+ ```
78
+
79
+ ### Duck types
80
+ ```ruby
81
+ # @param io [#read] cualquier objeto que responda a #read
82
+ # @param callable [#call] cualquier objeto callable
83
+ # @param io [#read, #close] debe responder a ambos
84
+ ```
85
+
86
+ ### Order-dependent lists
87
+ ```ruby
88
+ # @return [Array(String, Integer, Hash)] exactamente 3 elementos en ese orden
89
+ ```
90
+
91
+ ### Literals
92
+ ```ruby
93
+ # @return [true] siempre retorna true
94
+ # @return [false, nil] retorna false o nil
95
+ ```
96
+
97
+ ## Tags principales
98
+
99
+ ### @param
100
+ ```ruby
101
+ # @param name [String] el nombre del usuario
102
+ # @param age [Integer] la edad (debe ser > 0)
103
+ def create(name, age); end
104
+ ```
105
+
106
+ ### @option (para hashes de opciones)
107
+ ```ruby
108
+ # @param opts [Hash] opciones de configuración
109
+ # @option opts [String] :host ("localhost") el hostname
110
+ # @option opts [Integer] :port (3000) el puerto
111
+ # @option opts [Boolean] :ssl (false) usar SSL
112
+ def connect(opts = {}); end
113
+ ```
114
+
115
+ ### @return
116
+ ```ruby
117
+ # @return [String] la representación en texto
118
+ # @return [void] no usar el valor de retorno
119
+ def to_s; end
120
+ ```
121
+
122
+ ### @yield y @yieldparam
123
+ ```ruby
124
+ # @yield [user, index] itera sobre cada usuario
125
+ # @yieldparam user [User] el usuario actual
126
+ # @yieldparam index [Integer] la posición en la lista
127
+ # @yieldreturn [Boolean] true para continuar, false para detener
128
+ def each_user(&block); end
129
+ ```
130
+
131
+ ### @raise
132
+ ```ruby
133
+ # @raise [ArgumentError] si el nombre está vacío
134
+ # @raise [ActiveRecord::RecordNotFound] si no existe el registro
135
+ def find!(name); end
136
+ ```
137
+
138
+ ### @example
139
+ ```ruby
140
+ # @example Uso básico
141
+ # user = User.find("john")
142
+ # user.name #=> "john"
143
+ #
144
+ # @example Con opciones
145
+ # user = User.find("john", include: :posts)
146
+ def find(name, **opts); end
147
+ ```
148
+
149
+ ### @see
150
+ ```ruby
151
+ # @see User#destroy método relacionado
152
+ # @see https://api.example.com/docs documentación externa
153
+ ```
154
+
155
+ ### @deprecated
156
+ ```ruby
157
+ # @deprecated Usar {#new_method} en su lugar desde v2.0.
158
+ def old_method; end
159
+ ```
160
+
161
+ ### @abstract
162
+ ```ruby
163
+ # @abstract Subclases deben implementar {#execute}.
164
+ class BaseCommand; end
165
+ ```
166
+
167
+ ### @note y @todo
168
+ ```ruby
169
+ # @note Este método no es thread-safe.
170
+ # @todo Agregar soporte para paginación.
171
+ def fetch_all; end
172
+ ```
173
+
174
+ ### @since y @api
175
+ ```ruby
176
+ # @since 1.5.0
177
+ # @api private
178
+ def internal_method; end
179
+ ```
180
+
181
+ ## Directivas
182
+
183
+ ### @!method (documentar métodos dinámicos)
184
+ ```ruby
185
+ class User
186
+ # @!method name
187
+ # @return [String] el nombre del usuario
188
+ # @!method name=(value)
189
+ # @param value [String] el nuevo nombre
190
+ attr_accessor :name
191
+ end
192
+ ```
193
+
194
+ ### @!attribute
195
+ ```ruby
196
+ # @!attribute [r] count
197
+ # @return [Integer] el conteo actual
198
+ # @!attribute [rw] name
199
+ # @return [String] el nombre
200
+ ```
201
+
202
+ ### @!macro (evitar repetición)
203
+ ```ruby
204
+ # @!macro [attach] property
205
+ # @!method $1
206
+ # @return [$2] el valor de $1
207
+ property :name, String
208
+ property :age, Integer
209
+ ```
210
+
211
+ ### @!group / @!endgroup
212
+ ```ruby
213
+ # @!group Validaciones
214
+
215
+ def validate_name; end
216
+ def validate_age; end
217
+
218
+ # @!endgroup
219
+ ```
220
+
221
+ ### @!scope y @!visibility
222
+ ```ruby
223
+ # @!scope class
224
+ # @!visibility private
225
+ ```
226
+
227
+ ## Reference tags
228
+ ```ruby
229
+ # @param user [String] el usuario
230
+ # @param host [String] el host
231
+ def clean(user, host); end
232
+
233
+ # @param (see #clean)
234
+ def activate(user, host); end
235
+ ```
236
+
237
+ ## Antipatrones
238
+
239
+ **Documentar lo obvio** — No repitas el nombre del método en la descripción.
240
+ ```ruby
241
+ # MAL:
242
+ # Gets the name.
243
+ # @return [String] the name
244
+ def name; end
245
+
246
+ # BIEN:
247
+ # @return [String] nombre completo del usuario (nombre + apellido)
248
+ def name; end
249
+ ```
250
+
251
+ **Omitir tipos** — Siempre especificá tipos en `@param` y `@return`.
252
+ ```ruby
253
+ # MAL:
254
+ # @param name el nombre
255
+
256
+ # BIEN:
257
+ # @param name [String] el nombre
258
+ ```
259
+
260
+ **Usar Boolean sin aclarar** — Ruby no tiene clase Boolean. Es una convención YARD.
261
+ ```ruby
262
+ # MAL:
263
+ # @return [TrueClass, FalseClass]
264
+
265
+ # BIEN:
266
+ # @return [Boolean] true si el usuario es admin
267
+ ```
268
+
269
+ **@return void sin explicar** — Usá void cuando el retorno no importa, no cuando no sabés.
270
+ ```ruby
271
+ # BIEN: método que muta estado, el retorno no importa
272
+ # @return [void]
273
+ def save!; end
274
+
275
+ # MAL: el retorno SÍ importa, no uses void
276
+ # @return [void]
277
+ def valid?; end
278
+ ```
279
+
280
+ ## FAQ
281
+
282
+ ### ¿Cómo documento un método que acepta **kwargs?
283
+ ```ruby
284
+ # @param name [String] el nombre
285
+ # @param opts [Hash] opciones adicionales
286
+ # @option opts [Integer] :timeout (30) segundos de espera
287
+ # @option opts [Boolean] :retry (true) reintentar en fallo
288
+ def fetch(name, **opts); end
289
+ ```
290
+
291
+ ### ¿Cómo documento un método con splat?
292
+ ```ruby
293
+ # @param args [Array<String>] lista variable de nombres
294
+ def process(*args); end
295
+ ```
296
+
297
+ ### ¿Cómo verifico la cobertura?
298
+ ```bash
299
+ bundle exec yard stats --list-undoc
300
+ ```
301
+
302
+ ### ¿Cómo genero la documentación?
303
+ ```bash
304
+ bundle exec yard doc
305
+ # Servidor local:
306
+ bundle exec yard server --reload
307
+ ```
308
+
309
+ ## Referencias
310
+
311
+ - [Tipos completos](references/tipos.md) — Catálogo exhaustivo de type specifications