summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-14 10:50:58 +0200
committerPaul Buetow <paul@buetow.org>2026-03-14 10:50:58 +0200
commit2b4c2d4bbb47b59fb8bf7fec027efd36b1352857 (patch)
treecb45c69664461df14b90c47f2dd55ef4107e5e91
parent8a641ff347d0584ea1ddc16a6ef81a8c16ab172d (diff)
Validate agent configs during dry runs
-rw-r--r--AGENTS.md2
-rw-r--r--lib/dslkeywords/file.rb18
-rw-r--r--test/lib/dslkeywords/agent_test.rb37
3 files changed, 49 insertions, 8 deletions
diff --git a/AGENTS.md b/AGENTS.md
index df19b63..fac7f0f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -67,7 +67,7 @@ rake wireguard -- --dry # dry run mode
- **Lookup by object id**: Resolve named DSL objects with `RCM::DSL#object!` and `Keyword.id_for(...)`. Duplicate detection and lookup are id-based, not hash-based by ad hoc names.
- **Keep normalization in the keyword class**: If a DSL keyword accepts names, normalize them in the keyword class itself so registration and lookup use the same representation. Agent and prompt names may contain spaces.
- **Keep RuboCop clean on touched files**: Run RuboCop on edited files and keep disables narrow, justified, and local. Remove stale disable directives when they are no longer needed.
-- **Run tests after behavior changes**: At minimum run `rake test`. If you change examples, execute the relevant example commands from their own directories so relative paths behave as documented.
+- **Run tests and examples before committing**: Before committing any change, make sure the full unit test suite passes and all documented examples still work from their own directories so relative paths behave as documented.
- **Prefer documented execution paths**: Validate examples with the commands shown in the example README or Justfile unless you are explicitly fixing the docs themselves.
### Testing
diff --git a/lib/dslkeywords/file.rb b/lib/dslkeywords/file.rb
index 81f5f29..08f8f48 100644
--- a/lib/dslkeywords/file.rb
+++ b/lib/dslkeywords/file.rb
@@ -221,13 +221,15 @@ module RCM
def evaluate_agent_processing!
raise MissingAgentInput, "File #{@file_path} does not exist for agent processing" unless ::File.file?(@file_path)
+ agent_definition, prompt_definition = agent_configuration!
+
if option :dry
info "Processing #{@file_path} with agent #{@agent_name} and prompt #{@prompt_name} - dry run!"
return
end
input = ::File.read(@file_path)
- output = run_agent!(input)
+ output = run_agent!(input, agent_definition, prompt_definition)
create_parent_directory! unless ::File.directory?(::File.dirname(@file_path))
write!(output)
end
@@ -261,12 +263,7 @@ module RCM
# rubocop:enable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
- def run_agent!(input)
- agent_definition = dsl.object!(AgentDefinition, @agent_name,
- error_class: DSL::NoSuchAgentDefinition, kind: 'agent')
- prompt_definition = dsl.object!(PromptDefinition, @prompt_name,
- error_class: DSL::NoSuchPromptDefinition, kind: 'prompt')
-
+ def run_agent!(input, agent_definition, prompt_definition)
Tempfile.create(['rcm-agent-input', '.txt']) do |tmp|
tmp.write(input)
tmp.flush
@@ -285,6 +282,13 @@ module RCM
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
+ def agent_configuration!
+ [
+ dsl.object!(AgentDefinition, @agent_name, error_class: DSL::NoSuchAgentDefinition, kind: 'agent'),
+ dsl.object!(PromptDefinition, @prompt_name, error_class: DSL::NoSuchPromptDefinition, kind: 'prompt')
+ ]
+ end
+
def render_agent_command(template, prompt_text, input_path)
command = template.dup
command.gsub!(/\bINPUT\b/, Shellwords.escape(input_path))
diff --git a/test/lib/dslkeywords/agent_test.rb b/test/lib/dslkeywords/agent_test.rb
index 5eb5bc5..d3fc49f 100644
--- a/test/lib/dslkeywords/agent_test.rb
+++ b/test/lib/dslkeywords/agent_test.rb
@@ -283,6 +283,43 @@ class RCMAgentTest < Minitest::Test
assert_equal 'keep me', File.read(file_path)
end
+ def test_dry_run_unknown_agent_raises
+ file_path = path('dry-run-unknown-agent.txt')
+ File.write(file_path, 'keep me')
+ ARGV.replace(['--dry'])
+
+ assert_raises(RCM::DSL::NoSuchAgentDefinition) do
+ configure_from_scratch do
+ prompt 'no op' do
+ ''
+ end
+
+ file file_path do
+ agent 'missing agent', 'no op'
+ end
+ end
+ end
+ end
+
+ def test_dry_run_unknown_prompt_raises
+ file_path = path('dry-run-unknown-prompt.txt')
+ command = mock_agent_command(:pass_through)
+ File.write(file_path, 'keep me')
+ ARGV.replace(['--dry'])
+
+ assert_raises(RCM::DSL::NoSuchPromptDefinition) do
+ configure_from_scratch do
+ agent mock do
+ command
+ end
+
+ file file_path do
+ agent mock, 'missing prompt'
+ end
+ end
+ end
+ end
+
def test_non_zero_exit_raises
file_path = path('broken.txt')
command = mock_agent_command(:fail, 'boom', '7')