claude_agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,177 @@
1
+ # Release Conventions
2
+
3
+ Guidelines for versioning and releasing the claude_agent gem.
4
+
5
+ ## Semantic Versioning
6
+
7
+ Follow [Semantic Versioning 2.0.0](https://semver.org/):
8
+
9
+ | Version Part | When to Increment | Example |
10
+ |--------------|------------------------------------|-------------------|
11
+ | **MAJOR** | Breaking API changes | `1.0.0` → `2.0.0` |
12
+ | **MINOR** | New features (backward compatible) | `1.0.0` → `1.1.0` |
13
+ | **PATCH** | Bug fixes (backward compatible) | `1.0.0` → `1.0.1` |
14
+
15
+ ### Pre-release Versions
16
+
17
+ For beta/alpha releases, append a pre-release identifier:
18
+
19
+ ```
20
+ 1.0.0-alpha.1
21
+ 1.0.0-beta.1
22
+ 1.0.0-rc.1
23
+ ```
24
+
25
+ ## Changelog Format
26
+
27
+ Follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format:
28
+
29
+ ```markdown
30
+ ## [Unreleased]
31
+
32
+ ## [1.2.0] - 2025-03-15
33
+
34
+ ### Added
35
+ - New feature description
36
+
37
+ ### Changed
38
+ - Modified behavior description
39
+
40
+ ### Deprecated
41
+ - Feature scheduled for removal
42
+
43
+ ### Removed
44
+ - Deleted feature description
45
+
46
+ ### Fixed
47
+ - Bug fix description
48
+
49
+ ### Security
50
+ - Security patch description
51
+ ```
52
+
53
+ ### Changelog Guidelines
54
+
55
+ 1. **Maintain [Unreleased]** - Always keep an Unreleased section at the top
56
+ 2. **Add entries as you work** - Don't wait until release time
57
+ 3. **User-focused language** - Write for gem users, not developers
58
+ 4. **Link to issues/PRs** - Reference GitHub issues when relevant
59
+ 5. **Newest first** - Most recent version at top
60
+
61
+ ### What to Include
62
+
63
+ | Include | Exclude |
64
+ |---------|---------|
65
+ | API additions/changes | Internal refactors |
66
+ | Bug fixes users might hit | Code style changes |
67
+ | Deprecation notices | Test-only changes |
68
+ | Breaking changes (prominent) | Documentation typos |
69
+ | Security fixes | Dependency updates (minor) |
70
+
71
+ ## Release Process
72
+
73
+ ### Prerequisites
74
+
75
+ Before releasing:
76
+
77
+ 1. All tests pass (`bundle exec rake`)
78
+ 2. CHANGELOG.md has entry for new version
79
+ 3. No uncommitted changes
80
+ 4. On `main` branch (or confirm if not)
81
+
82
+ ### Release Command
83
+
84
+ ```bash
85
+ bin/release VERSION
86
+ ```
87
+
88
+ Example:
89
+ ```bash
90
+ bin/release 1.2.0
91
+ ```
92
+
93
+ ### What the Script Does
94
+
95
+ 1. Validates version format (semantic versioning)
96
+ 2. Checks CHANGELOG.md has entry for version
97
+ 3. Updates `lib/claude_agent/version.rb`
98
+ 4. Updates `Gemfile.lock`
99
+ 5. Commits with message "Release vX.Y.Z"
100
+ 6. Creates annotated tag `vX.Y.Z`
101
+ 7. Pushes commit and tag to remote
102
+ 8. Builds and publishes gem to RubyGems
103
+
104
+ ### Post-Release
105
+
106
+ After running `bin/release`:
107
+
108
+ 1. Create GitHub release at the new tag
109
+ 2. Add `## [Unreleased]` section to CHANGELOG.md
110
+
111
+ ## Version Bumping Guidelines
112
+
113
+ ### When to Bump MAJOR (Breaking)
114
+
115
+ - Removing public methods/classes
116
+ - Changing method signatures (required params)
117
+ - Changing return types
118
+ - Renaming public constants
119
+ - Dropping Ruby version support
120
+
121
+ ### When to Bump MINOR (Feature)
122
+
123
+ - Adding new public methods/classes
124
+ - Adding optional parameters
125
+ - New configuration options
126
+ - New message types or content blocks
127
+
128
+ ### When to Bump PATCH (Fix)
129
+
130
+ - Bug fixes
131
+ - Documentation corrections
132
+ - Performance improvements (no API change)
133
+ - Internal refactoring (no API change)
134
+
135
+ ## Example Release Workflow
136
+
137
+ ```bash
138
+ # 1. Ensure tests pass
139
+ bundle exec rake
140
+
141
+ # 2. Update CHANGELOG.md
142
+ # Add entry under [Unreleased], then rename to version:
143
+ ## [1.2.0] - 2025-03-15
144
+
145
+ ### Added
146
+ - New `Client#foo` method for bar functionality
147
+
148
+ ### Fixed
149
+ - Resolved timeout issue in subprocess transport
150
+
151
+ # 3. Commit changelog
152
+ git add CHANGELOG.md
153
+ git commit -m "docs: update changelog for 1.2.0"
154
+
155
+ # 4. Release
156
+ bin/release 1.2.0
157
+
158
+ # 5. Add new Unreleased section
159
+ # Edit CHANGELOG.md to add:
160
+ ## [Unreleased]
161
+
162
+ # 6. Commit
163
+ git add CHANGELOG.md
164
+ git commit -m "docs: add unreleased section"
165
+ git push
166
+ ```
167
+
168
+ ## Gem Metadata
169
+
170
+ The gemspec includes these URIs for RubyGems.org:
171
+
172
+ ```ruby
173
+ spec.metadata["source_code_uri"] = spec.homepage
174
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
175
+ ```
176
+
177
+ This enables the "Changelog" link on the RubyGems.org gem page.
@@ -0,0 +1,267 @@
1
+ # Testing Conventions
2
+
3
+ SDK-specific testing guidance. For general patterns (base classes, mocking, structure), see `conventions.md`.
4
+
5
+ ## Running Tests
6
+
7
+ ```bash
8
+ bundle exec rake test # Unit tests only
9
+ bundle exec rake test_integration # Integration tests (requires CLI v2.0.0+)
10
+ bundle exec rake test_all # All tests
11
+ bundle exec ruby -Itest test/claude_agent/test_foo.rb # Single file
12
+
13
+ # Binstubs
14
+ bin/test # Unit tests only
15
+ bin/test-integration # Integration tests
16
+ bin/test-all # All tests
17
+ ```
18
+
19
+ ## Directory Structure
20
+
21
+ ```
22
+ test/
23
+ ├── test_helper.rb # Central setup, requires, base class
24
+ ├── integration_helper.rb # Base class for integration tests
25
+ ├── support/ # Shared mocks, test transports, helpers
26
+ │ └── mock_transport.rb
27
+ ├── claude_agent/ # Unit tests (mirrors lib/claude_agent/)
28
+ │ ├── test_client.rb
29
+ │ ├── test_options.rb
30
+ │ └── mcp/
31
+ │ └── test_tool.rb
32
+ ├── integration/ # Integration tests (require Claude CLI)
33
+ │ ├── test_query.rb
34
+ │ ├── test_client.rb
35
+ │ └── ...
36
+ └── fixtures/ # JSON fixtures for parser tests
37
+ ├── assistant_message.json
38
+ └── tool_use_response.json
39
+ ```
40
+
41
+ ## File Naming
42
+
43
+ | Component | Convention |
44
+ |---------------|------------------------------------|
45
+ | Test files | `test_{module}.rb` |
46
+ | Test classes | `TestClaudeAgent{Module}` |
47
+ | Support files | `mock_{name}.rb`, `fake_{name}.rb` |
48
+
49
+ ## Extracting Test Support
50
+
51
+ Large mocks and fakes belong in `test/support/`, not inline:
52
+
53
+ ```ruby
54
+ # test/support/mock_transport.rb
55
+ class MockTransport < ClaudeAgent::Transport::Base
56
+ attr_reader :written_messages
57
+
58
+ def initialize(responses: [])
59
+ super()
60
+ @responses = responses
61
+ @written_messages = []
62
+ end
63
+
64
+ def write(data)
65
+ @written_messages << JSON.parse(data)
66
+ end
67
+
68
+ def read_messages
69
+ @responses.each { |r| yield r }
70
+ end
71
+
72
+ # ... minimal interface implementation
73
+ end
74
+ ```
75
+
76
+ ```ruby
77
+ # test/test_helper.rb
78
+ require "claude_agent"
79
+ require "minitest/autorun"
80
+ require "mocha/minitest"
81
+
82
+ Dir[File.join(__dir__, "support", "**", "*.rb")].each { |f| require f }
83
+ ```
84
+
85
+ ## Testing Data.define Types
86
+
87
+ This SDK uses `Data.define` for immutable message types:
88
+
89
+ ```ruby
90
+ test "data type attributes" do
91
+ block = ClaudeAgent::TextBlock.new(text: "hello")
92
+
93
+ assert_equal "hello", block.text
94
+ assert_equal :text, block.type
95
+ assert block.frozen?
96
+ assert_equal({ type: "text", text: "hello" }, block.to_h)
97
+ end
98
+
99
+ test "optional fields default to nil" do
100
+ block = ClaudeAgent::ToolResultBlock.new(tool_use_id: "123")
101
+ assert_nil block.content
102
+ assert_nil block.is_error
103
+ end
104
+ ```
105
+
106
+ ## Testing String and Symbol Keys
107
+
108
+ JSON parses to string keys, Ruby prefers symbols. Always test both:
109
+
110
+ ```ruby
111
+ test "accepts symbol keys" do
112
+ block = ClaudeAgent::ImageContentBlock.new(
113
+ source: { type: "base64", media_type: "image/png", data: "..." }
114
+ )
115
+ assert_equal "base64", block.source_type
116
+ end
117
+
118
+ test "accepts string keys" do
119
+ block = ClaudeAgent::ImageContentBlock.new(
120
+ source: { "type" => "base64", "media_type" => "image/png", "data" => "..." }
121
+ )
122
+ assert_equal "base64", block.source_type
123
+ end
124
+ ```
125
+
126
+ ## Fixtures vs Inline Data
127
+
128
+ **Use fixtures** for:
129
+ - Complex JSON structures reused across tests
130
+ - Large response payloads
131
+ - Realistic CLI output samples
132
+
133
+ **Use inline data** for:
134
+ - Simple, single-use test cases
135
+ - When the data structure IS the test (e.g., edge cases)
136
+
137
+ ```ruby
138
+ # test/fixtures/assistant_with_tool_use.json
139
+ {
140
+ "type": "assistant",
141
+ "message": {
142
+ "model": "claude",
143
+ "content": [
144
+ { "type": "text", "text": "Let me read that" },
145
+ { "type": "tool_use", "id": "tool_123", "name": "Read", "input": {} }
146
+ ]
147
+ }
148
+ }
149
+ ```
150
+
151
+ ```ruby
152
+ # Usage
153
+ test "parses complex message" do
154
+ data = json_fixture("assistant_with_tool_use")
155
+ message = parser.parse(data)
156
+
157
+ assert message.has_tool_use?
158
+ end
159
+ ```
160
+
161
+ ## Testing CLI Interaction
162
+
163
+ ### Prefer Mocha over manual mocks
164
+
165
+ ```ruby
166
+ test "spawns subprocess with correct args" do
167
+ Process.expects(:spawn).with(
168
+ "claude", "--print", "--output-format", "json",
169
+ has_entries(chdir: Dir.pwd)
170
+ ).returns(123)
171
+
172
+ transport.start
173
+ end
174
+ ```
175
+
176
+ ### Use MockTransport for Client tests
177
+
178
+ ```ruby
179
+ test "sends user message" do
180
+ transport = MockTransport.new(responses: [result_message])
181
+ client = ClaudeAgent::Client.new(transport: transport)
182
+ client.connect
183
+
184
+ client.send_message("Hello")
185
+
186
+ assert_equal 1, transport.written_messages.size
187
+ assert_equal "user", transport.written_messages.first["type"]
188
+ end
189
+ ```
190
+
191
+ ### Capturing I/O
192
+
193
+ Use Minitest's built-in `capture_io`:
194
+
195
+ ```ruby
196
+ test "logs to stderr" do
197
+ _, err = capture_io { client.send_message("test") }
198
+ assert_match(/sending message/, err)
199
+ end
200
+ ```
201
+
202
+ ## Error Testing
203
+
204
+ Test error hierarchy and context fields:
205
+
206
+ ```ruby
207
+ test "error includes context" do
208
+ error = ClaudeAgent::ProcessError.new(
209
+ "CLI failed",
210
+ exit_code: 1,
211
+ stderr: "error details"
212
+ )
213
+
214
+ assert_equal 1, error.exit_code
215
+ assert_equal "error details", error.stderr
216
+ assert_match(/CLI failed/, error.message)
217
+ end
218
+
219
+ test "error inheritance" do
220
+ error = ClaudeAgent::ProcessError.new("test")
221
+ assert_kind_of ClaudeAgent::Error, error
222
+ assert_kind_of StandardError, error
223
+ end
224
+ ```
225
+
226
+ ## Integration Tests
227
+
228
+ Integration tests live in `test/integration/` and inherit from `IntegrationTestCase`:
229
+
230
+ ```ruby
231
+ # test/integration/test_query.rb
232
+ require_relative "../integration_helper"
233
+
234
+ class TestIntegrationQuery < IntegrationTestCase
235
+ test "real query returns result" do
236
+ messages = ClaudeAgent.query(prompt: "Say hello", options: test_options).to_a
237
+ result = messages.find { |m| m.is_a?(ClaudeAgent::ResultMessage) }
238
+
239
+ assert_not_nil result
240
+ assert result.success?
241
+ end
242
+ end
243
+ ```
244
+
245
+ The `IntegrationTestCase` base class:
246
+ - Skips tests unless `INTEGRATION=true` is set (automatic with `rake test_integration`)
247
+ - Skips if Claude CLI is not installed
248
+ - Provides `test_options` helper with sensible defaults
249
+
250
+ ## What to Test
251
+
252
+ | Component | Focus Areas |
253
+ |----------------|---------------------------------------------|
254
+ | Options | Default values, validation, CLI arg mapping |
255
+ | Messages | Parsing, field access, type discrimination |
256
+ | Content Blocks | All block types, to_h serialization |
257
+ | Client | Message sending, streaming, error handling |
258
+ | Transport | Process lifecycle, I/O handling |
259
+ | Errors | Hierarchy, context fields, messages |
260
+ | MCP | Tool definition, server config |
261
+
262
+ ## Test Coverage Guidelines
263
+
264
+ - High coverage on public API surface
265
+ - Don't test private methods directly—test through public interface
266
+ - Cover edge cases: nil values, empty collections, invalid input
267
+ - Test both success and failure paths
@@ -0,0 +1,49 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(bundle:*)",
5
+ "Bash(gem:*)",
6
+ "Bash(rbs:*)",
7
+ "Bash(git log:*)",
8
+ "Bash(git diff:*)",
9
+ "Bash(git show:*)",
10
+ "Bash(git status:*)",
11
+ "Bash(git rev-parse:*)",
12
+ "Bash(git ls-tree:*)",
13
+ "Bash(git branch:*)",
14
+ "Bash(git fetch:*)",
15
+ "Bash(git worktree:*)",
16
+ "Bash(git mv:*)",
17
+ "Bash(gh pr view:*)",
18
+ "Bash(gh pr diff:*)",
19
+ "Bash(gh pr list:*)",
20
+ "Bash(gh pr checks:*)",
21
+ "Bash(gh run view:*)",
22
+ "Bash(ls:*)",
23
+ "Bash(find:*)",
24
+ "Bash(grep:*)",
25
+ "Bash(ps:*)",
26
+ "Bash(lsof:*)",
27
+ "Bash(wc:*)",
28
+ "Bash(mkdir:*)",
29
+ "Bash(bin/rbs-validate:*)",
30
+ "Bash(bin/setup:*)",
31
+ "Bash(bin/test:*)",
32
+ "Bash(bin/test-integration:*)",
33
+ "Bash(bin/test-all:*)",
34
+ "Bash(bin/update-reference-sdks:*)",
35
+ "WebFetch(domain:docs.anthropic.com)",
36
+ "WebFetch(domain:docs.claude.com)"
37
+ ],
38
+ "deny": [],
39
+ "ask": [],
40
+ "additionalDirectories": [],
41
+ "defaultMode": "default"
42
+ },
43
+ "hooks": {},
44
+ "env": {},
45
+ "attribution": {
46
+ "commit": "",
47
+ "pr": ""
48
+ }
49
+ }
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-01-10
11
+
12
+ ### Added
13
+ - MVP implementation of the Claude Agent SDK for Ruby
data/CLAUDE.md ADDED
@@ -0,0 +1,94 @@
1
+ # ClaudeAgent Ruby SDK
2
+
3
+ Ruby SDK for building autonomous AI agents that interact with Claude Code CLI.
4
+
5
+ ## Stack
6
+
7
+ - **Ruby** 3.2+ (uses `Data.define` for immutable types)
8
+ - **Minitest** for testing
9
+ - **RuboCop** with `rubocop-rails-omakase` for linting
10
+ - **RBS** for type signatures (in `sig/`)
11
+ - **No external API clients** - communicates with Claude Code CLI via JSON Lines protocol
12
+
13
+ ## Workflow
14
+
15
+ ```bash
16
+ bin/setup # Install dependencies
17
+ bundle exec rake # Run unit tests + rbs + rubocop (default)
18
+ bundle exec rake test # Unit tests only
19
+ bundle exec rake test_integration # Integration tests (requires CLI v2.0.0+)
20
+ bundle exec rake test_all # All tests (requires CLI v2.0.0+)
21
+ bundle exec rake rbs # Validate RBS signatures
22
+ bundle exec rake rbs:parse # RBS syntax check only (faster)
23
+ bundle exec rake rbs:prototype # Generate RBS from lib/ (for new code)
24
+ bundle exec rubocop # Lint only
25
+ bin/console # IRB with gem loaded
26
+
27
+ # Binstubs
28
+ bin/test # Unit tests only
29
+ bin/test-integration # Integration tests
30
+ bin/test-all # All tests
31
+ bin/rbs-validate # Validate RBS signatures
32
+ bin/release VERSION # Release gem (e.g., bin/release 1.2.0)
33
+ ```
34
+
35
+ ## Architecture
36
+
37
+ | Component | Purpose |
38
+ |-----------------------------|----------------------------------------------------|
39
+ | `Query` | One-shot stateless prompts |
40
+ | `Client` | Multi-turn bidirectional conversations |
41
+ | `ControlProtocol` | Handles handshake, hooks, permissions, MCP routing |
42
+ | `Transport::Subprocess` | Spawns CLI, manages stdin/stdout |
43
+ | `MCP::Tool` / `MCP::Server` | Custom tool definitions |
44
+
45
+ ## Conventions
46
+
47
+ - **Immutable data types**: All messages and options use `Data.define`
48
+ - **Frozen string literals**: Every file starts with `# frozen_string_literal: true`
49
+ - **Message polymorphism**: Use `case` statements or `is_a?()` for content block types
50
+ - **Error hierarchy**: All errors inherit from `ClaudeAgent::Error` with context (exit code, stderr, etc.)
51
+ - **Protocol flow**: Transport → ControlProtocol → MessageParser → typed message objects
52
+
53
+ ## Key Patterns
54
+
55
+ ```ruby
56
+ # One-shot query
57
+ result = ClaudeAgent.query("prompt", model: "sonnet")
58
+
59
+ # Interactive client
60
+ client = ClaudeAgent::Client.new(options)
61
+ client.send_message("prompt") { |msg| handle(msg) }
62
+
63
+ # Content blocks are polymorphic
64
+ message.content.each do |block|
65
+ case block
66
+ when ClaudeAgent::TextBlock then ...
67
+ when ClaudeAgent::ToolUseBlock then ...
68
+ end
69
+ end
70
+ ```
71
+
72
+ ## Testing Notes
73
+
74
+ - Unit tests in `test/claude_agent/` - run without Claude CLI
75
+ - Integration tests in `test/integration/` - require Claude Code CLI v2.0.0+
76
+ - Integration tests are skipped by default; set `INTEGRATION=true` to run them
77
+ - Skip CLI version check with `CLAUDE_AGENT_SDK_SKIP_VERSION_CHECK=true`
78
+
79
+ ## Releasing
80
+
81
+ Uses [Semantic Versioning](https://semver.org/) and [Keep a Changelog](https://keepachangelog.com/) format.
82
+
83
+ ```bash
84
+ # 1. Update CHANGELOG.md with version entry
85
+ # 2. Commit the changelog
86
+ git commit -am "docs: update changelog for X.Y.Z"
87
+
88
+ # 3. Run the release script
89
+ bin/release X.Y.Z
90
+ ```
91
+
92
+ The release script validates the changelog, updates version.rb, commits, tags, and publishes to RubyGems.
93
+
94
+ See `.claude/rules/releases.md` for detailed conventions.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Thomas Carr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.