Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 66 additions & 16 deletions .claude/rules/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -587,36 +587,86 @@ validate! \

## Advanced Patterns

### Global Coordinator (Use Sparingly)
### Stripe-Style Global Configuration

For CLI applications that need shared state:
Module-level config with `Forwardable` delegators for common fields. A `Configuration`
object holds defaults; `to_options(**overrides)` merges per-request overrides and returns
the internal `Options` type.

```ruby
# lib/gem_name/cli.rb
COORDINATOR = Coordinator.new
module GemName
require "forwardable"

@config = Configuration.setup

class << self
extend Forwardable
attr_reader :config

def_delegators :@config, :model, :model=,
:max_turns, :max_turns=

class Cli::Base
def initialize(*)
super
initialize_coordinator unless COORDINATOR.configured?
def configure = yield(@config)
def reset_config! = @config = Configuration.setup
end
end

# Usage:
GemName.model = "opus"
GemName.configure { |c| c.max_turns = 10 }
```

### Factory Methods in Coordinator
### DSL Builder Pattern

Declarative builders that compile to the format consumed by internal plumbing.
The builder accumulates rules/matchers, then `to_*` produces the final artifact.

```ruby
class Coordinator
def app(role: nil, host: nil)
Commands::App.new(config, role: role, host: host)
class PermissionPolicy
def initialize(&block)
@rules = []
yield self if block_given?
end

def builder
@builder ||= Commands::Builder.new(config)
def allow(*names)
names.each { |n| @rules << { name: n, action: :allow } }
self
end

def configured?
@config.present?
def to_can_use_tool
rules = @rules.dup.freeze
->(tool_name, input, ctx) { ... }
end
end
```

Key conventions:
- Constructor takes `&block` and yields `self`
- Methods return `self` for chaining
- `to_*` method compiles to the internal format
- `empty?` predicate for skipping when unconfigured

### Prepending Modules into Data.define Types

To add shared behavior (like `deconstruct_keys` overrides) to `Data.define` classes,
use `prepend` not `include`. Data.define generates methods on the class itself, so
`include` would be shadowed. When overriding `deconstruct_keys`, filter virtual keys
out before calling `super` — Data's implementation stops early on unknown member keys.

```ruby
module Message
def deconstruct_keys(keys)
if keys.nil?
{ type: type }.merge(super)
elsif keys.include?(:type)
member_keys = keys - [ :type ]
base = member_keys.empty? ? {} : super(member_keys)
{ type: type }.merge(base)
else
super
end
end
end

MESSAGE_TYPES.each { |klass| klass.prepend(Message) }
```
28 changes: 24 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
# ClaudeAgent Ruby SDK

Ruby SDK for building autonomous AI agents that interact with Claude Code CLI. See [README.md](README.md) for API usage, message types, configuration, and examples.
Ruby SDK for building autonomous AI agents that interact with Claude Code CLI. See [README.md](README.md) for a quick start, and the [docs/](docs/) folder for full guides:

| Guide | What's in it |
|--------------------------------------------|------------------------------------------------------|
| [Getting Started](docs/getting-started.md) | Install, first queries, multi-turn basics |
| [Configuration](docs/configuration.md) | Global config, Options, sandbox, agents |
| [Conversations](docs/conversations.md) | Multi-turn API, TurnResult, callbacks, tool tracking |
| [Queries](docs/queries.md) | One-shot: ask, query_turn, query |
| [Permissions](docs/permissions.md) | PermissionPolicy DSL, can_use_tool, queue |
| [Hooks](docs/hooks.md) | HookRegistry DSL, hook events, input types |
| [MCP Tools](docs/mcp.md) | In-process tools, servers, schemas |
| [Events](docs/events.md) | EventHandler, typed callbacks |
| [Messages](docs/messages.md) | 22 message types, 8 content blocks, pattern matching |
| [Sessions](docs/sessions.md) | Session discovery, mutations, forking |
| [Client](docs/client.md) | Low-level bidirectional API |
| [Errors](docs/errors.md) | Error hierarchy |
| [Logging](docs/logging.md) | Debug logging, log levels |
| [Architecture](docs/architecture.md) | Internal design, data flow |

## Stack

Expand Down Expand Up @@ -36,11 +53,14 @@ bin/release VERSION # Release gem (e.g., bin/release 1.2.0)

## Conventions

- **Immutable data types**: All messages and options use `Data.define`
- **Immutable data types**: All messages and content blocks use `Data.define`, frozen at construction
- **Frozen string literals**: Every file starts with `# frozen_string_literal: true`
- **Message polymorphism**: Use `case` statements or `is_a?()` for content block types
- **Message module**: All message/block types include `ClaudeAgent::Message` (text_content, pattern matching)
- **Stripe-style config**: `ClaudeAgent.model = "opus"`, `ClaudeAgent.configure { |c| ... }`, `Configuration#to_options`
- **Convenience entry points**: `ClaudeAgent.ask(prompt)` (one-shot), `ClaudeAgent.chat { |c| ... }` (multi-turn)
- **DSL builders**: `PermissionPolicy` and `HookRegistry` compile to lambdas/hashes consumed by Options
- **Error hierarchy**: All errors inherit from `ClaudeAgent::Error` with context (exit code, stderr, etc.)
- **Protocol flow**: Transport → ControlProtocol → MessageParser → typed message objects
- **Protocol flow**: Configuration → Options → Transport → ControlProtocol → MessageParser → typed messages

## Testing Notes

Expand Down
Loading
Loading