language-operator 0.0.1 → 0.1.31

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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +125 -0
  3. data/CHANGELOG.md +88 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +284 -0
  6. data/LICENSE +229 -21
  7. data/Makefile +82 -0
  8. data/README.md +3 -11
  9. data/Rakefile +63 -0
  10. data/bin/aictl +7 -0
  11. data/completions/_aictl +232 -0
  12. data/completions/aictl.bash +121 -0
  13. data/completions/aictl.fish +114 -0
  14. data/docs/architecture/agent-runtime.md +585 -0
  15. data/docs/dsl/SCHEMA_VERSION.md +250 -0
  16. data/docs/dsl/agent-reference.md +604 -0
  17. data/docs/dsl/best-practices.md +1078 -0
  18. data/docs/dsl/chat-endpoints.md +895 -0
  19. data/docs/dsl/constraints.md +671 -0
  20. data/docs/dsl/mcp-integration.md +1177 -0
  21. data/docs/dsl/webhooks.md +932 -0
  22. data/docs/dsl/workflows.md +744 -0
  23. data/lib/language_operator/agent/base.rb +110 -0
  24. data/lib/language_operator/agent/executor.rb +440 -0
  25. data/lib/language_operator/agent/instrumentation.rb +54 -0
  26. data/lib/language_operator/agent/metrics_tracker.rb +183 -0
  27. data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
  28. data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
  29. data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
  30. data/lib/language_operator/agent/safety/content_filter.rb +93 -0
  31. data/lib/language_operator/agent/safety/manager.rb +207 -0
  32. data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
  33. data/lib/language_operator/agent/safety/safe_executor.rb +127 -0
  34. data/lib/language_operator/agent/scheduler.rb +183 -0
  35. data/lib/language_operator/agent/telemetry.rb +116 -0
  36. data/lib/language_operator/agent/web_server.rb +610 -0
  37. data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
  38. data/lib/language_operator/agent.rb +149 -0
  39. data/lib/language_operator/cli/commands/agent.rb +1205 -0
  40. data/lib/language_operator/cli/commands/cluster.rb +371 -0
  41. data/lib/language_operator/cli/commands/install.rb +404 -0
  42. data/lib/language_operator/cli/commands/model.rb +266 -0
  43. data/lib/language_operator/cli/commands/persona.rb +393 -0
  44. data/lib/language_operator/cli/commands/quickstart.rb +22 -0
  45. data/lib/language_operator/cli/commands/status.rb +143 -0
  46. data/lib/language_operator/cli/commands/system.rb +772 -0
  47. data/lib/language_operator/cli/commands/tool.rb +537 -0
  48. data/lib/language_operator/cli/commands/use.rb +47 -0
  49. data/lib/language_operator/cli/errors/handler.rb +180 -0
  50. data/lib/language_operator/cli/errors/suggestions.rb +176 -0
  51. data/lib/language_operator/cli/formatters/code_formatter.rb +77 -0
  52. data/lib/language_operator/cli/formatters/log_formatter.rb +288 -0
  53. data/lib/language_operator/cli/formatters/progress_formatter.rb +49 -0
  54. data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
  55. data/lib/language_operator/cli/formatters/table_formatter.rb +163 -0
  56. data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
  57. data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
  58. data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
  59. data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
  60. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
  61. data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
  62. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
  63. data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
  64. data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
  65. data/lib/language_operator/cli/main.rb +236 -0
  66. data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
  67. data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
  68. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
  69. data/lib/language_operator/client/base.rb +214 -0
  70. data/lib/language_operator/client/config.rb +136 -0
  71. data/lib/language_operator/client/cost_calculator.rb +37 -0
  72. data/lib/language_operator/client/mcp_connector.rb +123 -0
  73. data/lib/language_operator/client.rb +19 -0
  74. data/lib/language_operator/config/cluster_config.rb +101 -0
  75. data/lib/language_operator/config/tool_patterns.yaml +57 -0
  76. data/lib/language_operator/config/tool_registry.rb +96 -0
  77. data/lib/language_operator/config.rb +138 -0
  78. data/lib/language_operator/dsl/adapter.rb +124 -0
  79. data/lib/language_operator/dsl/agent_context.rb +90 -0
  80. data/lib/language_operator/dsl/agent_definition.rb +427 -0
  81. data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
  82. data/lib/language_operator/dsl/config.rb +119 -0
  83. data/lib/language_operator/dsl/context.rb +50 -0
  84. data/lib/language_operator/dsl/execution_context.rb +47 -0
  85. data/lib/language_operator/dsl/helpers.rb +109 -0
  86. data/lib/language_operator/dsl/http.rb +184 -0
  87. data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
  88. data/lib/language_operator/dsl/parameter_definition.rb +124 -0
  89. data/lib/language_operator/dsl/registry.rb +36 -0
  90. data/lib/language_operator/dsl/schema.rb +1102 -0
  91. data/lib/language_operator/dsl/shell.rb +125 -0
  92. data/lib/language_operator/dsl/tool_definition.rb +112 -0
  93. data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
  94. data/lib/language_operator/dsl/webhook_definition.rb +106 -0
  95. data/lib/language_operator/dsl/workflow_definition.rb +259 -0
  96. data/lib/language_operator/dsl.rb +161 -0
  97. data/lib/language_operator/errors.rb +60 -0
  98. data/lib/language_operator/kubernetes/client.rb +279 -0
  99. data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
  100. data/lib/language_operator/loggable.rb +47 -0
  101. data/lib/language_operator/logger.rb +141 -0
  102. data/lib/language_operator/retry.rb +123 -0
  103. data/lib/language_operator/retryable.rb +132 -0
  104. data/lib/language_operator/templates/README.md +23 -0
  105. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +115 -0
  106. data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
  107. data/lib/language_operator/templates/schema/.gitkeep +0 -0
  108. data/lib/language_operator/templates/schema/CHANGELOG.md +93 -0
  109. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
  110. data/lib/language_operator/templates/schema/agent_dsl_schema.json +452 -0
  111. data/lib/language_operator/tool_loader.rb +242 -0
  112. data/lib/language_operator/validators.rb +170 -0
  113. data/lib/language_operator/version.rb +1 -1
  114. data/lib/language_operator.rb +65 -3
  115. data/requirements/tasks/challenge.md +9 -0
  116. data/requirements/tasks/iterate.md +36 -0
  117. data/requirements/tasks/optimize.md +21 -0
  118. data/requirements/tasks/tag.md +5 -0
  119. data/test_agent_dsl.rb +108 -0
  120. metadata +507 -20
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ # Common parameter validation utilities
5
+ #
6
+ # Provides reusable validators for common parameter validation patterns
7
+ # across tools. All validators follow the same convention:
8
+ # - Return `nil` if the value is valid
9
+ # - Return an error string if the value is invalid
10
+ #
11
+ # This allows for consistent usage in tools:
12
+ # error = LanguageOperator::Validators.http_url(params['url'])
13
+ # next error if error
14
+ #
15
+ # @example URL validation
16
+ # error = Validators.http_url('https://example.com')
17
+ # # => nil (valid)
18
+ #
19
+ # error = Validators.http_url('ftp://example.com')
20
+ # # => "Error: Invalid URL. Must start with http:// or https://"
21
+ #
22
+ # @example Enum validation
23
+ # error = Validators.one_of('GET', %w[GET POST PUT DELETE], 'HTTP method')
24
+ # # => nil (valid)
25
+ #
26
+ # error = Validators.one_of('INVALID', %w[GET POST PUT DELETE], 'HTTP method')
27
+ # # => "Error: Invalid HTTP method 'INVALID'. Expected: one of: GET, POST, PUT, DELETE"
28
+ module Validators
29
+ # Validate HTTP/HTTPS URL format
30
+ #
31
+ # @param url [String] URL to validate
32
+ # @return [String, nil] Error message if invalid, nil if valid
33
+ #
34
+ # @example Valid URLs
35
+ # Validators.http_url('http://example.com') # => nil
36
+ # Validators.http_url('https://api.example.com/v1') # => nil
37
+ #
38
+ # @example Invalid URLs
39
+ # Validators.http_url('') # => "Error: URL cannot be empty"
40
+ # Validators.http_url('ftp://example.com') # => "Error: Invalid URL..."
41
+ def self.http_url(url)
42
+ return 'Error: URL cannot be empty' if url.nil? || url.strip.empty?
43
+ return nil if url =~ %r{^https?://.+}
44
+
45
+ 'Error: Invalid URL. Must start with http:// or https://'
46
+ end
47
+
48
+ # Validate that a value is not nil or empty
49
+ #
50
+ # @param value [Object] Value to check
51
+ # @param field_name [String] Name of the field for error message
52
+ # @return [String, nil] Error message if invalid, nil if valid
53
+ #
54
+ # @example Valid values
55
+ # Validators.not_empty('value', 'Name') # => nil
56
+ # Validators.not_empty(' text ', 'Name') # => nil (has content after strip)
57
+ #
58
+ # @example Invalid values
59
+ # Validators.not_empty(nil, 'Name') # => "Error: Name cannot be empty"
60
+ # Validators.not_empty('', 'Name') # => "Error: Name cannot be empty"
61
+ # Validators.not_empty(' ', 'Name') # => "Error: Name cannot be empty"
62
+ def self.not_empty(value, field_name)
63
+ return nil if value && !value.to_s.strip.empty?
64
+
65
+ LanguageOperator::Errors.empty_field(field_name)
66
+ end
67
+
68
+ # Validate that a value is one of the allowed options
69
+ #
70
+ # @param value [String] Value to validate
71
+ # @param allowed [Array<String>] Allowed values
72
+ # @param field_name [String] Field name for error message
73
+ # @return [String, nil] Error message if invalid, nil if valid
74
+ #
75
+ # @example Valid choice
76
+ # Validators.one_of('GET', %w[GET POST PUT], 'method') # => nil
77
+ #
78
+ # @example Invalid choice
79
+ # Validators.one_of('DELETE', %w[GET POST PUT], 'method')
80
+ # # => "Error: Invalid method 'DELETE'. Expected: one of: GET, POST, PUT"
81
+ def self.one_of(value, allowed, field_name)
82
+ return nil if allowed.include?(value)
83
+
84
+ LanguageOperator::Errors.invalid_parameter(
85
+ field_name,
86
+ value,
87
+ "one of: #{allowed.join(', ')}"
88
+ )
89
+ end
90
+
91
+ # Validate numeric range
92
+ #
93
+ # @param value [Numeric] Value to validate
94
+ # @param min [Numeric, nil] Minimum value (inclusive)
95
+ # @param max [Numeric, nil] Maximum value (inclusive)
96
+ # @param field_name [String] Field name for error message
97
+ # @return [String, nil] Error message if invalid, nil if valid
98
+ #
99
+ # @example Valid ranges
100
+ # Validators.numeric_range(5, min: 1, max: 10) # => nil
101
+ # Validators.numeric_range(100, min: 50) # => nil
102
+ # Validators.numeric_range(5, max: 10) # => nil
103
+ #
104
+ # @example Invalid ranges
105
+ # Validators.numeric_range('foo', min: 1) # => "Error: value must be a number"
106
+ # Validators.numeric_range(0, min: 1) # => "Error: value must be at least 1"
107
+ # Validators.numeric_range(100, max: 50) # => "Error: value must be at most 50"
108
+ def self.numeric_range(value, min: nil, max: nil, field_name: 'value')
109
+ return "Error: #{field_name} must be a number" unless value.is_a?(Numeric)
110
+
111
+ return "Error: #{field_name} must be at least #{min}" if min && value < min
112
+
113
+ return "Error: #{field_name} must be at most #{max}" if max && value > max
114
+
115
+ nil
116
+ end
117
+
118
+ # Validate email address format (basic)
119
+ #
120
+ # Note: This is a basic format check, not full RFC 5322 validation.
121
+ # It checks for: something@domain.tld
122
+ #
123
+ # @param email [String] Email address to validate
124
+ # @return [String, nil] Error message if invalid, nil if valid
125
+ #
126
+ # @example Valid emails
127
+ # Validators.email('user@example.com') # => nil
128
+ # Validators.email('test.user+tag@domain.co.uk') # => nil
129
+ #
130
+ # @example Invalid emails
131
+ # Validators.email('') # => "Error: Email address cannot be empty"
132
+ # Validators.email('invalid') # => "Error: Invalid email address format"
133
+ # Validators.email('@example.com') # => "Error: Invalid email address format"
134
+ def self.email(email)
135
+ return 'Error: Email address cannot be empty' if email.nil? || email.strip.empty?
136
+ return nil if email =~ /\A[^@\s]+@[^@\s]+\.[^@\s]+\z/
137
+
138
+ 'Error: Invalid email address format'
139
+ end
140
+
141
+ # Validate that a path doesn't contain directory traversal attempts
142
+ #
143
+ # Checks for:
144
+ # - Parent directory references (..)
145
+ # - Null bytes (\0)
146
+ #
147
+ # Note: This is a basic safety check. Tool-specific path validation
148
+ # (like workspace sandboxing) should still be implemented in the tools.
149
+ #
150
+ # @param path [String] Path to validate
151
+ # @return [String, nil] Error message if invalid, nil if valid
152
+ #
153
+ # @example Valid paths
154
+ # Validators.safe_path('/absolute/path') # => nil
155
+ # Validators.safe_path('relative/path.txt') # => nil
156
+ #
157
+ # @example Invalid paths
158
+ # Validators.safe_path('') # => "Error: Path cannot be empty"
159
+ # Validators.safe_path('../../../etc/passwd') # => "Error: Path contains..."
160
+ # Validators.safe_path("file\0name") # => "Error: Path contains..."
161
+ def self.safe_path(path)
162
+ return 'Error: Path cannot be empty' if path.nil? || path.strip.empty?
163
+
164
+ # Check for obvious traversal attempts
165
+ return 'Error: Path contains invalid characters or directory traversal' if path.include?('..') || path.include?("\0")
166
+
167
+ nil
168
+ end
169
+ end
170
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LanguageOperator
4
- VERSION = "0.0.1"
4
+ VERSION = '0.1.31'
5
5
  end
@@ -1,8 +1,70 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "language_operator/version"
3
+ require 'language_operator/version'
4
+ require 'language_operator/errors'
5
+ require 'language_operator/retry'
6
+ require 'language_operator/config'
7
+ require 'language_operator/validators'
8
+ require 'language_operator/dsl'
9
+ require 'language_operator/client'
10
+ require 'language_operator/tool_loader'
4
11
 
5
- # This is a placeholder gem to reserve the name "language-operator" on RubyGems.
6
- # The actual implementation will be released when the project is ready.
12
+ # Agent module is optional - only load if dependencies are available
13
+ # This allows the SDK to be used in environments without agent dependencies
14
+ begin
15
+ require 'language_operator/agent'
16
+ rescue LoadError => e
17
+ # Agent dependencies not available, skip loading
18
+ warn "LanguageOperator: Agent module not loaded (missing dependency: #{e.message})" if ENV['DEBUG']
19
+ end
20
+
21
+ # Langop - Ruby SDK for building MCP tools and language agents
22
+ #
23
+ # This gem provides:
24
+ # - DSL for defining MCP tools with a clean, Ruby-like syntax
25
+ # - Client library for connecting to MCP servers
26
+ # - Agent framework for autonomous task execution
27
+ # - CLI for generating and running tools and agents
28
+ #
29
+ # @example Define a tool
30
+ # LanguageOperator::Dsl.define do
31
+ # tool "greet" do
32
+ # description "Greet a user"
33
+ # parameter :name do
34
+ # type :string
35
+ # required true
36
+ # end
37
+ # execute do |params|
38
+ # "Hello, #{params['name']}!"
39
+ # end
40
+ # end
41
+ # end
42
+ #
43
+ # @example Use the client
44
+ # config = LanguageOperator::Client::Config.from_env
45
+ # client = LanguageOperator::Client::Base.new(config)
46
+ # client.connect!
47
+ # response = client.send_message("What can you do?")
48
+ #
49
+ # @example Run an agent
50
+ # agent = LanguageOperator::Agent::Base.new(config)
51
+ # agent.run
7
52
  module LanguageOperator
53
+ class Error < StandardError; end
54
+
55
+ # Convenience method to define tools
56
+ #
57
+ # @yield Block containing tool definitions
58
+ # @return [LanguageOperator::Dsl::Registry] The global tool registry
59
+ def self.define(&)
60
+ Dsl.define(&)
61
+ end
62
+
63
+ # Convenience method to load tools from a file
64
+ #
65
+ # @param file_path [String] Path to tool definition file
66
+ # @return [LanguageOperator::Dsl::Registry] The global tool registry
67
+ def self.load_file(file_path)
68
+ Dsl.load_file(file_path)
69
+ end
8
70
  end
@@ -0,0 +1,9 @@
1
+ You are the public face, the user interface, of an extremely ambitious project called the Language Operator.
2
+
3
+ It synthesizes code *in production* securely from any arbitrary instructions written in plain text.
4
+
5
+ Read ../language-operator/README.md to understand your significance.
6
+
7
+ In that light, I challenge you to ensure that we're matching that vision in quality and supporting the full power of its CRDs.
8
+
9
+ Dive into this code, maybe into the language-operator itself. What can **this gem** do better at?
@@ -0,0 +1,36 @@
1
+ # Task
2
+
3
+ ## Persona
4
+
5
+ Adopt the [ruby-engineer](../../../requirements/personas/ruby-engineer.md) persona while executing these instructions, please.
6
+
7
+ ## Inputs
8
+
9
+ - id int -- A GitHub issue index ID.
10
+
11
+ ## Background
12
+
13
+ This is a early-phase project that works exclusively in main.
14
+ Issues are found using the `gh` command for this project:
15
+ - Owner: language-operator
16
+ - Repository: language-operator-gem
17
+
18
+ ## Instructions
19
+
20
+ Follow these directions closely:
21
+
22
+ 1. Use the ForgeJo tool to find the top issue for this repository.
23
+ 2. Investigate if it's valid, or a mis-use of the intended feature.
24
+ 3. **CRITICAL:** Switch to plan mode, and propose an implementation plan. Await my feedback.
25
+ 4. Add your implementation plan as a comment on the issue.
26
+ 5. Implement the changes.
27
+ 6. Run existing tests, and add new ones if necessary. Remember to include CI. Remember the linter and that bundler will fail if it's out of sync with its lockfile.
28
+ 7. **CRITICAL:** Test the actual functionality manually before committing. If it's a CLI command, run it. If it's library code, test it in the appropriate context. Never commit untested code.
29
+ 8. Commit the change and push to origin.
30
+ 9. **CRITICAL:** Halt while CI runs and await my feedback.
31
+ 10. Comment on your solution in the ForgeJo issue.
32
+ 11. Resolve the issue.
33
+
34
+ ## Output
35
+
36
+ An implementation, test coverage, updated CI, and a closed ticket.
@@ -0,0 +1,21 @@
1
+
2
+ # Task
3
+
4
+ ## Name
5
+
6
+ Optimize
7
+
8
+ ## Persona
9
+
10
+ Adopt the [ruby-engineer](../requirements/personas/ruby-engineer.md) persona while executing these instructions, please.
11
+
12
+ ## Instructions
13
+
14
+ Suggest an improvement that could improve the quality of the codebase or developer experience. Things like opportunities to reduce lines of code, DRYing up code, or eliminating dead code paths.
15
+
16
+ An important thing to consider is that this code has been written by different agents with different contexts, who may not have been aware of overall patterns. These kinds of optimizations are high priority.
17
+
18
+ ## Output
19
+
20
+ Switch to Plan mode.
21
+ A proposed list of three concrete optimizations.
@@ -0,0 +1,5 @@
1
+ Time to release a new version!
2
+
3
+ 1. Update the patch version in lib/language_operator/version.rb and re-run bundler to update the lockfile.
4
+ 2. Commit the version with the message "vX.Y.Z" and push to origin.
5
+ 3. Tag the new version and push to origin with the --tags argument.
data/test_agent_dsl.rb ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Quick test of the agent DSL without requiring full langop gem dependencies
5
+
6
+ require_relative 'lib/langop/dsl/agent_definition'
7
+ require_relative 'lib/langop/dsl/agent_context'
8
+ require_relative 'lib/langop/dsl/workflow_definition'
9
+
10
+ puts '=' * 70
11
+ puts 'Agent DSL Test'
12
+ puts '=' * 70
13
+ puts
14
+
15
+ # Create registry and context
16
+ registry = Langop::Dsl::AgentRegistry.new
17
+ context = Langop::Dsl::AgentContext.new(registry)
18
+
19
+ # Define an agent using the DSL (this is what the operator will synthesize)
20
+ context.agent 'kubernetes-news' do
21
+ description 'Daily Kubernetes news summarization agent'
22
+
23
+ # Persona (distilled by operator from LanguagePersona)
24
+ persona <<~PERSONA
25
+ You are a technical writer specializing in Kubernetes. When researching and
26
+ summarizing news, maintain a clear and precise tone, always cite your sources,
27
+ use proper technical terminology, and make complex topics accessible without
28
+ unnecessary jargon. Your summaries should be educational and well-structured.
29
+ PERSONA
30
+
31
+ # Schedule (extracted from: "once a day, preferably around lunchtime")
32
+ schedule '0 12 * * *'
33
+
34
+ # Objectives (extracted from instructions)
35
+ objectives [
36
+ 'Search for recent Kubernetes news using web_search tool',
37
+ 'Provide a concise summary of findings'
38
+ ]
39
+
40
+ # Workflow (synthesized by operator)
41
+ workflow do
42
+ step :search do
43
+ tool 'web_search'
44
+ params query: 'Kubernetes news latest'
45
+ end
46
+
47
+ step :summarize do
48
+ depends_on :search
49
+ prompt 'Provide a concise summary of these Kubernetes news items: {search.output}'
50
+ end
51
+ end
52
+
53
+ # Constraints (inferred by operator)
54
+ constraints do
55
+ max_iterations 20
56
+ timeout '5m'
57
+ end
58
+
59
+ # Output (inferred from workspace settings)
60
+ output do
61
+ workspace 'summaries/kubernetes-{date}.md'
62
+ end
63
+ end
64
+
65
+ # Retrieve and display the agent
66
+ agent = registry.get('kubernetes-news')
67
+
68
+ if agent
69
+ puts '✅ Agent Definition Loaded'
70
+ puts
71
+ puts "Name: #{agent.name}"
72
+ puts "Description: #{agent.description}"
73
+ puts "Mode: #{agent.execution_mode}"
74
+ puts "Schedule: #{agent.schedule}"
75
+ puts
76
+ puts '📋 Persona:'
77
+ puts agent.persona.lines.map { |l| " #{l}" }.join
78
+ puts
79
+ puts "🎯 Objectives (#{agent.objectives.size}):"
80
+ agent.objectives.each_with_index do |obj, i|
81
+ puts " #{i + 1}. #{obj}"
82
+ end
83
+ puts
84
+ puts "⚙️ Workflow Steps (#{agent.workflow.steps.size}):"
85
+ agent.workflow.steps.each do |name, step|
86
+ deps = step.dependencies.empty? ? '' : " [depends on: #{step.dependencies.join(', ')}]"
87
+ tool_info = step.tool ? " (tool: #{step.tool})" : ''
88
+ prompt_info = step.prompt ? " (prompt: #{step.prompt[0..50]}...)" : ''
89
+ puts " - #{name}#{deps}#{tool_info}#{prompt_info}"
90
+ end
91
+ puts
92
+ puts '📊 Constraints:'
93
+ agent.constraints.each do |key, value|
94
+ puts " #{key}: #{value}"
95
+ end
96
+ puts
97
+ puts '📤 Output:'
98
+ agent.output_config.each do |key, value|
99
+ puts " #{key}: #{value}"
100
+ end
101
+ puts
102
+ puts '=' * 70
103
+ puts '✅ DSL Test Complete - Agent ready for execution!'
104
+ puts '=' * 70
105
+ else
106
+ puts '❌ Failed to load agent'
107
+ exit 1
108
+ end