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.
- checksums.yaml +4 -4
- data/.agents/skills/documentation-writer/SKILL.md +45 -0
- data/.agents/skills/gem-release/SKILL.md +114 -0
- data/.agents/skills/quality-code/SKILL.md +51 -0
- data/.agents/skills/sentry/SKILL.md +135 -0
- data/.agents/skills/sentry/references/api-endpoints.md +147 -0
- data/.agents/skills/sentry/scripts/sentry.rb +194 -0
- data/.agents/skills/skill-builder/SKILL.md +232 -0
- data/.agents/skills/skill-manager/SKILL.md +172 -0
- data/.agents/skills/skill-manager/scripts/sync.rb +310 -0
- data/.agents/skills/yard/SKILL.md +311 -0
- data/.agents/skills/yard/references/tipos.md +144 -0
- data/CHANGELOG.md +8 -0
- data/CLAUDE.md +28 -231
- data/lib/bug_bunny/version.rb +1 -1
- data/skill/SKILL.md +230 -0
- data/skill/references/client-middleware.md +144 -0
- data/skill/references/consumer.md +104 -0
- data/skill/references/controller.md +105 -0
- data/skill/references/errores.md +97 -0
- data/skill/references/resource.md +116 -0
- data/skill/references/routing.md +82 -0
- data/skill/references/testing.md +138 -0
- data/skills.lock +24 -0
- data/skills.yml +19 -0
- metadata +24 -28
- data/.claude/commands/gem-ai-setup.md +0 -174
- data/.claude/commands/pr.md +0 -53
- data/.claude/commands/release.md +0 -52
- data/.claude/commands/rubocop.md +0 -22
- data/.claude/commands/service-ai-setup.md +0 -168
- data/.claude/commands/test.md +0 -28
- data/.claude/commands/yard.md +0 -46
- data/docs/_index.md +0 -50
- data/docs/ai/_index.md +0 -56
- data/docs/ai/antipatterns.md +0 -166
- data/docs/ai/api.md +0 -251
- data/docs/ai/architecture.md +0 -92
- data/docs/ai/errors.md +0 -158
- data/docs/ai/faq_external.md +0 -133
- data/docs/ai/faq_internal.md +0 -86
- data/docs/ai/glossary.md +0 -45
- data/docs/concepts.md +0 -140
- data/docs/howto/controller.md +0 -194
- data/docs/howto/middleware_client.md +0 -119
- data/docs/howto/middleware_consumer.md +0 -127
- data/docs/howto/rails.md +0 -214
- data/docs/howto/resource.md +0 -200
- data/docs/howto/routing.md +0 -133
- data/docs/howto/testing.md +0 -259
- data/docs/howto/tracing.md +0 -119
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# YARD — Catálogo completo de Type Specifications
|
|
2
|
+
|
|
3
|
+
Los tipos se especifican entre corchetes `[Type]` en tags como `@param`, `@return`, `@yield`, etc.
|
|
4
|
+
|
|
5
|
+
## Tipos básicos
|
|
6
|
+
|
|
7
|
+
| Tipo | Descripción |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `String` | Clase Ruby estándar |
|
|
10
|
+
| `Integer` | Clase Ruby estándar |
|
|
11
|
+
| `Float` | Clase Ruby estándar |
|
|
12
|
+
| `Symbol` | Clase Ruby estándar |
|
|
13
|
+
| `Boolean` | Convención YARD: `TrueClass` o `FalseClass` |
|
|
14
|
+
| `nil` | Valor nil literal |
|
|
15
|
+
| `void` | Sin valor de retorno significativo |
|
|
16
|
+
| `self` | Retorna self (métodos encadenables) |
|
|
17
|
+
| `true` | Literal true |
|
|
18
|
+
| `false` | Literal false |
|
|
19
|
+
|
|
20
|
+
## Union types (múltiples tipos)
|
|
21
|
+
|
|
22
|
+
Separados por comas dentro de los corchetes:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
[String, Symbol] → String o Symbol
|
|
26
|
+
[Integer, nil] → Integer o nil
|
|
27
|
+
[String, Symbol, nil] → cualquiera de los tres
|
|
28
|
+
[Boolean, nil] → true, false o nil
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Parametrized types (generics)
|
|
32
|
+
|
|
33
|
+
Sintaxis: `Collection<ElementType>`
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
[Array<String>] → array de strings
|
|
37
|
+
[Array<String, Symbol>] → array de strings y/o symbols
|
|
38
|
+
[Set<Integer>] → set de integers
|
|
39
|
+
[Hash<Symbol, String>] → hash con keys symbol, values string
|
|
40
|
+
[Hash<Symbol, Array<Integer>>] → hash con values que son arrays de integers
|
|
41
|
+
[Enumerator<User>] → enumerator de users
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Hash con estructura explícita
|
|
45
|
+
|
|
46
|
+
Sintaxis: `Hash{KeyType => ValueType}`
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
[Hash{Symbol => String}] → hash con keys symbol, values string
|
|
50
|
+
[Hash{String => Object}] → hash con keys string, values cualquiera
|
|
51
|
+
[Hash{Symbol => Array<String>}] → hash con values que son arrays de strings
|
|
52
|
+
[Hash{String, Symbol => Integer}] → keys string o symbol, values integer
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Duck types
|
|
56
|
+
|
|
57
|
+
Prefijo `#` indica que el objeto responde a ese método:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
[#read] → cualquier objeto con método #read
|
|
61
|
+
[#call] → cualquier callable (Proc, Lambda, etc.)
|
|
62
|
+
[#to_s] → cualquier objeto convertible a string
|
|
63
|
+
[#read, #close] → debe responder a ambos métodos
|
|
64
|
+
[#each] → cualquier enumerable
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Order-dependent lists
|
|
68
|
+
|
|
69
|
+
Sintaxis: `Collection(Type1, Type2, ...)` — exactamente esos tipos en ese orden:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
[Array(String, Integer)] → array de exactamente 2 elementos: [string, integer]
|
|
73
|
+
[Array(String, Integer, Hash)] → array de exactamente 3 elementos en ese orden
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Combinaciones comunes
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
[String, nil] → string nullable
|
|
80
|
+
[Array<String>] → array de strings
|
|
81
|
+
[Hash{Symbol => Object}] → options hash
|
|
82
|
+
[Boolean] → true/false
|
|
83
|
+
[void] → sin retorno
|
|
84
|
+
[self] → chainable
|
|
85
|
+
[#read, #write] → IO-like
|
|
86
|
+
[Integer, Float] → numeric
|
|
87
|
+
[String, Symbol] → string-like identifier
|
|
88
|
+
[Array<Hash{Symbol => String}>] → array de hashes
|
|
89
|
+
[Hash{Symbol => String, nil}] → hash con values nullable
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Patrones por contexto
|
|
93
|
+
|
|
94
|
+
### Métodos de búsqueda
|
|
95
|
+
```ruby
|
|
96
|
+
# @return [User, nil] el usuario o nil si no existe
|
|
97
|
+
def find(id); end
|
|
98
|
+
|
|
99
|
+
# @return [User] el usuario
|
|
100
|
+
# @raise [RecordNotFound] si no existe
|
|
101
|
+
def find!(id); end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Métodos booleanos
|
|
105
|
+
```ruby
|
|
106
|
+
# @return [Boolean] true si el usuario es admin
|
|
107
|
+
def admin?; end
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Métodos de mutación
|
|
111
|
+
```ruby
|
|
112
|
+
# @return [void]
|
|
113
|
+
def save!; end
|
|
114
|
+
|
|
115
|
+
# @return [self] para encadenar
|
|
116
|
+
def where(conditions); end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Métodos de colección
|
|
120
|
+
```ruby
|
|
121
|
+
# @return [Array<User>] lista de usuarios
|
|
122
|
+
def all; end
|
|
123
|
+
|
|
124
|
+
# @yield [user] itera sobre cada usuario
|
|
125
|
+
# @yieldparam user [User]
|
|
126
|
+
# @return [Enumerator<User>] si no se pasa bloque
|
|
127
|
+
def each(&block); end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Métodos con opciones
|
|
131
|
+
```ruby
|
|
132
|
+
# @param opts [Hash{Symbol => Object}] opciones
|
|
133
|
+
# @option opts [Integer] :limit (10) máximo de resultados
|
|
134
|
+
# @option opts [Integer] :offset (0) desde dónde empezar
|
|
135
|
+
# @option opts [Symbol] :order (:asc) dirección del ordenamiento
|
|
136
|
+
def search(query, **opts); end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Callbacks y Procs
|
|
140
|
+
```ruby
|
|
141
|
+
# @param callback [Proc, #call] bloque a ejecutar
|
|
142
|
+
# @param filter [Proc<User, Boolean>] filtro que recibe user y retorna boolean
|
|
143
|
+
def on_create(callback); end
|
|
144
|
+
```
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.8.1] - 2026-04-04
|
|
4
|
+
|
|
5
|
+
### Mejoras internas
|
|
6
|
+
* **Skills System:** Migración completa del sistema de documentación AI de `docs/` y `.claude/commands/` al nuevo estándar de skills. La documentación AI ahora se distribuye como `skill/SKILL.md` empaquetada en la gema, con 7 archivos de referencia detallados en `skill/references/`.
|
|
7
|
+
* **CLAUDE.md simplificado:** Se eliminaron ~230 líneas de instrucciones hardcodeadas. `CLAUDE.md` ahora delega el conocimiento a las skills en `.agents/skills/` y `skill/`.
|
|
8
|
+
* **Gemspec:** `documentation_uri` actualizado de `docs` a `skill/` para apuntar a la ubicación correcta de la documentación.
|
|
9
|
+
* **Skills de desarrollo:** Se incorporan 7 skills locales en `.agents/skills/` (documentation-writer, gem-release, quality-code, sentry, skill-builder, skill-manager, yard) con `skills.yml` como manifiesto de dependencias.
|
|
10
|
+
|
|
3
11
|
## [4.8.0] - 2026-04-02
|
|
4
12
|
|
|
5
13
|
### ✨ AI Documentation Standard (v4.3)
|
data/CLAUDE.md
CHANGED
|
@@ -7,234 +7,31 @@ BugBunny es una gema Ruby que implementa una capa de enrutamiento RESTful sobre
|
|
|
7
7
|
**Problema que resuelve:** Eliminar el acoplamiento directo entre microservicios via HTTP, usando RabbitMQ como bus de mensajes con la misma ergonomía de un framework web.
|
|
8
8
|
|
|
9
9
|
## Knowledge Base
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
| Clase | Responsabilidad |
|
|
39
|
-
|---|---|
|
|
40
|
-
| `BugBunny::Session` | Wrapper de canal Bunny. Declara exchanges y queues. |
|
|
41
|
-
| `BugBunny::Consumer` | Subscribe loop. Rutea mensajes a controladores via `BugBunny.routes`. |
|
|
42
|
-
| `BugBunny::ConsumerMiddleware::Stack` | Pipeline de middlewares antes de `process_message`. |
|
|
43
|
-
| `BugBunny::Producer` | Publica mensajes. Implementa RPC con `Concurrent::IVar`. |
|
|
44
|
-
| `BugBunny::Client` | API de alto nivel para el publicador. Pool de conexiones. |
|
|
45
|
-
| `BugBunny::Controller` | Base class tipo Rails. `around_action`, `before_action`, `render`. |
|
|
46
|
-
| `BugBunny::Resource` | ActiveRecord-like sobre AMQP. `find`, `where`, `create`, etc. |
|
|
47
|
-
| `BugBunny::Request` | Value object del mensaje saliente (path, method, params, headers). |
|
|
48
|
-
| `BugBunny::Observability` | Mixin de logging estructurado. `safe_log`, `exception_metadata`. |
|
|
49
|
-
| `BugBunny::Configuration` | Configuración global. Logger, timeouts, middleware hooks. |
|
|
50
|
-
|
|
51
|
-
### Flujo RPC completo
|
|
52
|
-
|
|
53
|
-
1. `Resource.find(id)` → `Client#request` → `Producer#rpc`
|
|
54
|
-
2. Producer publica en exchange con `reply_to: 'amq.rabbitmq.reply-to'`
|
|
55
|
-
3. `Concurrent::IVar` bloquea el thread principal (`future.value(timeout)`)
|
|
56
|
-
4. Consumer recibe → middleware stack → controller → `reply(response)`
|
|
57
|
-
5. Reply listener thread setea `future.set({ body:, headers: })`
|
|
58
|
-
6. Thread principal: `on_rpc_reply&.call(headers)` → `parse_response(body)`
|
|
59
|
-
|
|
60
|
-
## Hooks de extensión
|
|
61
|
-
|
|
62
|
-
```ruby
|
|
63
|
-
# Middleware antes de process_message (ej: tracing, auth)
|
|
64
|
-
BugBunny.consumer_middlewares.use MyMiddleware
|
|
65
|
-
|
|
66
|
-
# Headers a inyectar en el reply RPC (ej: trace context actualizado)
|
|
67
|
-
config.rpc_reply_headers = -> { { 'X-Amzn-Trace-Id' => Tracer.header } }
|
|
68
|
-
|
|
69
|
-
# Callback en el thread principal al recibir el reply (ej: hidratar tracer)
|
|
70
|
-
config.on_rpc_reply = ->(headers) { Tracer.hydrate(headers['X-Amzn-Trace-Id']) }
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## Dominio y Expertise
|
|
76
|
-
|
|
77
|
-
Al trabajar en esta gema aplicá expertise en:
|
|
78
|
-
|
|
79
|
-
- **Ruby idiomático**: módulos, mixins, metaprogramación, `class_attribute`, `Concurrent::*`
|
|
80
|
-
- **RabbitMQ / AMQP**: exchanges (direct/topic/fanout), queues, bindings, `reply_to`, `correlation_id`, `properties.headers`, publisher confirms, manual ack
|
|
81
|
-
- **Bunny**: la gema Ruby que wrappea AMQP. `channel`, `basic_consume`, `basic_publish`, `IVar`
|
|
82
|
-
- **Rails patterns**: `ActiveModel`, `ActiveSupport`, `class_attribute`, `concerns`, `constantize`
|
|
83
|
-
- **Rack**: `Rack::Utils.parse_nested_query`, `build_nested_query`
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## Observability — Estándar de Logging
|
|
88
|
-
|
|
89
|
-
Esta gema implementa su propio patrón de observability via `BugBunny::Observability`.
|
|
90
|
-
|
|
91
|
-
### Reglas fundamentales
|
|
92
|
-
|
|
93
|
-
- **Formato**: `component=x event=clase.evento [key=value ...]` — todo en una línea
|
|
94
|
-
- **Nunca** llamar al logger directamente. Siempre usar `safe_log`
|
|
95
|
-
- **Nunca** `Kernel#warn`, `$stderr`, `puts`
|
|
96
|
-
- **Niveles**: `ERROR`=excepción, `WARN`=inesperado+continuó, `INFO`=normal, `DEBUG`=detalle
|
|
97
|
-
- `DEBUG` siempre en bloque: `logger.debug { "k=#{v}" }` — `safe_log` lo maneja internamente
|
|
98
|
-
- Duraciones: `Process.clock_gettime(Process::CLOCK_MONOTONIC)`, nunca `Time.now`
|
|
99
|
-
- Logger failures **nunca** interrumpen el flujo — `safe_log` tiene `rescue StandardError`
|
|
100
|
-
|
|
101
|
-
### Uso en clases nuevas
|
|
102
|
-
|
|
103
|
-
```ruby
|
|
104
|
-
class BugBunny::MiClase
|
|
105
|
-
include BugBunny::Observability
|
|
106
|
-
|
|
107
|
-
def initialize
|
|
108
|
-
@logger = BugBunny.configuration.logger
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def mi_metodo
|
|
112
|
-
start = monotonic_now
|
|
113
|
-
# ...
|
|
114
|
-
safe_log(:info, "mi_clase.mi_evento", campo: valor, duration_s: duration_s(start))
|
|
115
|
-
rescue StandardError => e
|
|
116
|
-
safe_log(:error, "mi_clase.error", **exception_metadata(e))
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Naming de eventos
|
|
122
|
-
|
|
123
|
-
Formato estricto: `"clase.evento"` (string, nunca symbol)
|
|
124
|
-
|
|
125
|
-
| Evento | Nivel | Cuándo |
|
|
126
|
-
|---|---|---|
|
|
127
|
-
| `consumer.start` | INFO | Consumer inicia subscribe |
|
|
128
|
-
| `consumer.bound` | INFO | Queue bindeada al exchange |
|
|
129
|
-
| `consumer.message_received` | INFO | Mensaje recibido, antes del routing |
|
|
130
|
-
| `consumer.route_matched` | DEBUG | Ruta encontrada |
|
|
131
|
-
| `consumer.message_processed` | INFO | Procesamiento exitoso con duración |
|
|
132
|
-
| `consumer.execution_error` | ERROR | Excepción en el procesamiento |
|
|
133
|
-
| `producer.publish` | INFO | Mensaje publicado |
|
|
134
|
-
| `producer.rpc_waiting` | DEBUG | Bloqueando esperando respuesta |
|
|
135
|
-
| `producer.rpc_response_received` | DEBUG | Reply recibido (thread principal) |
|
|
136
|
-
|
|
137
|
-
### Campos estándar
|
|
138
|
-
|
|
139
|
-
```ruby
|
|
140
|
-
safe_log(:error, "clase.error", **exception_metadata(e))
|
|
141
|
-
# => error_class: "RuntimeError", error_message: "..."
|
|
142
|
-
|
|
143
|
-
safe_log(:info, "clase.evento", duration_s: duration_s(start_time))
|
|
144
|
-
# => duration_s: 0.001234
|
|
145
|
-
|
|
146
|
-
# Valores sensibles se filtran automáticamente:
|
|
147
|
-
# password, token, secret, api_key, auth → [FILTERED]
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
## Standards de Código
|
|
153
|
-
|
|
154
|
-
### RuboCop
|
|
155
|
-
|
|
156
|
-
Esta gema usa **rubocop-rails-omakase**. Todo código nuevo o modificado debe cumplir.
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8
|
|
160
|
-
bundle exec rubocop
|
|
161
|
-
bundle exec rubocop -a # autocorrect
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
**No corregir código existente no tocado.** Solo el código nuevo o modificado en el PR.
|
|
165
|
-
|
|
166
|
-
### YARD
|
|
167
|
-
|
|
168
|
-
Todo método público nuevo o modificado lleva documentación YARD:
|
|
169
|
-
|
|
170
|
-
```ruby
|
|
171
|
-
# Descripción breve.
|
|
172
|
-
#
|
|
173
|
-
# Descripción extendida si es necesario.
|
|
174
|
-
#
|
|
175
|
-
# @param name [Type] Descripción
|
|
176
|
-
# @return [Type] Descripción
|
|
177
|
-
# @raise [ErrorClass] Cuándo se lanza
|
|
178
|
-
# @example
|
|
179
|
-
# resultado = mi_metodo(arg)
|
|
180
|
-
def mi_metodo(name)
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
bundle exec yard doc
|
|
185
|
-
bundle exec yard stats --list-undoc
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### RSpec
|
|
189
|
-
|
|
190
|
-
Tests en `spec/`. Sin mocks de dependencias externas reales (RabbitMQ se mockea con doubles de Bunny).
|
|
191
|
-
|
|
192
|
-
```bash
|
|
193
|
-
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8
|
|
194
|
-
bundle exec rspec
|
|
195
|
-
bundle exec rspec spec/bug_bunny/consumer_spec.rb # archivo específico
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
## Entorno de Desarrollo
|
|
201
|
-
|
|
202
|
-
### Ruby
|
|
203
|
-
|
|
204
|
-
```bash
|
|
205
|
-
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
Nunca usar `bundle exec ruby` con el Ruby del sistema (2.6). Siempre sourcear chruby primero.
|
|
209
|
-
|
|
210
|
-
### Worktrees
|
|
211
|
-
|
|
212
|
-
- **Main**: `/Users/gabriel/src/gems/bug_bunny` (rama `main`)
|
|
213
|
-
- **Work**: `/Users/gabriel/src/gems/worktrees/current-5n3` (ramas de feature)
|
|
214
|
-
- `main` está checkeado en otro worktree — no se puede hacer `git checkout main` desde el worktree de trabajo
|
|
215
|
-
|
|
216
|
-
### Push a remoto
|
|
217
|
-
|
|
218
|
-
SSH está roto en este entorno. Para push siempre:
|
|
219
|
-
|
|
220
|
-
```bash
|
|
221
|
-
git remote set-url origin https://github.com/gedera/bug_bunny.git
|
|
222
|
-
git push origin main
|
|
223
|
-
git remote set-url origin git@github.com:gedera/bug_bunny.git # restaurar
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
## Release Workflow
|
|
229
|
-
|
|
230
|
-
Usá el comando `/release` para el flujo completo. Manualmente:
|
|
231
|
-
|
|
232
|
-
1. Determinar tipo: `patch`=bugfix, `minor`=feature nueva, `major`=breaking change
|
|
233
|
-
2. Actualizar `lib/bug_bunny/version.rb`
|
|
234
|
-
3. Agregar entrada al tope de `CHANGELOG.md`
|
|
235
|
-
4. Commit con mensaje convencional
|
|
236
|
-
5. Desde `/Users/gabriel/src/gems/bug_bunny`: `git merge --ff-only <branch>`
|
|
237
|
-
6. Push via HTTPS + restaurar SSH
|
|
238
|
-
7. `git tag vX.Y.Z && git push origin vX.Y.Z`
|
|
239
|
-
|
|
240
|
-
**Nunca commitear ni pushear sin permiso explícito del usuario.**
|
|
10
|
+
- Las skills en `.agents/skills/` incluyen conocimiento de dependencias.
|
|
11
|
+
- Leer la skill de una dependencia ANTES de responder sobre ella.
|
|
12
|
+
- Rebuild: `ruby .agents/skills/skill-manager/scripts/sync.rb`
|
|
13
|
+
|
|
14
|
+
## Entorno
|
|
15
|
+
- Versión de Ruby: leer `.ruby-version`
|
|
16
|
+
- Versión de Rails y gemas: leer `Gemfile.lock`
|
|
17
|
+
- Gestor de Ruby: chruby (no usar rvm ni rbenv)
|
|
18
|
+
- Package manager: Bundler
|
|
19
|
+
|
|
20
|
+
## RuboCop
|
|
21
|
+
- Usamos rubocop-rails-omakase como base.
|
|
22
|
+
- Correr `bundle exec rubocop -a` antes de commitear.
|
|
23
|
+
- No deshabilitar cops sin justificación en el PR.
|
|
24
|
+
|
|
25
|
+
## YARD
|
|
26
|
+
- Documentación incremental: si tocás un método, documentalo con YARD.
|
|
27
|
+
- Consultar la skill `yard` para tags y tipos correctos.
|
|
28
|
+
- Verificar cobertura: `bundle exec yard stats --list-undoc`
|
|
29
|
+
|
|
30
|
+
## Testing
|
|
31
|
+
- Framework: RSpec
|
|
32
|
+
- Correr: `bundle exec rspec`
|
|
33
|
+
- Todo código nuevo debe tener tests.
|
|
34
|
+
|
|
35
|
+
## Releases
|
|
36
|
+
- Gemas: `/gem-release`
|
|
37
|
+
- Servicios: `/service-release build` o `/service-release deploy`
|
data/lib/bug_bunny/version.rb
CHANGED
data/skill/SKILL.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# BugBunny Expert
|
|
2
|
+
|
|
3
|
+
Skill de conocimiento completo sobre BugBunny. Consultame para cualquier pregunta sobre integración, arquitectura, API, errores y antipatrones.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Glosario
|
|
8
|
+
|
|
9
|
+
**AMQP** — Advanced Message Queuing Protocol. Protocolo binario que implementa RabbitMQ.
|
|
10
|
+
**Bunny** — Cliente Ruby para AMQP. BugBunny lo usa internamente para conexiones, canales y publicación.
|
|
11
|
+
**Exchange** — Recibe mensajes del producer y los enruta a queues según reglas. Tipos: `direct` (match exacto), `topic` (wildcards), `fanout` (broadcast).
|
|
12
|
+
**Queue** — Almacena mensajes hasta que un consumer los consume. Las queues durables sobreviven reinicios del broker.
|
|
13
|
+
**Routing Key** — String que el producer adjunta al mensaje. El exchange lo usa para decidir a qué queues enrutar.
|
|
14
|
+
**Binding** — Enlace entre un exchange y una queue, opcionalmente con un patrón de routing key.
|
|
15
|
+
**Session** — `BugBunny::Session` envuelve canales de Bunny con thread-safety y double-checked locking.
|
|
16
|
+
**RPC** — Patrón síncrono que usa la pseudo-cola `amq.rabbitmq.reply-to` para respuestas sin crear queues temporales.
|
|
17
|
+
**Fire-and-Forget** — Patrón asíncrono donde el producer publica y continúa sin esperar respuesta. Retorna `{ 'status' => 202 }`.
|
|
18
|
+
**Resource** — ORM tipo ActiveRecord que mapea operaciones CRUD a llamadas AMQP.
|
|
19
|
+
**Consumer** — Worker bloqueante que despacha mensajes a controladores mediante un Router.
|
|
20
|
+
**Connection Pool** — Pool de conexiones (`connection_pool` gem) que comparte sessions entre threads. Cada slot cachea su `Session` y `Producer`.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Arquitectura: Flujo RPC
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
28
|
+
│ Resource │────>│ Client │────>│ Middleware│────>│ Producer │
|
|
29
|
+
└──────────┘ └──────────┘ │ Stack │ └────┬─────┘
|
|
30
|
+
└──────────┘ │
|
|
31
|
+
▼
|
|
32
|
+
┌──────────┐
|
|
33
|
+
│ Exchange │
|
|
34
|
+
└────┬─────┘
|
|
35
|
+
│
|
|
36
|
+
▼
|
|
37
|
+
┌──────────┐
|
|
38
|
+
│ Queue │
|
|
39
|
+
└────┬─────┘
|
|
40
|
+
│
|
|
41
|
+
▼
|
|
42
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
43
|
+
│ Reply │<────│Controller│<────│ Router │<────│ Consumer │
|
|
44
|
+
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
1. El `Client` pasa la petición por una pila de middlewares client-side.
|
|
48
|
+
2. El `Producer` publica en el exchange con `correlation_id`, `reply_to` y el path en el header `type`.
|
|
49
|
+
3. El hilo emisor se bloquea en un `Concurrent::IVar` esperando la respuesta.
|
|
50
|
+
4. El `Consumer` recibe, ejecuta consumer middlewares, rutea al controller.
|
|
51
|
+
5. El controller ejecuta callbacks y la acción, luego responde via `reply_to`.
|
|
52
|
+
6. Se aplican ganchos de traza (`on_rpc_reply`) y se devuelve el objeto hidratado.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Arquitectura: Componentes Clave
|
|
57
|
+
|
|
58
|
+
| Clase | Responsabilidad |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `BugBunny::Configuration` | Configuración global. Valida campos requeridos en `BugBunny.configure`. |
|
|
61
|
+
| `BugBunny::Session` | Wrapper de canal Bunny. Declara exchanges y queues. Thread-safe con double-checked locking. |
|
|
62
|
+
| `BugBunny::Producer` | Publica mensajes. Implementa RPC con `Concurrent::IVar` y direct reply-to. |
|
|
63
|
+
| `BugBunny::Client` | API de alto nivel. Pool de conexiones y middleware stack (onion architecture). |
|
|
64
|
+
| `BugBunny::Consumer` | Subscribe loop con health check. Rutea mensajes via `BugBunny.routes`. |
|
|
65
|
+
| `BugBunny::ConsumerMiddleware::Stack` | Pipeline de middlewares antes de `process_message`. Thread-safe. |
|
|
66
|
+
| `BugBunny::Controller` | Base class tipo Rails. `before_action`, `around_action`, `after_action`, `rescue_from`, `render`. |
|
|
67
|
+
| `BugBunny::Resource` | ORM sobre AMQP. `find`, `where`, `create`, `save`, `destroy`. ActiveModel validations y callbacks. |
|
|
68
|
+
| `BugBunny::Routing::RouteSet` | DSL de rutas: `resources`, `namespace`, `member`, `collection`. |
|
|
69
|
+
| `BugBunny::Observability` | Mixin de logging estructurado. `safe_log` nunca lanza excepciones. Filtra keys sensibles. |
|
|
70
|
+
| `BugBunny::Middleware::Stack` | Builder de middlewares client-side (onion architecture tipo Faraday). |
|
|
71
|
+
| `BugBunny::Request` | Value object del mensaje saliente con metadata AMQP completa. |
|
|
72
|
+
| `BugBunny::Railtie` | Integración Rails: autoload de `app/rabbit`, fork safety (Puma, Spring). |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## API: Configuración Global
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
BugBunny.configure do |config|
|
|
80
|
+
# Conexión
|
|
81
|
+
config.host = 'localhost' # default: '127.0.0.1'
|
|
82
|
+
config.port = 5672 # default: 5672
|
|
83
|
+
config.username = 'guest' # default: 'guest'
|
|
84
|
+
config.password = 'guest' # default: 'guest'
|
|
85
|
+
config.vhost = '/' # default: '/'
|
|
86
|
+
|
|
87
|
+
# Resiliencia
|
|
88
|
+
config.automatically_recover = true
|
|
89
|
+
config.network_recovery_interval = 5
|
|
90
|
+
config.max_reconnect_attempts = nil # nil = infinito
|
|
91
|
+
config.connection_timeout = 10
|
|
92
|
+
config.heartbeat = 15
|
|
93
|
+
|
|
94
|
+
# Performance
|
|
95
|
+
config.channel_prefetch = 1
|
|
96
|
+
config.rpc_timeout = 10
|
|
97
|
+
|
|
98
|
+
# Logging
|
|
99
|
+
config.logger = Rails.logger
|
|
100
|
+
config.log_tags = [:uuid]
|
|
101
|
+
|
|
102
|
+
# Propagación de trazas
|
|
103
|
+
config.rpc_reply_headers = -> { { 'X-Trace-Id' => Tracer.id } }
|
|
104
|
+
config.on_rpc_reply = ->(h) { Tracer.hydrate(h['X-Trace-Id']) }
|
|
105
|
+
|
|
106
|
+
# Infraestructura (cascade level 2)
|
|
107
|
+
config.exchange_options = { durable: true }
|
|
108
|
+
config.queue_options = { auto_delete: false }
|
|
109
|
+
|
|
110
|
+
# Health check
|
|
111
|
+
config.health_check_interval = 60
|
|
112
|
+
config.health_check_file = 'tmp/bb_health'
|
|
113
|
+
|
|
114
|
+
# Routing
|
|
115
|
+
config.controller_namespace = 'BugBunny::Controllers'
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
La validación es automática tras el bloque; lanza `ConfigurationError` si faltan campos requeridos o los valores están fuera de rango.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## API: Routing DSL
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
BugBunny.routes.draw do
|
|
127
|
+
resources :users
|
|
128
|
+
resources :orders, only: [:index, :show]
|
|
129
|
+
resources :products, except: [:destroy]
|
|
130
|
+
|
|
131
|
+
namespace :admin do
|
|
132
|
+
resources :reports
|
|
133
|
+
resources :nodes do
|
|
134
|
+
member do
|
|
135
|
+
put :drain # PUT nodes/:id/drain
|
|
136
|
+
end
|
|
137
|
+
collection do
|
|
138
|
+
get :stats # GET nodes/stats
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Genera rutas REST estándar (index, show, create, update, destroy) mapeadas a controladores. El `namespace` añade prefijo al path y busca controladores dentro del módulo correspondiente.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## API: RPC vs Fire-and-Forget
|
|
150
|
+
|
|
151
|
+
**RPC síncrono** — Bloquea hasta respuesta. Usa `amq.rabbitmq.reply-to`. Timeout configurable.
|
|
152
|
+
```ruby
|
|
153
|
+
response = client.request('users/42', method: :get)
|
|
154
|
+
# → { 'status' => 200, 'body' => { 'id' => 42, 'name' => 'John' } }
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Fire-and-Forget** — Publica y continúa. Sin confirmación.
|
|
158
|
+
```ruby
|
|
159
|
+
client.publish('events', method: :post, body: { type: 'order.placed' })
|
|
160
|
+
# → { 'status' => 202, 'body' => nil }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## FAQ
|
|
166
|
+
|
|
167
|
+
### ¿Cómo se integra con Rails?
|
|
168
|
+
`rails generate bug_bunny:install` genera el inicializador, crea `app/bug_bunny/controllers/` y actualiza `CLAUDE.md`. El pool se define en el inicializador y se asigna a `BugBunny::Resource.connection_pool`.
|
|
169
|
+
|
|
170
|
+
### ¿Cómo funciona el Health Check?
|
|
171
|
+
El consumer ejecuta un check periódico (default 60s) que verifica la conexión AMQP con un `queue.declare(passive: true)`. Si `health_check_file` está configurado, actualiza su mtime. En Kubernetes, usar un `livenessProbe` tipo `exec` que verifique recencia del archivo.
|
|
172
|
+
|
|
173
|
+
### ¿Cómo funciona el Connection Pool?
|
|
174
|
+
Cada slot del pool cachea su `Session` y `Producer` durante su vida útil. Esto evita recrear canales AMQP (costoso) y previene el error de doble `basic_consume`. Thread-safety garantizada por `ConnectionPool`.
|
|
175
|
+
|
|
176
|
+
### ¿Cómo funciona la cascada de configuración?
|
|
177
|
+
3 niveles: Gem defaults → Global config (`BugBunny.configure`) → Per-request (args en `client.request` o `Resource.with`). Se mergean con `merge`.
|
|
178
|
+
|
|
179
|
+
### ¿Cómo funciona fork safety?
|
|
180
|
+
`BugBunny::Railtie` registra hooks en `ActiveSupport::ForkTracker` (Rails 7.1+), `Puma.events.on_worker_boot` y `Spring.after_fork` para llamar `BugBunny.disconnect` y evitar sockets TCP heredados.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Antipatrones
|
|
185
|
+
|
|
186
|
+
### Consumer en Puma
|
|
187
|
+
No ejecutar el `Consumer` dentro de hilos de Puma. Es un bucle bloqueante que saturará el servidor web. Usar un proceso worker dedicado o una tarea Rake separada.
|
|
188
|
+
|
|
189
|
+
### Reasignación de Pool en Runtime
|
|
190
|
+
No asignar `Resource.connection_pool` dentro de controllers o models durante una petición. Es un ajuste global que causa condiciones de carrera y fugas de conexiones.
|
|
191
|
+
|
|
192
|
+
### Abuso de .with persistente
|
|
193
|
+
No guardar el resultado de `Order.with(...)` en una variable para múltiples llamadas. Lanzará error tras la primera ejecución. Para múltiples llamadas, usar siempre la forma de bloque.
|
|
194
|
+
|
|
195
|
+
### Registrar middleware durante call()
|
|
196
|
+
No registrar consumer middlewares durante la ejecución de `call()`. El stack toma un snapshot al inicio; los registros concurrentes no afectan la ejecución actual.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Errores Comunes
|
|
201
|
+
|
|
202
|
+
### BugBunny::RequestTimeout (408)
|
|
203
|
+
**Causa:** No hubo respuesta en `config.rpc_timeout` segundos.
|
|
204
|
+
**Resolución:** Verificar que el worker esté activo y que el controlador remoto no lance excepciones silenciosas.
|
|
205
|
+
|
|
206
|
+
### BugBunny::SecurityError
|
|
207
|
+
**Causa:** El mensaje intenta ejecutar un controlador que no hereda de `BugBunny::Controller`.
|
|
208
|
+
**Resolución:** Verificar la jerarquía de controladores y que `config.controller_namespace` coincida.
|
|
209
|
+
|
|
210
|
+
### BugBunny::UnprocessableEntity (422)
|
|
211
|
+
**Causa:** Fallo de validación en el servicio remoto.
|
|
212
|
+
**Resolución:** `resource.save` devuelve `false`. Acceder a `resource.errors` o `rescue` con `e.error_messages`.
|
|
213
|
+
|
|
214
|
+
### BugBunny::CommunicationError
|
|
215
|
+
**Causa:** Fallo de conexión o reconexión agotada.
|
|
216
|
+
**Resolución:** Verificar conectividad a RabbitMQ. Revisar `max_reconnect_attempts` y logs de reconexión.
|
|
217
|
+
|
|
218
|
+
Ver catálogo completo en [Errores](references/errores.md).
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Referencias
|
|
223
|
+
|
|
224
|
+
- [Routing](references/routing.md) — DSL de rutas, bindings, namespaces, member y collection
|
|
225
|
+
- [Controllers](references/controller.md) — Acciones, callbacks, render, rescue_from y log tags
|
|
226
|
+
- [Resources](references/resource.md) — CRUD sobre AMQP, .with, callbacks y change tracking
|
|
227
|
+
- [Client y Middleware](references/client-middleware.md) — Client, Producer, middleware stack onion
|
|
228
|
+
- [Consumer](references/consumer.md) — Subscribe loop, consumer middleware, health check
|
|
229
|
+
- [Catálogo de Errores](references/errores.md) — Jerarquía completa de excepciones con resolución
|
|
230
|
+
- [Testing](references/testing.md) — RSpec helpers, mocks, patrones de integración
|