-```
-
-**Helper extraction** - shared utilities in separate modules:
-```javascript
-// app/javascript/helpers/timing.js
-export function debounce(fn, delay) {
- let timeout
- return (...args) => {
- clearTimeout(timeout)
- timeout = setTimeout(() => fn(...args), delay)
- }
-}
-```
-
-**Event dispatching** for loose coupling:
-```javascript
-this.dispatch("selected", { detail: { id: this.idValue } })
-```
-
-
-
-## View Helpers (Stimulus-Integrated)
-
-**Dialog helper:**
-```ruby
-def dialog_tag(id, &block)
- tag.dialog(
- id: id,
- data: {
- controller: "dialog",
- action: "click->dialog#clickOutside keydown.esc->dialog#close"
- },
- &block
- )
-end
-```
-
-**Auto-submit form helper:**
-```ruby
-def auto_submit_form_with(model:, delay: 300, **options, &block)
- form_with(
- model: model,
- data: {
- controller: "auto-submit",
- auto_submit_delay_value: delay,
- action: "input->auto-submit#submit"
- },
- **options,
- &block
- )
-end
-```
-
-**Copy button helper:**
-```ruby
-def copy_button(content:, label: "Copy")
- tag.button(
- label,
- data: {
- controller: "copy",
- copy_content_value: content,
- action: "click->copy#copy"
- }
- )
-end
-```
-
-
-
-## CSS Architecture
-
-Vanilla CSS with modern features, no preprocessors.
-
-**CSS @layer** for cascade control:
-```css
-@layer reset, base, components, modules, utilities;
-
-@layer reset {
- *, *::before, *::after { box-sizing: border-box; }
-}
-
-@layer base {
- body { font-family: var(--font-sans); }
-}
-
-@layer components {
- .btn { /* button styles */ }
-}
-
-@layer modules {
- .card { /* card module styles */ }
-}
-
-@layer utilities {
- .hidden { display: none; }
-}
-```
-
-**OKLCH color system** for perceptual uniformity:
-```css
-:root {
- --color-primary: oklch(60% 0.15 250);
- --color-success: oklch(65% 0.2 145);
- --color-warning: oklch(75% 0.15 85);
- --color-danger: oklch(55% 0.2 25);
-}
-```
-
-**Dark mode** via CSS variables:
-```css
-:root {
- --bg: oklch(98% 0 0);
- --text: oklch(20% 0 0);
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --bg: oklch(15% 0 0);
- --text: oklch(90% 0 0);
- }
-}
-```
-
-**Native CSS nesting:**
-```css
-.card {
- padding: var(--space-4);
-
- & .title {
- font-weight: bold;
- }
-
- &:hover {
- background: var(--bg-hover);
- }
-}
-```
-
-**~60 minimal utilities** vs Tailwind's hundreds.
-
-**Modern features used:**
-- `@starting-style` for enter animations
-- `color-mix()` for color manipulation
-- `:has()` for parent selection
-- Logical properties (`margin-inline`, `padding-block`)
-- Container queries
-
-
-
-## View Patterns
-
-**Standard partials** - no ViewComponents:
-```erb
-<%# app/views/cards/_card.html.erb %>
-
- <%= render "cards/header", card: card %>
- <%= render "cards/body", card: card %>
- <%= render "cards/footer", card: card %>
-
-```
-
-**Fragment caching:**
-```erb
-<% cache card do %>
- <%= render "cards/card", card: card %>
-<% end %>
-```
-
-**Collection caching:**
-```erb
-<%= render partial: "card", collection: @cards, cached: true %>
-```
-
-**Simple component naming** - no strict BEM:
-```css
-.card { }
-.card .title { }
-.card .actions { }
-.card.golden { }
-.card.closed { }
-```
-
-
-
-## User-Specific Content in Caches
-
-Move personalization to client-side JavaScript to preserve caching:
-
-```erb
-<%# Cacheable fragment %>
-<% cache card do %>
-
-
-
-<% end %>
-```
-
-```javascript
-// Reveal user-specific elements after cache hit
-export default class extends Controller {
- static values = { currentUser: Number }
- static targets = ["ownerOnly"]
-
- connect() {
- const creatorId = parseInt(this.element.dataset.creatorId)
- if (creatorId === this.currentUserValue) {
- this.ownerOnlyTargets.forEach(el => el.classList.remove("hidden"))
- }
- }
-}
-```
-
-**Extract dynamic content** to separate frames:
-```erb
-<% cache [card, board] do %>
-
- <%= turbo_frame_tag card, :assignment,
- src: card_assignment_path(card),
- refresh: :morph %>
-
-<% end %>
-```
-
-Assignment dropdown updates independently without invalidating parent cache.
-
-
-
-## Broadcasting with Turbo Streams
-
-**Model callbacks** for real-time updates:
-```ruby
-class Card < ApplicationRecord
- include Broadcastable
-
- after_create_commit :broadcast_created
- after_update_commit :broadcast_updated
- after_destroy_commit :broadcast_removed
-
- private
- def broadcast_created
- broadcast_append_to [Current.account, board], :cards
- end
-
- def broadcast_updated
- broadcast_replace_to [Current.account, board], :cards
- end
-
- def broadcast_removed
- broadcast_remove_to [Current.account, board], :cards
- end
-end
-```
-
-**Scope by tenant** using `[Current.account, resource]` pattern.
-
diff --git a/plugins/compound-engineering/skills/dhh-rails-style/references/gems.md b/plugins/compound-engineering/skills/dhh-rails-style/references/gems.md
deleted file mode 100644
index 00933b9..0000000
--- a/plugins/compound-engineering/skills/dhh-rails-style/references/gems.md
+++ /dev/null
@@ -1,266 +0,0 @@
-# Gems - DHH Rails Style
-
-
-## What 37signals Uses
-
-**Core Rails stack:**
-- turbo-rails, stimulus-rails, importmap-rails
-- propshaft (asset pipeline)
-
-**Database-backed services (Solid suite):**
-- solid_queue - background jobs
-- solid_cache - caching
-- solid_cable - WebSockets/Action Cable
-
-**Authentication & Security:**
-- bcrypt (for any password hashing needed)
-
-**Their own gems:**
-- geared_pagination (cursor-based pagination)
-- lexxy (rich text editor)
-- mittens (mailer utilities)
-
-**Utilities:**
-- rqrcode (QR code generation)
-- redcarpet + rouge (Markdown rendering)
-- web-push (push notifications)
-
-**Deployment & Operations:**
-- kamal (Docker deployment)
-- thruster (HTTP/2 proxy)
-- mission_control-jobs (job monitoring)
-- autotuner (GC tuning)
-
-
-
-## What They Deliberately Avoid
-
-**Authentication:**
-```
-devise → Custom ~150-line auth
-```
-Why: Full control, no password liability with magic links, simpler.
-
-**Authorization:**
-```
-pundit/cancancan → Simple role checks in models
-```
-Why: Most apps don't need policy objects. A method on the model suffices:
-```ruby
-class Board < ApplicationRecord
- def editable_by?(user)
- user.admin? || user == creator
- end
-end
-```
-
-**Background Jobs:**
-```
-sidekiq → Solid Queue
-```
-Why: Database-backed means no Redis, same transactional guarantees.
-
-**Caching:**
-```
-redis → Solid Cache
-```
-Why: Database is already there, simpler infrastructure.
-
-**Search:**
-```
-elasticsearch → Custom sharded search
-```
-Why: Built exactly what they need, no external service dependency.
-
-**View Layer:**
-```
-view_component → Standard partials
-```
-Why: Partials work fine. ViewComponents add complexity without clear benefit for their use case.
-
-**API:**
-```
-GraphQL → REST with Turbo
-```
-Why: REST is sufficient when you control both ends. GraphQL complexity not justified.
-
-**Factories:**
-```
-factory_bot → Fixtures
-```
-Why: Fixtures are simpler, faster, and encourage thinking about data relationships upfront.
-
-**Service Objects:**
-```
-Interactor, Trailblazer → Fat models
-```
-Why: Business logic stays in models. Methods like `card.close` instead of `CardCloser.call(card)`.
-
-**Form Objects:**
-```
-Reform, dry-validation → params.expect + model validations
-```
-Why: Rails 7.1's `params.expect` is clean enough. Contextual validations on model.
-
-**Decorators:**
-```
-Draper → View helpers + partials
-```
-Why: Helpers and partials are simpler. No decorator indirection.
-
-**CSS:**
-```
-Tailwind, Sass → Native CSS
-```
-Why: Modern CSS has nesting, variables, layers. No build step needed.
-
-**Frontend:**
-```
-React, Vue, SPAs → Turbo + Stimulus
-```
-Why: Server-rendered HTML with sprinkles of JS. SPA complexity not justified.
-
-**Testing:**
-```
-RSpec → Minitest
-```
-Why: Simpler, faster boot, less DSL magic, ships with Rails.
-
-
-
-## Testing Philosophy
-
-**Minitest** - simpler, faster:
-```ruby
-class CardTest < ActiveSupport::TestCase
- test "closing creates closure" do
- card = cards(:one)
- assert_difference -> { Card::Closure.count } do
- card.close
- end
- assert card.closed?
- end
-end
-```
-
-**Fixtures** - loaded once, deterministic:
-```yaml
-# test/fixtures/cards.yml
-open_card:
- title: Open Card
- board: main
- creator: alice
-
-closed_card:
- title: Closed Card
- board: main
- creator: bob
-```
-
-**Dynamic timestamps** with ERB:
-```yaml
-recent:
- title: Recent
- created_at: <%= 1.hour.ago %>
-
-old:
- title: Old
- created_at: <%= 1.month.ago %>
-```
-
-**Time travel** for time-dependent tests:
-```ruby
-test "expires after 15 minutes" do
- magic_link = MagicLink.create!(user: users(:alice))
-
- travel 16.minutes
-
- assert magic_link.expired?
-end
-```
-
-**VCR** for external APIs:
-```ruby
-VCR.use_cassette("stripe/charge") do
- charge = Stripe::Charge.create(amount: 1000)
- assert charge.paid
-end
-```
-
-**Tests ship with features** - same commit, not before or after.
-
-
-
-## Decision Framework
-
-Before adding a gem, ask:
-
-1. **Can vanilla Rails do this?**
- - ActiveRecord can do most things Sequel can
- - ActionMailer handles email fine
- - ActiveJob works for most job needs
-
-2. **Is the complexity worth it?**
- - 150 lines of custom code vs. 10,000-line gem
- - You'll understand your code better
- - Fewer upgrade headaches
-
-3. **Does it add infrastructure?**
- - Redis? Consider database-backed alternatives
- - External service? Consider building in-house
- - Simpler infrastructure = fewer failure modes
-
-4. **Is it from someone you trust?**
- - 37signals gems: battle-tested at scale
- - Well-maintained, focused gems: usually fine
- - Kitchen-sink gems: probably overkill
-
-**The philosophy:**
-> "Build solutions before reaching for gems."
-
-Not anti-gem, but pro-understanding. Use gems when they genuinely solve a problem you have, not a problem you might have.
-
-
-
-## Gem Usage Patterns
-
-**Pagination:**
-```ruby
-# geared_pagination - cursor-based
-class CardsController < ApplicationController
- def index
- @cards = @board.cards.geared(page: params[:page])
- end
-end
-```
-
-**Markdown:**
-```ruby
-# redcarpet + rouge
-class MarkdownRenderer
- def self.render(text)
- Redcarpet::Markdown.new(
- Redcarpet::Render::HTML.new(filter_html: true),
- autolink: true,
- fenced_code_blocks: true
- ).render(text)
- end
-end
-```
-
-**Background jobs:**
-```ruby
-# solid_queue - no Redis
-class ApplicationJob < ActiveJob::Base
- queue_as :default
- # Just works, backed by database
-end
-```
-
-**Caching:**
-```ruby
-# solid_cache - no Redis
-# config/environments/production.rb
-config.cache_store = :solid_cache_store
-```
-
diff --git a/plugins/compound-engineering/skills/dhh-rails-style/references/models.md b/plugins/compound-engineering/skills/dhh-rails-style/references/models.md
deleted file mode 100644
index 4a8a15d..0000000
--- a/plugins/compound-engineering/skills/dhh-rails-style/references/models.md
+++ /dev/null
@@ -1,359 +0,0 @@
-# Models - DHH Rails Style
-
-
-## Concerns for Horizontal Behavior
-
-Models heavily use concerns. A typical Card model includes 14+ concerns:
-
-```ruby
-class Card < ApplicationRecord
- include Assignable
- include Attachments
- include Broadcastable
- include Closeable
- include Colored
- include Eventable
- include Golden
- include Mentions
- include Multistep
- include Pinnable
- include Postponable
- include Readable
- include Searchable
- include Taggable
- include Watchable
-end
-```
-
-Each concern is self-contained with associations, scopes, and methods.
-
-**Naming:** Adjectives describing capability (`Closeable`, `Publishable`, `Watchable`)
-
-
-
-## State as Records, Not Booleans
-
-Instead of boolean columns, create separate records:
-
-```ruby
-# Instead of:
-closed: boolean
-is_golden: boolean
-postponed: boolean
-
-# Create records:
-class Card::Closure < ApplicationRecord
- belongs_to :card
- belongs_to :creator, class_name: "User"
-end
-
-class Card::Goldness < ApplicationRecord
- belongs_to :card
- belongs_to :creator, class_name: "User"
-end
-
-class Card::NotNow < ApplicationRecord
- belongs_to :card
- belongs_to :creator, class_name: "User"
-end
-```
-
-**Benefits:**
-- Automatic timestamps (when it happened)
-- Track who made changes
-- Easy filtering via joins and `where.missing`
-- Enables rich UI showing when/who
-
-**In the model:**
-```ruby
-module Closeable
- extend ActiveSupport::Concern
-
- included do
- has_one :closure, dependent: :destroy
- end
-
- def closed?
- closure.present?
- end
-
- def close(creator: Current.user)
- create_closure!(creator: creator)
- end
-
- def reopen
- closure&.destroy
- end
-end
-```
-
-**Querying:**
-```ruby
-Card.joins(:closure) # closed cards
-Card.where.missing(:closure) # open cards
-```
-
-
-
-## Callbacks - Used Sparingly
-
-Only 38 callback occurrences across 30 files in Fizzy. Guidelines:
-
-**Use for:**
-- `after_commit` for async work
-- `before_save` for derived data
-- `after_create_commit` for side effects
-
-**Avoid:**
-- Complex callback chains
-- Business logic in callbacks
-- Synchronous external calls
-
-```ruby
-class Card < ApplicationRecord
- after_create_commit :notify_watchers_later
- before_save :update_search_index, if: :title_changed?
-
- private
- def notify_watchers_later
- NotifyWatchersJob.perform_later(self)
- end
-end
-```
-
-
-
-## Scope Naming
-
-Standard scope names:
-
-```ruby
-class Card < ApplicationRecord
- scope :chronologically, -> { order(created_at: :asc) }
- scope :reverse_chronologically, -> { order(created_at: :desc) }
- scope :alphabetically, -> { order(title: :asc) }
- scope :latest, -> { reverse_chronologically.limit(10) }
-
- # Standard eager loading
- scope :preloaded, -> { includes(:creator, :assignees, :tags) }
-
- # Parameterized
- scope :indexed_by, ->(column) { order(column => :asc) }
- scope :sorted_by, ->(column, direction = :asc) { order(column => direction) }
-end
-```
-
-
-
-## Plain Old Ruby Objects
-
-POROs namespaced under parent models:
-
-```ruby
-# app/models/event/description.rb
-class Event::Description
- def initialize(event)
- @event = event
- end
-
- def to_s
- # Presentation logic for event description
- end
-end
-
-# app/models/card/eventable/system_commenter.rb
-class Card::Eventable::SystemCommenter
- def initialize(card)
- @card = card
- end
-
- def comment(message)
- # Business logic
- end
-end
-
-# app/models/user/filtering.rb
-class User::Filtering
- # View context bundling
-end
-```
-
-**NOT used for service objects.** Business logic stays in models.
-
-
-
-## Method Naming
-
-**Verbs** - Actions that change state:
-```ruby
-card.close
-card.reopen
-card.gild # make golden
-card.ungild
-board.publish
-board.archive
-```
-
-**Predicates** - Queries derived from state:
-```ruby
-card.closed? # closure.present?
-card.golden? # goldness.present?
-board.published?
-```
-
-**Avoid** generic setters:
-```ruby
-# Bad
-card.set_closed(true)
-card.update_golden_status(false)
-
-# Good
-card.close
-card.ungild
-```
-
-
-
-## Validation Philosophy
-
-Minimal validations on models. Use contextual validations on form/operation objects:
-
-```ruby
-# Model - minimal
-class User < ApplicationRecord
- validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
-end
-
-# Form object - contextual
-class Signup
- include ActiveModel::Model
-
- attr_accessor :email, :name, :terms_accepted
-
- validates :email, :name, presence: true
- validates :terms_accepted, acceptance: true
-
- def save
- return false unless valid?
- User.create!(email: email, name: name)
- end
-end
-```
-
-**Prefer database constraints** over model validations for data integrity:
-```ruby
-# migration
-add_index :users, :email, unique: true
-add_foreign_key :cards, :boards
-```
-
-
-
-## Let It Crash Philosophy
-
-Use bang methods that raise exceptions on failure:
-
-```ruby
-# Preferred - raises on failure
-@card = Card.create!(card_params)
-@card.update!(title: new_title)
-@comment.destroy!
-
-# Avoid - silent failures
-@card = Card.create(card_params) # returns false on failure
-if @card.save
- # ...
-end
-```
-
-Let errors propagate naturally. Rails handles ActiveRecord::RecordInvalid with 422 responses.
-
-
-
-## Default Values with Lambdas
-
-Use lambda defaults for associations with Current:
-
-```ruby
-class Card < ApplicationRecord
- belongs_to :creator, class_name: "User", default: -> { Current.user }
- belongs_to :account, default: -> { Current.account }
-end
-
-class Comment < ApplicationRecord
- belongs_to :commenter, class_name: "User", default: -> { Current.user }
-end
-```
-
-Lambdas ensure dynamic resolution at creation time.
-
-
-
-## Rails 7.1+ Model Patterns
-
-**Normalizes** - clean data before validation:
-```ruby
-class User < ApplicationRecord
- normalizes :email, with: ->(email) { email.strip.downcase }
- normalizes :phone, with: ->(phone) { phone.gsub(/\D/, "") }
-end
-```
-
-**Delegated Types** - replace polymorphic associations:
-```ruby
-class Message < ApplicationRecord
- delegated_type :messageable, types: %w[Comment Reply Announcement]
-end
-
-# Now you get:
-message.comment? # true if Comment
-message.comment # returns the Comment
-Message.comments # scope for Comment messages
-```
-
-**Store Accessor** - structured JSON storage:
-```ruby
-class User < ApplicationRecord
- store :settings, accessors: [:theme, :notifications_enabled], coder: JSON
-end
-
-user.theme = "dark"
-user.notifications_enabled = true
-```
-
-
-
-## Concern Guidelines
-
-- **50-150 lines** per concern (most are ~100)
-- **Cohesive** - related functionality only
-- **Named for capabilities** - `Closeable`, `Watchable`, not `CardHelpers`
-- **Self-contained** - associations, scopes, methods together
-- **Not for mere organization** - create when genuine reuse needed
-
-**Touch chains** for cache invalidation:
-```ruby
-class Comment < ApplicationRecord
- belongs_to :card, touch: true
-end
-
-class Card < ApplicationRecord
- belongs_to :board, touch: true
-end
-```
-
-When comment updates, card's `updated_at` changes, which cascades to board.
-
-**Transaction wrapping** for related updates:
-```ruby
-class Card < ApplicationRecord
- def close(creator: Current.user)
- transaction do
- create_closure!(creator: creator)
- record_event(:closed)
- notify_watchers_later
- end
- end
-end
-```
-
diff --git a/plugins/compound-engineering/skills/dhh-rails-style/references/testing.md b/plugins/compound-engineering/skills/dhh-rails-style/references/testing.md
deleted file mode 100644
index 4316fad..0000000
--- a/plugins/compound-engineering/skills/dhh-rails-style/references/testing.md
+++ /dev/null
@@ -1,338 +0,0 @@
-# Testing - DHH Rails Style
-
-## Core Philosophy
-
-"Minitest with fixtures - simple, fast, deterministic." The approach prioritizes pragmatism over convention.
-
-## Why Minitest Over RSpec
-
-- **Simpler**: Less DSL magic, plain Ruby assertions
-- **Ships with Rails**: No additional dependencies
-- **Faster boot times**: Less overhead
-- **Plain Ruby**: No specialized syntax to learn
-
-## Fixtures as Test Data
-
-Rather than factories, fixtures provide preloaded data:
-- Loaded once, reused across tests
-- No runtime object creation overhead
-- Explicit relationship visibility
-- Deterministic IDs for easier debugging
-
-### Fixture Structure
-```yaml
-# test/fixtures/users.yml
-david:
- identity: david
- account: basecamp
- role: admin
-
-jason:
- identity: jason
- account: basecamp
- role: member
-
-# test/fixtures/rooms.yml
-watercooler:
- name: Water Cooler
- creator: david
- direct: false
-
-# test/fixtures/messages.yml
-greeting:
- body: Hello everyone!
- room: watercooler
- creator: david
-```
-
-### Using Fixtures in Tests
-```ruby
-test "sending a message" do
- user = users(:david)
- room = rooms(:watercooler)
-
- # Test with fixture data
-end
-```
-
-### Dynamic Fixture Values
-ERB enables time-sensitive data:
-```yaml
-recent_card:
- title: Recent Card
- created_at: <%= 1.hour.ago %>
-
-old_card:
- title: Old Card
- created_at: <%= 1.month.ago %>
-```
-
-## Test Organization
-
-### Unit Tests
-Verify business logic using setup blocks and standard assertions:
-
-```ruby
-class CardTest < ActiveSupport::TestCase
- setup do
- @card = cards(:one)
- @user = users(:david)
- end
-
- test "closing a card creates a closure" do
- assert_difference -> { Card::Closure.count } do
- @card.close(creator: @user)
- end
-
- assert @card.closed?
- assert_equal @user, @card.closure.creator
- end
-
- test "reopening a card destroys the closure" do
- @card.close(creator: @user)
-
- assert_difference -> { Card::Closure.count }, -1 do
- @card.reopen
- end
-
- refute @card.closed?
- end
-end
-```
-
-### Integration Tests
-Test full request/response cycles:
-
-```ruby
-class CardsControllerTest < ActionDispatch::IntegrationTest
- setup do
- @user = users(:david)
- sign_in @user
- end
-
- test "closing a card" do
- card = cards(:one)
-
- post card_closure_path(card)
-
- assert_response :success
- assert card.reload.closed?
- end
-
- test "unauthorized user cannot close card" do
- sign_in users(:guest)
- card = cards(:one)
-
- post card_closure_path(card)
-
- assert_response :forbidden
- refute card.reload.closed?
- end
-end
-```
-
-### System Tests
-Browser-based tests using Capybara:
-
-```ruby
-class MessagesTest < ApplicationSystemTestCase
- test "sending a message" do
- sign_in users(:david)
- visit room_path(rooms(:watercooler))
-
- fill_in "Message", with: "Hello, world!"
- click_button "Send"
-
- assert_text "Hello, world!"
- end
-
- test "editing own message" do
- sign_in users(:david)
- visit room_path(rooms(:watercooler))
-
- within "#message_#{messages(:greeting).id}" do
- click_on "Edit"
- end
-
- fill_in "Message", with: "Updated message"
- click_button "Save"
-
- assert_text "Updated message"
- end
-
- test "drag and drop card to new column" do
- sign_in users(:david)
- visit board_path(boards(:main))
-
- card = find("#card_#{cards(:one).id}")
- target = find("#column_#{columns(:done).id}")
-
- card.drag_to target
-
- assert_selector "#column_#{columns(:done).id} #card_#{cards(:one).id}"
- end
-end
-```
-
-## Advanced Patterns
-
-### Time Testing
-Use `travel_to` for deterministic time-dependent assertions:
-
-```ruby
-test "card expires after 30 days" do
- card = cards(:one)
-
- travel_to 31.days.from_now do
- assert card.expired?
- end
-end
-```
-
-### External API Testing with VCR
-Record and replay HTTP interactions:
-
-```ruby
-test "fetches user data from API" do
- VCR.use_cassette("user_api") do
- user_data = ExternalApi.fetch_user(123)
-
- assert_equal "John", user_data[:name]
- end
-end
-```
-
-### Background Job Testing
-Assert job enqueueing and email delivery:
-
-```ruby
-test "closing card enqueues notification job" do
- card = cards(:one)
-
- assert_enqueued_with(job: NotifyWatchersJob, args: [card]) do
- card.close
- end
-end
-
-test "welcome email is sent on signup" do
- assert_emails 1 do
- Identity.create!(email: "new@example.com")
- end
-end
-```
-
-### Testing Turbo Streams
-```ruby
-test "message creation broadcasts to room" do
- room = rooms(:watercooler)
-
- assert_turbo_stream_broadcasts [room, :messages] do
- room.messages.create!(body: "Test", creator: users(:david))
- end
-end
-```
-
-## Testing Principles
-
-### 1. Test Observable Behavior
-Focus on what the code does, not how it does it:
-
-```ruby
-# ❌ Testing implementation
-test "calls notify method on each watcher" do
- card.expects(:notify).times(3)
- card.close
-end
-
-# ✅ Testing behavior
-test "watchers receive notifications when card closes" do
- assert_difference -> { Notification.count }, 3 do
- card.close
- end
-end
-```
-
-### 2. Don't Mock Everything
-
-```ruby
-# ❌ Over-mocked test
-test "sending message" do
- room = mock("room")
- user = mock("user")
- message = mock("message")
-
- room.expects(:messages).returns(stub(create!: message))
- message.expects(:broadcast_create)
-
- MessagesController.new.create
-end
-
-# ✅ Test the real thing
-test "sending message" do
- sign_in users(:david)
- post room_messages_url(rooms(:watercooler)),
- params: { message: { body: "Hello" } }
-
- assert_response :success
- assert Message.exists?(body: "Hello")
-end
-```
-
-### 3. Tests Ship with Features
-Same commit, not TDD-first but together. Neither before (strict TDD) nor after (deferred testing).
-
-### 4. Security Fixes Always Include Regression Tests
-Every security fix must include a test that would have caught the vulnerability.
-
-### 5. Integration Tests Validate Complete Workflows
-Don't just test individual pieces - test that they work together.
-
-## File Organization
-
-```
-test/
-├── controllers/ # Integration tests for controllers
-├── fixtures/ # YAML fixtures for all models
-├── helpers/ # Helper method tests
-├── integration/ # API integration tests
-├── jobs/ # Background job tests
-├── mailers/ # Mailer tests
-├── models/ # Unit tests for models
-├── system/ # Browser-based system tests
-└── test_helper.rb # Test configuration
-```
-
-## Test Helper Setup
-
-```ruby
-# test/test_helper.rb
-ENV["RAILS_ENV"] ||= "test"
-require_relative "../config/environment"
-require "rails/test_help"
-
-class ActiveSupport::TestCase
- fixtures :all
-
- parallelize(workers: :number_of_processors)
-end
-
-class ActionDispatch::IntegrationTest
- include SignInHelper
-end
-
-class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
- driven_by :selenium, using: :headless_chrome
-end
-```
-
-## Sign In Helper
-
-```ruby
-# test/support/sign_in_helper.rb
-module SignInHelper
- def sign_in(user)
- session = user.identity.sessions.create!
- cookies.signed[:session_id] = session.id
- end
-end
-```
diff --git a/plugins/compound-engineering/skills/dspy-python/SKILL.md b/plugins/compound-engineering/skills/dspy-python/SKILL.md
new file mode 100644
index 0000000..f191b26
--- /dev/null
+++ b/plugins/compound-engineering/skills/dspy-python/SKILL.md
@@ -0,0 +1,627 @@
+---
+name: dspy-python
+description: This skill should be used when working with DSPy, the Python framework for programming language models instead of prompting them. Use this when implementing LLM-powered features, creating DSPy signatures and modules, configuring language model providers (OpenAI, Anthropic, Gemini, Ollama), building agent systems with tools, optimizing prompts with teleprompters, integrating with FastAPI endpoints, or testing DSPy modules with pytest.
+---
+
+# DSPy Expert (Python)
+
+## Overview
+
+DSPy is a Python framework that enables developers to **program language models, not prompt them**. Instead of manually crafting prompts, define application requirements through composable, optimizable modules that can be tested, improved, and version-controlled like regular code.
+
+This skill provides comprehensive guidance on:
+- Creating signatures for LLM operations
+- Building composable modules and workflows
+- Configuring multiple LLM providers
+- Implementing agents with tools (ReAct)
+- Testing with pytest
+- Optimizing with teleprompters (MIPROv2, BootstrapFewShot)
+- Integrating with FastAPI for production APIs
+- Production deployment patterns
+
+## Core Capabilities
+
+### 1. Signatures
+
+Create input/output specifications for LLM operations using inline or class-based signatures.
+
+**When to use**: Defining any LLM task, from simple classification to complex analysis.
+
+**Quick reference**:
+```python
+import dspy
+
+# Inline signature (simple tasks)
+classify = dspy.Predict("email: str -> category: str, priority: str")
+
+# Class-based signature (complex tasks with documentation)
+class EmailClassification(dspy.Signature):
+ """Classify customer support emails into categories."""
+
+ email_subject: str = dspy.InputField(desc="Subject line of the email")
+ email_body: str = dspy.InputField(desc="Full body content of the email")
+ category: str = dspy.OutputField(desc="One of: Technical, Billing, General")
+ priority: str = dspy.OutputField(desc="One of: Low, Medium, High")
+```
+
+**Templates**: See [signature-template.py](./assets/signature-template.py) for comprehensive examples including:
+- Inline signatures for quick tasks
+- Class-based signatures with type hints
+- Signatures with Pydantic model outputs
+- Multi-field complex signatures
+
+**Best practices**:
+- Always provide clear docstrings for class-based signatures
+- Use `desc` parameter for field documentation
+- Prefer specific descriptions over generic ones
+- Use Pydantic models for structured complex outputs
+
+**Full documentation**: See [core-concepts.md](./references/core-concepts.md) sections on Signatures and Type Safety.
+
+### 2. Modules
+
+Build reusable, composable modules that encapsulate LLM operations.
+
+**When to use**: Implementing any LLM-powered feature, especially complex multi-step workflows.
+
+**Quick reference**:
+```python
+import dspy
+
+class EmailProcessor(dspy.Module):
+ def __init__(self):
+ super().__init__()
+ self.classifier = dspy.ChainOfThought(EmailClassification)
+
+ def forward(self, email_subject: str, email_body: str) -> dspy.Prediction:
+ return self.classifier(
+ email_subject=email_subject,
+ email_body=email_body
+ )
+```
+
+**Templates**: See [module-template.py](./assets/module-template.py) for comprehensive examples including:
+- Basic modules with single predictors
+- Multi-step pipelines that chain modules
+- Modules with conditional logic
+- Error handling and retry patterns
+- Async modules for FastAPI
+- Caching implementations
+
+**Module composition**: Chain modules together to create complex workflows:
+```python
+class Pipeline(dspy.Module):
+ def __init__(self):
+ super().__init__()
+ self.step1 = Classifier()
+ self.step2 = Analyzer()
+ self.step3 = Responder()
+
+ def forward(self, input_text):
+ result1 = self.step1(text=input_text)
+ result2 = self.step2(classification=result1.category)
+ return self.step3(analysis=result2.analysis)
+```
+
+**Full documentation**: See [core-concepts.md](./references/core-concepts.md) sections on Modules and Module Composition.
+
+### 3. Predictor Types
+
+Choose the right predictor for your task:
+
+**Predict**: Basic LLM inference
+```python
+predictor = dspy.Predict(TaskSignature)
+result = predictor(input="data")
+```
+
+**ChainOfThought**: Adds automatic step-by-step reasoning
+```python
+predictor = dspy.ChainOfThought(TaskSignature)
+result = predictor(input="data")
+# result.reasoning contains the thought process
+```
+
+**ReAct**: Tool-using agents with iterative reasoning
+```python
+predictor = dspy.ReAct(
+ TaskSignature,
+ tools=[search_tool, calculator_tool],
+ max_iters=5
+)
+```
+
+**ProgramOfThought**: Generates and executes Python code
+```python
+predictor = dspy.ProgramOfThought(TaskSignature)
+result = predictor(task="Calculate factorial of 10")
+```
+
+**When to use each**:
+- **Predict**: Simple tasks, classification, extraction
+- **ChainOfThought**: Complex reasoning, analysis, multi-step thinking
+- **ReAct**: Tasks requiring external tools (search, calculation, API calls)
+- **ProgramOfThought**: Tasks best solved with generated code
+
+**Full documentation**: See [core-concepts.md](./references/core-concepts.md) section on Predictors.
+
+### 4. LLM Provider Configuration
+
+Support for OpenAI, Anthropic Claude, Google, Ollama, and many more via LiteLLM.
+
+**Quick configuration examples**:
+```python
+import dspy
+
+# OpenAI
+lm = dspy.LM('openai/gpt-4o-mini', api_key=os.environ['OPENAI_API_KEY'])
+dspy.configure(lm=lm)
+
+# Anthropic Claude
+lm = dspy.LM('anthropic/claude-3-5-sonnet-20241022', api_key=os.environ['ANTHROPIC_API_KEY'])
+dspy.configure(lm=lm)
+
+# Google Gemini
+lm = dspy.LM('google/gemini-1.5-pro', api_key=os.environ['GOOGLE_API_KEY'])
+dspy.configure(lm=lm)
+
+# Local Ollama (free, private)
+lm = dspy.LM('ollama_chat/llama3.1', api_base='http://localhost:11434')
+dspy.configure(lm=lm)
+```
+
+**Templates**: See [config-template.py](./assets/config-template.py) for comprehensive examples including:
+- Environment-based configuration
+- Multi-model setups for different tasks
+- Async LM configuration
+- Retry logic and fallback strategies
+- Caching with dspy.cache
+
+**Provider compatibility matrix**:
+
+| Feature | OpenAI | Anthropic | Google | Ollama |
+|---------|--------|-----------|--------|--------|
+| Structured Output | Full | Full | Full | Partial |
+| Vision (Images) | Full | Full | Full | Limited |
+| Tool Calling | Full | Full | Full | Varies |
+| Streaming | Full | Full | Full | Full |
+
+**Cost optimization strategy**:
+- Development: Ollama (free) or gpt-4o-mini (cheap)
+- Testing: gpt-4o-mini with temperature=0.0
+- Production simple tasks: gpt-4o-mini, claude-3-haiku, gemini-1.5-flash
+- Production complex tasks: gpt-4o, claude-3-5-sonnet, gemini-1.5-pro
+
+**Full documentation**: See [providers.md](./references/providers.md) for all configuration options.
+
+### 5. FastAPI Integration
+
+Serve DSPy modules as production API endpoints.
+
+**Quick reference**:
+```python
+from fastapi import FastAPI
+from pydantic import BaseModel
+import dspy
+
+app = FastAPI()
+
+# Initialize DSPy
+lm = dspy.LM('openai/gpt-4o-mini')
+dspy.configure(lm=lm)
+
+# Load optimized module
+classifier = EmailProcessor()
+
+class EmailRequest(BaseModel):
+ subject: str
+ body: str
+
+class EmailResponse(BaseModel):
+ category: str
+ priority: str
+
+@app.post("/classify", response_model=EmailResponse)
+async def classify_email(request: EmailRequest):
+ result = classifier(
+ email_subject=request.subject,
+ email_body=request.body
+ )
+ return EmailResponse(
+ category=result.category,
+ priority=result.priority
+ )
+```
+
+**Production patterns**:
+- Load optimized modules at startup
+- Use Pydantic models for request/response validation
+- Implement proper error handling
+- Add observability with OpenTelemetry
+- Use async where possible
+
+**Full documentation**: See [fastapi-integration.md](./references/fastapi-integration.md) for complete patterns.
+
+### 6. Testing DSPy Modules
+
+Write standard pytest tests for LLM logic.
+
+**Quick reference**:
+```python
+import pytest
+import dspy
+
+@pytest.fixture(scope="module")
+def configure_dspy():
+ lm = dspy.LM('openai/gpt-4o-mini', api_key=os.environ['OPENAI_API_KEY'])
+ dspy.configure(lm=lm)
+
+def test_email_classifier(configure_dspy):
+ classifier = EmailProcessor()
+ result = classifier(
+ email_subject="Can't log in",
+ email_body="Unable to access account"
+ )
+
+ assert result.category in ['Technical', 'Billing', 'General']
+ assert result.priority in ['High', 'Medium', 'Low']
+
+def test_technical_email_classification(configure_dspy):
+ classifier = EmailProcessor()
+ result = classifier(
+ email_subject="Error 500 on checkout",
+ email_body="Getting server error when trying to complete purchase"
+ )
+
+ assert result.category == 'Technical'
+```
+
+**Testing patterns**:
+- Use pytest fixtures for DSPy configuration
+- Test type correctness of outputs
+- Test edge cases (empty inputs, special characters, long texts)
+- Use VCR/responses for deterministic API testing
+- Integration test complete workflows
+
+**Full documentation**: See [optimization.md](./references/optimization.md) section on Testing.
+
+### 7. Optimization with Teleprompters
+
+Automatically improve prompts and modules using optimization techniques.
+
+**MIPROv2 optimization**:
+```python
+import dspy
+from dspy.teleprompt import MIPROv2
+
+# Define evaluation metric
+def accuracy_metric(example, pred, trace=None):
+ return example.category == pred.category
+
+# Prepare training data
+trainset = [
+ dspy.Example(
+ email_subject="Can't log in",
+ email_body="Password reset not working",
+ category="Technical"
+ ).with_inputs("email_subject", "email_body"),
+ # More examples...
+]
+
+# Run optimization
+optimizer = MIPROv2(
+ metric=accuracy_metric,
+ num_candidates=10,
+ init_temperature=0.7
+)
+
+optimized_module = optimizer.compile(
+ EmailProcessor(),
+ trainset=trainset,
+ max_bootstrapped_demos=3,
+ max_labeled_demos=5
+)
+
+# Save optimized module
+optimized_module.save("optimized_classifier.json")
+```
+
+**BootstrapFewShot** (simpler, faster):
+```python
+from dspy.teleprompt import BootstrapFewShot
+
+optimizer = BootstrapFewShot(
+ metric=accuracy_metric,
+ max_bootstrapped_demos=4
+)
+
+optimized = optimizer.compile(
+ EmailProcessor(),
+ trainset=trainset
+)
+```
+
+**Full documentation**: See [optimization.md](./references/optimization.md) section on Teleprompters.
+
+### 8. Caching and Performance
+
+Optimize performance with built-in caching.
+
+**Enable caching**:
+```python
+import dspy
+
+# Enable global caching
+dspy.configure(
+ lm=lm,
+ cache=True # Uses SQLite by default
+)
+
+# Or with custom cache directory
+dspy.configure(
+ lm=lm,
+ cache_dir="/path/to/cache"
+)
+```
+
+**Cache control**:
+```python
+# Clear cache
+dspy.cache.clear()
+
+# Disable cache for specific call
+with dspy.settings.context(cache=False):
+ result = module(input="data")
+```
+
+**Full documentation**: See [optimization.md](./references/optimization.md) section on Caching.
+
+## Quick Start Workflow
+
+### For New Projects
+
+1. **Install DSPy**:
+```bash
+pip install dspy-ai
+```
+
+2. **Configure LLM provider** (see [config-template.py](./assets/config-template.py)):
+```python
+import dspy
+import os
+
+lm = dspy.LM('openai/gpt-4o-mini', api_key=os.environ['OPENAI_API_KEY'])
+dspy.configure(lm=lm)
+```
+
+3. **Create a signature** (see [signature-template.py](./assets/signature-template.py)):
+```python
+class MySignature(dspy.Signature):
+ """Clear description of task."""
+
+ input_field: str = dspy.InputField(desc="Description")
+ output_field: str = dspy.OutputField(desc="Description")
+```
+
+4. **Create a module** (see [module-template.py](./assets/module-template.py)):
+```python
+class MyModule(dspy.Module):
+ def __init__(self):
+ super().__init__()
+ self.predictor = dspy.Predict(MySignature)
+
+ def forward(self, input_field: str):
+ return self.predictor(input_field=input_field)
+```
+
+5. **Use the module**:
+```python
+module = MyModule()
+result = module(input_field="test")
+print(result.output_field)
+```
+
+6. **Add tests** (see [optimization.md](./references/optimization.md)):
+```python
+def test_my_module():
+ result = MyModule()(input_field="test")
+ assert isinstance(result.output_field, str)
+```
+
+### For FastAPI Applications
+
+1. **Install dependencies**:
+```bash
+pip install dspy-ai fastapi uvicorn pydantic
+```
+
+2. **Create app structure**:
+```
+my_app/
+├── app/
+│ ├── __init__.py
+│ ├── main.py # FastAPI app
+│ ├── dspy_modules/ # DSPy modules
+│ │ ├── __init__.py
+│ │ └── classifier.py
+│ ├── models/ # Pydantic models
+│ │ └── __init__.py
+│ └── config.py # DSPy configuration
+├── tests/
+│ └── test_classifier.py
+└── requirements.txt
+```
+
+3. **Configure DSPy** in `config.py`:
+```python
+import dspy
+import os
+
+def configure_dspy():
+ lm = dspy.LM(
+ 'openai/gpt-4o-mini',
+ api_key=os.environ['OPENAI_API_KEY']
+ )
+ dspy.configure(lm=lm, cache=True)
+```
+
+4. **Create FastAPI app** in `main.py`:
+```python
+from fastapi import FastAPI
+from contextlib import asynccontextmanager
+from app.config import configure_dspy
+from app.dspy_modules.classifier import EmailProcessor
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ configure_dspy()
+ yield
+
+app = FastAPI(lifespan=lifespan)
+classifier = EmailProcessor()
+
+@app.post("/classify")
+async def classify(request: EmailRequest):
+ result = classifier(
+ email_subject=request.subject,
+ email_body=request.body
+ )
+ return {"category": result.category, "priority": result.priority}
+```
+
+## Common Patterns
+
+### Pattern: Multi-Step Analysis Pipeline
+
+```python
+class AnalysisPipeline(dspy.Module):
+ def __init__(self):
+ super().__init__()
+ self.extract = dspy.Predict(ExtractSignature)
+ self.analyze = dspy.ChainOfThought(AnalyzeSignature)
+ self.summarize = dspy.Predict(SummarizeSignature)
+
+ def forward(self, text: str):
+ extracted = self.extract(text=text)
+ analyzed = self.analyze(data=extracted.data)
+ return self.summarize(analysis=analyzed.result)
+```
+
+### Pattern: Agent with Tools
+
+```python
+import dspy
+
+def search_web(query: str) -> str:
+ """Search the web for information."""
+ # Implementation here
+ return f"Results for: {query}"
+
+def calculate(expression: str) -> str:
+ """Evaluate a mathematical expression."""
+ return str(eval(expression))
+
+class ResearchAgent(dspy.Module):
+ def __init__(self):
+ super().__init__()
+ self.agent = dspy.ReAct(
+ ResearchSignature,
+ tools=[search_web, calculate],
+ max_iters=10
+ )
+
+ def forward(self, question: str):
+ return self.agent(question=question)
+```
+
+### Pattern: Conditional Routing
+
+```python
+class SmartRouter(dspy.Module):
+ def __init__(self):
+ super().__init__()
+ self.classifier = dspy.Predict(ClassifyComplexity)
+ self.simple_handler = SimpleModule()
+ self.complex_handler = ComplexModule()
+
+ def forward(self, input_text: str):
+ classification = self.classifier(text=input_text)
+
+ if classification.complexity == "Simple":
+ return self.simple_handler(input=input_text)
+ else:
+ return self.complex_handler(input=input_text)
+```
+
+### Pattern: Retry with Fallback
+
+```python
+import dspy
+from tenacity import retry, stop_after_attempt, wait_exponential
+
+class RobustModule(dspy.Module):
+ def __init__(self):
+ super().__init__()
+ self.predictor = dspy.Predict(TaskSignature)
+
+ @retry(
+ stop=stop_after_attempt(3),
+ wait=wait_exponential(multiplier=1, min=2, max=10)
+ )
+ def forward(self, input_text: str):
+ result = self.predictor(input=input_text)
+ self._validate(result)
+ return result
+
+ def _validate(self, result):
+ if not result.output:
+ raise ValueError("Empty output from LLM")
+```
+
+### Pattern: Pydantic Output Models
+
+```python
+from pydantic import BaseModel, Field
+import dspy
+
+class ClassificationResult(BaseModel):
+ category: str = Field(description="Category: Technical, Billing, or General")
+ priority: str = Field(description="Priority: Low, Medium, or High")
+ confidence: float = Field(ge=0.0, le=1.0, description="Confidence score")
+
+class TypedClassifier(dspy.Signature):
+ """Classify with structured output."""
+
+ text: str = dspy.InputField()
+ result: ClassificationResult = dspy.OutputField()
+```
+
+## Resources
+
+This skill includes comprehensive reference materials and templates:
+
+### References (load as needed for detailed information)
+
+- [core-concepts.md](./references/core-concepts.md): Complete guide to signatures, modules, predictors, and best practices
+- [providers.md](./references/providers.md): All LLM provider configurations, compatibility matrix, and troubleshooting
+- [optimization.md](./references/optimization.md): Testing patterns, teleprompters, caching, and monitoring
+- [fastapi-integration.md](./references/fastapi-integration.md): Production patterns for serving DSPy with FastAPI
+
+### Assets (templates for quick starts)
+
+- [signature-template.py](./assets/signature-template.py): Examples of signatures including inline, class-based, and Pydantic outputs
+- [module-template.py](./assets/module-template.py): Module patterns including pipelines, agents, async, and caching
+- [config-template.py](./assets/config-template.py): Configuration examples for all providers and environments
+
+## When to Use This Skill
+
+Trigger this skill when:
+- Implementing LLM-powered features in Python applications
+- Creating programmatic interfaces for AI operations
+- Building agent systems with tool usage
+- Setting up or troubleshooting LLM providers with DSPy
+- Optimizing prompts using teleprompters
+- Testing LLM functionality with pytest
+- Integrating DSPy with FastAPI
+- Converting from manual prompt engineering to programmatic approach
+- Debugging DSPy code or configuration issues
diff --git a/plugins/compound-engineering/skills/dspy-ruby/SKILL.md b/plugins/compound-engineering/skills/dspy-ruby/SKILL.md
deleted file mode 100644
index 359a642..0000000
--- a/plugins/compound-engineering/skills/dspy-ruby/SKILL.md
+++ /dev/null
@@ -1,594 +0,0 @@
----
-name: dspy-ruby
-description: This skill should be used when working with DSPy.rb, a Ruby framework for building type-safe, composable LLM applications. Use this when implementing predictable AI features, creating LLM signatures and modules, configuring language model providers (OpenAI, Anthropic, Gemini, Ollama), building agent systems with tools, optimizing prompts, or testing LLM-powered functionality in Ruby applications.
----
-
-# DSPy.rb Expert
-
-## Overview
-
-DSPy.rb is a Ruby framework that enables developers to **program LLMs, not prompt them**. Instead of manually crafting prompts, define application requirements through type-safe, composable modules that can be tested, optimized, and version-controlled like regular code.
-
-This skill provides comprehensive guidance on:
-- Creating type-safe signatures for LLM operations
-- Building composable modules and workflows
-- Configuring multiple LLM providers
-- Implementing agents with tools
-- Testing and optimizing LLM applications
-- Production deployment patterns
-
-## Core Capabilities
-
-### 1. Type-Safe Signatures
-
-Create input/output contracts for LLM operations with runtime type checking.
-
-**When to use**: Defining any LLM task, from simple classification to complex analysis.
-
-**Quick reference**:
-```ruby
-class EmailClassificationSignature < DSPy::Signature
- description "Classify customer support emails"
-
- input do
- const :email_subject, String
- const :email_body, String
- end
-
- output do
- const :category, T.enum(["Technical", "Billing", "General"])
- const :priority, T.enum(["Low", "Medium", "High"])
- end
-end
-```
-
-**Templates**: See `assets/signature-template.rb` for comprehensive examples including:
-- Basic signatures with multiple field types
-- Vision signatures for multimodal tasks
-- Sentiment analysis signatures
-- Code generation signatures
-
-**Best practices**:
-- Always provide clear, specific descriptions
-- Use enums for constrained outputs
-- Include field descriptions with `desc:` parameter
-- Prefer specific types over generic String when possible
-
-**Full documentation**: See `references/core-concepts.md` sections on Signatures and Type Safety.
-
-### 2. Composable Modules
-
-Build reusable, chainable modules that encapsulate LLM operations.
-
-**When to use**: Implementing any LLM-powered feature, especially complex multi-step workflows.
-
-**Quick reference**:
-```ruby
-class EmailProcessor < DSPy::Module
- def initialize
- super
- @classifier = DSPy::Predict.new(EmailClassificationSignature)
- end
-
- def forward(email_subject:, email_body:)
- @classifier.forward(
- email_subject: email_subject,
- email_body: email_body
- )
- end
-end
-```
-
-**Templates**: See `assets/module-template.rb` for comprehensive examples including:
-- Basic modules with single predictors
-- Multi-step pipelines that chain modules
-- Modules with conditional logic
-- Error handling and retry patterns
-- Stateful modules with history
-- Caching implementations
-
-**Module composition**: Chain modules together to create complex workflows:
-```ruby
-class Pipeline < DSPy::Module
- def initialize
- super
- @step1 = Classifier.new
- @step2 = Analyzer.new
- @step3 = Responder.new
- end
-
- def forward(input)
- result1 = @step1.forward(input)
- result2 = @step2.forward(result1)
- @step3.forward(result2)
- end
-end
-```
-
-**Full documentation**: See `references/core-concepts.md` sections on Modules and Module Composition.
-
-### 3. Multiple Predictor Types
-
-Choose the right predictor for your task:
-
-**Predict**: Basic LLM inference with type-safe inputs/outputs
-```ruby
-predictor = DSPy::Predict.new(TaskSignature)
-result = predictor.forward(input: "data")
-```
-
-**ChainOfThought**: Adds automatic reasoning for improved accuracy
-```ruby
-predictor = DSPy::ChainOfThought.new(TaskSignature)
-result = predictor.forward(input: "data")
-# Returns: { reasoning: "...", output: "..." }
-```
-
-**ReAct**: Tool-using agents with iterative reasoning
-```ruby
-predictor = DSPy::ReAct.new(
- TaskSignature,
- tools: [SearchTool.new, CalculatorTool.new],
- max_iterations: 5
-)
-```
-
-**CodeAct**: Dynamic code generation (requires `dspy-code_act` gem)
-```ruby
-predictor = DSPy::CodeAct.new(TaskSignature)
-result = predictor.forward(task: "Calculate factorial of 5")
-```
-
-**When to use each**:
-- **Predict**: Simple tasks, classification, extraction
-- **ChainOfThought**: Complex reasoning, analysis, multi-step thinking
-- **ReAct**: Tasks requiring external tools (search, calculation, API calls)
-- **CodeAct**: Tasks best solved with generated code
-
-**Full documentation**: See `references/core-concepts.md` section on Predictors.
-
-### 4. LLM Provider Configuration
-
-Support for OpenAI, Anthropic Claude, Google Gemini, Ollama, and OpenRouter.
-
-**Quick configuration examples**:
-```ruby
-# OpenAI
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o-mini',
- api_key: ENV['OPENAI_API_KEY'])
-end
-
-# Anthropic Claude
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('anthropic/claude-3-5-sonnet-20241022',
- api_key: ENV['ANTHROPIC_API_KEY'])
-end
-
-# Google Gemini
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('gemini/gemini-1.5-pro',
- api_key: ENV['GOOGLE_API_KEY'])
-end
-
-# Local Ollama (free, private)
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('ollama/llama3.1')
-end
-```
-
-**Templates**: See `assets/config-template.rb` for comprehensive examples including:
-- Environment-based configuration
-- Multi-model setups for different tasks
-- Configuration with observability (OpenTelemetry, Langfuse)
-- Retry logic and fallback strategies
-- Budget tracking
-- Rails initializer patterns
-
-**Provider compatibility matrix**:
-
-| Feature | OpenAI | Anthropic | Gemini | Ollama |
-|---------|--------|-----------|--------|--------|
-| Structured Output | ✅ | ✅ | ✅ | ✅ |
-| Vision (Images) | ✅ | ✅ | ✅ | ⚠️ Limited |
-| Image URLs | ✅ | ❌ | ❌ | ❌ |
-| Tool Calling | ✅ | ✅ | ✅ | Varies |
-
-**Cost optimization strategy**:
-- Development: Ollama (free) or gpt-4o-mini (cheap)
-- Testing: gpt-4o-mini with temperature=0.0
-- Production simple tasks: gpt-4o-mini, claude-3-haiku, gemini-1.5-flash
-- Production complex tasks: gpt-4o, claude-3-5-sonnet, gemini-1.5-pro
-
-**Full documentation**: See `references/providers.md` for all configuration options, provider-specific features, and troubleshooting.
-
-### 5. Multimodal & Vision Support
-
-Process images alongside text using the unified `DSPy::Image` interface.
-
-**Quick reference**:
-```ruby
-class VisionSignature < DSPy::Signature
- description "Analyze image and answer questions"
-
- input do
- const :image, DSPy::Image
- const :question, String
- end
-
- output do
- const :answer, String
- end
-end
-
-predictor = DSPy::Predict.new(VisionSignature)
-result = predictor.forward(
- image: DSPy::Image.from_file("path/to/image.jpg"),
- question: "What objects are visible?"
-)
-```
-
-**Image loading methods**:
-```ruby
-# From file
-DSPy::Image.from_file("path/to/image.jpg")
-
-# From URL (OpenAI only)
-DSPy::Image.from_url("https://example.com/image.jpg")
-
-# From base64
-DSPy::Image.from_base64(base64_data, mime_type: "image/jpeg")
-```
-
-**Provider support**:
-- OpenAI: Full support including URLs
-- Anthropic, Gemini: Base64 or file loading only
-- Ollama: Limited multimodal depending on model
-
-**Full documentation**: See `references/core-concepts.md` section on Multimodal Support.
-
-### 6. Testing LLM Applications
-
-Write standard RSpec tests for LLM logic.
-
-**Quick reference**:
-```ruby
-RSpec.describe EmailClassifier do
- before do
- DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o-mini',
- api_key: ENV['OPENAI_API_KEY'])
- end
- end
-
- it 'classifies technical emails correctly' do
- classifier = EmailClassifier.new
- result = classifier.forward(
- email_subject: "Can't log in",
- email_body: "Unable to access account"
- )
-
- expect(result[:category]).to eq('Technical')
- expect(result[:priority]).to be_in(['High', 'Medium', 'Low'])
- end
-end
-```
-
-**Testing patterns**:
-- Mock LLM responses for unit tests
-- Use VCR for deterministic API testing
-- Test type safety and validation
-- Test edge cases (empty inputs, special characters, long texts)
-- Integration test complete workflows
-
-**Full documentation**: See `references/optimization.md` section on Testing.
-
-### 7. Optimization & Improvement
-
-Automatically improve prompts and modules using optimization techniques.
-
-**MIPROv2 optimization**:
-```ruby
-require 'dspy/mipro'
-
-# Define evaluation metric
-def accuracy_metric(example, prediction)
- example[:expected_output][:category] == prediction[:category] ? 1.0 : 0.0
-end
-
-# Prepare training data
-training_examples = [
- {
- input: { email_subject: "...", email_body: "..." },
- expected_output: { category: 'Technical' }
- },
- # More examples...
-]
-
-# Run optimization
-optimizer = DSPy::MIPROv2.new(
- metric: method(:accuracy_metric),
- num_candidates: 10
-)
-
-optimized_module = optimizer.compile(
- EmailClassifier.new,
- trainset: training_examples
-)
-```
-
-**A/B testing different approaches**:
-```ruby
-# Test ChainOfThought vs ReAct
-approach_a_score = evaluate_approach(ChainOfThoughtModule, test_set)
-approach_b_score = evaluate_approach(ReActModule, test_set)
-```
-
-**Full documentation**: See `references/optimization.md` section on Optimization.
-
-### 8. Observability & Monitoring
-
-Track performance, token usage, and behavior in production.
-
-**OpenTelemetry integration**:
-```ruby
-require 'opentelemetry/sdk'
-
-OpenTelemetry::SDK.configure do |c|
- c.service_name = 'my-dspy-app'
- c.use_all
-end
-
-# DSPy automatically creates traces
-```
-
-**Langfuse tracing**:
-```ruby
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o-mini',
- api_key: ENV['OPENAI_API_KEY'])
-
- c.langfuse = {
- public_key: ENV['LANGFUSE_PUBLIC_KEY'],
- secret_key: ENV['LANGFUSE_SECRET_KEY']
- }
-end
-```
-
-**Custom monitoring**:
-- Token tracking
-- Performance monitoring
-- Error rate tracking
-- Custom logging
-
-**Full documentation**: See `references/optimization.md` section on Observability.
-
-## Quick Start Workflow
-
-### For New Projects
-
-1. **Install DSPy.rb and provider gems**:
-```bash
-gem install dspy dspy-openai # or dspy-anthropic, dspy-gemini
-```
-
-2. **Configure LLM provider** (see `assets/config-template.rb`):
-```ruby
-require 'dspy'
-
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o-mini',
- api_key: ENV['OPENAI_API_KEY'])
-end
-```
-
-3. **Create a signature** (see `assets/signature-template.rb`):
-```ruby
-class MySignature < DSPy::Signature
- description "Clear description of task"
-
- input do
- const :input_field, String, desc: "Description"
- end
-
- output do
- const :output_field, String, desc: "Description"
- end
-end
-```
-
-4. **Create a module** (see `assets/module-template.rb`):
-```ruby
-class MyModule < DSPy::Module
- def initialize
- super
- @predictor = DSPy::Predict.new(MySignature)
- end
-
- def forward(input_field:)
- @predictor.forward(input_field: input_field)
- end
-end
-```
-
-5. **Use the module**:
-```ruby
-module_instance = MyModule.new
-result = module_instance.forward(input_field: "test")
-puts result[:output_field]
-```
-
-6. **Add tests** (see `references/optimization.md`):
-```ruby
-RSpec.describe MyModule do
- it 'produces expected output' do
- result = MyModule.new.forward(input_field: "test")
- expect(result[:output_field]).to be_a(String)
- end
-end
-```
-
-### For Rails Applications
-
-1. **Add to Gemfile**:
-```ruby
-gem 'dspy'
-gem 'dspy-openai' # or other provider
-```
-
-2. **Create initializer** at `config/initializers/dspy.rb` (see `assets/config-template.rb` for full example):
-```ruby
-require 'dspy'
-
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o-mini',
- api_key: ENV['OPENAI_API_KEY'])
-end
-```
-
-3. **Create modules in** `app/llm/` directory:
-```ruby
-# app/llm/email_classifier.rb
-class EmailClassifier < DSPy::Module
- # Implementation here
-end
-```
-
-4. **Use in controllers/services**:
-```ruby
-class EmailsController < ApplicationController
- def classify
- classifier = EmailClassifier.new
- result = classifier.forward(
- email_subject: params[:subject],
- email_body: params[:body]
- )
- render json: result
- end
-end
-```
-
-## Common Patterns
-
-### Pattern: Multi-Step Analysis Pipeline
-
-```ruby
-class AnalysisPipeline < DSPy::Module
- def initialize
- super
- @extract = DSPy::Predict.new(ExtractSignature)
- @analyze = DSPy::ChainOfThought.new(AnalyzeSignature)
- @summarize = DSPy::Predict.new(SummarizeSignature)
- end
-
- def forward(text:)
- extracted = @extract.forward(text: text)
- analyzed = @analyze.forward(data: extracted[:data])
- @summarize.forward(analysis: analyzed[:result])
- end
-end
-```
-
-### Pattern: Agent with Tools
-
-```ruby
-class ResearchAgent < DSPy::Module
- def initialize
- super
- @agent = DSPy::ReAct.new(
- ResearchSignature,
- tools: [
- WebSearchTool.new,
- DatabaseQueryTool.new,
- SummarizerTool.new
- ],
- max_iterations: 10
- )
- end
-
- def forward(question:)
- @agent.forward(question: question)
- end
-end
-
-class WebSearchTool < DSPy::Tool
- def call(query:)
- results = perform_search(query)
- { results: results }
- end
-end
-```
-
-### Pattern: Conditional Routing
-
-```ruby
-class SmartRouter < DSPy::Module
- def initialize
- super
- @classifier = DSPy::Predict.new(ClassifySignature)
- @simple_handler = SimpleModule.new
- @complex_handler = ComplexModule.new
- end
-
- def forward(input:)
- classification = @classifier.forward(text: input)
-
- if classification[:complexity] == 'Simple'
- @simple_handler.forward(input: input)
- else
- @complex_handler.forward(input: input)
- end
- end
-end
-```
-
-### Pattern: Retry with Fallback
-
-```ruby
-class RobustModule < DSPy::Module
- MAX_RETRIES = 3
-
- def forward(input, retry_count: 0)
- begin
- @predictor.forward(input)
- rescue DSPy::ValidationError => e
- if retry_count < MAX_RETRIES
- sleep(2 ** retry_count)
- forward(input, retry_count: retry_count + 1)
- else
- # Fallback to default or raise
- raise
- end
- end
- end
-end
-```
-
-## Resources
-
-This skill includes comprehensive reference materials and templates:
-
-### References (load as needed for detailed information)
-
-- [core-concepts.md](./references/core-concepts.md): Complete guide to signatures, modules, predictors, multimodal support, and best practices
-- [providers.md](./references/providers.md): All LLM provider configurations, compatibility matrix, cost optimization, and troubleshooting
-- [optimization.md](./references/optimization.md): Testing patterns, optimization techniques, observability setup, and monitoring
-
-### Assets (templates for quick starts)
-
-- [signature-template.rb](./assets/signature-template.rb): Examples of signatures including basic, vision, sentiment analysis, and code generation
-- [module-template.rb](./assets/module-template.rb): Module patterns including pipelines, agents, error handling, caching, and state management
-- [config-template.rb](./assets/config-template.rb): Configuration examples for all providers, environments, observability, and production patterns
-
-## When to Use This Skill
-
-Trigger this skill when:
-- Implementing LLM-powered features in Ruby applications
-- Creating type-safe interfaces for AI operations
-- Building agent systems with tool usage
-- Setting up or troubleshooting LLM providers
-- Optimizing prompts and improving accuracy
-- Testing LLM functionality
-- Adding observability to AI applications
-- Converting from manual prompt engineering to programmatic approach
-- Debugging DSPy.rb code or configuration issues
diff --git a/plugins/compound-engineering/skills/dspy-ruby/assets/config-template.rb b/plugins/compound-engineering/skills/dspy-ruby/assets/config-template.rb
deleted file mode 100644
index 16a01d2..0000000
--- a/plugins/compound-engineering/skills/dspy-ruby/assets/config-template.rb
+++ /dev/null
@@ -1,359 +0,0 @@
-# frozen_string_literal: true
-
-# DSPy.rb Configuration Examples
-# This file demonstrates various configuration patterns for different use cases
-
-require 'dspy'
-
-# ============================================================================
-# Basic Configuration
-# ============================================================================
-
-# Simple OpenAI configuration
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o-mini',
- api_key: ENV['OPENAI_API_KEY'])
-end
-
-# ============================================================================
-# Multi-Provider Configuration
-# ============================================================================
-
-# Anthropic Claude
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('anthropic/claude-3-5-sonnet-20241022',
- api_key: ENV['ANTHROPIC_API_KEY'])
-end
-
-# Google Gemini
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('gemini/gemini-1.5-pro',
- api_key: ENV['GOOGLE_API_KEY'])
-end
-
-# Local Ollama
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('ollama/llama3.1',
- base_url: 'http://localhost:11434')
-end
-
-# OpenRouter (access to 200+ models)
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('openrouter/anthropic/claude-3.5-sonnet',
- api_key: ENV['OPENROUTER_API_KEY'],
- base_url: 'https://openrouter.ai/api/v1')
-end
-
-# ============================================================================
-# Environment-Based Configuration
-# ============================================================================
-
-# Different models for different environments
-if Rails.env.development?
- # Use local Ollama for development (free, private)
- DSPy.configure do |c|
- c.lm = DSPy::LM.new('ollama/llama3.1')
- end
-elsif Rails.env.test?
- # Use cheap model for testing
- DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o-mini',
- api_key: ENV['OPENAI_API_KEY'])
- end
-else
- # Use powerful model for production
- DSPy.configure do |c|
- c.lm = DSPy::LM.new('anthropic/claude-3-5-sonnet-20241022',
- api_key: ENV['ANTHROPIC_API_KEY'])
- end
-end
-
-# ============================================================================
-# Configuration with Custom Parameters
-# ============================================================================
-
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o',
- api_key: ENV['OPENAI_API_KEY'],
- temperature: 0.7, # Creativity (0.0-2.0, default: 1.0)
- max_tokens: 2000, # Maximum response length
- top_p: 0.9, # Nucleus sampling
- frequency_penalty: 0.0, # Reduce repetition (-2.0 to 2.0)
- presence_penalty: 0.0 # Encourage new topics (-2.0 to 2.0)
- )
-end
-
-# ============================================================================
-# Multiple Model Configuration (Task-Specific)
-# ============================================================================
-
-# Create different language models for different tasks
-module MyApp
- # Fast model for simple tasks
- FAST_LM = DSPy::LM.new('openai/gpt-4o-mini',
- api_key: ENV['OPENAI_API_KEY'],
- temperature: 0.3 # More deterministic
- )
-
- # Powerful model for complex tasks
- POWERFUL_LM = DSPy::LM.new('anthropic/claude-3-5-sonnet-20241022',
- api_key: ENV['ANTHROPIC_API_KEY'],
- temperature: 0.7
- )
-
- # Creative model for content generation
- CREATIVE_LM = DSPy::LM.new('openai/gpt-4o',
- api_key: ENV['OPENAI_API_KEY'],
- temperature: 1.2, # More creative
- top_p: 0.95
- )
-
- # Vision-capable model
- VISION_LM = DSPy::LM.new('openai/gpt-4o',
- api_key: ENV['OPENAI_API_KEY'])
-end
-
-# Use in modules
-class SimpleClassifier < DSPy::Module
- def initialize
- super
- DSPy.configure { |c| c.lm = MyApp::FAST_LM }
- @predictor = DSPy::Predict.new(SimpleSignature)
- end
-end
-
-class ComplexAnalyzer < DSPy::Module
- def initialize
- super
- DSPy.configure { |c| c.lm = MyApp::POWERFUL_LM }
- @predictor = DSPy::ChainOfThought.new(ComplexSignature)
- end
-end
-
-# ============================================================================
-# Configuration with Observability (OpenTelemetry)
-# ============================================================================
-
-require 'opentelemetry/sdk'
-
-# Configure OpenTelemetry
-OpenTelemetry::SDK.configure do |c|
- c.service_name = 'my-dspy-app'
- c.use_all
-end
-
-# Configure DSPy (automatically integrates with OpenTelemetry)
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o-mini',
- api_key: ENV['OPENAI_API_KEY'])
-end
-
-# ============================================================================
-# Configuration with Langfuse Tracing
-# ============================================================================
-
-require 'dspy/langfuse'
-
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o-mini',
- api_key: ENV['OPENAI_API_KEY'])
-
- # Enable Langfuse tracing
- c.langfuse = {
- public_key: ENV['LANGFUSE_PUBLIC_KEY'],
- secret_key: ENV['LANGFUSE_SECRET_KEY'],
- host: ENV['LANGFUSE_HOST'] || 'https://cloud.langfuse.com'
- }
-end
-
-# ============================================================================
-# Configuration with Retry Logic
-# ============================================================================
-
-class RetryableConfig
- MAX_RETRIES = 3
-
- def self.configure
- DSPy.configure do |c|
- c.lm = create_lm_with_retry
- end
- end
-
- def self.create_lm_with_retry
- lm = DSPy::LM.new('openai/gpt-4o-mini',
- api_key: ENV['OPENAI_API_KEY'])
-
- # Wrap with retry logic
- lm.extend(RetryBehavior)
- lm
- end
-
- module RetryBehavior
- def forward(input, retry_count: 0)
- super(input)
- rescue RateLimitError, TimeoutError => e
- if retry_count < MAX_RETRIES
- sleep(2 ** retry_count) # Exponential backoff
- forward(input, retry_count: retry_count + 1)
- else
- raise
- end
- end
- end
-end
-
-RetryableConfig.configure
-
-# ============================================================================
-# Configuration with Fallback Models
-# ============================================================================
-
-class FallbackConfig
- def self.configure
- DSPy.configure do |c|
- c.lm = create_lm_with_fallback
- end
- end
-
- def self.create_lm_with_fallback
- primary = DSPy::LM.new('anthropic/claude-3-5-sonnet-20241022',
- api_key: ENV['ANTHROPIC_API_KEY'])
-
- fallback = DSPy::LM.new('openai/gpt-4o',
- api_key: ENV['OPENAI_API_KEY'])
-
- FallbackLM.new(primary, fallback)
- end
-
- class FallbackLM
- def initialize(primary, fallback)
- @primary = primary
- @fallback = fallback
- end
-
- def forward(input)
- @primary.forward(input)
- rescue => e
- puts "Primary model failed: #{e.message}. Falling back..."
- @fallback.forward(input)
- end
- end
-end
-
-FallbackConfig.configure
-
-# ============================================================================
-# Configuration with Budget Tracking
-# ============================================================================
-
-class BudgetTrackedConfig
- def self.configure(monthly_budget_usd:)
- DSPy.configure do |c|
- c.lm = BudgetTracker.new(
- DSPy::LM.new('openai/gpt-4o',
- api_key: ENV['OPENAI_API_KEY']),
- monthly_budget_usd: monthly_budget_usd
- )
- end
- end
-
- class BudgetTracker
- def initialize(lm, monthly_budget_usd:)
- @lm = lm
- @monthly_budget_usd = monthly_budget_usd
- @monthly_cost = 0.0
- end
-
- def forward(input)
- result = @lm.forward(input)
-
- # Track cost (simplified - actual costs vary by model)
- tokens = result.metadata[:usage][:total_tokens]
- cost = estimate_cost(tokens)
- @monthly_cost += cost
-
- if @monthly_cost > @monthly_budget_usd
- raise "Monthly budget of $#{@monthly_budget_usd} exceeded!"
- end
-
- result
- end
-
- private
-
- def estimate_cost(tokens)
- # Simplified cost estimation (check provider pricing)
- (tokens / 1_000_000.0) * 5.0 # $5 per 1M tokens
- end
- end
-end
-
-BudgetTrackedConfig.configure(monthly_budget_usd: 100)
-
-# ============================================================================
-# Configuration Initializer for Rails
-# ============================================================================
-
-# Save this as config/initializers/dspy.rb
-#
-# require 'dspy'
-#
-# DSPy.configure do |c|
-# # Environment-specific configuration
-# model_config = case Rails.env.to_sym
-# when :development
-# { provider: 'ollama', model: 'llama3.1' }
-# when :test
-# { provider: 'openai', model: 'gpt-4o-mini', temperature: 0.0 }
-# when :production
-# { provider: 'anthropic', model: 'claude-3-5-sonnet-20241022' }
-# end
-#
-# # Configure language model
-# c.lm = DSPy::LM.new(
-# "#{model_config[:provider]}/#{model_config[:model]}",
-# api_key: ENV["#{model_config[:provider].upcase}_API_KEY"],
-# **model_config.except(:provider, :model)
-# )
-#
-# # Optional: Add observability
-# if Rails.env.production?
-# c.langfuse = {
-# public_key: ENV['LANGFUSE_PUBLIC_KEY'],
-# secret_key: ENV['LANGFUSE_SECRET_KEY']
-# }
-# end
-# end
-
-# ============================================================================
-# Testing Configuration
-# ============================================================================
-
-# In spec/spec_helper.rb or test/test_helper.rb
-#
-# RSpec.configure do |config|
-# config.before(:suite) do
-# DSPy.configure do |c|
-# c.lm = DSPy::LM.new('openai/gpt-4o-mini',
-# api_key: ENV['OPENAI_API_KEY'],
-# temperature: 0.0 # Deterministic for testing
-# )
-# end
-# end
-# end
-
-# ============================================================================
-# Configuration Best Practices
-# ============================================================================
-
-# 1. Use environment variables for API keys (never hardcode)
-# 2. Use different models for different environments
-# 3. Use cheaper/faster models for development and testing
-# 4. Configure temperature based on use case:
-# - 0.0-0.3: Deterministic, factual tasks
-# - 0.7-1.0: Balanced creativity
-# - 1.0-2.0: High creativity, content generation
-# 5. Add observability in production (OpenTelemetry, Langfuse)
-# 6. Implement retry logic and fallbacks for reliability
-# 7. Track costs and set budgets for production
-# 8. Use max_tokens to control response length and costs
diff --git a/plugins/compound-engineering/skills/dspy-ruby/assets/module-template.rb b/plugins/compound-engineering/skills/dspy-ruby/assets/module-template.rb
deleted file mode 100644
index cc76edb..0000000
--- a/plugins/compound-engineering/skills/dspy-ruby/assets/module-template.rb
+++ /dev/null
@@ -1,326 +0,0 @@
-# frozen_string_literal: true
-
-# Example DSPy Module Template
-# This template demonstrates best practices for creating composable modules
-
-# Basic module with single predictor
-class BasicModule < DSPy::Module
- def initialize
- super
- # Initialize predictor with signature
- @predictor = DSPy::Predict.new(ExampleSignature)
- end
-
- def forward(input_hash)
- # Forward pass through the predictor
- @predictor.forward(input_hash)
- end
-end
-
-# Module with Chain of Thought reasoning
-class ChainOfThoughtModule < DSPy::Module
- def initialize
- super
- # ChainOfThought automatically adds reasoning to output
- @predictor = DSPy::ChainOfThought.new(EmailClassificationSignature)
- end
-
- def forward(email_subject:, email_body:)
- result = @predictor.forward(
- email_subject: email_subject,
- email_body: email_body
- )
-
- # Result includes :reasoning field automatically
- {
- category: result[:category],
- priority: result[:priority],
- reasoning: result[:reasoning],
- confidence: calculate_confidence(result)
- }
- end
-
- private
-
- def calculate_confidence(result)
- # Add custom logic to calculate confidence
- # For example, based on reasoning length or specificity
- result[:confidence] || 0.8
- end
-end
-
-# Composable module that chains multiple steps
-class MultiStepPipeline < DSPy::Module
- def initialize
- super
- # Initialize multiple predictors for different steps
- @step1 = DSPy::Predict.new(Step1Signature)
- @step2 = DSPy::ChainOfThought.new(Step2Signature)
- @step3 = DSPy::Predict.new(Step3Signature)
- end
-
- def forward(input)
- # Chain predictors together
- result1 = @step1.forward(input)
- result2 = @step2.forward(result1)
- result3 = @step3.forward(result2)
-
- # Combine results as needed
- {
- step1_output: result1,
- step2_output: result2,
- final_result: result3
- }
- end
-end
-
-# Module with conditional logic
-class ConditionalModule < DSPy::Module
- def initialize
- super
- @simple_classifier = DSPy::Predict.new(SimpleClassificationSignature)
- @complex_analyzer = DSPy::ChainOfThought.new(ComplexAnalysisSignature)
- end
-
- def forward(text:, complexity_threshold: 100)
- # Use different predictors based on input characteristics
- if text.length < complexity_threshold
- @simple_classifier.forward(text: text)
- else
- @complex_analyzer.forward(text: text)
- end
- end
-end
-
-# Module with error handling and retry logic
-class RobustModule < DSPy::Module
- MAX_RETRIES = 3
-
- def initialize
- super
- @predictor = DSPy::Predict.new(RobustSignature)
- @logger = Logger.new(STDOUT)
- end
-
- def forward(input, retry_count: 0)
- @logger.info "Processing input: #{input.inspect}"
-
- begin
- result = @predictor.forward(input)
- validate_result!(result)
- result
- rescue DSPy::ValidationError => e
- @logger.error "Validation error: #{e.message}"
-
- if retry_count < MAX_RETRIES
- @logger.info "Retrying (#{retry_count + 1}/#{MAX_RETRIES})..."
- sleep(2 ** retry_count) # Exponential backoff
- forward(input, retry_count: retry_count + 1)
- else
- @logger.error "Max retries exceeded"
- raise
- end
- end
- end
-
- private
-
- def validate_result!(result)
- # Add custom validation logic
- raise DSPy::ValidationError, "Invalid result" unless result[:category]
- raise DSPy::ValidationError, "Low confidence" if result[:confidence] && result[:confidence] < 0.5
- end
-end
-
-# Module with ReAct agent and tools
-class AgentModule < DSPy::Module
- def initialize
- super
-
- # Define tools for the agent
- tools = [
- SearchTool.new,
- CalculatorTool.new,
- DatabaseQueryTool.new
- ]
-
- # ReAct provides iterative reasoning and tool usage
- @agent = DSPy::ReAct.new(
- AgentSignature,
- tools: tools,
- max_iterations: 5
- )
- end
-
- def forward(task:)
- # Agent will autonomously use tools to complete the task
- @agent.forward(task: task)
- end
-end
-
-# Tool definition example
-class SearchTool < DSPy::Tool
- def call(query:)
- # Implement search functionality
- results = perform_search(query)
- { results: results }
- end
-
- private
-
- def perform_search(query)
- # Actual search implementation
- # Could call external API, database, etc.
- ["result1", "result2", "result3"]
- end
-end
-
-# Module with state management
-class StatefulModule < DSPy::Module
- attr_reader :history
-
- def initialize
- super
- @predictor = DSPy::ChainOfThought.new(StatefulSignature)
- @history = []
- end
-
- def forward(input)
- # Process with context from history
- context = build_context_from_history
- result = @predictor.forward(
- input: input,
- context: context
- )
-
- # Store in history
- @history << {
- input: input,
- result: result,
- timestamp: Time.now
- }
-
- result
- end
-
- def reset!
- @history.clear
- end
-
- private
-
- def build_context_from_history
- @history.last(5).map { |h| h[:result][:summary] }.join("\n")
- end
-end
-
-# Module that uses different LLMs for different tasks
-class MultiModelModule < DSPy::Module
- def initialize
- super
-
- # Fast, cheap model for simple classification
- @fast_predictor = create_predictor(
- 'openai/gpt-4o-mini',
- SimpleClassificationSignature
- )
-
- # Powerful model for complex analysis
- @powerful_predictor = create_predictor(
- 'anthropic/claude-3-5-sonnet-20241022',
- ComplexAnalysisSignature
- )
- end
-
- def forward(input, use_complex: false)
- if use_complex
- @powerful_predictor.forward(input)
- else
- @fast_predictor.forward(input)
- end
- end
-
- private
-
- def create_predictor(model, signature)
- lm = DSPy::LM.new(model, api_key: ENV["#{model.split('/').first.upcase}_API_KEY"])
- DSPy::Predict.new(signature, lm: lm)
- end
-end
-
-# Module with caching
-class CachedModule < DSPy::Module
- def initialize
- super
- @predictor = DSPy::Predict.new(CachedSignature)
- @cache = {}
- end
-
- def forward(input)
- # Create cache key from input
- cache_key = create_cache_key(input)
-
- # Return cached result if available
- if @cache.key?(cache_key)
- puts "Cache hit for #{cache_key}"
- return @cache[cache_key]
- end
-
- # Compute and cache result
- result = @predictor.forward(input)
- @cache[cache_key] = result
- result
- end
-
- def clear_cache!
- @cache.clear
- end
-
- private
-
- def create_cache_key(input)
- # Create deterministic hash from input
- Digest::MD5.hexdigest(input.to_s)
- end
-end
-
-# Usage Examples:
-#
-# Basic usage:
-# module = BasicModule.new
-# result = module.forward(field_name: "value")
-#
-# Chain of Thought:
-# module = ChainOfThoughtModule.new
-# result = module.forward(
-# email_subject: "Can't log in",
-# email_body: "I'm unable to access my account"
-# )
-# puts result[:reasoning]
-#
-# Multi-step pipeline:
-# pipeline = MultiStepPipeline.new
-# result = pipeline.forward(input_data)
-#
-# With error handling:
-# module = RobustModule.new
-# begin
-# result = module.forward(input_data)
-# rescue DSPy::ValidationError => e
-# puts "Failed after retries: #{e.message}"
-# end
-#
-# Agent with tools:
-# agent = AgentModule.new
-# result = agent.forward(task: "Find the population of Tokyo")
-#
-# Stateful processing:
-# module = StatefulModule.new
-# result1 = module.forward("First input")
-# result2 = module.forward("Second input") # Has context from first
-# module.reset! # Clear history
-#
-# With caching:
-# module = CachedModule.new
-# result1 = module.forward(input) # Computes result
-# result2 = module.forward(input) # Returns cached result
diff --git a/plugins/compound-engineering/skills/dspy-ruby/assets/signature-template.rb b/plugins/compound-engineering/skills/dspy-ruby/assets/signature-template.rb
deleted file mode 100644
index ea13f81..0000000
--- a/plugins/compound-engineering/skills/dspy-ruby/assets/signature-template.rb
+++ /dev/null
@@ -1,143 +0,0 @@
-# frozen_string_literal: true
-
-# Example DSPy Signature Template
-# This template demonstrates best practices for creating type-safe signatures
-
-class ExampleSignature < DSPy::Signature
- # Clear, specific description of what this signature does
- # Good: "Classify customer support emails into Technical, Billing, or General categories"
- # Avoid: "Classify emails"
- description "Describe what this signature accomplishes and what output it produces"
-
- # Input fields: Define what data the LLM receives
- input do
- # Basic field with description
- const :field_name, String, desc: "Clear description of this input field"
-
- # Numeric fields
- const :count, Integer, desc: "Number of items to process"
- const :score, Float, desc: "Confidence score between 0.0 and 1.0"
-
- # Boolean fields
- const :is_active, T::Boolean, desc: "Whether the item is currently active"
-
- # Array fields
- const :tags, T::Array[String], desc: "List of tags associated with the item"
-
- # Optional: Enum for constrained values
- const :priority, T.enum(["Low", "Medium", "High"]), desc: "Priority level"
- end
-
- # Output fields: Define what data the LLM produces
- output do
- # Primary output
- const :result, String, desc: "The main result of the operation"
-
- # Classification result with enum
- const :category, T.enum(["Technical", "Billing", "General"]),
- desc: "Category classification - must be one of: Technical, Billing, General"
-
- # Confidence/metadata
- const :confidence, Float, desc: "Confidence score (0.0-1.0) for this classification"
-
- # Optional reasoning (automatically added by ChainOfThought)
- # const :reasoning, String, desc: "Step-by-step reasoning for the classification"
- end
-end
-
-# Example with multimodal input (vision)
-class VisionExampleSignature < DSPy::Signature
- description "Analyze an image and answer questions about its content"
-
- input do
- const :image, DSPy::Image, desc: "The image to analyze"
- const :question, String, desc: "Question about the image content"
- end
-
- output do
- const :answer, String, desc: "Detailed answer to the question about the image"
- const :confidence, Float, desc: "Confidence in the answer (0.0-1.0)"
- end
-end
-
-# Example for complex analysis task
-class SentimentAnalysisSignature < DSPy::Signature
- description "Analyze the sentiment of text with nuanced emotion detection"
-
- input do
- const :text, String, desc: "The text to analyze for sentiment"
- const :context, String, desc: "Additional context about the text source or situation"
- end
-
- output do
- const :sentiment, T.enum(["Positive", "Negative", "Neutral", "Mixed"]),
- desc: "Overall sentiment - must be Positive, Negative, Neutral, or Mixed"
-
- const :emotions, T::Array[String],
- desc: "List of specific emotions detected (e.g., joy, anger, sadness, fear)"
-
- const :intensity, T.enum(["Low", "Medium", "High"]),
- desc: "Intensity of the detected sentiment"
-
- const :confidence, Float,
- desc: "Confidence in the sentiment classification (0.0-1.0)"
- end
-end
-
-# Example for code generation task
-class CodeGenerationSignature < DSPy::Signature
- description "Generate Ruby code based on natural language requirements"
-
- input do
- const :requirements, String,
- desc: "Natural language description of what the code should do"
-
- const :constraints, String,
- desc: "Any specific requirements or constraints (e.g., libraries to use, style preferences)"
- end
-
- output do
- const :code, String,
- desc: "Complete, working Ruby code that fulfills the requirements"
-
- const :explanation, String,
- desc: "Brief explanation of how the code works and any important design decisions"
-
- const :dependencies, T::Array[String],
- desc: "List of required gems or dependencies"
- end
-end
-
-# Usage Examples:
-#
-# Basic usage with Predict:
-# predictor = DSPy::Predict.new(ExampleSignature)
-# result = predictor.forward(
-# field_name: "example value",
-# count: 5,
-# score: 0.85,
-# is_active: true,
-# tags: ["tag1", "tag2"],
-# priority: "High"
-# )
-# puts result[:result]
-# puts result[:category]
-# puts result[:confidence]
-#
-# With Chain of Thought reasoning:
-# predictor = DSPy::ChainOfThought.new(SentimentAnalysisSignature)
-# result = predictor.forward(
-# text: "I absolutely love this product! It exceeded all my expectations.",
-# context: "Product review on e-commerce site"
-# )
-# puts result[:reasoning] # See the LLM's step-by-step thinking
-# puts result[:sentiment]
-# puts result[:emotions]
-#
-# With Vision:
-# predictor = DSPy::Predict.new(VisionExampleSignature)
-# result = predictor.forward(
-# image: DSPy::Image.from_file("path/to/image.jpg"),
-# question: "What objects are visible in this image?"
-# )
-# puts result[:answer]
diff --git a/plugins/compound-engineering/skills/dspy-ruby/references/core-concepts.md b/plugins/compound-engineering/skills/dspy-ruby/references/core-concepts.md
deleted file mode 100644
index 66f0b02..0000000
--- a/plugins/compound-engineering/skills/dspy-ruby/references/core-concepts.md
+++ /dev/null
@@ -1,265 +0,0 @@
-# DSPy.rb Core Concepts
-
-## Philosophy
-
-DSPy.rb enables developers to **program LLMs, not prompt them**. Instead of manually crafting prompts, define application requirements through code using type-safe, composable modules.
-
-## Signatures
-
-Signatures define type-safe input/output contracts for LLM operations. They specify what data goes in and what data comes out, with runtime type checking.
-
-### Basic Signature Structure
-
-```ruby
-class TaskSignature < DSPy::Signature
- description "Brief description of what this signature does"
-
- input do
- const :field_name, String, desc: "Description of this input field"
- const :another_field, Integer, desc: "Another input field"
- end
-
- output do
- const :result_field, String, desc: "Description of the output"
- const :confidence, Float, desc: "Confidence score (0.0-1.0)"
- end
-end
-```
-
-### Type Safety
-
-Signatures support Sorbet types including:
-- `String` - Text data
-- `Integer`, `Float` - Numeric data
-- `T::Boolean` - Boolean values
-- `T::Array[Type]` - Arrays of specific types
-- Custom enums and classes
-
-### Field Descriptions
-
-Always provide clear field descriptions using the `desc:` parameter. These descriptions:
-- Guide the LLM on expected input/output format
-- Serve as documentation for developers
-- Improve prediction accuracy
-
-## Modules
-
-Modules are composable building blocks that use signatures to perform LLM operations. They can be chained together to create complex workflows.
-
-### Basic Module Structure
-
-```ruby
-class MyModule < DSPy::Module
- def initialize
- super
- @predictor = DSPy::Predict.new(MySignature)
- end
-
- def forward(input_hash)
- @predictor.forward(input_hash)
- end
-end
-```
-
-### Module Composition
-
-Modules can call other modules to create pipelines:
-
-```ruby
-class ComplexWorkflow < DSPy::Module
- def initialize
- super
- @step1 = FirstModule.new
- @step2 = SecondModule.new
- end
-
- def forward(input)
- result1 = @step1.forward(input)
- result2 = @step2.forward(result1)
- result2
- end
-end
-```
-
-## Predictors
-
-Predictors are the core execution engines that take signatures and perform LLM inference. DSPy.rb provides several predictor types.
-
-### Predict
-
-Basic LLM inference with type-safe inputs and outputs.
-
-```ruby
-predictor = DSPy::Predict.new(TaskSignature)
-result = predictor.forward(field_name: "value", another_field: 42)
-# Returns: { result_field: "...", confidence: 0.85 }
-```
-
-### ChainOfThought
-
-Automatically adds a reasoning field to the output, improving accuracy for complex tasks.
-
-```ruby
-class EmailClassificationSignature < DSPy::Signature
- description "Classify customer support emails"
-
- input do
- const :email_subject, String
- const :email_body, String
- end
-
- output do
- const :category, String # "Technical", "Billing", or "General"
- const :priority, String # "High", "Medium", or "Low"
- end
-end
-
-predictor = DSPy::ChainOfThought.new(EmailClassificationSignature)
-result = predictor.forward(
- email_subject: "Can't log in to my account",
- email_body: "I've been trying to access my account for hours..."
-)
-# Returns: {
-# reasoning: "This appears to be a technical issue...",
-# category: "Technical",
-# priority: "High"
-# }
-```
-
-### ReAct
-
-Tool-using agents with iterative reasoning. Enables autonomous problem-solving by allowing the LLM to use external tools.
-
-```ruby
-class SearchTool < DSPy::Tool
- def call(query:)
- # Perform search and return results
- { results: search_database(query) }
- end
-end
-
-predictor = DSPy::ReAct.new(
- TaskSignature,
- tools: [SearchTool.new],
- max_iterations: 5
-)
-```
-
-### CodeAct
-
-Dynamic code generation for solving problems programmatically. Requires the optional `dspy-code_act` gem.
-
-```ruby
-predictor = DSPy::CodeAct.new(TaskSignature)
-result = predictor.forward(task: "Calculate the factorial of 5")
-# The LLM generates and executes Ruby code to solve the task
-```
-
-## Multimodal Support
-
-DSPy.rb supports vision capabilities across compatible models using the unified `DSPy::Image` interface.
-
-```ruby
-class VisionSignature < DSPy::Signature
- description "Describe what's in an image"
-
- input do
- const :image, DSPy::Image
- const :question, String
- end
-
- output do
- const :description, String
- end
-end
-
-predictor = DSPy::Predict.new(VisionSignature)
-result = predictor.forward(
- image: DSPy::Image.from_file("path/to/image.jpg"),
- question: "What objects are visible in this image?"
-)
-```
-
-### Image Input Methods
-
-```ruby
-# From file path
-DSPy::Image.from_file("path/to/image.jpg")
-
-# From URL (OpenAI only)
-DSPy::Image.from_url("https://example.com/image.jpg")
-
-# From base64-encoded data
-DSPy::Image.from_base64(base64_string, mime_type: "image/jpeg")
-```
-
-## Best Practices
-
-### 1. Clear Signature Descriptions
-
-Always provide clear, specific descriptions for signatures and fields:
-
-```ruby
-# Good
-description "Classify customer support emails into Technical, Billing, or General categories"
-
-# Avoid
-description "Classify emails"
-```
-
-### 2. Type Safety
-
-Use specific types rather than generic String when possible:
-
-```ruby
-# Good - Use enums for constrained outputs
-output do
- const :category, T.enum(["Technical", "Billing", "General"])
-end
-
-# Less ideal - Generic string
-output do
- const :category, String, desc: "Must be Technical, Billing, or General"
-end
-```
-
-### 3. Composable Architecture
-
-Build complex workflows from simple, reusable modules:
-
-```ruby
-class EmailPipeline < DSPy::Module
- def initialize
- super
- @classifier = EmailClassifier.new
- @prioritizer = EmailPrioritizer.new
- @responder = EmailResponder.new
- end
-
- def forward(email)
- classification = @classifier.forward(email)
- priority = @prioritizer.forward(classification)
- @responder.forward(classification.merge(priority))
- end
-end
-```
-
-### 4. Error Handling
-
-Always handle potential type validation errors:
-
-```ruby
-begin
- result = predictor.forward(input_data)
-rescue DSPy::ValidationError => e
- # Handle validation error
- logger.error "Invalid output from LLM: #{e.message}"
-end
-```
-
-## Limitations
-
-Current constraints to be aware of:
-- No streaming support (single-request processing only)
-- Limited multimodal support through Ollama for local deployments
-- Vision capabilities vary by provider (see providers.md for compatibility matrix)
diff --git a/plugins/compound-engineering/skills/dspy-ruby/references/optimization.md b/plugins/compound-engineering/skills/dspy-ruby/references/optimization.md
deleted file mode 100644
index 7ff5466..0000000
--- a/plugins/compound-engineering/skills/dspy-ruby/references/optimization.md
+++ /dev/null
@@ -1,623 +0,0 @@
-# DSPy.rb Testing, Optimization & Observability
-
-## Testing
-
-DSPy.rb enables standard RSpec testing patterns for LLM logic, making your AI applications testable and maintainable.
-
-### Basic Testing Setup
-
-```ruby
-require 'rspec'
-require 'dspy'
-
-RSpec.describe EmailClassifier do
- before do
- DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
- end
- end
-
- describe '#classify' do
- it 'classifies technical support emails correctly' do
- classifier = EmailClassifier.new
- result = classifier.forward(
- email_subject: "Can't log in",
- email_body: "I'm unable to access my account"
- )
-
- expect(result[:category]).to eq('Technical')
- expect(result[:priority]).to be_in(['High', 'Medium', 'Low'])
- end
- end
-end
-```
-
-### Mocking LLM Responses
-
-Test your modules without making actual API calls:
-
-```ruby
-RSpec.describe MyModule do
- it 'handles mock responses correctly' do
- # Create a mock predictor that returns predetermined results
- mock_predictor = instance_double(DSPy::Predict)
- allow(mock_predictor).to receive(:forward).and_return({
- category: 'Technical',
- priority: 'High',
- confidence: 0.95
- })
-
- # Inject mock into your module
- module_instance = MyModule.new
- module_instance.instance_variable_set(:@predictor, mock_predictor)
-
- result = module_instance.forward(input: 'test data')
- expect(result[:category]).to eq('Technical')
- end
-end
-```
-
-### Testing Type Safety
-
-Verify that signatures enforce type constraints:
-
-```ruby
-RSpec.describe EmailClassificationSignature do
- it 'validates output types' do
- predictor = DSPy::Predict.new(EmailClassificationSignature)
-
- # This should work
- result = predictor.forward(
- email_subject: 'Test',
- email_body: 'Test body'
- )
- expect(result[:category]).to be_a(String)
-
- # Test that invalid types are caught
- expect {
- # Simulate LLM returning invalid type
- predictor.send(:validate_output, { category: 123 })
- }.to raise_error(DSPy::ValidationError)
- end
-end
-```
-
-### Testing Edge Cases
-
-Always test boundary conditions and error scenarios:
-
-```ruby
-RSpec.describe EmailClassifier do
- it 'handles empty emails' do
- classifier = EmailClassifier.new
- result = classifier.forward(
- email_subject: '',
- email_body: ''
- )
- # Define expected behavior for edge case
- expect(result[:category]).to eq('General')
- end
-
- it 'handles very long emails' do
- long_body = 'word ' * 10000
- classifier = EmailClassifier.new
-
- expect {
- classifier.forward(
- email_subject: 'Test',
- email_body: long_body
- )
- }.not_to raise_error
- end
-
- it 'handles special characters' do
- classifier = EmailClassifier.new
- result = classifier.forward(
- email_subject: 'Test ',
- email_body: 'Body with émojis 🎉 and spëcial çharacters'
- )
-
- expect(result[:category]).to be_in(['Technical', 'Billing', 'General'])
- end
-end
-```
-
-### Integration Testing
-
-Test complete workflows end-to-end:
-
-```ruby
-RSpec.describe EmailProcessingPipeline do
- it 'processes email through complete pipeline' do
- pipeline = EmailProcessingPipeline.new
-
- result = pipeline.forward(
- email_subject: 'Billing question',
- email_body: 'How do I update my payment method?'
- )
-
- # Verify the complete pipeline output
- expect(result[:classification]).to eq('Billing')
- expect(result[:priority]).to eq('Medium')
- expect(result[:suggested_response]).to include('payment')
- expect(result[:assigned_team]).to eq('billing_support')
- end
-end
-```
-
-### VCR for Deterministic Tests
-
-Use VCR to record and replay API responses:
-
-```ruby
-require 'vcr'
-
-VCR.configure do |config|
- config.cassette_library_dir = 'spec/vcr_cassettes'
- config.hook_into :webmock
- config.filter_sensitive_data('
') { ENV['OPENAI_API_KEY'] }
-end
-
-RSpec.describe EmailClassifier do
- it 'classifies emails consistently', :vcr do
- VCR.use_cassette('email_classification') do
- classifier = EmailClassifier.new
- result = classifier.forward(
- email_subject: 'Test subject',
- email_body: 'Test body'
- )
-
- expect(result[:category]).to eq('Technical')
- end
- end
-end
-```
-
-## Optimization
-
-DSPy.rb provides powerful optimization capabilities to automatically improve your prompts and modules.
-
-### MIPROv2 Optimization
-
-MIPROv2 is an advanced multi-prompt optimization technique that uses bootstrap sampling, instruction generation, and Bayesian optimization.
-
-```ruby
-require 'dspy/mipro'
-
-# Define your module to optimize
-class EmailClassifier < DSPy::Module
- def initialize
- super
- @predictor = DSPy::ChainOfThought.new(EmailClassificationSignature)
- end
-
- def forward(input)
- @predictor.forward(input)
- end
-end
-
-# Prepare training data
-training_examples = [
- {
- input: { email_subject: "Can't log in", email_body: "Password reset not working" },
- expected_output: { category: 'Technical', priority: 'High' }
- },
- {
- input: { email_subject: "Billing question", email_body: "How much does premium cost?" },
- expected_output: { category: 'Billing', priority: 'Medium' }
- },
- # Add more examples...
-]
-
-# Define evaluation metric
-def accuracy_metric(example, prediction)
- (example[:expected_output][:category] == prediction[:category]) ? 1.0 : 0.0
-end
-
-# Run optimization
-optimizer = DSPy::MIPROv2.new(
- metric: method(:accuracy_metric),
- num_candidates: 10,
- num_threads: 4
-)
-
-optimized_module = optimizer.compile(
- EmailClassifier.new,
- trainset: training_examples
-)
-
-# Use optimized module
-result = optimized_module.forward(
- email_subject: "New email",
- email_body: "New email content"
-)
-```
-
-### Bootstrap Few-Shot Learning
-
-Automatically generate few-shot examples from your training data:
-
-```ruby
-require 'dspy/teleprompt'
-
-# Create a teleprompter for few-shot optimization
-teleprompter = DSPy::BootstrapFewShot.new(
- metric: method(:accuracy_metric),
- max_bootstrapped_demos: 5,
- max_labeled_demos: 3
-)
-
-# Compile the optimized module
-optimized = teleprompter.compile(
- MyModule.new,
- trainset: training_examples
-)
-```
-
-### Custom Optimization Metrics
-
-Define custom metrics for your specific use case:
-
-```ruby
-def custom_metric(example, prediction)
- score = 0.0
-
- # Category accuracy (60% weight)
- score += 0.6 if example[:expected_output][:category] == prediction[:category]
-
- # Priority accuracy (40% weight)
- score += 0.4 if example[:expected_output][:priority] == prediction[:priority]
-
- score
-end
-
-# Use in optimization
-optimizer = DSPy::MIPROv2.new(
- metric: method(:custom_metric),
- num_candidates: 10
-)
-```
-
-### A/B Testing Different Approaches
-
-Compare different module implementations:
-
-```ruby
-# Approach A: ChainOfThought
-class ApproachA < DSPy::Module
- def initialize
- super
- @predictor = DSPy::ChainOfThought.new(EmailClassificationSignature)
- end
-
- def forward(input)
- @predictor.forward(input)
- end
-end
-
-# Approach B: ReAct with tools
-class ApproachB < DSPy::Module
- def initialize
- super
- @predictor = DSPy::ReAct.new(
- EmailClassificationSignature,
- tools: [KnowledgeBaseTool.new]
- )
- end
-
- def forward(input)
- @predictor.forward(input)
- end
-end
-
-# Evaluate both approaches
-def evaluate_approach(approach_class, test_set)
- approach = approach_class.new
- scores = test_set.map do |example|
- prediction = approach.forward(example[:input])
- accuracy_metric(example, prediction)
- end
- scores.sum / scores.size
-end
-
-approach_a_score = evaluate_approach(ApproachA, test_examples)
-approach_b_score = evaluate_approach(ApproachB, test_examples)
-
-puts "Approach A accuracy: #{approach_a_score}"
-puts "Approach B accuracy: #{approach_b_score}"
-```
-
-## Observability
-
-Track your LLM application's performance, token usage, and behavior in production.
-
-### OpenTelemetry Integration
-
-DSPy.rb automatically integrates with OpenTelemetry when configured:
-
-```ruby
-require 'opentelemetry/sdk'
-require 'dspy'
-
-# Configure OpenTelemetry
-OpenTelemetry::SDK.configure do |c|
- c.service_name = 'my-dspy-app'
- c.use_all # Use all available instrumentation
-end
-
-# DSPy automatically creates traces for predictions
-predictor = DSPy::Predict.new(MySignature)
-result = predictor.forward(input: 'data')
-# Traces are automatically sent to your OpenTelemetry collector
-```
-
-### Langfuse Integration
-
-Track detailed LLM execution traces with Langfuse:
-
-```ruby
-require 'dspy/langfuse'
-
-# Configure Langfuse
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
- c.langfuse = {
- public_key: ENV['LANGFUSE_PUBLIC_KEY'],
- secret_key: ENV['LANGFUSE_SECRET_KEY'],
- host: ENV['LANGFUSE_HOST'] || 'https://cloud.langfuse.com'
- }
-end
-
-# All predictions are automatically traced
-predictor = DSPy::Predict.new(MySignature)
-result = predictor.forward(input: 'data')
-# View detailed traces in Langfuse dashboard
-```
-
-### Manual Token Tracking
-
-Track token usage without external services:
-
-```ruby
-class TokenTracker
- def initialize
- @total_tokens = 0
- @request_count = 0
- end
-
- def track_prediction(predictor, input)
- start_time = Time.now
- result = predictor.forward(input)
- duration = Time.now - start_time
-
- # Get token usage from response metadata
- tokens = result.metadata[:usage][:total_tokens] rescue 0
- @total_tokens += tokens
- @request_count += 1
-
- puts "Request ##{@request_count}: #{tokens} tokens in #{duration}s"
- puts "Total tokens used: #{@total_tokens}"
-
- result
- end
-end
-
-# Usage
-tracker = TokenTracker.new
-predictor = DSPy::Predict.new(MySignature)
-
-result = tracker.track_prediction(predictor, { input: 'data' })
-```
-
-### Custom Logging
-
-Add detailed logging to your modules:
-
-```ruby
-class EmailClassifier < DSPy::Module
- def initialize
- super
- @predictor = DSPy::ChainOfThought.new(EmailClassificationSignature)
- @logger = Logger.new(STDOUT)
- end
-
- def forward(input)
- @logger.info "Classifying email: #{input[:email_subject]}"
-
- start_time = Time.now
- result = @predictor.forward(input)
- duration = Time.now - start_time
-
- @logger.info "Classification: #{result[:category]} (#{duration}s)"
-
- if result[:reasoning]
- @logger.debug "Reasoning: #{result[:reasoning]}"
- end
-
- result
- rescue => e
- @logger.error "Classification failed: #{e.message}"
- raise
- end
-end
-```
-
-### Performance Monitoring
-
-Monitor latency and performance metrics:
-
-```ruby
-class PerformanceMonitor
- def initialize
- @metrics = {
- total_requests: 0,
- total_duration: 0.0,
- errors: 0,
- success_count: 0
- }
- end
-
- def monitor_request
- start_time = Time.now
- @metrics[:total_requests] += 1
-
- begin
- result = yield
- @metrics[:success_count] += 1
- result
- rescue => e
- @metrics[:errors] += 1
- raise
- ensure
- duration = Time.now - start_time
- @metrics[:total_duration] += duration
-
- if @metrics[:total_requests] % 10 == 0
- print_stats
- end
- end
- end
-
- def print_stats
- avg_duration = @metrics[:total_duration] / @metrics[:total_requests]
- success_rate = @metrics[:success_count].to_f / @metrics[:total_requests]
-
- puts "\n=== Performance Stats ==="
- puts "Total requests: #{@metrics[:total_requests]}"
- puts "Average duration: #{avg_duration.round(3)}s"
- puts "Success rate: #{(success_rate * 100).round(2)}%"
- puts "Errors: #{@metrics[:errors]}"
- puts "========================\n"
- end
-end
-
-# Usage
-monitor = PerformanceMonitor.new
-predictor = DSPy::Predict.new(MySignature)
-
-result = monitor.monitor_request do
- predictor.forward(input: 'data')
-end
-```
-
-### Error Rate Tracking
-
-Monitor and alert on error rates:
-
-```ruby
-class ErrorRateMonitor
- def initialize(alert_threshold: 0.1)
- @alert_threshold = alert_threshold
- @recent_results = []
- @window_size = 100
- end
-
- def track_result(success:)
- @recent_results << success
- @recent_results.shift if @recent_results.size > @window_size
-
- error_rate = calculate_error_rate
- alert_if_needed(error_rate)
-
- error_rate
- end
-
- private
-
- def calculate_error_rate
- failures = @recent_results.count(false)
- failures.to_f / @recent_results.size
- end
-
- def alert_if_needed(error_rate)
- if error_rate > @alert_threshold
- puts "⚠️ ALERT: Error rate #{(error_rate * 100).round(2)}% exceeds threshold!"
- # Send notification, page oncall, etc.
- end
- end
-end
-```
-
-## Best Practices
-
-### 1. Start with Tests
-
-Write tests before optimizing:
-
-```ruby
-# Define test cases first
-test_cases = [
- { input: {...}, expected: {...} },
- # More test cases...
-]
-
-# Ensure baseline functionality
-test_cases.each do |tc|
- result = module.forward(tc[:input])
- assert result[:category] == tc[:expected][:category]
-end
-
-# Then optimize
-optimized = optimizer.compile(module, trainset: test_cases)
-```
-
-### 2. Use Meaningful Metrics
-
-Define metrics that align with business goals:
-
-```ruby
-def business_aligned_metric(example, prediction)
- # High-priority errors are more costly
- if example[:expected_output][:priority] == 'High'
- return prediction[:priority] == 'High' ? 1.0 : 0.0
- else
- return prediction[:category] == example[:expected_output][:category] ? 0.8 : 0.0
- end
-end
-```
-
-### 3. Monitor in Production
-
-Always track production performance:
-
-```ruby
-class ProductionModule < DSPy::Module
- def initialize
- super
- @predictor = DSPy::ChainOfThought.new(MySignature)
- @monitor = PerformanceMonitor.new
- @error_tracker = ErrorRateMonitor.new
- end
-
- def forward(input)
- @monitor.monitor_request do
- result = @predictor.forward(input)
- @error_tracker.track_result(success: true)
- result
- rescue => e
- @error_tracker.track_result(success: false)
- raise
- end
- end
-end
-```
-
-### 4. Version Your Modules
-
-Track which version of your module is deployed:
-
-```ruby
-class EmailClassifierV2 < DSPy::Module
- VERSION = '2.1.0'
-
- def initialize
- super
- @predictor = DSPy::ChainOfThought.new(EmailClassificationSignature)
- end
-
- def forward(input)
- result = @predictor.forward(input)
- result.merge(model_version: VERSION)
- end
-end
-```
diff --git a/plugins/compound-engineering/skills/dspy-ruby/references/providers.md b/plugins/compound-engineering/skills/dspy-ruby/references/providers.md
deleted file mode 100644
index 5dd56f3..0000000
--- a/plugins/compound-engineering/skills/dspy-ruby/references/providers.md
+++ /dev/null
@@ -1,338 +0,0 @@
-# DSPy.rb LLM Providers
-
-## Supported Providers
-
-DSPy.rb provides unified support across multiple LLM providers through adapter gems that automatically load when installed.
-
-### Provider Overview
-
-- **OpenAI**: GPT-4, GPT-4o, GPT-4o-mini, GPT-3.5-turbo
-- **Anthropic**: Claude 3 family (Sonnet, Opus, Haiku), Claude 3.5 Sonnet
-- **Google Gemini**: Gemini 1.5 Pro, Gemini 1.5 Flash, other versions
-- **Ollama**: Local model support via OpenAI compatibility layer
-- **OpenRouter**: Unified multi-provider API for 200+ models
-
-## Configuration
-
-### Basic Setup
-
-```ruby
-require 'dspy'
-
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('provider/model-name', api_key: ENV['API_KEY'])
-end
-```
-
-### OpenAI Configuration
-
-**Required gem**: `dspy-openai`
-
-```ruby
-DSPy.configure do |c|
- # GPT-4o Mini (recommended for development)
- c.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
-
- # GPT-4o (more capable)
- c.lm = DSPy::LM.new('openai/gpt-4o', api_key: ENV['OPENAI_API_KEY'])
-
- # GPT-4 Turbo
- c.lm = DSPy::LM.new('openai/gpt-4-turbo', api_key: ENV['OPENAI_API_KEY'])
-end
-```
-
-**Environment variable**: `OPENAI_API_KEY`
-
-### Anthropic Configuration
-
-**Required gem**: `dspy-anthropic`
-
-```ruby
-DSPy.configure do |c|
- # Claude 3.5 Sonnet (latest, most capable)
- c.lm = DSPy::LM.new('anthropic/claude-3-5-sonnet-20241022',
- api_key: ENV['ANTHROPIC_API_KEY'])
-
- # Claude 3 Opus (most capable in Claude 3 family)
- c.lm = DSPy::LM.new('anthropic/claude-3-opus-20240229',
- api_key: ENV['ANTHROPIC_API_KEY'])
-
- # Claude 3 Sonnet (balanced)
- c.lm = DSPy::LM.new('anthropic/claude-3-sonnet-20240229',
- api_key: ENV['ANTHROPIC_API_KEY'])
-
- # Claude 3 Haiku (fast, cost-effective)
- c.lm = DSPy::LM.new('anthropic/claude-3-haiku-20240307',
- api_key: ENV['ANTHROPIC_API_KEY'])
-end
-```
-
-**Environment variable**: `ANTHROPIC_API_KEY`
-
-### Google Gemini Configuration
-
-**Required gem**: `dspy-gemini`
-
-```ruby
-DSPy.configure do |c|
- # Gemini 1.5 Pro (most capable)
- c.lm = DSPy::LM.new('gemini/gemini-1.5-pro',
- api_key: ENV['GOOGLE_API_KEY'])
-
- # Gemini 1.5 Flash (faster, cost-effective)
- c.lm = DSPy::LM.new('gemini/gemini-1.5-flash',
- api_key: ENV['GOOGLE_API_KEY'])
-end
-```
-
-**Environment variable**: `GOOGLE_API_KEY` or `GEMINI_API_KEY`
-
-### Ollama Configuration
-
-**Required gem**: None (uses OpenAI compatibility layer)
-
-```ruby
-DSPy.configure do |c|
- # Local Ollama instance
- c.lm = DSPy::LM.new('ollama/llama3.1',
- base_url: 'http://localhost:11434')
-
- # Other Ollama models
- c.lm = DSPy::LM.new('ollama/mistral')
- c.lm = DSPy::LM.new('ollama/codellama')
-end
-```
-
-**Note**: Ensure Ollama is running locally: `ollama serve`
-
-### OpenRouter Configuration
-
-**Required gem**: `dspy-openai` (uses OpenAI adapter)
-
-```ruby
-DSPy.configure do |c|
- # Access 200+ models through OpenRouter
- c.lm = DSPy::LM.new('openrouter/anthropic/claude-3.5-sonnet',
- api_key: ENV['OPENROUTER_API_KEY'],
- base_url: 'https://openrouter.ai/api/v1')
-
- # Other examples
- c.lm = DSPy::LM.new('openrouter/google/gemini-pro')
- c.lm = DSPy::LM.new('openrouter/meta-llama/llama-3.1-70b-instruct')
-end
-```
-
-**Environment variable**: `OPENROUTER_API_KEY`
-
-## Provider Compatibility Matrix
-
-### Feature Support
-
-| Feature | OpenAI | Anthropic | Gemini | Ollama |
-|---------|--------|-----------|--------|--------|
-| Structured Output | ✅ | ✅ | ✅ | ✅ |
-| Vision (Images) | ✅ | ✅ | ✅ | ⚠️ Limited |
-| Image URLs | ✅ | ❌ | ❌ | ❌ |
-| Tool Calling | ✅ | ✅ | ✅ | Varies |
-| Streaming | ❌ | ❌ | ❌ | ❌ |
-| Function Calling | ✅ | ✅ | ✅ | Varies |
-
-**Legend**: ✅ Full support | ⚠️ Partial support | ❌ Not supported
-
-### Vision Capabilities
-
-**Image URLs**: Only OpenAI supports direct URL references. For other providers, load images as base64 or from files.
-
-```ruby
-# OpenAI - supports URLs
-DSPy::Image.from_url("https://example.com/image.jpg")
-
-# Anthropic, Gemini - use file or base64
-DSPy::Image.from_file("path/to/image.jpg")
-DSPy::Image.from_base64(base64_data, mime_type: "image/jpeg")
-```
-
-**Ollama**: Limited multimodal functionality. Check specific model capabilities.
-
-## Advanced Configuration
-
-### Custom Parameters
-
-Pass provider-specific parameters during configuration:
-
-```ruby
-DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o',
- api_key: ENV['OPENAI_API_KEY'],
- temperature: 0.7,
- max_tokens: 2000,
- top_p: 0.9
- )
-end
-```
-
-### Multiple Providers
-
-Use different models for different tasks:
-
-```ruby
-# Fast model for simple tasks
-fast_lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
-
-# Powerful model for complex tasks
-powerful_lm = DSPy::LM.new('anthropic/claude-3-5-sonnet-20241022',
- api_key: ENV['ANTHROPIC_API_KEY'])
-
-# Use different models in different modules
-class SimpleClassifier < DSPy::Module
- def initialize
- super
- DSPy.configure { |c| c.lm = fast_lm }
- @predictor = DSPy::Predict.new(SimpleSignature)
- end
-end
-
-class ComplexAnalyzer < DSPy::Module
- def initialize
- super
- DSPy.configure { |c| c.lm = powerful_lm }
- @predictor = DSPy::ChainOfThought.new(ComplexSignature)
- end
-end
-```
-
-### Per-Request Configuration
-
-Override configuration for specific predictions:
-
-```ruby
-predictor = DSPy::Predict.new(MySignature)
-
-# Use default configuration
-result1 = predictor.forward(input: "data")
-
-# Override temperature for this request
-result2 = predictor.forward(
- input: "data",
- config: { temperature: 0.2 } # More deterministic
-)
-```
-
-## Cost Optimization
-
-### Model Selection Strategy
-
-1. **Development**: Use cheaper, faster models (gpt-4o-mini, claude-3-haiku, gemini-1.5-flash)
-2. **Production Simple Tasks**: Continue with cheaper models if quality is sufficient
-3. **Production Complex Tasks**: Upgrade to more capable models (gpt-4o, claude-3.5-sonnet, gemini-1.5-pro)
-4. **Local Development**: Use Ollama for privacy and zero API costs
-
-### Example Cost-Conscious Setup
-
-```ruby
-# Development environment
-if Rails.env.development?
- DSPy.configure do |c|
- c.lm = DSPy::LM.new('ollama/llama3.1') # Free, local
- end
-elsif Rails.env.test?
- DSPy.configure do |c|
- c.lm = DSPy::LM.new('openai/gpt-4o-mini', # Cheap for testing
- api_key: ENV['OPENAI_API_KEY'])
- end
-else # production
- DSPy.configure do |c|
- c.lm = DSPy::LM.new('anthropic/claude-3-5-sonnet-20241022',
- api_key: ENV['ANTHROPIC_API_KEY'])
- end
-end
-```
-
-## Provider-Specific Best Practices
-
-### OpenAI
-
-- Use `gpt-4o-mini` for development and simple tasks
-- Use `gpt-4o` for production complex tasks
-- Best vision support including URL loading
-- Excellent function calling capabilities
-
-### Anthropic
-
-- Claude 3.5 Sonnet is currently the most capable model
-- Excellent for complex reasoning and analysis
-- Strong safety features and helpful outputs
-- Requires base64 for images (no URL support)
-
-### Google Gemini
-
-- Gemini 1.5 Pro for complex tasks, Flash for speed
-- Strong multimodal capabilities
-- Good balance of cost and performance
-- Requires base64 for images
-
-### Ollama
-
-- Best for privacy-sensitive applications
-- Zero API costs
-- Requires local hardware resources
-- Limited multimodal support depending on model
-- Good for development and testing
-
-## Troubleshooting
-
-### API Key Issues
-
-```ruby
-# Verify API key is set
-if ENV['OPENAI_API_KEY'].nil?
- raise "OPENAI_API_KEY environment variable not set"
-end
-
-# Test connection
-begin
- DSPy.configure { |c| c.lm = DSPy::LM.new('openai/gpt-4o-mini',
- api_key: ENV['OPENAI_API_KEY']) }
- predictor = DSPy::Predict.new(TestSignature)
- predictor.forward(test: "data")
- puts "✅ Connection successful"
-rescue => e
- puts "❌ Connection failed: #{e.message}"
-end
-```
-
-### Rate Limiting
-
-Handle rate limits gracefully:
-
-```ruby
-def call_with_retry(predictor, input, max_retries: 3)
- retries = 0
- begin
- predictor.forward(input)
- rescue RateLimitError => e
- retries += 1
- if retries < max_retries
- sleep(2 ** retries) # Exponential backoff
- retry
- else
- raise
- end
- end
-end
-```
-
-### Model Not Found
-
-Ensure the correct gem is installed:
-
-```bash
-# For OpenAI
-gem install dspy-openai
-
-# For Anthropic
-gem install dspy-anthropic
-
-# For Gemini
-gem install dspy-gemini
-```
diff --git a/plugins/compound-engineering/skills/fastapi-style/SKILL.md b/plugins/compound-engineering/skills/fastapi-style/SKILL.md
new file mode 100644
index 0000000..1fedce7
--- /dev/null
+++ b/plugins/compound-engineering/skills/fastapi-style/SKILL.md
@@ -0,0 +1,221 @@
+---
+name: fastapi-style
+description: This skill should be used when writing Python and FastAPI code following opinionated best practices. It applies when building APIs, creating Pydantic models, working with SQLAlchemy, or any FastAPI application. Triggers on FastAPI code generation, API design, refactoring requests, code review, or when discussing async Python patterns. Embodies thin routers, rich Pydantic models, dependency injection, async-first design, and the "explicit is better than implicit" philosophy.
+---
+
+
+Apply opinionated FastAPI conventions to Python API code. This skill provides comprehensive domain expertise for building maintainable, performant FastAPI applications following established patterns from production codebases.
+
+
+
+## Core Philosophy
+
+"Explicit is better than implicit. Simple is better than complex."
+
+**The FastAPI Way:**
+- Thin routers, rich Pydantic models with validation
+- Dependency injection for everything
+- Async-first with SQLAlchemy 2.0
+- Type hints everywhere - let the tools help you
+- Settings via pydantic-settings, not raw env vars
+- Database-backed solutions where possible
+
+**What to deliberately avoid:**
+- Flask patterns (global request context)
+- Django ORM in FastAPI (use SQLAlchemy 2.0)
+- Synchronous database calls (use async)
+- Manual JSON serialization (Pydantic handles it)
+- Global state (use dependency injection)
+- `*` imports (explicit imports only)
+- Circular imports (proper module structure)
+
+**Development Philosophy:**
+- Type everything - mypy should pass
+- Fail fast with descriptive errors
+- Write-time validation over read-time checks
+- Database constraints complement Pydantic validation
+- Tests are documentation
+
+
+
+What are you working on?
+
+1. **Routers** - Route organization, dependency injection, response models
+2. **Models** - Pydantic schemas, SQLAlchemy models, validation patterns
+3. **Database** - SQLAlchemy 2.0 async, Alembic migrations, transactions
+4. **Testing** - pytest, httpx TestClient, fixtures, async testing
+5. **Security** - OAuth2, JWT, permissions, CORS, rate limiting
+6. **Background Tasks** - Celery, ARQ, or FastAPI BackgroundTasks
+7. **Code Review** - Review code against FastAPI best practices
+8. **General Guidance** - Philosophy and conventions
+
+**Specify a number or describe your task.**
+
+
+
+
+| Response | Reference to Read |
+|----------|-------------------|
+| 1, router, route, endpoint | [routers.md](./references/routers.md) |
+| 2, model, pydantic, schema, sqlalchemy | [models.md](./references/models.md) |
+| 3, database, db, alembic, migration, transaction | [database.md](./references/database.md) |
+| 4, test, testing, pytest, fixture | [testing.md](./references/testing.md) |
+| 5, security, auth, oauth, jwt, permission | [security.md](./references/security.md) |
+| 6, background, task, celery, arq, queue | [background_tasks.md](./references/background_tasks.md) |
+| 7, review | Read all references, then review code |
+| 8, general task | Read relevant references based on context |
+
+**After reading relevant references, apply patterns to the user's code.**
+
+
+
+## Project Structure
+
+```
+app/
+├── main.py # FastAPI app creation, middleware
+├── config.py # Settings via pydantic-settings
+├── dependencies.py # Shared dependencies
+├── database.py # Database session, engine
+├── models/ # SQLAlchemy models
+│ ├── __init__.py
+│ ├── base.py # Base model class
+│ └── user.py
+├── schemas/ # Pydantic models
+│ ├── __init__.py
+│ └── user.py
+├── routers/ # API routers
+│ ├── __init__.py
+│ └── users.py
+├── services/ # Business logic (if needed)
+├── utils/ # Shared utilities
+└── tests/
+ ├── conftest.py # Fixtures
+ └── test_users.py
+```
+
+## Naming Conventions
+
+**Pydantic Schemas:**
+- `UserCreate` - input for creation
+- `UserUpdate` - input for updates (all fields Optional)
+- `UserRead` - output representation
+- `UserInDB` - internal with hashed password
+
+**SQLAlchemy Models:** Singular nouns (`User`, `Item`, `Order`)
+
+**Routers:** Plural resource names (`users.py`, `items.py`)
+
+**Dependencies:** Verb phrases (`get_current_user`, `get_db_session`)
+
+## Type Hints
+
+```python
+# Always type function signatures
+async def get_user(
+ user_id: int,
+ db: AsyncSession = Depends(get_db),
+) -> User:
+ ...
+
+# Use Annotated for dependency injection
+from typing import Annotated
+CurrentUser = Annotated[User, Depends(get_current_user)]
+DBSession = Annotated[AsyncSession, Depends(get_db)]
+```
+
+## Response Patterns
+
+```python
+# Explicit response_model
+@router.get("/users/{user_id}", response_model=UserRead)
+async def get_user(user_id: int, db: DBSession) -> User:
+ ...
+
+# Status codes
+@router.post("/users", status_code=status.HTTP_201_CREATED)
+async def create_user(...) -> UserRead:
+ ...
+
+# Multiple response types
+@router.get("/users/{user_id}", responses={404: {"model": ErrorResponse}})
+```
+
+## Error Handling
+
+```python
+from fastapi import HTTPException, status
+
+# Specific exceptions
+raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail="User not found",
+)
+
+# Custom exception handlers
+@app.exception_handler(ValidationError)
+async def validation_exception_handler(request, exc):
+ return JSONResponse(status_code=422, content={"detail": exc.errors()})
+```
+
+## Dependency Injection
+
+```python
+# Simple dependency
+async def get_db() -> AsyncGenerator[AsyncSession, None]:
+ async with async_session() as session:
+ yield session
+
+# Parameterized dependency
+def get_pagination(
+ skip: int = Query(0, ge=0),
+ limit: int = Query(100, ge=1, le=1000),
+) -> dict:
+ return {"skip": skip, "limit": limit}
+
+# Class-based dependency
+class CommonQueryParams:
+ def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+```
+
+
+
+## Domain Knowledge
+
+All detailed patterns in `references/`:
+
+| File | Topics |
+|------|--------|
+| [routers.md](./references/routers.md) | Route organization, dependency injection, response models, middleware, versioning |
+| [models.md](./references/models.md) | Pydantic schemas, SQLAlchemy models, validation, serialization, mixins |
+| [database.md](./references/database.md) | SQLAlchemy 2.0 async, Alembic migrations, transactions, connection pooling |
+| [testing.md](./references/testing.md) | pytest, httpx TestClient, fixtures, async testing, mocking patterns |
+| [security.md](./references/security.md) | OAuth2, JWT, permissions, CORS, rate limiting, secrets management |
+| [background_tasks.md](./references/background_tasks.md) | FastAPI BackgroundTasks, Celery, ARQ, task patterns |
+
+
+
+Code follows FastAPI best practices when:
+- Routers are thin, focused on HTTP concerns only
+- Pydantic models handle all validation and serialization
+- SQLAlchemy 2.0 async patterns used correctly
+- Dependencies injected, not imported as globals
+- Type hints on all function signatures
+- Settings via pydantic-settings
+- Tests use pytest with async support
+- Error handling is explicit and informative
+- Security follows OAuth2/JWT standards
+- Background tasks use appropriate tool for the job
+
+
+
+Based on FastAPI best practices from the official documentation, real-world production patterns, and the Python community's collective wisdom.
+
+**Key Resources:**
+- [FastAPI Documentation](https://fastapi.tiangolo.com/)
+- [SQLAlchemy 2.0 Documentation](https://docs.sqlalchemy.org/)
+- [Pydantic V2 Documentation](https://docs.pydantic.dev/)
+
diff --git a/plugins/compound-engineering/skills/python-package-writer/SKILL.md b/plugins/compound-engineering/skills/python-package-writer/SKILL.md
new file mode 100644
index 0000000..595a0fe
--- /dev/null
+++ b/plugins/compound-engineering/skills/python-package-writer/SKILL.md
@@ -0,0 +1,369 @@
+---
+name: python-package-writer
+description: This skill should be used when writing Python packages following production-ready patterns and philosophy. It applies when creating new Python packages, refactoring existing packages, designing package APIs, or when clean, minimal, well-tested Python library code is needed. Triggers on requests like "create a package", "write a Python library", "design a package API", or mentions of PyPI publishing.
+---
+
+# Python Package Writer
+
+Write Python packages following battle-tested patterns from production-ready libraries. Emphasis on simplicity, minimal dependencies, comprehensive testing, and modern packaging standards (pyproject.toml, type hints, pytest).
+
+## Core Philosophy
+
+**Simplicity over cleverness.** Zero or minimal dependencies. Explicit code over magic. Framework integration without framework coupling. Every pattern serves production use cases.
+
+## Package Structure (src layout)
+
+The modern recommended layout with proper namespace isolation:
+
+```
+package-name/
+├── pyproject.toml # All metadata and configuration
+├── README.md
+├── LICENSE
+├── py.typed # PEP 561 marker for type hints
+├── src/
+│ └── package_name/ # Actual package code
+│ ├── __init__.py # Entry point, exports, version
+│ ├── core.py # Core functionality
+│ ├── models.py # Data models (Pydantic/dataclasses)
+│ ├── exceptions.py # Custom exceptions
+│ └── py.typed # Type hint marker (also here)
+└── tests/
+ ├── conftest.py # Pytest fixtures
+ ├── test_core.py
+ └── test_models.py
+```
+
+## Entry Point Structure
+
+Every package follows this pattern in `src/package_name/__init__.py`:
+
+```python
+"""Package description - one line."""
+
+# Public API exports
+from package_name.core import Client, process_data
+from package_name.models import Config, Result
+from package_name.exceptions import PackageError, ValidationError
+
+__version__ = "1.0.0"
+__all__ = [
+ "Client",
+ "process_data",
+ "Config",
+ "Result",
+ "PackageError",
+ "ValidationError",
+]
+```
+
+## pyproject.toml Configuration
+
+Modern packaging with all metadata in one file:
+
+```toml
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "package-name"
+version = "1.0.0"
+description = "Brief description of what the package does"
+readme = "README.md"
+license = "MIT"
+requires-python = ">=3.10"
+authors = [
+ { name = "Your Name", email = "you@example.com" }
+]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Typing :: Typed",
+]
+keywords = ["keyword1", "keyword2"]
+
+# Zero or minimal runtime dependencies
+dependencies = []
+
+[project.optional-dependencies]
+dev = [
+ "pytest>=8.0",
+ "pytest-cov>=4.0",
+ "ruff>=0.4",
+ "mypy>=1.0",
+]
+# Optional integrations
+fastapi = ["fastapi>=0.100", "pydantic>=2.0"]
+
+[project.urls]
+Homepage = "https://github.com/username/package-name"
+Documentation = "https://package-name.readthedocs.io"
+Repository = "https://github.com/username/package-name"
+Changelog = "https://github.com/username/package-name/blob/main/CHANGELOG.md"
+
+[tool.hatch.build.targets.wheel]
+packages = ["src/package_name"]
+
+[tool.ruff]
+target-version = "py310"
+line-length = 88
+
+[tool.ruff.lint]
+select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"]
+
+[tool.mypy]
+python_version = "3.10"
+strict = true
+warn_return_any = true
+warn_unused_ignores = true
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+addopts = "-ra -q"
+
+[tool.coverage.run]
+source = ["src/package_name"]
+branch = true
+```
+
+## Configuration Pattern
+
+Use module-level configuration with dataclasses or simple attributes:
+
+```python
+# src/package_name/config.py
+from dataclasses import dataclass, field
+from os import environ
+from typing import Any
+
+
+@dataclass
+class Config:
+ """Package configuration with sensible defaults."""
+
+ timeout: int = 30
+ retries: int = 3
+ api_key: str | None = field(default=None)
+ debug: bool = False
+
+ def __post_init__(self) -> None:
+ # Environment variable fallbacks
+ if self.api_key is None:
+ self.api_key = environ.get("PACKAGE_API_KEY")
+
+
+# Module-level singleton (optional)
+_config: Config | None = None
+
+
+def get_config() -> Config:
+ """Get or create the global config instance."""
+ global _config
+ if _config is None:
+ _config = Config()
+ return _config
+
+
+def configure(**kwargs: Any) -> Config:
+ """Configure the package with custom settings."""
+ global _config
+ _config = Config(**kwargs)
+ return _config
+```
+
+## Error Handling
+
+Simple hierarchy with informative messages:
+
+```python
+# src/package_name/exceptions.py
+class PackageError(Exception):
+ """Base exception for all package errors."""
+ pass
+
+
+class ConfigError(PackageError):
+ """Invalid configuration."""
+ pass
+
+
+class ValidationError(PackageError):
+ """Data validation failed."""
+
+ def __init__(self, message: str, field: str | None = None) -> None:
+ self.field = field
+ super().__init__(message)
+
+
+class APIError(PackageError):
+ """External API error."""
+
+ def __init__(self, message: str, status_code: int | None = None) -> None:
+ self.status_code = status_code
+ super().__init__(message)
+
+
+# Validate early with ValueError
+def process(data: bytes) -> str:
+ if not data:
+ raise ValueError("Data cannot be empty")
+ if len(data) > 1_000_000:
+ raise ValueError(f"Data too large: {len(data)} bytes (max 1MB)")
+ return data.decode("utf-8")
+```
+
+## Type Hints
+
+Always use type hints with modern syntax (Python 3.10+):
+
+```python
+# Use built-in generics, not typing module
+from collections.abc import Callable, Iterator, Mapping, Sequence
+
+def process_items(
+ items: list[str],
+ transform: Callable[[str], str] | None = None,
+ *,
+ batch_size: int = 100,
+) -> Iterator[str]:
+ """Process items with optional transformation."""
+ for item in items:
+ if transform:
+ yield transform(item)
+ else:
+ yield item
+
+
+# Use | for unions, not Union
+def get_value(key: str) -> str | None:
+ return _cache.get(key)
+
+
+# Use Self for return type annotations (Python 3.11+)
+from typing import Self
+
+class Client:
+ def configure(self, **kwargs: str) -> Self:
+ # Update configuration
+ return self
+```
+
+## Testing (pytest)
+
+```python
+# tests/conftest.py
+import pytest
+from package_name import Config, configure
+
+
+@pytest.fixture
+def config() -> Config:
+ """Fresh config for each test."""
+ return configure(timeout=5, debug=True)
+
+
+@pytest.fixture
+def sample_data() -> bytes:
+ """Sample input data."""
+ return b"test data content"
+
+
+# tests/test_core.py
+import pytest
+from package_name import process_data, PackageError
+
+
+class TestProcessData:
+ """Tests for process_data function."""
+
+ def test_basic_functionality(self, sample_data: bytes) -> None:
+ result = process_data(sample_data)
+ assert result == "test data content"
+
+ def test_empty_input_raises_error(self) -> None:
+ with pytest.raises(ValueError, match="cannot be empty"):
+ process_data(b"")
+
+ def test_with_transform(self, sample_data: bytes) -> None:
+ result = process_data(sample_data, transform=str.upper)
+ assert result == "TEST DATA CONTENT"
+
+
+class TestConfig:
+ """Tests for configuration."""
+
+ def test_defaults(self) -> None:
+ config = Config()
+ assert config.timeout == 30
+ assert config.retries == 3
+
+ def test_env_fallback(self, monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("PACKAGE_API_KEY", "test-key")
+ config = Config()
+ assert config.api_key == "test-key"
+```
+
+## FastAPI Integration
+
+Optional FastAPI integration pattern:
+
+```python
+# src/package_name/fastapi.py
+"""FastAPI integration - only import if FastAPI is installed."""
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from fastapi import FastAPI
+
+from package_name.config import get_config
+
+
+def init_app(app: "FastAPI") -> None:
+ """Initialize package with FastAPI app."""
+ config = get_config()
+
+ @app.on_event("startup")
+ async def startup() -> None:
+ # Initialize connections, caches, etc.
+ pass
+
+ @app.on_event("shutdown")
+ async def shutdown() -> None:
+ # Cleanup resources
+ pass
+
+
+# Usage in FastAPI app:
+# from package_name.fastapi import init_app
+# init_app(app)
+```
+
+## Anti-Patterns to Avoid
+
+- `__getattr__` magic (use explicit imports)
+- Global mutable state (use configuration objects)
+- `*` imports in `__init__.py` (explicit `__all__`)
+- Many runtime dependencies
+- Committing `.venv/` or `__pycache__/`
+- Not including `py.typed` marker
+- Using `setup.py` (use `pyproject.toml`)
+- Mixing src layout and flat layout
+- `print()` for debugging (use logging)
+- Bare `except:` clauses
+
+## Reference Files
+
+For deeper patterns, see:
+- **[references/package-structure.md](./references/package-structure.md)** - Directory layouts, module organization
+- **[references/pyproject-config.md](./references/pyproject-config.md)** - Complete pyproject.toml examples
+- **[references/testing-patterns.md](./references/testing-patterns.md)** - pytest patterns, fixtures, CI setup
+- **[references/type-hints.md](./references/type-hints.md)** - Modern typing patterns
+- **[references/fastapi-integration.md](./references/fastapi-integration.md)** - FastAPI/Pydantic integration
+- **[references/publishing.md](./references/publishing.md)** - PyPI publishing, CI/CD
+- **[references/resources.md](./references/resources.md)** - Links to exemplary Python packages