agent_c 2.71828
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.
- checksums.yaml +7 -0
- data/.rubocop.yml +10 -0
- data/.ruby-version +1 -0
- data/CLAUDE.md +21 -0
- data/README.md +360 -0
- data/Rakefile +16 -0
- data/TODO.md +12 -0
- data/agent_c.gemspec +38 -0
- data/docs/chat-methods.md +157 -0
- data/docs/cost-reporting.md +86 -0
- data/docs/pipeline-tips-and-tricks.md +71 -0
- data/docs/session-configuration.md +274 -0
- data/docs/testing.md +747 -0
- data/docs/tools.md +103 -0
- data/docs/versioned-store.md +840 -0
- data/lib/agent_c/agent/chat.rb +211 -0
- data/lib/agent_c/agent/chat_response.rb +32 -0
- data/lib/agent_c/agent/chats/anthropic_bedrock.rb +48 -0
- data/lib/agent_c/batch.rb +102 -0
- data/lib/agent_c/configs/repo.rb +90 -0
- data/lib/agent_c/context.rb +56 -0
- data/lib/agent_c/costs/data.rb +39 -0
- data/lib/agent_c/costs/report.rb +219 -0
- data/lib/agent_c/db/store.rb +162 -0
- data/lib/agent_c/errors.rb +19 -0
- data/lib/agent_c/pipeline.rb +188 -0
- data/lib/agent_c/processor.rb +98 -0
- data/lib/agent_c/prompts.yml +53 -0
- data/lib/agent_c/schema.rb +85 -0
- data/lib/agent_c/session.rb +207 -0
- data/lib/agent_c/store.rb +72 -0
- data/lib/agent_c/test_helpers.rb +173 -0
- data/lib/agent_c/tools/dir_glob.rb +46 -0
- data/lib/agent_c/tools/edit_file.rb +112 -0
- data/lib/agent_c/tools/file_metadata.rb +43 -0
- data/lib/agent_c/tools/grep.rb +119 -0
- data/lib/agent_c/tools/paths.rb +36 -0
- data/lib/agent_c/tools/read_file.rb +94 -0
- data/lib/agent_c/tools/run_rails_test.rb +87 -0
- data/lib/agent_c/tools.rb +60 -0
- data/lib/agent_c/utils/git.rb +75 -0
- data/lib/agent_c/utils/shell.rb +58 -0
- data/lib/agent_c/version.rb +5 -0
- data/lib/agent_c.rb +32 -0
- data/lib/versioned_store/base.rb +314 -0
- data/lib/versioned_store/config.rb +26 -0
- data/lib/versioned_store/stores/schema.rb +127 -0
- data/lib/versioned_store/version.rb +5 -0
- data/lib/versioned_store.rb +5 -0
- data/template/Gemfile +9 -0
- data/template/Gemfile.lock +152 -0
- data/template/README.md +61 -0
- data/template/Rakefile +50 -0
- data/template/bin/rake +27 -0
- data/template/lib/autoload.rb +10 -0
- data/template/lib/config.rb +59 -0
- data/template/lib/pipeline.rb +19 -0
- data/template/lib/prompts.yml +57 -0
- data/template/lib/store.rb +17 -0
- data/template/test/pipeline_test.rb +221 -0
- data/template/test/test_helper.rb +18 -0
- metadata +191 -0
data/template/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Overview
|
|
2
|
+
|
|
3
|
+
This directory contains a small script demonstrating how to run a batch of pipelines.
|
|
4
|
+
|
|
5
|
+
For each `summary` record created, Claude will choose a random file, summarize it, and write the summary to disk. Then our pipeline commits the changes at the end.
|
|
6
|
+
|
|
7
|
+
It creates two records and runs them across two worktrees.
|
|
8
|
+
|
|
9
|
+
The main entrypoint is the Rakefile.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Running the Batch
|
|
13
|
+
|
|
14
|
+
You'll need to set the relevant environment variables specified in the Config. (The environment variables necessary assume you're using Claude on Bedrock.)
|
|
15
|
+
|
|
16
|
+
Then run:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
bin/rake run
|
|
20
|
+
# => Summary report:
|
|
21
|
+
# => Succeeded: 2
|
|
22
|
+
# => Pending: 0
|
|
23
|
+
# => Failed: 0
|
|
24
|
+
# => Run cost: $0.31
|
|
25
|
+
# => Project total cost: $1.67
|
|
26
|
+
# => ---
|
|
27
|
+
# => task: 1 - wrote summary to /tmp/example-worktrees/summary-examples-0/VERSIONED_STORE_BASE_SUMMARY.md
|
|
28
|
+
# => task: 2 - wrote summary to /tmp/example-worktrees/summary-examples-1/SUMMARY-es.md
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Watching progress
|
|
32
|
+
|
|
33
|
+
You can tail the logs with:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
# See EVERYTHING
|
|
37
|
+
tail -f log/run.log
|
|
38
|
+
|
|
39
|
+
# See just progress
|
|
40
|
+
tail -f log/run.log | grep INFO
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Poking around the data
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
bin/rake console
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Resetting state
|
|
50
|
+
|
|
51
|
+
Delete the state stored in the `./tmp` directory to start all over:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
rm -rf ./tmp
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Run the tests:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
bin/rake test
|
|
61
|
+
```
|
data/template/Rakefile
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
require_relative "./lib/autoload"
|
|
7
|
+
|
|
8
|
+
Rake::TestTask.new(:test) do |t|
|
|
9
|
+
t.libs << "test"
|
|
10
|
+
t.libs << "lib"
|
|
11
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
12
|
+
t.verbose = true
|
|
13
|
+
t.warning = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
desc "Run the pipeline"
|
|
17
|
+
task :run do
|
|
18
|
+
batch = AgentC::Batch.new(**Config::BATCH)
|
|
19
|
+
|
|
20
|
+
["english", "spanish"].map do |language|
|
|
21
|
+
record = batch.store.summary.find_or_create_by!(language:)
|
|
22
|
+
|
|
23
|
+
batch.add_task(record)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
batch.call
|
|
27
|
+
|
|
28
|
+
puts "Summary report:"
|
|
29
|
+
puts batch.report
|
|
30
|
+
puts "---\n"
|
|
31
|
+
|
|
32
|
+
batch.store.task.all.each do |task|
|
|
33
|
+
next unless task.done?
|
|
34
|
+
|
|
35
|
+
full_path = File.join(
|
|
36
|
+
task.workspace.dir,
|
|
37
|
+
task.record.summary_path
|
|
38
|
+
)
|
|
39
|
+
puts "task: #{task.id} - wrote summary to #{full_path}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
task :console do
|
|
44
|
+
batch = AgentC::Batch.new(**Config::BATCH)
|
|
45
|
+
|
|
46
|
+
# poke around the data here
|
|
47
|
+
binding.irb
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
task default: :test
|
data/template/bin/rake
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
12
|
+
|
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
|
14
|
+
|
|
15
|
+
if File.file?(bundle_binstub)
|
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
|
17
|
+
load(bundle_binstub)
|
|
18
|
+
else
|
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
require "rubygems"
|
|
25
|
+
require "bundler/setup"
|
|
26
|
+
|
|
27
|
+
load Gem.bin_path("rake", "rake")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
|
|
6
|
+
module Config
|
|
7
|
+
LOG_PATH = "./log/run.log"
|
|
8
|
+
FileUtils.mkdir_p(File.dirname(LOG_PATH))
|
|
9
|
+
|
|
10
|
+
LOGGER = Logger.new(LOG_PATH)
|
|
11
|
+
|
|
12
|
+
PROJECT = "TemplateProject.v1"
|
|
13
|
+
|
|
14
|
+
BATCH = {
|
|
15
|
+
record_type: :summary,
|
|
16
|
+
pipeline: Pipeline,
|
|
17
|
+
|
|
18
|
+
store: {
|
|
19
|
+
class: Store,
|
|
20
|
+
config: {
|
|
21
|
+
logger: LOGGER,
|
|
22
|
+
dir: File.join(
|
|
23
|
+
File.expand_path("../tmp", __dir__),
|
|
24
|
+
PROJECT,
|
|
25
|
+
)
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
repo: {
|
|
30
|
+
dir: File.expand_path("../../", __dir__),
|
|
31
|
+
initial_revision: "main",
|
|
32
|
+
working_subdir: "", # use the root-level of the repo
|
|
33
|
+
worktrees_root_dir: "/tmp/example-worktrees",
|
|
34
|
+
worktree_branch_prefix: "summary-examples",
|
|
35
|
+
worktree_envs: [
|
|
36
|
+
{
|
|
37
|
+
SOME_ENV: "1",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
SOME_ENV: "2",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
session: {
|
|
46
|
+
agent_db_path: File.expand_path("../../tmp/claude.sqlite", __dir__),
|
|
47
|
+
logger: LOGGER,
|
|
48
|
+
i18n_path: File.expand_path("prompts.yml", __dir__),
|
|
49
|
+
project: PROJECT,
|
|
50
|
+
ruby_llm: {
|
|
51
|
+
bedrock_api_key: ENV.fetch("AWS_ACCESS_KEY_ID"),
|
|
52
|
+
bedrock_secret_key: ENV.fetch("AWS_SECRET_ACCESS_KEY"),
|
|
53
|
+
bedrock_session_token: ENV.fetch("AWS_SESSION_TOKEN"),
|
|
54
|
+
bedrock_region: ENV.fetch("AWS_REGION", "us-west-2"),
|
|
55
|
+
default_model: ENV.fetch("LLM_MODEL", "us.anthropic.claude-sonnet-4-5-20250929-v1:0")
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Pipeline < AgentC::Pipeline
|
|
4
|
+
agent_step(:pick_a_random_file)
|
|
5
|
+
agent_step(:summarize_the_file)
|
|
6
|
+
agent_step(:write_summary_to_disk)
|
|
7
|
+
|
|
8
|
+
step(:finalize) do
|
|
9
|
+
if repo.uncommitted_changes?
|
|
10
|
+
repo.commit_all(
|
|
11
|
+
<<~TXT
|
|
12
|
+
claude: added file: #{record.summary_path}
|
|
13
|
+
TXT
|
|
14
|
+
)
|
|
15
|
+
else
|
|
16
|
+
task.fail!("didn't create a file")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
en:
|
|
2
|
+
# This prompt will be used by all steps, it will be cached
|
|
3
|
+
# to save $$$.
|
|
4
|
+
global_summary_cache: &global_summary_cache |
|
|
5
|
+
This project is a ruby gem. You will be summarizing a file using
|
|
6
|
+
the language specified.
|
|
7
|
+
pick_a_random_file:
|
|
8
|
+
tools: [read_file, grep, dir_glob]
|
|
9
|
+
cached_prompts:
|
|
10
|
+
- *global_summary_cache
|
|
11
|
+
prompt: |
|
|
12
|
+
# YOUR JOB
|
|
13
|
+
|
|
14
|
+
Find a random ruby file within the repository. Bonus points for picking
|
|
15
|
+
a file with many lines.
|
|
16
|
+
response_schema:
|
|
17
|
+
input_path:
|
|
18
|
+
description: |
|
|
19
|
+
The path to the file you have chosen
|
|
20
|
+
|
|
21
|
+
summarize_the_file:
|
|
22
|
+
tools: [read_file]
|
|
23
|
+
cached_prompts:
|
|
24
|
+
- *global_summary_cache
|
|
25
|
+
prompt: |
|
|
26
|
+
You will be given a file path. Your job is to summarize the file for a
|
|
27
|
+
developer to read and understand.
|
|
28
|
+
|
|
29
|
+
You must write your summary using the language provided.
|
|
30
|
+
|
|
31
|
+
language: %{language}
|
|
32
|
+
file path: %{input_path}
|
|
33
|
+
response_schema:
|
|
34
|
+
summary_body:
|
|
35
|
+
description: |
|
|
36
|
+
Your summary of the file
|
|
37
|
+
|
|
38
|
+
write_summary_to_disk:
|
|
39
|
+
tools: [edit_file]
|
|
40
|
+
cached_prompts:
|
|
41
|
+
- *global_summary_cache
|
|
42
|
+
prompt: |
|
|
43
|
+
You will be given a file path and some summary text. Write the text to the
|
|
44
|
+
a well-named file at the top-level of the repository. You will return the
|
|
45
|
+
path that you wrote.
|
|
46
|
+
|
|
47
|
+
IMPORTANT: you **must** invoke the edit_file tool to process this request.
|
|
48
|
+
|
|
49
|
+
summary:
|
|
50
|
+
---BEGIN-SUMMARY---
|
|
51
|
+
%{summary_body}
|
|
52
|
+
---END-SUMMARY---
|
|
53
|
+
|
|
54
|
+
response_schema:
|
|
55
|
+
summary_path:
|
|
56
|
+
description: |
|
|
57
|
+
The path to the file you wrote.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Store < VersionedStore::Base
|
|
4
|
+
include AgentC::Store
|
|
5
|
+
|
|
6
|
+
record(:summary) do
|
|
7
|
+
schema do |t|
|
|
8
|
+
# we'll input this data
|
|
9
|
+
t.string(:language)
|
|
10
|
+
|
|
11
|
+
# claude will generate this data
|
|
12
|
+
t.string(:input_path)
|
|
13
|
+
t.string(:summary_body)
|
|
14
|
+
t.string(:summary_path)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
|
|
5
|
+
class PipelineTest < Minitest::Test
|
|
6
|
+
include TestHelpers
|
|
7
|
+
|
|
8
|
+
def setup
|
|
9
|
+
@store = Store.new(
|
|
10
|
+
logger: Logger.new(nil),
|
|
11
|
+
dir: File.join(
|
|
12
|
+
Dir.mktmpdir,
|
|
13
|
+
"template_test"
|
|
14
|
+
),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
@workspace = @store.workspace.create!(
|
|
18
|
+
dir: Dir.mktmpdir,
|
|
19
|
+
env: {}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Load I18n translations from prompts.yml
|
|
23
|
+
I18n.load_path << File.expand_path("../lib/prompts.yml", __dir__)
|
|
24
|
+
I18n.backend.load_translations
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_pipeline_end_to_end
|
|
28
|
+
summary = @store.summary.create!(language: "Spanish")
|
|
29
|
+
task = @store.task.create!(
|
|
30
|
+
record: summary,
|
|
31
|
+
workspace: @workspace
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
dummy_chat = AgentC::TestHelpers::DummyChat.new(responses: {
|
|
35
|
+
/Find a random ruby file/ =>
|
|
36
|
+
'{"status": "success", "input_path": "lib/pipeline.rb"}',
|
|
37
|
+
/language: Spanish/ =>
|
|
38
|
+
'{"status": "success", "summary_body": "Este archivo define un pipeline que resume archivos de Ruby"}',
|
|
39
|
+
/---BEGIN-SUMMARY---/ =>
|
|
40
|
+
'{"status": "success", "summary_path": "resumen_spanish.md"}'
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
# Setup dummy git to avoid actual git operations
|
|
44
|
+
dummy_git = AgentC::TestHelpers::DummyGit.new(@workspace.dir)
|
|
45
|
+
dummy_git.simulate_file_created!
|
|
46
|
+
|
|
47
|
+
session = test_session(
|
|
48
|
+
workspace_dir: @workspace.dir,
|
|
49
|
+
chat_provider: ->(**params) { dummy_chat }
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
Pipeline.call(task:, session:, git: ->(_dir) { dummy_git })
|
|
53
|
+
|
|
54
|
+
summary.reload
|
|
55
|
+
assert_equal "lib/pipeline.rb", summary.input_path
|
|
56
|
+
assert_equal "Este archivo define un pipeline que resume archivos de Ruby", summary.summary_body
|
|
57
|
+
assert_equal "resumen_spanish.md", summary.summary_path
|
|
58
|
+
assert task.reload.done?
|
|
59
|
+
assert_equal ["pick_a_random_file", "summarize_the_file", "write_summary_to_disk", "finalize"],
|
|
60
|
+
task.completed_steps
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_pipeline_with_different_languages
|
|
64
|
+
summary = @store.summary.create!(language: "French")
|
|
65
|
+
task = @store.task.create!(
|
|
66
|
+
record: summary,
|
|
67
|
+
workspace: @workspace
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
dummy_chat = AgentC::TestHelpers::DummyChat.new(responses: {
|
|
71
|
+
/Find a random ruby file/ =>
|
|
72
|
+
'{"status": "success", "input_path": "lib/store.rb"}',
|
|
73
|
+
/language: French/ =>
|
|
74
|
+
'{"status": "success", "summary_body": "Ce fichier définit le schéma de données pour les résumés"}',
|
|
75
|
+
/---BEGIN-SUMMARY---/ =>
|
|
76
|
+
'{"status": "success", "summary_path": "resume_french.md"}'
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
dummy_git = AgentC::TestHelpers::DummyGit.new(@workspace.dir)
|
|
80
|
+
dummy_git.simulate_file_created!
|
|
81
|
+
|
|
82
|
+
session = test_session(
|
|
83
|
+
workspace_dir: @workspace.dir,
|
|
84
|
+
chat_provider: ->(**params) { dummy_chat }
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
Pipeline.call(task:, session:, git: ->(_dir) { dummy_git })
|
|
88
|
+
|
|
89
|
+
summary.reload
|
|
90
|
+
assert_equal "lib/store.rb", summary.input_path
|
|
91
|
+
assert_equal "Ce fichier définit le schéma de données pour les résumés", summary.summary_body
|
|
92
|
+
assert_equal "resume_french.md", summary.summary_path
|
|
93
|
+
assert task.reload.done?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def test_pipeline_finalize_commits_when_file_created
|
|
97
|
+
summary = @store.summary.create!(
|
|
98
|
+
language: "English",
|
|
99
|
+
input_path: "lib/pipeline.rb",
|
|
100
|
+
summary_body: "This file defines a pipeline",
|
|
101
|
+
summary_path: "summary.md"
|
|
102
|
+
)
|
|
103
|
+
task = @store.task.create!(
|
|
104
|
+
record: summary,
|
|
105
|
+
workspace: @workspace
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Mark all agent steps as completed
|
|
109
|
+
task.update!(completed_steps: ["pick_a_random_file", "summarize_the_file", "write_summary_to_disk"])
|
|
110
|
+
|
|
111
|
+
dummy_git = AgentC::TestHelpers::DummyGit.new(@workspace.dir)
|
|
112
|
+
dummy_git.simulate_file_created!
|
|
113
|
+
|
|
114
|
+
session = test_session(workspace_dir: @workspace.dir)
|
|
115
|
+
|
|
116
|
+
Pipeline.call(task:, session:, git: ->(_dir) { dummy_git })
|
|
117
|
+
|
|
118
|
+
assert task.reload.done?
|
|
119
|
+
assert_equal 1, dummy_git.invocations.count
|
|
120
|
+
commit = dummy_git.invocations.first
|
|
121
|
+
assert_equal :commit_all, commit[:method]
|
|
122
|
+
assert_match(/claude: added file: summary\.md/, commit.dig(:args, 0))
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_pipeline_finalize_fails_when_no_file_created
|
|
126
|
+
summary = @store.summary.create!(
|
|
127
|
+
language: "English",
|
|
128
|
+
input_path: "lib/pipeline.rb",
|
|
129
|
+
summary_body: "This file defines a pipeline",
|
|
130
|
+
summary_path: "summary.md"
|
|
131
|
+
)
|
|
132
|
+
task = @store.task.create!(
|
|
133
|
+
record: summary,
|
|
134
|
+
workspace: @workspace
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Mark all agent steps as completed
|
|
138
|
+
task.update!(completed_steps: ["pick_a_random_file", "summarize_the_file", "write_summary_to_disk"])
|
|
139
|
+
|
|
140
|
+
dummy_git = AgentC::TestHelpers::DummyGit.new(@workspace.dir)
|
|
141
|
+
# Don't simulate file creation - no changes
|
|
142
|
+
|
|
143
|
+
session = test_session(workspace_dir: @workspace.dir)
|
|
144
|
+
|
|
145
|
+
Pipeline.call(task:, session:, git: ->(_dir) { dummy_git })
|
|
146
|
+
|
|
147
|
+
assert task.reload.failed?
|
|
148
|
+
assert_match(/didn't create a file/, task.error_message)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def test_pipeline_handles_agent_step_failure
|
|
152
|
+
summary = @store.summary.create!(language: "English")
|
|
153
|
+
task = @store.task.create!(
|
|
154
|
+
record: summary,
|
|
155
|
+
workspace: @workspace
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
dummy_chat = AgentC::TestHelpers::DummyChat.new(responses: {
|
|
159
|
+
/Find a random ruby file/ =>
|
|
160
|
+
'{"status": "error", "message": "No suitable files found in repository"}'
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
dummy_git = AgentC::TestHelpers::DummyGit.new(@workspace.dir)
|
|
164
|
+
|
|
165
|
+
session = test_session(
|
|
166
|
+
workspace_dir: @workspace.dir,
|
|
167
|
+
chat_provider: ->(**params) { dummy_chat }
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
Pipeline.call(task:, session:, git: ->(_dir) { dummy_git })
|
|
171
|
+
|
|
172
|
+
assert task.reload.failed?
|
|
173
|
+
assert_match(/No suitable files found/, task.error_message)
|
|
174
|
+
assert_nil summary.reload.input_path
|
|
175
|
+
assert_equal [], task.completed_steps
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def test_pipeline_resumes_from_completed_steps
|
|
179
|
+
summary = @store.summary.create!(
|
|
180
|
+
language: "Japanese",
|
|
181
|
+
input_path: "lib/config.rb",
|
|
182
|
+
summary_body: "このファイルは設定を定義します"
|
|
183
|
+
)
|
|
184
|
+
task = @store.task.create!(
|
|
185
|
+
record: summary,
|
|
186
|
+
workspace: @workspace
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Mark first two steps as completed
|
|
190
|
+
task.update!(completed_steps: ["pick_a_random_file", "summarize_the_file"])
|
|
191
|
+
|
|
192
|
+
dummy_chat = AgentC::TestHelpers::DummyChat.new(responses: {
|
|
193
|
+
/---BEGIN-SUMMARY---/ =>
|
|
194
|
+
'{"status": "success", "summary_path": "config_summary_ja.md"}'
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
dummy_git = AgentC::TestHelpers::DummyGit.new(@workspace.dir)
|
|
198
|
+
dummy_git.simulate_file_created!
|
|
199
|
+
|
|
200
|
+
session = test_session(
|
|
201
|
+
workspace_dir: @workspace.dir,
|
|
202
|
+
chat_provider: ->(**params) { dummy_chat }
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
Pipeline.call(task:, session:, git: ->(_dir) { dummy_git })
|
|
206
|
+
|
|
207
|
+
summary.reload
|
|
208
|
+
# First two steps' data should still be there
|
|
209
|
+
assert_equal "lib/config.rb", summary.input_path
|
|
210
|
+
assert_equal "このファイルは設定を定義します", summary.summary_body
|
|
211
|
+
# Only the last step's data should be updated
|
|
212
|
+
assert_equal "config_summary_ja.md", summary.summary_path
|
|
213
|
+
assert task.reload.done?
|
|
214
|
+
assert_equal ["pick_a_random_file", "summarize_the_file", "write_summary_to_disk", "finalize"],
|
|
215
|
+
task.completed_steps
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def dummy_chat_factory(responses)
|
|
219
|
+
->(**_kwargs) { AgentC::TestHelpers::DummyChat.new(responses: responses) }
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "minitest/autorun"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
require_relative "../lib/autoload"
|
|
8
|
+
|
|
9
|
+
# Require the base gems first
|
|
10
|
+
require "agent_c/test_helpers"
|
|
11
|
+
|
|
12
|
+
module TestHelpers
|
|
13
|
+
include AgentC::TestHelpers
|
|
14
|
+
|
|
15
|
+
def dummy_chat_factory(responses)
|
|
16
|
+
->(**_kwargs) { AgentC::TestHelpers::DummyChat.new(responses: responses) }
|
|
17
|
+
end
|
|
18
|
+
end
|