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,622 @@
1
+ # Ruby Conventions
2
+
3
+ Idiomatic Ruby patterns for well-structured, maintainable gems.
4
+
5
+ ## File Organization
6
+
7
+ ### Directory Structure
8
+
9
+ Organize by concern with clear architectural layers:
10
+
11
+ ```
12
+ lib/
13
+ ├── gem_name.rb # Entry point, requires everything
14
+ ├── gem_name/
15
+ │ ├── version.rb # Version constant only
16
+ │ ├── error.rb # Error hierarchy
17
+ │ ├── cli/ # CLI layer (orchestration)
18
+ │ │ ├── base.rb
19
+ │ │ └── feature.rb
20
+ │ ├── commands/ # Command builders (what to execute)
21
+ │ │ ├── base.rb
22
+ │ │ └── feature.rb
23
+ │ ├── configuration/ # Config loading and validation
24
+ │ │ ├── validation.rb
25
+ │ │ └── validator/
26
+ │ └── utils.rb # Utility module
27
+ ```
28
+
29
+ ### Three-Tier Architecture
30
+
31
+ Separate concerns into distinct layers:
32
+
33
+ | Layer | Purpose | Example |
34
+ |-------|---------|---------|
35
+ | CLI | Orchestrates operations, user interaction | `Cli::App#deploy` |
36
+ | Commands | Builds command arrays, no execution | `Commands::App#run` |
37
+ | Configuration | Loads, validates, provides config | `Configuration::Role` |
38
+
39
+ ### Entry Point Pattern
40
+
41
+ The main file loads dependencies and defines the top-level interface:
42
+
43
+ ```ruby
44
+ # lib/gem_name.rb
45
+ require_relative "gem_name/version"
46
+ require_relative "gem_name/error"
47
+ require_relative "gem_name/configuration"
48
+ # ... other requires
49
+
50
+ module GemName
51
+ class Error < StandardError; end
52
+ class ConfigurationError < Error; end
53
+
54
+ class << self
55
+ def query(prompt, **options)
56
+ # Convenience method for common use case
57
+ end
58
+ end
59
+ end
60
+ ```
61
+
62
+ ## Module & Class Patterns
63
+
64
+ ### Mixins over Deep Inheritance
65
+
66
+ Compose behavior using modules:
67
+
68
+ ```ruby
69
+ class App < Base
70
+ include Assets, Containers, Logging, Proxy
71
+ end
72
+ ```
73
+
74
+ ### ActiveSupport::Concern
75
+
76
+ Structure mixins with clear class/instance separation:
77
+
78
+ ```ruby
79
+ module Validation
80
+ extend ActiveSupport::Concern
81
+
82
+ class_methods do
83
+ def validation_doc
84
+ @validation_doc ||= load_validation_doc
85
+ end
86
+ end
87
+
88
+ def validate!(config, context:, with: Validator)
89
+ with.new(config, context: context).validate!
90
+ end
91
+ end
92
+ ```
93
+
94
+ ### Delegation
95
+
96
+ Reduce boilerplate with `delegate`:
97
+
98
+ ```ruby
99
+ class Configuration
100
+ delegate :service, :hooks_path, to: :raw_config, allow_nil: true
101
+ delegate :escape_shell_value, :argumentize, to: Utils
102
+ end
103
+
104
+ class Boot
105
+ delegate :execute, :capture_with_info, to: :sshkit
106
+ delegate :assets?, :running_proxy?, to: :role
107
+ end
108
+ ```
109
+
110
+ ### Factory Methods
111
+
112
+ Prefer factory class methods over complex constructors:
113
+
114
+ ```ruby
115
+ class << self
116
+ def create_from(file:, version: nil)
117
+ new(load_file(file), version: version)
118
+ end
119
+
120
+ private
121
+
122
+ def load_file(path)
123
+ # ...
124
+ end
125
+ end
126
+ ```
127
+
128
+ ### Inquiry Objects
129
+
130
+ Use ActiveSupport's `inquiry` for readable predicates:
131
+
132
+ ```ruby
133
+ def initialize(name, config:)
134
+ @name = name.inquiry
135
+ @config = config
136
+ end
137
+
138
+ # Later:
139
+ role.name.web? # => true if name is "web"
140
+ role.name.worker? # => true if name is "worker"
141
+ ```
142
+
143
+ ## Error Handling
144
+
145
+ ### Simple Hierarchy
146
+
147
+ Inherit directly from `StandardError` with clear namespacing:
148
+
149
+ ```ruby
150
+ module GemName
151
+ class Error < StandardError; end
152
+ class ConfigurationError < Error; end
153
+ class ProcessError < Error; end
154
+
155
+ module Cli
156
+ class BootError < StandardError; end
157
+ class LockError < StandardError; end
158
+ end
159
+ end
160
+ ```
161
+
162
+ ### Context in Errors
163
+
164
+ Include relevant context as attributes:
165
+
166
+ ```ruby
167
+ class ProcessError < Error
168
+ attr_reader :exit_code, :stderr
169
+
170
+ def initialize(message, exit_code: nil, stderr: nil)
171
+ @exit_code = exit_code
172
+ @stderr = stderr
173
+ super(message)
174
+ end
175
+ end
176
+ ```
177
+
178
+ ## Method Organization
179
+
180
+ ### Public/Private Separation
181
+
182
+ Keep implementation details private. Use Rails-style indentation under `private`:
183
+
184
+ ```ruby
185
+ class Command
186
+ # Public interface
187
+ def execute(host:)
188
+ build_command(host).tap { |cmd| validate!(cmd) }
189
+ end
190
+
191
+ private
192
+ def build_command(host)
193
+ # Implementation detail
194
+ end
195
+
196
+ def validate!(cmd)
197
+ # Implementation detail
198
+ end
199
+ end
200
+ ```
201
+
202
+ ### Group Related Helpers
203
+
204
+ ```ruby
205
+ private
206
+ # --- Command Building ---
207
+
208
+ def combine(*commands, by: "&&")
209
+ commands.compact.flatten.join(" #{by} ")
210
+ end
211
+
212
+ def chain(*commands)
213
+ combine(*commands, by: ";")
214
+ end
215
+
216
+ def pipe(*commands)
217
+ combine(*commands, by: "|")
218
+ end
219
+
220
+ # --- Validation ---
221
+
222
+ def valid_name?(name)
223
+ name.match?(/\A[a-z0-9_-]+\z/)
224
+ end
225
+ ```
226
+
227
+ ### Naming Conventions
228
+
229
+ | Element | Convention | Example |
230
+ |--------------|----------------------------|-------------------------------------------|
231
+ | Predicates | `?` suffix | `valid?`, `running?`, `configured?` |
232
+ | Mutators | `!` suffix | `validate!`, `reset!`, `configure!` |
233
+ | Setters | `=` suffix | `name=`, `options=` |
234
+ | Converters | `to_*` prefix | `to_h`, `to_json`, `to_cli_args` |
235
+ | Builders | `build_*` prefix | `build_command`, `build_options` |
236
+ | Initializers | `initialize_*` prefix | `initialize_env`, `initialize_proxy` |
237
+ | Validators | `ensure_*` or `validate_*` | `ensure_required_keys`, `validate_config` |
238
+
239
+ ## Configuration & Validation
240
+
241
+ ### Validate at Construction
242
+
243
+ Don't defer validation:
244
+
245
+ ```ruby
246
+ def initialize(name, config:)
247
+ @name = name.inquiry
248
+ @config = config
249
+
250
+ validate! \
251
+ config,
252
+ context: "accessories/#{name}",
253
+ with: Validator::Accessory
254
+
255
+ @env = initialize_env
256
+ @proxy = initialize_proxy if running_proxy?
257
+ end
258
+ ```
259
+
260
+ ### Validator Classes
261
+
262
+ Dedicated validator with context tracking:
263
+
264
+ ```ruby
265
+ class Validator
266
+ attr_reader :config, :example, :context
267
+
268
+ def initialize(config, example:, context:)
269
+ @config = config
270
+ @example = example
271
+ @context = context
272
+ end
273
+
274
+ def validate!
275
+ ensure_required_keys_present
276
+ validate_against_example
277
+ end
278
+
279
+ private
280
+ def with_context(key)
281
+ "#{context}/#{key}"
282
+ end
283
+
284
+ def error(message)
285
+ raise ConfigurationError, "[#{context}] #{message}"
286
+ end
287
+ end
288
+ ```
289
+
290
+ ### Validation Mixin
291
+
292
+ Reusable validation behavior:
293
+
294
+ ```ruby
295
+ module Validation
296
+ extend ActiveSupport::Concern
297
+
298
+ class_methods do
299
+ def validation_config_key
300
+ name.demodulize.underscore
301
+ end
302
+ end
303
+
304
+ def validate!(config, context: nil, with: Validator)
305
+ context ||= self.class.validation_config_key
306
+ with.new(config, example: validation_example, context: context).validate!
307
+ end
308
+ end
309
+
310
+ class Accessory
311
+ include Validation
312
+
313
+ def initialize(name, config:)
314
+ @name = name
315
+ validate!(config, context: "accessories/#{name}")
316
+ end
317
+ end
318
+ ```
319
+
320
+ ### Lazy Initialization
321
+
322
+ Use `||=` for computed properties:
323
+
324
+ ```ruby
325
+ def tags
326
+ @tags ||= raw_config.fetch("tags", []).map { |t| Tag.new(t) }
327
+ end
328
+
329
+ def env_tags
330
+ @env_tags ||= if (tags = raw_config.env["tags"])
331
+ tags.map { |name, config| Env::Tag.new(name, config: config) }
332
+ else
333
+ []
334
+ end
335
+ end
336
+ ```
337
+
338
+ ## Command Pattern
339
+
340
+ ### Return Data, Don't Execute
341
+
342
+ Build command representations rather than executing directly:
343
+
344
+ ```ruby
345
+ def run(hostname: nil)
346
+ docker :run,
347
+ "--detach",
348
+ "--restart unless-stopped",
349
+ "--name", container_name,
350
+ *env_args(host),
351
+ image,
352
+ cmd
353
+ end
354
+
355
+ # Returns: [:docker, :run, "--detach", ..., "image:tag", "cmd"]
356
+ ```
357
+
358
+ This enables:
359
+ - Testing without execution
360
+ - Command composition
361
+ - Auditing what will run
362
+ - SSH tunneling via execution layer
363
+
364
+ ### Private Helpers for Command Building
365
+
366
+ ```ruby
367
+ private
368
+ def combine(*commands, by: "&&")
369
+ commands
370
+ .compact
371
+ .collect { |command| Array(command) + [by] }.flatten
372
+ .tap { |commands| commands.pop }
373
+ end
374
+
375
+ def docker(*args)
376
+ args.compact.unshift(:docker)
377
+ end
378
+
379
+ def pipe(*commands)
380
+ combine(*commands, by: "|")
381
+ end
382
+
383
+ def chain(*commands)
384
+ combine(*commands, by: ";")
385
+ end
386
+ ```
387
+
388
+ ## Utility Modules
389
+
390
+ ### Extend Self Pattern
391
+
392
+ ```ruby
393
+ module Utils
394
+ extend self
395
+
396
+ def escape_shell_value(value)
397
+ Shellwords.escape(value.to_s)
398
+ end
399
+
400
+ def argumentize(argument, attributes, sensitive: false)
401
+ Array(attributes).flat_map do |key, value|
402
+ [argument, sensitive ? Sensitive.new(value) : value]
403
+ end
404
+ end
405
+
406
+ def filter_specific_items(filters, items)
407
+ filters.empty? ? items : items.select { |item| filters.include?(item) }
408
+ end
409
+ end
410
+
411
+ # Usage: Utils.escape_shell_value(val)
412
+ # Or via delegation: delegate :escape_shell_value, to: Utils
413
+ ```
414
+
415
+ ## Testing Patterns
416
+
417
+ ### Test Framework
418
+
419
+ Use Minitest with ActiveSupport::TestCase:
420
+
421
+ ```ruby
422
+ class ConfigurationTest < ActiveSupport::TestCase
423
+ setup do
424
+ @deploy = { service: "app", image: "app:latest" }
425
+ @config = Configuration.new(@deploy)
426
+ end
427
+
428
+ teardown do
429
+ # Clean up
430
+ end
431
+
432
+ test "service name valid" do
433
+ assert_nothing_raised do
434
+ Configuration.new(@deploy.merge(service: "valid-name"))
435
+ end
436
+ end
437
+
438
+ test "raises on invalid service name" do
439
+ assert_raises(ConfigurationError) do
440
+ Configuration.new(@deploy.merge(service: "INVALID"))
441
+ end
442
+ end
443
+ end
444
+ ```
445
+
446
+ ### Custom Test Base Classes
447
+
448
+ Share setup across tests:
449
+
450
+ ```ruby
451
+ class CliTestCase < ActiveSupport::TestCase
452
+ setup do
453
+ @original_env = ENV.to_h
454
+ ENV["VERSION"] = "test"
455
+ end
456
+
457
+ teardown do
458
+ ENV.replace(@original_env)
459
+ end
460
+
461
+ private
462
+ def stub_setup
463
+ # Shared test helpers
464
+ end
465
+ end
466
+
467
+ class CommandsTestCase < ActiveSupport::TestCase
468
+ setup do
469
+ @config = Configuration.new(default_config)
470
+ end
471
+
472
+ private
473
+ def default_config
474
+ { service: "app", image: "app:latest" }
475
+ end
476
+ end
477
+ ```
478
+
479
+ ### Mocking with Mocha
480
+
481
+ ```ruby
482
+ require "mocha/minitest"
483
+
484
+ class ClientTest < ActiveSupport::TestCase
485
+ test "spawns subprocess with correct args" do
486
+ Process.expects(:spawn).with(
487
+ "claude", "--print", "--output-format", "json",
488
+ has_entries(chdir: "/tmp")
489
+ ).returns(123)
490
+
491
+ client.start
492
+ end
493
+
494
+ test "handles timeout" do
495
+ transport = Transport.new
496
+ transport.stubs(:read_line).raises(Timeout::Error)
497
+
498
+ assert_raises(TimeoutError) { transport.receive }
499
+ end
500
+ end
501
+ ```
502
+
503
+ ### Stubbing Methods
504
+
505
+ ```ruby
506
+ test "uses custom model when specified" do
507
+ options = Options.new(model: "opus")
508
+ options.stubs(:validate!).returns(true)
509
+
510
+ assert_equal "opus", options.model
511
+ end
512
+
513
+ test "retries on connection failure" do
514
+ sequence = sequence("retry")
515
+
516
+ client.expects(:connect).in_sequence(sequence).raises(ConnectionError)
517
+ client.expects(:connect).in_sequence(sequence).returns(true)
518
+
519
+ client.connect_with_retry
520
+ end
521
+ ```
522
+
523
+ ## Code Style
524
+
525
+ ### Frozen String Literals
526
+
527
+ Every file starts with:
528
+
529
+ ```ruby
530
+ # frozen_string_literal: true
531
+ ```
532
+
533
+ ### Hash Formatting
534
+
535
+ ```ruby
536
+ # Single line for few keys
537
+ { name: "value", type: :string }
538
+
539
+ # Multi-line for many keys or nested structures
540
+ {
541
+ name: "value",
542
+ type: :string,
543
+ options: {
544
+ required: true,
545
+ default: nil
546
+ }
547
+ }
548
+ ```
549
+
550
+ ### Method Chaining
551
+
552
+ ```ruby
553
+ # Fluent interfaces return self
554
+ def configure(key, value)
555
+ @config[key] = value
556
+ self
557
+ end
558
+
559
+ # Usage
560
+ builder.configure(:timeout, 30).configure(:retries, 3)
561
+ ```
562
+
563
+ ### Guard Clauses
564
+
565
+ Prefer early returns:
566
+
567
+ ```ruby
568
+ def process(input)
569
+ return if input.nil?
570
+ return default_value if input.empty?
571
+
572
+ # Main logic
573
+ end
574
+ ```
575
+
576
+ ### Line Continuation
577
+
578
+ Use backslash for method calls spanning lines:
579
+
580
+ ```ruby
581
+ validate! \
582
+ config,
583
+ example: validation_yml["accessories"]["mysql"],
584
+ context: "accessories/#{name}",
585
+ with: Validator::Accessory
586
+ ```
587
+
588
+ ## Advanced Patterns
589
+
590
+ ### Global Coordinator (Use Sparingly)
591
+
592
+ For CLI applications that need shared state:
593
+
594
+ ```ruby
595
+ # lib/gem_name/cli.rb
596
+ COORDINATOR = Coordinator.new
597
+
598
+ class Cli::Base
599
+ def initialize(*)
600
+ super
601
+ initialize_coordinator unless COORDINATOR.configured?
602
+ end
603
+ end
604
+ ```
605
+
606
+ ### Factory Methods in Coordinator
607
+
608
+ ```ruby
609
+ class Coordinator
610
+ def app(role: nil, host: nil)
611
+ Commands::App.new(config, role: role, host: host)
612
+ end
613
+
614
+ def builder
615
+ @builder ||= Commands::Builder.new(config)
616
+ end
617
+
618
+ def configured?
619
+ @config.present?
620
+ end
621
+ end
622
+ ```
@@ -0,0 +1,86 @@
1
+ # Git Etiquette
2
+
3
+ ## Commit Messages
4
+
5
+ - Never reference Claude, Anthropic, or AI agents in commit messages
6
+ - Always create atomic commits that do not leave the app in a broken state
7
+
8
+ ## Conventional Commits Format
9
+
10
+ ```
11
+ <type>[optional scope]: <description>
12
+
13
+ [optional body]
14
+
15
+ [optional footer(s)]
16
+ ```
17
+
18
+ ## Commit Types
19
+
20
+ | Label | Purpose |
21
+ |-------------|------------------------------------------------------------------|
22
+ | `feat:` | New feature for the user (MINOR version bump) |
23
+ | `fix:` | Bug fix for the user (PATCH version bump) |
24
+ | `docs:` | Documentation changes |
25
+ | `style:` | Formatting, missing semicolons, etc. (no production code change) |
26
+ | `refactor:` | Refactoring production code (e.g., renaming a variable) |
27
+ | `perf:` | Performance improvements |
28
+ | `test:` | Adding/refactoring tests (no production code change) |
29
+ | `build:` | Build system or external dependency changes |
30
+ | `ci:` | CI configuration changes |
31
+ | `chore:` | Other tasks (no production code change) |
32
+
33
+ ## Scope (Optional)
34
+
35
+ Use scope to specify what area of the codebase is affected:
36
+
37
+ ```bash
38
+ feat(client): add session resumption support
39
+ fix(transport): handle CLI process timeout
40
+ refactor(mcp): extract schema normalization to module
41
+ ```
42
+
43
+ ## Breaking Changes
44
+
45
+ Indicate breaking changes using either method:
46
+
47
+ ```bash
48
+ # Method 1: Add ! before the colon
49
+ feat!: change Options class to use keyword arguments
50
+ feat(hooks)!: rename callback parameter to handler
51
+
52
+ # Method 2: BREAKING CHANGE footer
53
+ feat: restructure message content blocks
54
+
55
+ BREAKING CHANGE: ToolUseBlock now returns input as Hash instead of JSON string
56
+ ```
57
+
58
+ ## Multi-line Commits
59
+
60
+ For complex changes, add a body and/or footer:
61
+
62
+ ```bash
63
+ fix(protocol): resolve nil error in message parsing
64
+
65
+ The parser was failing when content blocks had no text field.
66
+ Added nil check and default to empty string.
67
+
68
+ Fixes: #42
69
+ ```
70
+
71
+ ## Examples
72
+
73
+ ```bash
74
+ feat: add streaming support to Query interface
75
+ feat(client): add session resumption support
76
+ fix: resolve timeout error in subprocess transport
77
+ fix(mcp)!: change tool schema format
78
+ docs: update README with hook examples
79
+ style: fix indentation in control_protocol.rb
80
+ refactor: extract JSON parsing to MessageParser
81
+ perf: reduce memory allocation in message streaming
82
+ test: add specs for permission callbacks
83
+ build: bump minimum Ruby version to 3.2
84
+ ci: add integration test workflow
85
+ chore: update rubocop configuration
86
+ ```
@@ -0,0 +1,31 @@
1
+ # Pull Request Guidelines
2
+
3
+ ## Description Format
4
+
5
+ Use the What/Why/How structure:
6
+
7
+ ```markdown
8
+ ## What
9
+ Brief description of the changes (1-2 sentences).
10
+
11
+ ## Why
12
+ Business or engineering goal this addresses. Link to GitHub issue if applicable.
13
+
14
+ ## How
15
+ Significant implementation decisions or trade-offs (omit for simple changes).
16
+ ```
17
+
18
+ ## Writing Guidelines
19
+
20
+ - **Be explicit** - Don't just say "#123", explain what was fixed
21
+ - **Why > What** - Reviewers can read the code; explain the reasoning
22
+ - **Keep it scannable** - Use bullet points over paragraphs
23
+ - **Link issues after context** - Reference GitHub issues, but explain first
24
+
25
+ ## Pre-flight Criteria
26
+
27
+ Before creating a PR, verify:
28
+
29
+ 1. All changes are committed (no uncommitted work)
30
+ 2. Branch is pushed to remote with `-u` flag
31
+ 3. PR leaves the app in a stable state (not broken)