ag-cortex 0.1.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 (162) hide show
  1. package/.agent/commands/test-browser.md +339 -0
  2. package/.agent/rules/00-constitution.md +46 -0
  3. package/.agent/rules/project-rules.md +49 -0
  4. package/.agent/skills/agent-browser/SKILL.md +223 -0
  5. package/.agent/skills/agent-native-architecture/SKILL.md +435 -0
  6. package/.agent/skills/agent-native-architecture/references/action-parity-discipline.md +409 -0
  7. package/.agent/skills/agent-native-architecture/references/agent-execution-patterns.md +467 -0
  8. package/.agent/skills/agent-native-architecture/references/agent-native-testing.md +582 -0
  9. package/.agent/skills/agent-native-architecture/references/architecture-patterns.md +478 -0
  10. package/.agent/skills/agent-native-architecture/references/dynamic-context-injection.md +338 -0
  11. package/.agent/skills/agent-native-architecture/references/files-universal-interface.md +301 -0
  12. package/.agent/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +359 -0
  13. package/.agent/skills/agent-native-architecture/references/mcp-tool-design.md +506 -0
  14. package/.agent/skills/agent-native-architecture/references/mobile-patterns.md +871 -0
  15. package/.agent/skills/agent-native-architecture/references/product-implications.md +443 -0
  16. package/.agent/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +317 -0
  17. package/.agent/skills/agent-native-architecture/references/self-modification.md +269 -0
  18. package/.agent/skills/agent-native-architecture/references/shared-workspace-architecture.md +680 -0
  19. package/.agent/skills/agent-native-architecture/references/system-prompt-design.md +250 -0
  20. package/.agent/skills/agent-native-reviewer/SKILL.md +246 -0
  21. package/.agent/skills/andrew-kane-gem-writer/SKILL.md +184 -0
  22. package/.agent/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
  23. package/.agent/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
  24. package/.agent/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
  25. package/.agent/skills/andrew-kane-gem-writer/references/resources.md +119 -0
  26. package/.agent/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
  27. package/.agent/skills/ankane-readme-writer/SKILL.md +50 -0
  28. package/.agent/skills/architecture-strategist/SKILL.md +52 -0
  29. package/.agent/skills/best-practices-researcher/SKILL.md +100 -0
  30. package/.agent/skills/bug-reproduction-validator/SKILL.md +67 -0
  31. package/.agent/skills/code-simplicity-reviewer/SKILL.md +85 -0
  32. package/.agent/skills/coding-tutor/.claude-plugin/plugin.json +9 -0
  33. package/.agent/skills/coding-tutor/README.md +37 -0
  34. package/.agent/skills/coding-tutor/commands/quiz-me.md +1 -0
  35. package/.agent/skills/coding-tutor/commands/sync-tutorials.md +25 -0
  36. package/.agent/skills/coding-tutor/commands/teach-me.md +1 -0
  37. package/.agent/skills/coding-tutor/skills/coding-tutor/SKILL.md +214 -0
  38. package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/create_tutorial.py +202 -0
  39. package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/index_tutorials.py +203 -0
  40. package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/quiz_priority.py +190 -0
  41. package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/setup_tutorials.py +132 -0
  42. package/.agent/skills/compound-docs/SKILL.md +510 -0
  43. package/.agent/skills/compound-docs/assets/critical-pattern-template.md +34 -0
  44. package/.agent/skills/compound-docs/assets/resolution-template.md +93 -0
  45. package/.agent/skills/compound-docs/references/yaml-schema.md +65 -0
  46. package/.agent/skills/compound-docs/schema.yaml +176 -0
  47. package/.agent/skills/create-agent-skills/SKILL.md +299 -0
  48. package/.agent/skills/create-agent-skills/references/api-security.md +226 -0
  49. package/.agent/skills/create-agent-skills/references/be-clear-and-direct.md +531 -0
  50. package/.agent/skills/create-agent-skills/references/best-practices.md +404 -0
  51. package/.agent/skills/create-agent-skills/references/common-patterns.md +595 -0
  52. package/.agent/skills/create-agent-skills/references/core-principles.md +437 -0
  53. package/.agent/skills/create-agent-skills/references/executable-code.md +175 -0
  54. package/.agent/skills/create-agent-skills/references/iteration-and-testing.md +474 -0
  55. package/.agent/skills/create-agent-skills/references/official-spec.md +185 -0
  56. package/.agent/skills/create-agent-skills/references/recommended-structure.md +168 -0
  57. package/.agent/skills/create-agent-skills/references/skill-structure.md +372 -0
  58. package/.agent/skills/create-agent-skills/references/using-scripts.md +113 -0
  59. package/.agent/skills/create-agent-skills/references/using-templates.md +112 -0
  60. package/.agent/skills/create-agent-skills/references/workflows-and-validation.md +510 -0
  61. package/.agent/skills/create-agent-skills/templates/router-skill.md +73 -0
  62. package/.agent/skills/create-agent-skills/templates/simple-skill.md +33 -0
  63. package/.agent/skills/create-agent-skills/workflows/add-reference.md +96 -0
  64. package/.agent/skills/create-agent-skills/workflows/add-script.md +93 -0
  65. package/.agent/skills/create-agent-skills/workflows/add-template.md +74 -0
  66. package/.agent/skills/create-agent-skills/workflows/add-workflow.md +120 -0
  67. package/.agent/skills/create-agent-skills/workflows/audit-skill.md +138 -0
  68. package/.agent/skills/create-agent-skills/workflows/create-domain-expertise-skill.md +605 -0
  69. package/.agent/skills/create-agent-skills/workflows/create-new-skill.md +191 -0
  70. package/.agent/skills/create-agent-skills/workflows/get-guidance.md +121 -0
  71. package/.agent/skills/create-agent-skills/workflows/upgrade-to-router.md +161 -0
  72. package/.agent/skills/create-agent-skills/workflows/verify-skill.md +204 -0
  73. package/.agent/skills/data-integrity-guardian/SKILL.md +70 -0
  74. package/.agent/skills/data-migration-expert/SKILL.md +97 -0
  75. package/.agent/skills/deployment-verification-agent/SKILL.md +159 -0
  76. package/.agent/skills/design-implementation-reviewer/SKILL.md +85 -0
  77. package/.agent/skills/design-iterator/SKILL.md +197 -0
  78. package/.agent/skills/dhh-rails-reviewer/SKILL.md +45 -0
  79. package/.agent/skills/dhh-rails-style/SKILL.md +184 -0
  80. package/.agent/skills/dhh-rails-style/references/architecture.md +653 -0
  81. package/.agent/skills/dhh-rails-style/references/controllers.md +303 -0
  82. package/.agent/skills/dhh-rails-style/references/frontend.md +510 -0
  83. package/.agent/skills/dhh-rails-style/references/gems.md +266 -0
  84. package/.agent/skills/dhh-rails-style/references/models.md +359 -0
  85. package/.agent/skills/dhh-rails-style/references/testing.md +338 -0
  86. package/.agent/skills/dspy-ruby/SKILL.md +594 -0
  87. package/.agent/skills/dspy-ruby/assets/config-template.rb +359 -0
  88. package/.agent/skills/dspy-ruby/assets/module-template.rb +326 -0
  89. package/.agent/skills/dspy-ruby/assets/signature-template.rb +143 -0
  90. package/.agent/skills/dspy-ruby/references/core-concepts.md +265 -0
  91. package/.agent/skills/dspy-ruby/references/optimization.md +623 -0
  92. package/.agent/skills/dspy-ruby/references/providers.md +305 -0
  93. package/.agent/skills/every-style-editor/SKILL.md +134 -0
  94. package/.agent/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
  95. package/.agent/skills/figma-design-sync/SKILL.md +166 -0
  96. package/.agent/skills/file-todos/SKILL.md +251 -0
  97. package/.agent/skills/file-todos/assets/todo-template.md +155 -0
  98. package/.agent/skills/framework-docs-researcher/SKILL.md +83 -0
  99. package/.agent/skills/frontend-design/SKILL.md +42 -0
  100. package/.agent/skills/gemini-imagegen/SKILL.md +237 -0
  101. package/.agent/skills/gemini-imagegen/requirements.txt +2 -0
  102. package/.agent/skills/gemini-imagegen/scripts/compose_images.py +168 -0
  103. package/.agent/skills/gemini-imagegen/scripts/edit_image.py +157 -0
  104. package/.agent/skills/gemini-imagegen/scripts/gemini_images.py +265 -0
  105. package/.agent/skills/gemini-imagegen/scripts/generate_image.py +147 -0
  106. package/.agent/skills/gemini-imagegen/scripts/multi_turn_chat.py +215 -0
  107. package/.agent/skills/git-history-analyzer/SKILL.md +42 -0
  108. package/.agent/skills/git-worktree/SKILL.md +302 -0
  109. package/.agent/skills/git-worktree/scripts/worktree-manager.sh +345 -0
  110. package/.agent/skills/julik-frontend-races-reviewer/SKILL.md +222 -0
  111. package/.agent/skills/kieran-python-reviewer/SKILL.md +104 -0
  112. package/.agent/skills/kieran-rails-reviewer/SKILL.md +86 -0
  113. package/.agent/skills/kieran-typescript-reviewer/SKILL.md +95 -0
  114. package/.agent/skills/lint/SKILL.md +16 -0
  115. package/.agent/skills/pattern-recognition-specialist/SKILL.md +57 -0
  116. package/.agent/skills/performance-oracle/SKILL.md +110 -0
  117. package/.agent/skills/pr-comment-resolver/SKILL.md +69 -0
  118. package/.agent/skills/rclone/SKILL.md +150 -0
  119. package/.agent/skills/rclone/scripts/check_setup.sh +60 -0
  120. package/.agent/skills/repo-research-analyst/SKILL.md +113 -0
  121. package/.agent/skills/security-sentinel/SKILL.md +93 -0
  122. package/.agent/skills/skill-creator/SKILL.md +209 -0
  123. package/.agent/skills/skill-creator/scripts/init_skill.py +304 -0
  124. package/.agent/skills/skill-creator/scripts/package_skill.py +112 -0
  125. package/.agent/skills/skill-creator/scripts/quick_validate.py +72 -0
  126. package/.agent/skills/spec-flow-analyzer/SKILL.md +113 -0
  127. package/.agent/skills/test-agent/SKILL.md +4 -0
  128. package/.agent/workflows/agent-native-audit.md +277 -0
  129. package/.agent/workflows/ask-user-question.md +21 -0
  130. package/.agent/workflows/changelog.md +137 -0
  131. package/.agent/workflows/compound.md +202 -0
  132. package/.agent/workflows/create-agent-skill.md +8 -0
  133. package/.agent/workflows/deepen-plan-research.md +334 -0
  134. package/.agent/workflows/deepen-plan-synthesis.md +182 -0
  135. package/.agent/workflows/deepen-plan.md +79 -0
  136. package/.agent/workflows/feature-video.md +342 -0
  137. package/.agent/workflows/generate-command.md +162 -0
  138. package/.agent/workflows/heal-skill.md +142 -0
  139. package/.agent/workflows/lfg.md +20 -0
  140. package/.agent/workflows/plan-analysis.md +67 -0
  141. package/.agent/workflows/plan-next-steps.md +63 -0
  142. package/.agent/workflows/plan-review.md +33 -0
  143. package/.agent/workflows/plan-synthesis.md +106 -0
  144. package/.agent/workflows/plan.md +49 -0
  145. package/.agent/workflows/report-bug.md +150 -0
  146. package/.agent/workflows/reproduce-bug.md +99 -0
  147. package/.agent/workflows/resolve-parallel.md +34 -0
  148. package/.agent/workflows/resolve-pr-parallel.md +49 -0
  149. package/.agent/workflows/resolve-todo-parallel.md +35 -0
  150. package/.agent/workflows/review-analysis.md +145 -0
  151. package/.agent/workflows/review-synthesis.md +262 -0
  152. package/.agent/workflows/review.md +64 -0
  153. package/.agent/workflows/ship.md +90 -0
  154. package/.agent/workflows/test-command.md +3 -0
  155. package/.agent/workflows/triage.md +310 -0
  156. package/.agent/workflows/work.md +157 -0
  157. package/.agent/workflows/xcode-test.md +332 -0
  158. package/LICENSE +22 -0
  159. package/README.md +49 -0
  160. package/bin/ag-cortex.js +54 -0
  161. package/lib/core.js +165 -0
  162. package/package.json +31 -0
