agent_c 2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +10 -0
  3. data/.ruby-version +1 -0
  4. data/CLAUDE.md +21 -0
  5. data/README.md +360 -0
  6. data/Rakefile +16 -0
  7. data/TODO.md +105 -0
  8. data/agent_c.gemspec +38 -0
  9. data/docs/batch.md +503 -0
  10. data/docs/chat-methods.md +156 -0
  11. data/docs/cost-reporting.md +86 -0
  12. data/docs/pipeline-tips-and-tricks.md +453 -0
  13. data/docs/session-configuration.md +274 -0
  14. data/docs/testing.md +747 -0
  15. data/docs/tools.md +103 -0
  16. data/docs/versioned-store.md +840 -0
  17. data/lib/agent_c/agent/chat.rb +211 -0
  18. data/lib/agent_c/agent/chat_response.rb +38 -0
  19. data/lib/agent_c/agent/chats/anthropic_bedrock.rb +48 -0
  20. data/lib/agent_c/batch.rb +102 -0
  21. data/lib/agent_c/configs/repo.rb +90 -0
  22. data/lib/agent_c/context.rb +56 -0
  23. data/lib/agent_c/costs/data.rb +39 -0
  24. data/lib/agent_c/costs/report.rb +219 -0
  25. data/lib/agent_c/db/store.rb +162 -0
  26. data/lib/agent_c/errors.rb +19 -0
  27. data/lib/agent_c/pipeline.rb +152 -0
  28. data/lib/agent_c/pipelines/agent.rb +219 -0
  29. data/lib/agent_c/processor.rb +98 -0
  30. data/lib/agent_c/prompts.yml +53 -0
  31. data/lib/agent_c/schema.rb +71 -0
  32. data/lib/agent_c/session.rb +206 -0
  33. data/lib/agent_c/store.rb +72 -0
  34. data/lib/agent_c/test_helpers.rb +173 -0
  35. data/lib/agent_c/tools/dir_glob.rb +46 -0
  36. data/lib/agent_c/tools/edit_file.rb +114 -0
  37. data/lib/agent_c/tools/file_metadata.rb +43 -0
  38. data/lib/agent_c/tools/git_status.rb +30 -0
  39. data/lib/agent_c/tools/grep.rb +119 -0
  40. data/lib/agent_c/tools/paths.rb +36 -0
  41. data/lib/agent_c/tools/read_file.rb +94 -0
  42. data/lib/agent_c/tools/run_rails_test.rb +87 -0
  43. data/lib/agent_c/tools.rb +61 -0
  44. data/lib/agent_c/utils/git.rb +87 -0
  45. data/lib/agent_c/utils/shell.rb +58 -0
  46. data/lib/agent_c/version.rb +5 -0
  47. data/lib/agent_c.rb +32 -0
  48. data/lib/versioned_store/base.rb +314 -0
  49. data/lib/versioned_store/config.rb +26 -0
  50. data/lib/versioned_store/stores/schema.rb +127 -0
  51. data/lib/versioned_store/version.rb +5 -0
  52. data/lib/versioned_store.rb +5 -0
  53. data/template/Gemfile +9 -0
  54. data/template/Gemfile.lock +152 -0
  55. data/template/README.md +61 -0
  56. data/template/Rakefile +50 -0
  57. data/template/bin/rake +27 -0
  58. data/template/lib/autoload.rb +10 -0
  59. data/template/lib/config.rb +59 -0
  60. data/template/lib/pipeline.rb +19 -0
  61. data/template/lib/prompts.yml +57 -0
  62. data/template/lib/store.rb +17 -0
  63. data/template/test/pipeline_test.rb +221 -0
  64. data/template/test/test_helper.rb +18 -0
  65. metadata +194 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d647a877e7e6660d1c9ab7f5e2ce9e00d8f932c78d9ca6a47467265cc2c62e4e
