claude-agent-framework 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +128 -0
  2. package/bin/claude-framework +3 -0
  3. package/framework/agents/design-lead.md +240 -0
  4. package/framework/agents/product-owner.md +179 -0
  5. package/framework/agents/tech-lead.md +226 -0
  6. package/framework/commands/ayuda.md +127 -0
  7. package/framework/commands/a/303/261adir.md +98 -0
  8. package/framework/commands/backup.md +397 -0
  9. package/framework/commands/cambiar.md +110 -0
  10. package/framework/commands/cloud.md +457 -0
  11. package/framework/commands/code.md +142 -0
  12. package/framework/commands/debug.md +334 -0
  13. package/framework/commands/deploy.md +383 -0
  14. package/framework/commands/deshacer.md +120 -0
  15. package/framework/commands/estado.md +218 -0
  16. package/framework/commands/explica.md +227 -0
  17. package/framework/commands/feature.md +120 -0
  18. package/framework/commands/git.md +427 -0
  19. package/framework/commands/historial.md +202 -0
  20. package/framework/commands/learn.md +408 -0
  21. package/framework/commands/movil.md +245 -0
  22. package/framework/commands/nuevo.md +118 -0
  23. package/framework/commands/plan.md +134 -0
  24. package/framework/commands/prd.md +113 -0
  25. package/framework/commands/probar.md +148 -0
  26. package/framework/commands/revisar.md +208 -0
  27. package/framework/commands/seeds.md +230 -0
  28. package/framework/commands/seguridad.md +226 -0
  29. package/framework/commands/tasks.md +157 -0
  30. package/framework/skills/architecture/algorithms.md +970 -0
  31. package/framework/skills/architecture/clean-code.md +1080 -0
  32. package/framework/skills/architecture/design-patterns.md +1984 -0
  33. package/framework/skills/architecture/functional-programming.md +972 -0
  34. package/framework/skills/architecture/solid.md +991 -0
  35. package/framework/skills/cloud/cloud-aws.md +848 -0
  36. package/framework/skills/cloud/cloud-azure.md +931 -0
  37. package/framework/skills/cloud/cloud-gcp.md +848 -0
  38. package/framework/skills/cloud/message-queues.md +1229 -0
  39. package/framework/skills/core/accessibility.md +401 -0
  40. package/framework/skills/core/api.md +474 -0
  41. package/framework/skills/core/authentication.md +306 -0
  42. package/framework/skills/core/authorization.md +388 -0
  43. package/framework/skills/core/background-jobs.md +341 -0
  44. package/framework/skills/core/caching.md +473 -0
  45. package/framework/skills/core/code-review.md +341 -0
  46. package/framework/skills/core/controllers.md +290 -0
  47. package/framework/skills/core/cua.md +285 -0
  48. package/framework/skills/core/documentation.md +472 -0
  49. package/framework/skills/core/file-uploads.md +351 -0
  50. package/framework/skills/core/hotwire-native.md +296 -0
  51. package/framework/skills/core/hotwire.md +278 -0
  52. package/framework/skills/core/i18n.md +334 -0
  53. package/framework/skills/core/imports-exports.md +750 -0
  54. package/framework/skills/core/infrastructure.md +337 -0
  55. package/framework/skills/core/models.md +228 -0
  56. package/framework/skills/core/notifications.md +672 -0
  57. package/framework/skills/core/payments.md +581 -0
  58. package/framework/skills/core/performance.md +361 -0
  59. package/framework/skills/core/rails-scaffold.md +131 -0
  60. package/framework/skills/core/search.md +518 -0
  61. package/framework/skills/core/security.md +565 -0
  62. package/framework/skills/core/seeds.md +307 -0
  63. package/framework/skills/core/seo.md +542 -0
  64. package/framework/skills/core/testing.md +393 -0
  65. package/framework/skills/core/views.md +260 -0
  66. package/framework/skills/core/websockets.md +564 -0
  67. package/framework/skills/data/advanced-sql.md +1204 -0
  68. package/framework/skills/data/nosql.md +1141 -0
  69. package/framework/skills/devops/containers-advanced.md +1237 -0
  70. package/framework/skills/devops/debugging.md +834 -0
  71. package/framework/skills/devops/git-workflow.md +752 -0
  72. package/framework/skills/devops/networking.md +932 -0
  73. package/framework/skills/devops/shell-scripting.md +1132 -0
  74. package/framework/sub-agents/architecture-patterns-agent.md +1450 -0
  75. package/framework/sub-agents/cloud-agent.md +677 -0
  76. package/framework/sub-agents/data.md +504 -0
  77. package/framework/sub-agents/debugging-agent.md +554 -0
  78. package/framework/sub-agents/devops.md +483 -0
  79. package/framework/sub-agents/docs.md +176 -0
  80. package/framework/sub-agents/frontend-dev.md +349 -0
  81. package/framework/sub-agents/git-workflow-agent.md +697 -0
  82. package/framework/sub-agents/integrations.md +630 -0
  83. package/framework/sub-agents/native-dev.md +434 -0
  84. package/framework/sub-agents/qa.md +138 -0
  85. package/framework/sub-agents/rails-dev.md +375 -0
  86. package/framework/sub-agents/security.md +526 -0
  87. package/framework/sub-agents/ui.md +437 -0
  88. package/framework/sub-agents/ux.md +284 -0
  89. package/framework/templates/api-spec.md +500 -0
  90. package/framework/templates/component-spec.md +248 -0
  91. package/framework/templates/feature.json +13 -0
  92. package/framework/templates/model-spec.md +318 -0
  93. package/framework/templates/prd-template.md +80 -0
  94. package/framework/templates/task-plan.md +122 -0
  95. package/framework/templates/task-user-story.md +52 -0
  96. package/framework/templates/technical-spec.md +260 -0
  97. package/framework/templates/user-story.md +95 -0
  98. package/package.json +42 -0
  99. package/project-templates/CLAUDE.md +42 -0
  100. package/project-templates/contexts/architecture.md +25 -0
  101. package/project-templates/contexts/conventions.md +46 -0
  102. package/project-templates/contexts/design-system.md +47 -0
  103. package/project-templates/contexts/requirements.md +38 -0
  104. package/project-templates/contexts/stack.md +30 -0
  105. package/project-templates/history/active/models.md +11 -0
  106. package/project-templates/history/changelog.md +15 -0
  107. package/project-templates/workspace/.gitkeep +0 -0
  108. package/src/cli.js +52 -0
  109. package/src/init.js +104 -0
  110. package/src/status.js +75 -0
  111. package/src/update.js +88 -0