@@ -0,0 +1,653 @@
1
+ # Architecture - DHH Rails Style
2
+
3
+ <routing>
4
+ ## Routing
5
+
6
+ Everything maps to CRUD. Nested resources for related actions:
7
+
8
+ ```ruby
9
+ Rails.application.routes.draw do
10
+ resources :boards do
11
+ resources :cards do
12
+ resource :closure
13
+ resource :goldness
14
+ resource :not_now
15
+ resources :assignments
16
+ resources :comments
17
+ end
18
+ end
19
+ end
20
+ ```
21
+
22
+ **Verb-to-noun conversion:**
23
+ | Action | Resource |
24
+ |--------|----------|
25
+ | close a card | `card.closure` |
26
+ | watch a board | `board.watching` |
27
+ | mark as golden | `card.goldness` |
28
+ | archive a card | `card.archival` |
29
+
30
+ **Shallow nesting** - avoid deep URLs:
31
+ ```ruby
32
+ resources :boards do
33
+ resources :cards, shallow: true # /boards/:id/cards, but /cards/:id
34
+ end
35
+ ```
36
+
37
+ **Singular resources** for one-per-parent:
38
+ ```ruby
39
+ resource :closure # not resources
40
+ resource :goldness
41
+ ```
42
+
43
+ **Resolve for URL generation:**
44
+ ```ruby
45
+ # config/routes.rb
46
+ resolve("Comment") { |comment| [comment.card, anchor: dom_id(comment)] }
47
+
48
+ # Now url_for(@comment) works correctly
49
+ ```
50
+ </routing>
51
+
52
+ <multi_tenancy>
53
+ ## Multi-Tenancy (Path-Based)
54
+
55
+ **Middleware extracts tenant** from URL prefix:
56
+
57
+ ```ruby
58
+ # lib/tenant_extractor.rb
59
+ class TenantExtractor
60
+ def initialize(app)
61
+ @app = app
62
+ end
63
+
64
+ def call(env)
65
+ path = env["PATH_INFO"]
66
+ if match = path.match(%r{^/(\d+)(/.*)?$})
67
+ env["SCRIPT_NAME"] = "/#{match[1]}"
68
+ env["PATH_INFO"] = match[2] || "/"
69
+ end
70
+ @app.call(env)
71
+ end
72
+ end
73
+ ```
74
+
75
+ **Cookie scoping** per tenant:
76
+ ```ruby
77
+ # Cookies scoped to tenant path
78
+ cookies.signed[:session_id] = {
79
+ value: session.id,
80
+ path: "/#{Current.account.id}"
81
+ }
82
+ ```
83
+
84
+ **Background job context** - serialize tenant:
85
+ ```ruby
86
+ class ApplicationJob < ActiveJob::Base
87
+ around_perform do |job, block|
88
+ Current.set(account: job.arguments.first.account) { block.call }
89
+ end
90
+ end
91
+ ```
92
+
93
+ **Recurring jobs** must iterate all tenants:
94
+ ```ruby
95
+ class DailyDigestJob < ApplicationJob
96
+ def perform
97
+ Account.find_each do |account|
98
+ Current.set(account: account) do
99
+ send_digest_for(account)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ ```
105
+
106
+ **Controller security** - always scope through tenant:
107
+ ```ruby
108
+ # Good - scoped through user's accessible records
109
+ @card = Current.user.accessible_cards.find(params[:id])
110
+
111
+ # Avoid - direct lookup
112
+ @card = Card.find(params[:id])
113
+ ```
114
+ </multi_tenancy>
115
+
116
+ <authentication>
117
+ ## Authentication
118
+
119
+ Custom passwordless magic link auth (~150 lines total):
120
+
121
+ ```ruby
122
+ # app/models/session.rb
123
+ class Session < ApplicationRecord
124
+ belongs_to :user
125
+
126
+ before_create { self.token = SecureRandom.urlsafe_base64(32) }
127
+ end
128
+
129
+ # app/models/magic_link.rb
130
+ class MagicLink < ApplicationRecord
131
+ belongs_to :user
132
+
133
+ before_create do
134
+ self.code = SecureRandom.random_number(100_000..999_999).to_s
135
+ self.expires_at = 15.minutes.from_now
136
+ end
137
+
138
+ def expired?
139
+ expires_at < Time.current
140
+ end
141
+ end
142
+ ```
143
+
144
+ **Why not Devise:**
145
+ - ~150 lines vs massive dependency
146
+ - No password storage liability
147
+ - Simpler UX for users
148
+ - Full control over flow
149
+
150
+ **Bearer token** for APIs:
151
+ ```ruby
152
+ module Authentication
153
+ extend ActiveSupport::Concern
154
+
155
+ included do
156
+ before_action :authenticate
157
+ end
158
+
159
+ private
160
+ def authenticate
161
+ if bearer_token = request.headers["Authorization"]&.split(" ")&.last
162
+ Current.session = Session.find_by(token: bearer_token)
163
+ else
164
+ Current.session = Session.find_by(id: cookies.signed[:session_id])
165
+ end
166
+
167
+ redirect_to login_path unless Current.session
168
+ end
169
+ end
170
+ ```
171
+ </authentication>
172
+
173
+ <background_jobs>
174
+ ## Background Jobs
175
+
176
+ Jobs are shallow wrappers calling model methods:
177
+
178
+ ```ruby
179
+ class NotifyWatchersJob < ApplicationJob
180
+ def perform(card)
181
+ card.notify_watchers
182
+ end
183
+ end
184
+ ```
185
+
186
+ **Naming convention:**
187
+ - `_later` suffix for async: `card.notify_watchers_later`
188
+ - `_now` suffix for immediate: `card.notify_watchers_now`
189
+
190
+ ```ruby
191
+ module Watchable
192
+ def notify_watchers_later
193
+ NotifyWatchersJob.perform_later(self)
194
+ end
195
+
196
+ def notify_watchers_now
197
+ NotifyWatchersJob.perform_now(self)
198
+ end
199
+
200
+ def notify_watchers
201
+ watchers.each do |watcher|
202
+ WatcherMailer.notification(watcher, self).deliver_later
203
+ end
204
+ end
205
+ end
206
+ ```
207
+
208
+ **Database-backed** with Solid Queue:
209
+ - No Redis required
210
+ - Same transactional guarantees as your data
211
+ - Simpler infrastructure
212
+
213
+ **Transaction safety:**
214
+ ```ruby
215
+ # config/application.rb
216
+ config.active_job.enqueue_after_transaction_commit = true
217
+ ```
218
+
219
+ **Error handling** by type:
220
+ ```ruby
221
+ class DeliveryJob < ApplicationJob
222
+ # Transient errors - retry with backoff
223
+ retry_on Net::OpenTimeout, Net::ReadTimeout,
224
+ Resolv::ResolvError,
225
+ wait: :polynomially_longer
226
+
227
+ # Permanent errors - log and discard
228
+ discard_on Net::SMTPSyntaxError do |job, error|
229
+ Sentry.capture_exception(error, level: :info)
230
+ end
231
+ end
232
+ ```
233
+
234
+ **Batch processing** with continuable:
235
+ ```ruby
236
+ class ProcessCardsJob < ApplicationJob
237
+ include ActiveJob::Continuable
238
+
239
+ def perform
240
+ Card.in_batches.each_record do |card|
241
+ checkpoint! # Resume from here if interrupted
242
+ process(card)
243
+ end
244
+ end
245
+ end
246
+ ```
247
+ </background_jobs>
248
+
249
+ <database_patterns>
250
+ ## Database Patterns
251
+
252
+ **UUIDs as primary keys** (time-sortable UUIDv7):
253
+ ```ruby
254
+ # migration
255
+ create_table :cards, id: :uuid do |t|
256
+ t.references :board, type: :uuid, foreign_key: true
257
+ end
258
+ ```
259
+
260
+ Benefits: No ID enumeration, distributed-friendly, client-side generation.
261
+
262
+ **State as records** (not booleans):
263
+ ```ruby
264
+ # Instead of closed: boolean
265
+ class Card::Closure < ApplicationRecord
266
+ belongs_to :card
267
+ belongs_to :creator, class_name: "User"
268
+ end
269
+
270
+ # Queries become joins
271
+ Card.joins(:closure) # closed
272
+ Card.where.missing(:closure) # open
273
+ ```
274
+
275
+ **Hard deletes** - no soft delete:
276
+ ```ruby
277
+ # Just destroy
278
+ card.destroy!
279
+
280
+ # Use events for history
281
+ card.record_event(:deleted, by: Current.user)
282
+ ```
283
+
284
+ Simplifies queries, uses event logs for auditing.
285
+
286
+ **Counter caches** for performance:
287
+ ```ruby
288
+ class Comment < ApplicationRecord
289
+ belongs_to :card, counter_cache: true
290
+ end
291
+
292
+ # card.comments_count available without query
293
+ ```
294
+
295
+ **Account scoping** on every table:
296
+ ```ruby
297
+ class Card < ApplicationRecord
298
+ belongs_to :account
299
+ default_scope { where(account: Current.account) }
300
+ end
301
+ ```
302
+ </database_patterns>
303
+
304
+ <current_attributes>
305
+ ## Current Attributes
306
+
307
+ Use `Current` for request-scoped state:
308
+
309
+ ```ruby
310
+ # app/models/current.rb
311
+ class Current < ActiveSupport::CurrentAttributes
312
+ attribute :session, :user, :account, :request_id
313
+
314
+ delegate :user, to: :session, allow_nil: true
315
+
316
+ def account=(account)
317
+ super
318
+ Time.zone = account&.time_zone || "UTC"
319
+ end
320
+ end
321
+ ```
322
+
323
+ Set in controller:
324
+ ```ruby
325
+ class ApplicationController < ActionController::Base
326
+ before_action :set_current_request
327
+
328
+ private
329
+ def set_current_request
330
+ Current.session = authenticated_session
331
+ Current.account = Account.find(params[:account_id])
332
+ Current.request_id = request.request_id
333
+ end
334
+ end
335
+ ```
336
+
337
+ Use throughout app:
338
+ ```ruby
339
+ class Card < ApplicationRecord
340
+ belongs_to :creator, default: -> { Current.user }
341
+ end
342
+ ```
343
+ </current_attributes>
344
+
345
+ <caching>
346
+ ## Caching
347
+
348
+ **HTTP caching** with ETags:
349
+ ```ruby
350
+ fresh_when etag: [@card, Current.user.timezone]
351
+ ```
352
+
353
+ **Fragment caching:**
354
+ ```erb
355
+ <% cache card do %>
356
+ <%= render card %>
357
+ <% end %>
358
+ ```
359
+
360
+ **Russian doll caching:**
361
+ ```erb
362
+ <% cache @board do %>
363
+ <% @board.cards.each do |card| %>
364
+ <% cache card do %>
365
+ <%= render card %>
366
+ <% end %>
367
+ <% end %>
368
+ <% end %>
369
+ ```
370
+
371
+ **Cache invalidation** via `touch: true`:
372
+ ```ruby
373
+ class Card < ApplicationRecord
374
+ belongs_to :board, touch: true
375
+ end
376
+ ```
377
+
378
+ **Solid Cache** - database-backed:
379
+ - No Redis required
380
+ - Consistent with application data
381
+ - Simpler infrastructure
382
+ </caching>
383
+
384
+ <configuration>
385
+ ## Configuration
386
+
387
+ **ENV.fetch with defaults:**
388
+ ```ruby
389
+ # config/application.rb
390
+ config.active_job.queue_adapter = ENV.fetch("QUEUE_ADAPTER", "solid_queue").to_sym
391
+ config.cache_store = ENV.fetch("CACHE_STORE", "solid_cache").to_sym
392
+ ```
393
+
394
+ **Multiple databases:**
395
+ ```yaml
396
+ # config/database.yml
397
+ production:
398
+ primary:
399
+ <<: *default
400
+ cable:
401
+ <<: *default
402
+ migrations_paths: db/cable_migrate
403
+ queue:
404
+ <<: *default
405
+ migrations_paths: db/queue_migrate
406
+ cache:
407
+ <<: *default
408
+ migrations_paths: db/cache_migrate
409
+ ```
410
+
411
+ **Switch between SQLite and MySQL via ENV:**
412
+ ```ruby
413
+ adapter = ENV.fetch("DATABASE_ADAPTER", "sqlite3")
414
+ ```
415
+
416
+ **CSP extensible via ENV:**
417
+ ```ruby
418
+ config.content_security_policy do |policy|
419
+ policy.default_src :self
420
+ policy.script_src :self, *ENV.fetch("CSP_SCRIPT_SRC", "").split(",")
421
+ end
422
+ ```
423
+ </configuration>
424
+
425
+ <testing>
426
+ ## Testing
427
+
428
+ **Minitest**, not RSpec:
429
+ ```ruby
430
+ class CardTest < ActiveSupport::TestCase
431
+ test "closing a card creates a closure" do
432
+ card = cards(:one)
433
+
434
+ card.close
435
+
436
+ assert card.closed?
437
+ assert_not_nil card.closure
438
+ end
439
+ end
440
+ ```
441
+
442
+ **Fixtures** instead of factories:
443
+ ```yaml
444
+ # test/fixtures/cards.yml
445
+ one:
446
+ title: First Card
447
+ board: main
448
+ creator: alice
449
+
450
+ two:
451
+ title: Second Card
452
+ board: main
453
+ creator: bob
454
+ ```
455
+
456
+ **Integration tests** for controllers:
457
+ ```ruby
458
+ class CardsControllerTest < ActionDispatch::IntegrationTest
459
+ test "closing a card" do
460
+ card = cards(:one)
461
+ sign_in users(:alice)
462
+
463
+ post card_closure_path(card)
464
+
465
+ assert_response :success
466
+ assert card.reload.closed?
467
+ end
468
+ end
469
+ ```
470
+
471
+ **Tests ship with features** - same commit, not TDD-first but together.
472
+
473
+ **Regression tests for security fixes** - always.
474
+ </testing>
475
+
476
+ <events>
477
+ ## Event Tracking
478
+
479
+ Events are the single source of truth:
480
+
481
+ ```ruby
482
+ class Event < ApplicationRecord
483
+ belongs_to :creator, class_name: "User"
484
+ belongs_to :eventable, polymorphic: true
485
+
486
+ serialize :particulars, coder: JSON
487
+ end
488
+ ```
489
+
490
+ **Eventable concern:**
491
+ ```ruby
492
+ module Eventable
493
+ extend ActiveSupport::Concern
494
+
495
+ included do
496
+ has_many :events, as: :eventable, dependent: :destroy
497
+ end
498
+
499
+ def record_event(action, particulars = {})
500
+ events.create!(
501
+ creator: Current.user,
502
+ action: action,
503
+ particulars: particulars
504
+ )
505
+ end
506
+ end
507
+ ```
508
+
509
+ **Webhooks driven by events** - events are the canonical source.
510
+ </events>
511
+
512
+ <email_patterns>
513
+ ## Email Patterns
514
+
515
+ **Multi-tenant URL helpers:**
516
+ ```ruby
517
+ class ApplicationMailer < ActionMailer::Base
518
+ def default_url_options
519
+ options = super
520
+ if Current.account
521
+ options[:script_name] = "/#{Current.account.id}"
522
+ end
523
+ options
524
+ end
525
+ end
526
+ ```
527
+
528
+ **Timezone-aware delivery:**
529
+ ```ruby
530
+ class NotificationMailer < ApplicationMailer
531
+ def daily_digest(user)
532
+ Time.use_zone(user.timezone) do
533
+ @user = user
534
+ @digest = user.digest_for_today
535
+ mail(to: user.email, subject: "Daily Digest")
536
+ end
537
+ end
538
+ end
539
+ ```
540
+
541
+ **Batch delivery:**
542
+ ```ruby
543
+ emails = users.map { |user| NotificationMailer.digest(user) }
544
+ ActiveJob.perform_all_later(emails.map(&:deliver_later))
545
+ ```
546
+
547
+ **One-click unsubscribe (RFC 8058):**
548
+ ```ruby
549
+ class ApplicationMailer < ActionMailer::Base
550
+ after_action :set_unsubscribe_headers
551
+
552
+ private
553
+ def set_unsubscribe_headers
554
+ headers["List-Unsubscribe-Post"] = "List-Unsubscribe=One-Click"
555
+ headers["List-Unsubscribe"] = "<#{unsubscribe_url}>"
556
+ end
557
+ end
558
+ ```
559
+ </email_patterns>
560
+
561
+ <security_patterns>
562
+ ## Security Patterns
563
+
564
+ **XSS prevention** - escape in helpers:
565
+ ```ruby
566
+ def formatted_content(text)
567
+ # Escape first, then mark safe
568
+ simple_format(h(text)).html_safe
569
+ end
570
+ ```
571
+
572
+ **SSRF protection:**
573
+ ```ruby
574
+ # Resolve DNS once, pin the IP
575
+ def fetch_safely(url)
576
+ uri = URI.parse(url)
577
+ ip = Resolv.getaddress(uri.host)
578
+
579
+ # Block private networks
580
+ raise "Private IP" if private_ip?(ip)
581
+
582
+ # Use pinned IP for request
583
+ Net::HTTP.start(uri.host, uri.port, ipaddr: ip) { |http| ... }
584
+ end
585
+
586
+ def private_ip?(ip)
587
+ ip.start_with?("127.", "10.", "192.168.") ||
588
+ ip.match?(/^172\.(1[6-9]|2[0-9]|3[0-1])\./)
589
+ end
590
+ ```
591
+
592
+ **Content Security Policy:**
593
+ ```ruby
594
+ # config/initializers/content_security_policy.rb
595
+ Rails.application.configure do
596
+ config.content_security_policy do |policy|
597
+ policy.default_src :self
598
+ policy.script_src :self
599
+ policy.style_src :self, :unsafe_inline
600
+ policy.base_uri :none
601
+ policy.form_action :self
602
+ policy.frame_ancestors :self
603
+ end
604
+ end
605
+ ```
606
+
607
+ **ActionText sanitization:**
608
+ ```ruby
609
+ # config/initializers/action_text.rb
610
+ Rails.application.config.after_initialize do
611
+ ActionText::ContentHelper.allowed_tags = %w[
612
+ strong em a ul ol li p br h1 h2 h3 h4 blockquote
613
+ ]
614
+ end
615
+ ```
616
+ </security_patterns>
617
+
618
+ <active_storage>
619
+ ## Active Storage Patterns
620
+
621
+ **Variant preprocessing:**
622
+ ```ruby
623
+ class User < ApplicationRecord
624
+ has_one_attached :avatar do |attachable|
625
+ attachable.variant :thumb, resize_to_limit: [100, 100], preprocessed: true
626
+ attachable.variant :medium, resize_to_limit: [300, 300], preprocessed: true
627
+ end
628
+ end
629
+ ```
630
+
631
+ **Direct upload expiry** - extend for slow connections:
632
+ ```ruby
633
+ # config/initializers/active_storage.rb
634
+ Rails.application.config.active_storage.service_urls_expire_in = 48.hours
635
+ ```
636
+
637
+ **Avatar optimization** - redirect to blob:
638
+ ```ruby
639
+ def show
640
+ expires_in 1.year, public: true
641
+ redirect_to @user.avatar.variant(:thumb).processed.url, allow_other_host: true
642
+ end
643
+ ```
644
+
645
+ **Mirror service** for migrations:
646
+ ```yaml
647
+ # config/storage.yml
648
+ production:
649
+ service: Mirror
650
+ primary: amazon
651
+ mirrors: [google]
652
+ ```
653
+ </active_storage>