4
+ data.tar.gz: 83e0796a0545c207c5afdebd457ac3134ea0b8bd5b9cd327a02de5f2049cc02b
5
+ SHA512:
6
+ metadata.gz: a248627251111087a4814c86a5c980cdafa7936f0ae2b420b397485b367c7e0f693b2d4f6dcc6a46070c4381d2e2eaff775e31615bfdd5c0ea72e984655bec74
7
+ data.tar.gz: ce5adb048f393142b008a15943e2337597f2969a76cd8446a1890e5440ad9c73d39af74052085d916b58fbbbd551a23385db2b6285f68e359f676b5fb5fa67d4
data/.rubocop.yml ADDED
@@ -0,0 +1,10 @@
1
+ AllCops:
2
+ Exclude: [ "bin/*" ]
3
+ TargetRubyVersion: 3.2
4
+ DisabledByDefault: true
5
+
6
+ Style/FrozenStringLiteralComment:
7
+ Enabled: true
8
+
9
+ Layout/EmptyLineAfterMagicComment:
10
+ Enabled: true
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.9
data/CLAUDE.md ADDED
@@ -0,0 +1,21 @@
1
+ # Rules
2
+
3
+ - Leave modules modules if they do not need state. If they apply behaviors to other schema/classes leave them modules.
4
+ - Prefer to not store lambdas as variables unless necessary. If they are just going to be passed to other methods, leave them as blocks
5
+ - If a class is not trivial (eg, more than one method and/or more than like 30 lines) then extract it to its own file.
6
+ - This project is using Zeitwerk. You should not use require_relative, just match the module names to file path and it will load automatically.
7
+ - When you commit, use the --no-gpg-sign flag. Start commit messages with "claude: "
8
+ - DO NOT add example scripts. Either add it to the readme or make a test.
9
+ - DO NOT add documentation outside of the README
10
+ - DO NOT program defensively. If something should respond_to?() a method then just invoke the method. An error is better than a false positive
11
+ - DO NOT write a one-off script for debugging, write a test-case and run it instead.
12
+ - DO NOT edit the singleton class of an object. If you think you need to do this, ideas for avoiding: inject an object, create a module and include it, make a base class.
13
+
14
+ # TESTING
15
+
16
+ - We do not use stubbing in our test. If you need to stub something (or monkey-patch it) to test it, that thing should be injectable.
17
+ - Run tests with `bin/rake test` You can pass TESTOPTS to run a specific file.
18
+
19
+ # Style
20
+
21
+ - For multiline Strings always use a HEREDOC
data/README.md ADDED
@@ -0,0 +1,360 @@
1
+ # AgentC
2
+
3
+ A small Ruby wrapper around [RubyLLM](https://github.com/alexrudall/ruby_llm) that helps you write a pipeline of AI prompts and run it many times, in bulk. Built for automating repetitive refactors across a large codebase.
4
+
5
+ <small>Most of what's below is generated by an LLM. I take no responsibility for any of it, unless it's awesome... then it was pure prompting skills which I will take credit for.</small>
6
+
7
+ ## Overview
8
+
9
+ AgentC provides batch processing and pipeline orchestration for AI-powered tasks:
10
+
11
+ - **Batch Processing** - Execute pipelines across multiple records with automatic parallelization via worktrees
12
+ - **Pipeline Orchestration** - Define multi-step workflows with AI-powered agent steps and custom logic
13
+ - **Resumable Execution** - Automatically skip completed steps when pipelines are rerun
14
+ - **Automatic query persistence** - All interactions saved to SQLite
15
+ - **Cost tracking** - Detailed reports on token usage and costs
16
+ - **Custom tools** - File operations, grep, Rails tests, and more
17
+ - **Schema validation** - RubyLLM Schema support for structured responses
18
+
19
+ ## Installation
20
+
21
+ This gem is not pushed to rubygems. Instead, you should add a git reference to your Gemfile (use a revision because I'm going to make changes with complete disregard for backwards compatibility).
22
+
23
+ ## Example template
24
+
25
+ See an [example template](./template) you can run in the `template/` directory of this repo. Poke around there after perusing this section.
26
+
27
+ You can copy this template to start building your own.
28
+
29
+ ## Quick Start
30
+
31
+ A "Pipeline" is a series of prompts for Claude to perform. Data gathered from prior steps are fed into subsequent steps (you'll define an ActiveRecord class to capture the data). If any step fails, the pipeline aborts.
32
+
33
+ A "Batch" is a collection of pipelines to be run. They can be run against a single directory in series, or concurrently across multiple git worktrees. If a pipeline fails, the failure will be recorded but the batch will continue.
34
+
35
+ ### The necessary structures
36
+
37
+ In this example, we'll have Claude choose a random file, summarize its contents in a language of our choosing, then write it to disk and commit.
38
+
39
+ ```ruby
40
+ # Define the records your agent will interact with.
41
+ # Normally you'd only have one record.
42
+ #
43
+ # A versioned store saves a full db backup per-transaction
44
+ # so that you can recover from any step of the process.
45
+ # Just trying to save tokens...
46
+ class MyStore < VersionedStore::Base
47
+ include AgentC::Store
48
+
49
+ record(:summary) do
50
+
51
+ # the migration schema is defined in line
52
+ schema do |t|
53
+ # we'll input this data
54
+ t.string(:language)
55
+
56
+ # claude will generate this data
57
+ t.string(:input_path)
58
+ t.text(:summary_text)
59
+ t.text(:summary_path)
60
+ end
61
+
62
+ # this is the body of your ActiveRecord class
63
+ # add methods here as needed
64
+ end
65
+ end
66
+
67
+ # A "pipeline" processes a single record
68
+ class MyPipeline < AgentC::Pipeline
69
+ # The prompts for these steps will
70
+ # live in our prompts.yml file
71
+ agent_step(:analyze_code)
72
+ agent_step(:write_summary_to_file)
73
+
74
+ step(:finalize) do
75
+ repo.commit_all("claude: analyzed code")
76
+ end
77
+
78
+ # if this pipeline fails, we want to
79
+ # leave the repo in a clean state
80
+ # for the next pipeline.
81
+ on_failure do
82
+ repo.reset_hard_all
83
+ end
84
+ end
85
+ ```
86
+ ```yaml
87
+ # define your prompts in a prompts.yml file:
88
+
89
+ en:
90
+
91
+ # the key names must match up to the `agent_step` invocation above
92
+ analyze_code:
93
+ # prompts here will be cached across pipelines.
94
+ # These prompts cannot interpolate any attributes.
95
+ # Suggested use is to put as much in the cached_prompts
96
+ # as possible and put variable data in the prompt.
97
+ cached_prompts:
98
+ - "Choose a random file. Read it and summarize it in the provided language."
99
+
100
+ # You can interpolate any attribute from your record class
101
+ prompt: "lanuage: %{language}"
102
+
103
+ # Tools available:
104
+ # - dir_glob
105
+ # - read_file
106
+ # - edit_file
107
+ # - grep
108
+ # - run_rails_test
109
+ # you can add more...
110
+ tools: [read_file, dir_glob]
111
+
112
+ # The response schema defines what Claude will return.
113
+ # The keys must be attributes from your record. What Claude
114
+ # returns will automatically be saved to your record.
115
+ response_schema:
116
+ summary_text:
117
+ type: string # this is the default
118
+ required: true # this is the default
119
+ description: "The summary text"
120
+ input_path:
121
+ type: string # this is the default
122
+ required: true # this is the default
123
+ description: "The path of the file you summarized"
124
+
125
+ write_summary_to_file:
126
+ cached_prompts:
127
+ - |
128
+ You will be given some text.
129
+ Choose a well-named file and write the text to it"
130
+
131
+ prompt: "Here is the text to write: %{summary_text}"
132
+ tools: [edit_file]
133
+ response_schema:
134
+ summary_path:
135
+ description: "the path of the file you wrote"
136
+ ```
137
+
138
+ Now, make a Batch and invoke it. A batch requires a lot of configuration, related to data storage, where your repo is, and claude API credentials:
139
+
140
+ ```ruby
141
+
142
+ batch = Batch.new(
143
+ record_type: :summary, # the class name you want to work on
144
+ pipeline: Pipeline, # the Pipeline class you made
145
+
146
+ # A batch has a "project" and a "run". These are ways
147
+ # to track Claude usage. Your Batch will have a
148
+ # "project". Each time you call batch.new you get
149
+ # a new "run".
150
+ project: "TemplateProject",
151
+
152
+ # We'll set some spending limits. Once these are
153
+ # reached, the Batch will abort.
154
+ max_spend_project: 100.0,
155
+ max_spend_run: 20.0,
156
+
157
+ store: {
158
+ class: Store, # the Store class you made
159
+ config: {
160
+ logger: Logger.new("/dev/null"), # a logger for the store
161
+ dir: "/where/you/want/your/store/saved"
162
+ }
163
+ },
164
+
165
+ # Where Claude will work
166
+ workspace: {
167
+ dir: "/where/claude/will/be/working",
168
+ env: {
169
+ # available to your tools
170
+ # only used by run_rails_test currently
171
+ SOME_ENV_VAR: "1"
172
+ }
173
+ }
174
+
175
+ # If you prefer, you can have the Batch manage
176
+ # some git worktrees for you. It will parallelize
177
+ # your tasks across your worktrees for MAXIMUM
178
+ # TOKEN BURN.
179
+ #
180
+ # Worktrees will be created for you if you are
181
+ # starting a new Batch. If you are continuing an
182
+ # existing Batch (after an error, for example),
183
+ # the worktrees will be left in their current
184
+ # state.
185
+ #
186
+ # You must pass *either* a workspace or a repo
187
+ repo: {
188
+ dir: "/path/to/your/repo",
189
+
190
+ # an existing git revision or branch name
191
+ initial_revision: "main",
192
+
193
+ # optional: limit Claude to a subdir from your repo
194
+ working_subdir: "./",
195
+
196
+ # Where to put your worktrees
197
+ worktrees_root_dir: "/tmp/example-worktrees",
198
+
199
+ # Each worktree gets a branch, they'll be suffixed
200
+ # with a counter
201
+ worktree_branch_prefix: "summary-examples",
202
+
203
+ # Currently, this defines how many worktrees to
204
+ # create. It's obnoxious I know, but hey, it works.
205
+ worktree_envs: [{}, {}],
206
+ }
207
+
208
+ # The claude configuration:
209
+ session: {
210
+ # all chats with claude are saved to a sqlite db.
211
+ # this is separate than your Store's db because
212
+ # why throw anything away. Can be useful for
213
+ # debugging why Claude did what it did
214
+ agent_db_path: "/path/to/your/claude/db.sqlite",
215
+ logger: Logger.new("/dev/null"), # probably use the same logger for everything...
216
+ i18n_path: "/path/to/your/prompts.yml",
217
+
218
+ # as you debug your pipeline, you'll probably run it
219
+ # many times. We tag all Claude chat records with a
220
+ # project so you can track costs.
221
+ project: "SomeProject",
222
+
223
+ # only available for Bedrock...
224
+ ruby_llm: {
225
+ bedrock_api_key: ENV.fetch("AWS_ACCESS_KEY_ID"),
226
+ bedrock_secret_key: ENV.fetch("AWS_SECRET_ACCESS_KEY"),
227
+ bedrock_session_token: ENV.fetch("AWS_SESSION_TOKEN"),
228
+ bedrock_region: ENV.fetch("AWS_REGION", "us-west-2"),
229
+ default_model: ENV.fetch("LLM_MODEL", "us.anthropic.claude-sonnet-4-5-20250929-v1:0")
230
+ }
231
+ },
232
+ )
233
+
234
+ # WHEW that's a lot of config,
235
+
236
+ # Now we add some records for processing.
237
+ # The batches "store" is just a bunch of
238
+ # ActiveRecord classes, but you reference
239
+ # them by the name you gave them in the
240
+ # store.
241
+ #
242
+ # We'll add some summary records.
243
+ # This seeded data represents the input
244
+ # into your pipelines.
245
+ #
246
+ # Because your batch can be stopped and
247
+ # restarted, we need our data creation
248
+ # to be idemptotent.
249
+ record_1 = (
250
+ batch
251
+ .store
252
+ .summary
253
+ .find_or_create_by!(language: "english")
254
+ )
255
+ record_2 = (
256
+ batch
257
+ .store
258
+ .summary
259
+ .find_or_create_by!(language: "spanish")
260
+ )
261
+
262
+ # Add the records to be processed.
263
+ # add_task is idempotent
264
+ batch.add_task(record_1)
265
+ batch.add_task(record_2)
266
+
267
+ batch.call
268
+
269
+ # See the details of what happened
270
+ puts batch.report
271
+ # =>
272
+ # Summary report:
273
+ # Succeeded: 2
274
+ # Pending: 0
275
+ # Failed: 0
276
+ # Run cost: $2.34
277
+ # Project total cost: $10.40
278
+ # ---
279
+ # task: 1 - wrote summary to /tmp/example-worktrees/summary-examples-0/CHAT_TEST_SUMMARY.md
280
+ # task: 2 - wrote summary to /tmp/example-worktrees/summary-examples-1/RESUMEN_BASE.md
281
+
282
+ # Get a more detailed breakdown
283
+ cost = batch.cost
284
+
285
+ # Explore the records created
286
+ tasks = batch.store.task.all
287
+ summaries = batch.store.summary.all
288
+ ```
289
+
290
+ You can tail your logs to see what's happening. The full text of you Claude chats are logged to DEBUG.
291
+
292
+ If you just want to see your pipeline's progression:
293
+
294
+ ```shell
295
+ # Only see INFO
296
+ tail -f /path/to/log.log | grep INFO
297
+ ```
298
+
299
+ #### Batch errors
300
+
301
+ If your batch is interupted (by an exception or you kill it), you can continue it by simply running your batch again. The progress is persisted in the Batch's store.
302
+
303
+ If you need to correct any data or go back in time, you can peruse the store's versions by doing:
304
+
305
+ ```ruby
306
+ # see how many versions
307
+ puts batch.store.versions.count
308
+
309
+ # peruse your store:
310
+ batch.store.versions[12].summary.count
311
+
312
+ # restore a prior version
313
+ batch.store.version[12].restore
314
+
315
+ # re-run the batch
316
+ batch.call
317
+ ```
318
+
319
+ ### Resetting a Batch
320
+
321
+ You can delete the sqlite database for your store.
322
+
323
+ Delete the database you configured at `store: { config: { dir: "/path/to/db" } }`
324
+
325
+ ### Debugging a Batch
326
+
327
+ If you make multiple worktrees, they will be processed concurrently. This makes things hard to debug using `binding.irb`.
328
+
329
+ I suggest making one worktree until it's running successfully.
330
+
331
+ ### Structuring your project
332
+
333
+ I suggest following the structure of the [example template](./template).
334
+
335
+ # Detailed Documentation
336
+
337
+ Detailed guides for all features:
338
+
339
+ - **[Batch](docs/batch.md)** - Batch configuration, methods, and pipeline integration
340
+ - **[Pipeline Tips and Tricks](docs/pipeline-tips-and-tricks.md)** - Useful patterns and techniques for pipelines
341
+ - **[Chat Methods](docs/chat-methods.md)** - Using session.prompt and session.chat for direct interactions
342
+ - **[Tools](docs/tools.md)** - Built-in tools for file operations and code interaction
343
+ - **[Testing](docs/testing.md)** - Using TestHelpers::DummyChat for testing without real LLM calls
344
+ - **[Cost Reporting](docs/cost-reporting.md)** - Track token usage and costs
345
+ - **[Session Configuration](docs/session-configuration.md)** - All configuration options
346
+ - **[Store Versioning](docs/versioned-store.md)** - All configuration options
347
+
348
+ ## Requirements
349
+
350
+ - Ruby >= 3.0.0
351
+ - AWS credentials configured for Bedrock access
352
+ - SQLite3
353
+
354
+ ## License
355
+
356
+ WTFPL - Do What The Fuck You Want To Public License
357
+
358
+ ## Author
359
+
360
+ Pete Kinnecom (git@k7u7.com)
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "."
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ t.verbose = false
11
+ t.warning = false
12
+ # Enable parallel test execution based on number of processors
13
+ ENV["MT_CPU"] ||= Etc.nprocessors.to_s
14
+ end
15
+
16
+ task default: :test
data/TODO.md ADDED
@@ -0,0 +1,105 @@
1
+ # TODOs
2
+
3
+ Things I'd like to work on:
4
+
5
+ - Make injecting a Chat record simpler.
6
+ - Make injecting Git simpler (make injecting anything easier)
7
+ - Add a request queue to AgentC::Chat so that we can rate-limit and retry on error
8
+ - Use spring for run_rails_test, but add a timeout condition where it kills the process if no stdout appears for a while and tries again without spring.
9
+ - tool calls should write the full results to file (except for readfile) and pass back a reference for future queries. For example, if RunRailsTest gives way too much output, we have to truncate but how to see the rest?
10
+
11
+ ## Immplement plan, implement, review looping
12
+
13
+ Some scratch:
14
+
15
+ ```ruby
16
+ agent_iterate_loop(
17
+ :implement_query_object,
18
+ max_tries: 17,
19
+ plan: [
20
+ :plan_step_1,
21
+ :plan_step_2,
22
+ ],
23
+ implement: [
24
+ :implement_step_1,
25
+ :implement_step_2,
26
+ ],
27
+ # optional: defaults to implement
28
+ iterate: [
29
+ :address_feedback_1
30
+ ]
31
+ review: [
32
+ :review_step_1,
33
+ :review_step_2
34
+ ],
35
+ )
36
+ ```
37
+
38
+ Thoughts:
39
+
40
+ - This makes a demand on the `response_schema` of the prompts.
41
+ - Does the `task` track the looping here or the `record`?
42
+ - Is the progress tracked in memory? This can be one step...
43
+ - What if you have multiple `agent_iterate_loop` invocations? How do we store diffs/reviews for each of those?
44
+ - The `agent_step` applies the result to the record. If we track it on the task, we could get the attributes to the right place. Can we extend the response schema? The `plan` step definitely needs to update the record, so it needs to specify response_schema. The `implement/iterate` steps *might* need to accept response_schema (eg, path of file created? Or not necessary?, maybe not necessary.)
45
+ - Does a failed review trigger plan -> iterate -> review or just iterate -> review
46
+ - The implement/iterate arrays could include "plan" steps so that they know if they're starting from scratch or not.
47
+ - Do we run every review or stop at the first one?
48
+
49
+
50
+ ```ruby
51
+ def agent_iterate_loop(
52
+ name,
53
+ max_tries: 3,
54
+ implement: [],
55
+ iterate: implement,
56
+ review: [],
57
+ )
58
+ step(name) do
59
+ tries = 0
60
+
61
+ while(tries < max_tries)
62
+ if tries == 0
63
+ implement.each do |name|
64
+ process_prompt(name)
65
+ end
66
+ else
67
+ iterate.each do |name|
68
+ process_prompt(
69
+ name,
70
+ additional_i18n_attrs: {
71
+ feedback: feedback.join("\n---\n")
72
+ }
73
+ )
74
+ end
75
+ end
76
+
77
+ feedbacks = []
78
+
79
+ diff = git.diff
80
+ review.each do |name|
81
+ result = process_prompt(
82
+ name,
83
+ schema: -> {
84
+ t.boolean(:pass)
85
+ t.string(:feedback)
86
+ },
87
+ additional_i18n_attrs: {
88
+ diff:
89
+ }
90
+ )
91
+
92
+ if !result.fetch("pass")
93
+ feedbacks << result.fetch("feedback")
94
+ end
95
+ end
96
+
97
+ if record.respond_to?(:add_review)
98
+ record.add_review(diff:, feedbacks:)
99
+ end
100
+
101
+ break if feedbacks.empty?
102
+ end
103
+ end
104
+ end
105
+ ```
data/agent_c.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/agent_c/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "agent_c"
7
+ spec.version = AgentC::VERSION
8
+ spec.authors = ["Pete Kinnecom"]
9
+ spec.email = ["git@k7u7.com"]
10
+
11
+ spec.summary = <<~TEXT.strip
12
+ Batch processing for pipelines of steps for AI. AgentC, get it?
13
+ TEXT
14
+ spec.homepage = "https://github.com/petekinnecom/agent_c"
15
+ spec.license = "WTFPL"
16
+ spec.required_ruby_version = ">= 3.0.0"
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = spec.homepage
20
+ spec.metadata["changelog_uri"] = spec.homepage
21
+
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (File.expand_path(f) == __FILE__) ||
25
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
26
+ end
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency("zeitwerk", "~> 2.7")
33
+ spec.add_dependency("activerecord", "~> 8.1")
34
+ spec.add_dependency("sqlite3", "~> 2.9")
35
+ spec.add_dependency("async", "~> 2.35")
36
+ spec.add_dependency("ruby_llm", "~> 1.9")
37
+ spec.add_dependency("json-schema", "~> 6.1")
38
+ end