@@ -0,0 +1,518 @@
1
+ # Skill: Search
2
+
3
+ ## Purpose
4
+
5
+ Implementar funcionalidades de búsqueda en aplicaciones Rails, desde búsqueda simple hasta full-text search.
6
+
7
+ ## Búsqueda simple con SQLite
8
+
9
+ ### LIKE queries
10
+
11
+ ```ruby
12
+ # app/models/post.rb
13
+ class Post < ApplicationRecord
14
+ scope :search, ->(query) {
15
+ return all if query.blank?
16
+
17
+ where("title LIKE ? OR body LIKE ?", "%#{query}%", "%#{query}%")
18
+ }
19
+ end
20
+
21
+ # Uso
22
+ Post.search(params[:q])
23
+ ```
24
+
25
+ ### Búsqueda case-insensitive
26
+
27
+ ```ruby
28
+ # SQLite
29
+ scope :search, ->(query) {
30
+ return all if query.blank?
31
+
32
+ where("LOWER(title) LIKE LOWER(?) OR LOWER(body) LIKE LOWER(?)",
33
+ "%#{query}%", "%#{query}%")
34
+ }
35
+
36
+ # O con COLLATE NOCASE
37
+ scope :search, ->(query) {
38
+ return all if query.blank?
39
+
40
+ where("title LIKE ? COLLATE NOCASE OR body LIKE ? COLLATE NOCASE",
41
+ "%#{query}%", "%#{query}%")
42
+ }
43
+ ```
44
+
45
+ ### Búsqueda en múltiples campos
46
+
47
+ ```ruby
48
+ class Post < ApplicationRecord
49
+ include Searchable
50
+
51
+ searchable_fields :title, :body, :author_name
52
+
53
+ def author_name
54
+ author&.name
55
+ end
56
+ end
57
+
58
+ # app/models/concerns/searchable.rb
59
+ module Searchable
60
+ extend ActiveSupport::Concern
61
+
62
+ class_methods do
63
+ def searchable_fields(*fields)
64
+ @searchable_fields = fields
65
+ end
66
+
67
+ def search(query)
68
+ return all if query.blank?
69
+
70
+ fields = @searchable_fields || [:name]
71
+ conditions = fields.map { |f| "LOWER(#{f}) LIKE LOWER(?)" }.join(" OR ")
72
+ values = fields.map { "%#{query}%" }
73
+
74
+ where(conditions, *values)
75
+ end
76
+ end
77
+ end
78
+ ```
79
+
80
+ ## SQLite FTS5 (Full-Text Search)
81
+
82
+ ### Setup
83
+
84
+ ```ruby
85
+ # db/migrate/xxx_create_posts_fts.rb
86
+ class CreatePostsFts < ActiveRecord::Migration[8.0]
87
+ def up
88
+ execute <<-SQL
89
+ CREATE VIRTUAL TABLE posts_fts USING fts5(
90
+ title,
91
+ body,
92
+ content='posts',
93
+ content_rowid='id'
94
+ );
95
+
96
+ -- Triggers para mantener sincronizado
97
+ CREATE TRIGGER posts_ai AFTER INSERT ON posts BEGIN
98
+ INSERT INTO posts_fts(rowid, title, body)
99
+ VALUES (new.id, new.title, new.body);
100
+ END;
101
+
102
+ CREATE TRIGGER posts_ad AFTER DELETE ON posts BEGIN
103
+ INSERT INTO posts_fts(posts_fts, rowid, title, body)
104
+ VALUES ('delete', old.id, old.title, old.body);
105
+ END;
106
+
107
+ CREATE TRIGGER posts_au AFTER UPDATE ON posts BEGIN
108
+ INSERT INTO posts_fts(posts_fts, rowid, title, body)
109
+ VALUES ('delete', old.id, old.title, old.body);
110
+ INSERT INTO posts_fts(rowid, title, body)
111
+ VALUES (new.id, new.title, new.body);
112
+ END;
113
+ SQL
114
+
115
+ # Poblar FTS con datos existentes
116
+ execute <<-SQL
117
+ INSERT INTO posts_fts(rowid, title, body)
118
+ SELECT id, title, body FROM posts;
119
+ SQL
120
+ end
121
+
122
+ def down
123
+ execute "DROP TABLE IF EXISTS posts_fts"
124
+ end
125
+ end
126
+ ```
127
+
128
+ ### Modelo con FTS
129
+
130
+ ```ruby
131
+ # app/models/post.rb
132
+ class Post < ApplicationRecord
133
+ scope :full_text_search, ->(query) {
134
+ return all if query.blank?
135
+
136
+ # Sanitizar query
137
+ sanitized = query.gsub(/[^\w\s]/, "")
138
+
139
+ joins("INNER JOIN posts_fts ON posts_fts.rowid = posts.id")
140
+ .where("posts_fts MATCH ?", sanitized)
141
+ .select("posts.*, rank AS search_rank")
142
+ .order("search_rank")
143
+ }
144
+
145
+ # Con highlighting
146
+ scope :search_with_highlights, ->(query) {
147
+ return all if query.blank?
148
+
149
+ sanitized = query.gsub(/[^\w\s]/, "")
150
+
151
+ joins("INNER JOIN posts_fts ON posts_fts.rowid = posts.id")
152
+ .where("posts_fts MATCH ?", sanitized)
153
+ .select(
154
+ "posts.*",
155
+ "highlight(posts_fts, 0, '<mark>', '</mark>') AS title_highlight",
156
+ "snippet(posts_fts, 1, '<mark>', '</mark>', '...', 32) AS body_snippet"
157
+ )
158
+ .order("rank")
159
+ }
160
+ end
161
+ ```
162
+
163
+ ## Ransack (búsqueda avanzada)
164
+
165
+ ### Setup
166
+
167
+ ```ruby
168
+ # Gemfile
169
+ gem "ransack"
170
+ ```
171
+
172
+ ### Controller
173
+
174
+ ```ruby
175
+ class PostsController < ApplicationController
176
+ def index
177
+ @q = Post.ransack(params[:q])
178
+ @posts = @q.result(distinct: true)
179
+ .includes(:author)
180
+ .page(params[:page])
181
+ end
182
+ end
183
+ ```
184
+
185
+ ### Vista con formulario de búsqueda
186
+
187
+ ```erb
188
+ <%# app/views/posts/index.html.erb %>
189
+ <%= search_form_for @q, url: posts_path, method: :get do |f| %>
190
+ <div class="flex gap-4">
191
+ <%# Búsqueda de texto %>
192
+ <%= f.search_field :title_or_body_cont,
193
+ placeholder: t(".search_placeholder"),
194
+ class: "input" %>
195
+
196
+ <%# Filtro por estado %>
197
+ <%= f.select :status_eq,
198
+ Post.statuses.keys.map { |s| [s.humanize, s] },
199
+ { include_blank: t(".all_statuses") },
200
+ class: "select" %>
201
+
202
+ <%# Rango de fechas %>
203
+ <%= f.date_field :created_at_gteq, class: "input" %>
204
+ <%= f.date_field :created_at_lteq, class: "input" %>
205
+
206
+ <%# Ordenamiento %>
207
+ <%= f.select :s,
208
+ [
209
+ [t(".newest"), "created_at desc"],
210
+ [t(".oldest"), "created_at asc"],
211
+ [t(".title_az"), "title asc"]
212
+ ],
213
+ {},
214
+ class: "select" %>
215
+
216
+ <%= f.submit t(".search"), class: "btn btn-primary" %>
217
+ </div>
218
+ <% end %>
219
+ ```
220
+
221
+ ### Predicados de Ransack
222
+
223
+ ```ruby
224
+ # Predicados disponibles
225
+ title_eq # título igual a
226
+ title_cont # título contiene
227
+ title_start # título empieza con
228
+ title_end # título termina con
229
+ title_present # título está presente
230
+ title_blank # título está vacío
231
+ created_at_gt # fecha mayor que
232
+ created_at_lt # fecha menor que
233
+ created_at_gteq # fecha mayor o igual
234
+ created_at_lteq # fecha menor o igual
235
+ id_in # id en array
236
+ status_not_eq # status diferente de
237
+
238
+ # Combinaciones
239
+ title_or_body_cont # título O cuerpo contiene
240
+ ```
241
+
242
+ ### Custom ransackers
243
+
244
+ ```ruby
245
+ # app/models/post.rb
246
+ class Post < ApplicationRecord
247
+ # Búsqueda personalizada
248
+ ransacker :published_year do
249
+ Arel.sql("strftime('%Y', published_at)")
250
+ end
251
+
252
+ # Búsqueda en asociación
253
+ ransacker :author_name do
254
+ Arel.sql("(SELECT name FROM users WHERE users.id = posts.author_id)")
255
+ end
256
+ end
257
+
258
+ # Uso: params[:q][:published_year_eq] = "2024"
259
+ ```
260
+
261
+ ## Meilisearch (motor de búsqueda externo)
262
+
263
+ ### Setup
264
+
265
+ ```ruby
266
+ # Gemfile
267
+ gem "meilisearch-rails"
268
+ ```
269
+
270
+ ```ruby
271
+ # config/initializers/meilisearch.rb
272
+ MeiliSearch::Rails.configuration = {
273
+ meilisearch_url: ENV.fetch("MEILISEARCH_URL", "http://localhost:7700"),
274
+ meilisearch_api_key: Rails.application.credentials.dig(:meilisearch, :api_key)
275
+ }
276
+ ```
277
+
278
+ ### Modelo
279
+
280
+ ```ruby
281
+ # app/models/post.rb
282
+ class Post < ApplicationRecord
283
+ include MeiliSearch::Rails
284
+
285
+ meilisearch do
286
+ # Atributos a indexar
287
+ attribute :title, :body, :status
288
+ attribute :author_name do
289
+ author&.name
290
+ end
291
+ attribute :created_at_timestamp do
292
+ created_at.to_i
293
+ end
294
+
295
+ # Atributos buscables
296
+ searchable_attributes [:title, :body, :author_name]
297
+
298
+ # Atributos filtrables
299
+ filterable_attributes [:status, :author_id, :created_at_timestamp]
300
+
301
+ # Atributos ordenables
302
+ sortable_attributes [:created_at_timestamp, :title]
303
+
304
+ # Ranking personalizado
305
+ ranking_rules [
306
+ "words",
307
+ "typo",
308
+ "proximity",
309
+ "attribute",
310
+ "sort",
311
+ "exactness"
312
+ ]
313
+ end
314
+
315
+ # Solo indexar posts publicados
316
+ def self.meilisearch_settings
317
+ {
318
+ if: -> (post) { post.published? }
319
+ }
320
+ end
321
+ end
322
+ ```
323
+
324
+ ### Controller
325
+
326
+ ```ruby
327
+ class SearchController < ApplicationController
328
+ def index
329
+ if params[:q].present?
330
+ @results = Post.search(
331
+ params[:q],
332
+ {
333
+ filters: build_filters,
334
+ sort: [params[:sort] || "created_at_timestamp:desc"],
335
+ limit: 20,
336
+ offset: (params[:page].to_i - 1) * 20,
337
+ attributes_to_highlight: ["title", "body"],
338
+ highlight_pre_tag: "<mark>",
339
+ highlight_post_tag: "</mark>"
340
+ }
341
+ )
342
+ else
343
+ @results = []
344
+ end
345
+ end
346
+
347
+ private
348
+
349
+ def build_filters
350
+ filters = []
351
+ filters << "status = published"
352
+ filters << "author_id = #{params[:author_id]}" if params[:author_id].present?
353
+ filters.join(" AND ")
354
+ end
355
+ end
356
+ ```
357
+
358
+ ### Vista con highlights
359
+
360
+ ```erb
361
+ <% @results.each do |result| %>
362
+ <article>
363
+ <h2>
364
+ <%# Usar highlighted si está disponible %>
365
+ <%= raw result._formatted&.dig("title") || result.title %>
366
+ </h2>
367
+ <p>
368
+ <%= raw result._formatted&.dig("body")&.truncate(200) || result.body.truncate(200) %>
369
+ </p>
370
+ </article>
371
+ <% end %>
372
+ ```
373
+
374
+ ## Elasticsearch (alternativa enterprise)
375
+
376
+ ### Setup básico
377
+
378
+ ```ruby
379
+ # Gemfile
380
+ gem "elasticsearch-model"
381
+ gem "elasticsearch-rails"
382
+ ```
383
+
384
+ ```ruby
385
+ # app/models/post.rb
386
+ class Post < ApplicationRecord
387
+ include Elasticsearch::Model
388
+ include Elasticsearch::Model::Callbacks
389
+
390
+ settings do
391
+ mappings dynamic: "false" do
392
+ indexes :title, type: "text", analyzer: "spanish"
393
+ indexes :body, type: "text", analyzer: "spanish"
394
+ indexes :status, type: "keyword"
395
+ indexes :created_at, type: "date"
396
+ end
397
+ end
398
+
399
+ def as_indexed_json(options = {})
400
+ as_json(only: [:title, :body, :status, :created_at])
401
+ end
402
+ end
403
+ ```
404
+
405
+ ## Autocompletado
406
+
407
+ ### Con Stimulus
408
+
409
+ ```javascript
410
+ // app/javascript/controllers/autocomplete_controller.js
411
+ import { Controller } from "@hotwired/stimulus"
412
+ import { debounce } from "lodash-es"
413
+
414
+ export default class extends Controller {
415
+ static targets = ["input", "results"]
416
+ static values = { url: String, minLength: { type: Number, default: 2 } }
417
+
418
+ connect() {
419
+ this.search = debounce(this.search.bind(this), 300)
420
+ }
421
+
422
+ async search() {
423
+ const query = this.inputTarget.value
424
+
425
+ if (query.length < this.minLengthValue) {
426
+ this.hideResults()
427
+ return
428
+ }
429
+
430
+ const response = await fetch(`${this.urlValue}?q=${encodeURIComponent(query)}`)
431
+ const html = await response.text()
432
+
433
+ this.resultsTarget.innerHTML = html
434
+ this.showResults()
435
+ }
436
+
437
+ select(event) {
438
+ this.inputTarget.value = event.currentTarget.dataset.value
439
+ this.hideResults()
440
+ }
441
+
442
+ showResults() {
443
+ this.resultsTarget.classList.remove("hidden")
444
+ }
445
+
446
+ hideResults() {
447
+ this.resultsTarget.classList.add("hidden")
448
+ }
449
+
450
+ // Cerrar al hacer click fuera
451
+ clickOutside(event) {
452
+ if (!this.element.contains(event.target)) {
453
+ this.hideResults()
454
+ }
455
+ }
456
+ }
457
+ ```
458
+
459
+ ### Controller de autocompletado
460
+
461
+ ```ruby
462
+ class AutocompleteController < ApplicationController
463
+ def posts
464
+ @posts = Post.search(params[:q]).limit(10)
465
+ render partial: "autocomplete/posts", locals: { posts: @posts }
466
+ end
467
+ end
468
+ ```
469
+
470
+ ### Vista
471
+
472
+ ```erb
473
+ <%# Formulario con autocompletado %>
474
+ <div data-controller="autocomplete"
475
+ data-autocomplete-url-value="<%= autocomplete_posts_path %>"
476
+ data-action="click@window->autocomplete#clickOutside">
477
+
478
+ <%= text_field_tag :q,
479
+ params[:q],
480
+ placeholder: "Buscar...",
481
+ data: {
482
+ autocomplete_target: "input",
483
+ action: "input->autocomplete#search"
484
+ },
485
+ class: "input w-full" %>
486
+
487
+ <div data-autocomplete-target="results"
488
+ class="hidden absolute bg-white border rounded-lg shadow-lg mt-1 w-full z-50">
489
+ <%# Los resultados se cargan aquí %>
490
+ </div>
491
+ </div>
492
+
493
+ <%# app/views/autocomplete/_posts.html.erb %>
494
+ <ul class="divide-y">
495
+ <% posts.each do |post| %>
496
+ <li>
497
+ <button type="button"
498
+ data-action="autocomplete#select"
499
+ data-value="<%= post.title %>"
500
+ class="w-full text-left px-4 py-2 hover:bg-gray-100">
501
+ <%= post.title %>
502
+ <span class="text-gray-500 text-sm block"><%= truncate(post.body, length: 50) %></span>
503
+ </button>
504
+ </li>
505
+ <% end %>
506
+ </ul>
507
+ ```
508
+
509
+ ## Checklist
510
+
511
+ - [ ] Índices creados en columnas buscadas
512
+ - [ ] Queries sanitizadas (no SQL injection)
513
+ - [ ] Paginación implementada
514
+ - [ ] Búsqueda case-insensitive
515
+ - [ ] Resultados destacados (highlights)
516
+ - [ ] Debounce en autocompletado
517
+ - [ ] Manejo de queries vacías
518
+ - [ ] Performance testeada con datos reales