language-operator 0.1.61 → 0.1.62

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/persona.md +9 -0
  3. data/.claude/commands/task.md +46 -1
  4. data/.rubocop.yml +13 -0
  5. data/.rubocop_custom/use_ux_helper.rb +44 -0
  6. data/CHANGELOG.md +8 -0
  7. data/Gemfile.lock +12 -1
  8. data/Makefile +26 -7
  9. data/Makefile.common +50 -0
  10. data/bin/aictl +8 -1
  11. data/components/agent/Gemfile +1 -1
  12. data/components/agent/bin/langop-agent +7 -0
  13. data/docs/README.md +58 -0
  14. data/docs/{dsl/best-practices.md → best-practices.md} +4 -4
  15. data/docs/cli-reference.md +274 -0
  16. data/docs/{dsl/constraints.md → constraints.md} +5 -5
  17. data/docs/how-agents-work.md +156 -0
  18. data/docs/installation.md +218 -0
  19. data/docs/quickstart.md +299 -0
  20. data/docs/understanding-generated-code.md +265 -0
  21. data/docs/using-tools.md +457 -0
  22. data/docs/webhooks.md +509 -0
  23. data/examples/ux_helpers_demo.rb +296 -0
  24. data/lib/language_operator/agent/base.rb +11 -1
  25. data/lib/language_operator/agent/executor.rb +23 -6
  26. data/lib/language_operator/agent/safety/safe_executor.rb +41 -39
  27. data/lib/language_operator/agent/task_executor.rb +346 -63
  28. data/lib/language_operator/agent/web_server.rb +110 -14
  29. data/lib/language_operator/agent/webhook_authenticator.rb +39 -5
  30. data/lib/language_operator/agent.rb +88 -2
  31. data/lib/language_operator/cli/base_command.rb +17 -11
  32. data/lib/language_operator/cli/command_loader.rb +72 -0
  33. data/lib/language_operator/cli/commands/agent/base.rb +837 -0
  34. data/lib/language_operator/cli/commands/agent/code_operations.rb +102 -0
  35. data/lib/language_operator/cli/commands/agent/helpers/cluster_llm_client.rb +116 -0
  36. data/lib/language_operator/cli/commands/agent/helpers/code_parser.rb +115 -0
  37. data/lib/language_operator/cli/commands/agent/helpers/synthesis_watcher.rb +96 -0
  38. data/lib/language_operator/cli/commands/agent/learning.rb +289 -0
  39. data/lib/language_operator/cli/commands/agent/lifecycle.rb +102 -0
  40. data/lib/language_operator/cli/commands/agent/logs.rb +125 -0
  41. data/lib/language_operator/cli/commands/agent/workspace.rb +327 -0
  42. data/lib/language_operator/cli/commands/cluster.rb +129 -84
  43. data/lib/language_operator/cli/commands/install.rb +1 -1
  44. data/lib/language_operator/cli/commands/model/base.rb +215 -0
  45. data/lib/language_operator/cli/commands/model/test.rb +165 -0
  46. data/lib/language_operator/cli/commands/persona.rb +16 -34
  47. data/lib/language_operator/cli/commands/quickstart.rb +3 -2
  48. data/lib/language_operator/cli/commands/status.rb +40 -67
  49. data/lib/language_operator/cli/commands/system/base.rb +44 -0
  50. data/lib/language_operator/cli/commands/system/exec.rb +147 -0
  51. data/lib/language_operator/cli/commands/system/helpers/llm_synthesis.rb +183 -0
  52. data/lib/language_operator/cli/commands/system/helpers/pod_manager.rb +212 -0
  53. data/lib/language_operator/cli/commands/system/helpers/template_loader.rb +57 -0
  54. data/lib/language_operator/cli/commands/system/helpers/template_validator.rb +174 -0
  55. data/lib/language_operator/cli/commands/system/schema.rb +92 -0
  56. data/lib/language_operator/cli/commands/system/synthesis_template.rb +151 -0
  57. data/lib/language_operator/cli/commands/system/synthesize.rb +224 -0
  58. data/lib/language_operator/cli/commands/system/validate_template.rb +130 -0
  59. data/lib/language_operator/cli/commands/tool/base.rb +271 -0
  60. data/lib/language_operator/cli/commands/tool/install.rb +255 -0
  61. data/lib/language_operator/cli/commands/tool/search.rb +69 -0
  62. data/lib/language_operator/cli/commands/tool/test.rb +115 -0
  63. data/lib/language_operator/cli/commands/use.rb +29 -6
  64. data/lib/language_operator/cli/errors/handler.rb +20 -17
  65. data/lib/language_operator/cli/errors/suggestions.rb +3 -5
  66. data/lib/language_operator/cli/errors/thor_errors.rb +55 -0
  67. data/lib/language_operator/cli/formatters/code_formatter.rb +4 -11
  68. data/lib/language_operator/cli/formatters/log_formatter.rb +8 -15
  69. data/lib/language_operator/cli/formatters/progress_formatter.rb +6 -8
  70. data/lib/language_operator/cli/formatters/status_formatter.rb +26 -7
  71. data/lib/language_operator/cli/formatters/table_formatter.rb +47 -36
  72. data/lib/language_operator/cli/formatters/value_formatter.rb +75 -0
  73. data/lib/language_operator/cli/helpers/cluster_context.rb +5 -3
  74. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +2 -1
  75. data/lib/language_operator/cli/helpers/label_utils.rb +97 -0
  76. data/lib/language_operator/{ux/concerns/provider_helpers.rb → cli/helpers/provider_helper.rb} +10 -29
  77. data/lib/language_operator/cli/helpers/schedule_builder.rb +21 -1
  78. data/lib/language_operator/cli/helpers/user_prompts.rb +19 -11
  79. data/lib/language_operator/cli/helpers/ux_helper.rb +538 -0
  80. data/lib/language_operator/{ux/concerns/input_validation.rb → cli/helpers/validation_helper.rb} +13 -66
  81. data/lib/language_operator/cli/main.rb +50 -40
  82. data/lib/language_operator/cli/templates/tools/generic.yaml +3 -0
  83. data/lib/language_operator/cli/wizards/agent_wizard.rb +12 -20
  84. data/lib/language_operator/cli/wizards/model_wizard.rb +271 -0
  85. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +8 -34
  86. data/lib/language_operator/client/base.rb +28 -0
  87. data/lib/language_operator/client/config.rb +4 -1
  88. data/lib/language_operator/client/mcp_connector.rb +1 -1
  89. data/lib/language_operator/config/cluster_config.rb +3 -2
  90. data/lib/language_operator/config.rb +38 -11
  91. data/lib/language_operator/constants/kubernetes_labels.rb +80 -0
  92. data/lib/language_operator/constants.rb +13 -0
  93. data/lib/language_operator/dsl/http.rb +127 -10
  94. data/lib/language_operator/dsl.rb +153 -6
  95. data/lib/language_operator/errors.rb +50 -0
  96. data/lib/language_operator/kubernetes/client.rb +11 -6
  97. data/lib/language_operator/kubernetes/resource_builder.rb +58 -84
  98. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  99. data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
  100. data/lib/language_operator/type_coercion.rb +118 -34
  101. data/lib/language_operator/utils/secure_path.rb +74 -0
  102. data/lib/language_operator/utils.rb +7 -0
  103. data/lib/language_operator/validators.rb +54 -2
  104. data/lib/language_operator/version.rb +1 -1
  105. data/synth/001/Makefile +10 -2
  106. data/synth/001/agent.rb +16 -15
  107. data/synth/001/output.log +27 -10
  108. data/synth/002/Makefile +10 -2
  109. data/synth/003/Makefile +1 -1
  110. data/synth/003/README.md +205 -133
  111. data/synth/003/agent.optimized.rb +66 -0
  112. data/synth/003/agent.synthesized.rb +41 -0
  113. metadata +111 -35
  114. data/docs/dsl/agent-reference.md +0 -604
  115. data/docs/dsl/mcp-integration.md +0 -1177
  116. data/docs/dsl/webhooks.md +0 -932
  117. data/docs/dsl/workflows.md +0 -744
  118. data/lib/language_operator/cli/commands/agent.rb +0 -1712
  119. data/lib/language_operator/cli/commands/model.rb +0 -366
  120. data/lib/language_operator/cli/commands/system.rb +0 -1259
  121. data/lib/language_operator/cli/commands/tool.rb +0 -654
  122. data/lib/language_operator/cli/formatters/optimization_formatter.rb +0 -226
  123. data/lib/language_operator/cli/helpers/pastel_helper.rb +0 -24
  124. data/lib/language_operator/learning/adapters/base_adapter.rb +0 -149
  125. data/lib/language_operator/learning/adapters/jaeger_adapter.rb +0 -221
  126. data/lib/language_operator/learning/adapters/signoz_adapter.rb +0 -435
  127. data/lib/language_operator/learning/adapters/tempo_adapter.rb +0 -239
  128. data/lib/language_operator/learning/optimizer.rb +0 -319
  129. data/lib/language_operator/learning/pattern_detector.rb +0 -260
  130. data/lib/language_operator/learning/task_synthesizer.rb +0 -288
  131. data/lib/language_operator/learning/trace_analyzer.rb +0 -285
  132. data/lib/language_operator/templates/task_synthesis.tmpl +0 -98
  133. data/lib/language_operator/ux/base.rb +0 -81
  134. data/lib/language_operator/ux/concerns/README.md +0 -155
  135. data/lib/language_operator/ux/concerns/headings.rb +0 -90
  136. data/lib/language_operator/ux/create_agent.rb +0 -255
  137. data/lib/language_operator/ux/create_model.rb +0 -267
  138. data/lib/language_operator/ux/quickstart.rb +0 -594
  139. data/synth/003/agent.rb +0 -41
  140. data/synth/003/output.log +0 -68
  141. /data/docs/{architecture/agent-runtime.md → agent-internals.md} +0 -0
  142. /data/docs/{dsl/chat-endpoints.md → chat-endpoints.md} +0 -0
  143. /data/docs/{dsl/SCHEMA_VERSION.md → schema-versioning.md} +0 -0
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'yaml'
5
+
6
+ module LanguageOperator
7
+ module CLI
8
+ module Commands
9
+ module System
10
+ # DSL schema export command
11
+ module Schema
12
+ def self.included(base)
13
+ base.class_eval do
14
+ desc 'schema', 'Export the DSL schema in various formats'
15
+ long_desc <<-DESC
16
+ Export the Language Operator Agent DSL schema in various formats.
17
+
18
+ The schema documents all available DSL methods, parameters, validation
19
+ patterns, and structure. Useful for template validation, documentation
20
+ generation, and IDE autocomplete.
21
+
22
+ Examples:
23
+ # Export JSON schema (default)
24
+ aictl system schema
25
+
26
+ # Export as YAML
27
+ aictl system schema --format yaml
28
+
29
+ # Export OpenAPI 3.0 specification
30
+ aictl system schema --format openapi
31
+
32
+ # Show schema version only
33
+ aictl system schema --version
34
+
35
+ # Save to file
36
+ aictl system schema > schema.json
37
+ aictl system schema --format openapi > openapi.json
38
+ DESC
39
+ option :format, type: :string, default: 'json', desc: 'Output format (json, yaml, openapi)'
40
+ option :version, type: :boolean, default: false, desc: 'Show schema version only'
41
+ def schema
42
+ handle_command_error('generate schema') do
43
+ # Handle version flag
44
+ if options[:version]
45
+ puts Dsl::Schema.version
46
+ return
47
+ end
48
+
49
+ # Generate schema based on format
50
+ format = options[:format].downcase
51
+ case format
52
+ when 'json'
53
+ output_json_schema
54
+ when 'yaml'
55
+ output_yaml_schema
56
+ when 'openapi'
57
+ output_openapi_schema
58
+ else
59
+ Formatters::ProgressFormatter.error("Invalid format: #{format}")
60
+ puts
61
+ puts 'Supported formats: json, yaml, openapi'
62
+ exit 1
63
+ end
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ # Output JSON Schema v7
70
+ def output_json_schema
71
+ schema = Dsl::Schema.to_json_schema
72
+ puts JSON.pretty_generate(schema)
73
+ end
74
+
75
+ # Output YAML Schema
76
+ def output_yaml_schema
77
+ schema = Dsl::Schema.to_json_schema
78
+ puts YAML.dump(schema.transform_keys(&:to_s))
79
+ end
80
+
81
+ # Output OpenAPI 3.0 specification
82
+ def output_openapi_schema
83
+ spec = Dsl::Schema.to_openapi
84
+ puts JSON.pretty_generate(spec)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'yaml'
5
+
6
+ module LanguageOperator
7
+ module CLI
8
+ module Commands
9
+ module System
10
+ # Synthesis template export command
11
+ module SynthesisTemplate
12
+ def self.included(base)
13
+ base.class_eval do
14
+ desc 'synthesis-template', 'Export synthesis templates for agent code generation'
15
+ long_desc <<-DESC
16
+ Export the synthesis templates used by the Language Operator to generate
17
+ agent code from natural language instructions.
18
+
19
+ These templates are used by the operator's synthesis engine to convert
20
+ user instructions into executable Ruby DSL code.
21
+
22
+ Examples:
23
+ # Export agent synthesis template (default)
24
+ aictl system synthesis-template
25
+
26
+ # Export persona distillation template
27
+ aictl system synthesis-template --type persona
28
+
29
+ # Export as JSON with schema included
30
+ aictl system synthesis-template --format json --with-schema
31
+
32
+ # Export as YAML
33
+ aictl system synthesis-template --format yaml
34
+
35
+ # Validate template syntax
36
+ aictl system synthesis-template --validate
37
+
38
+ # Save to file
39
+ aictl system synthesis-template > agent_synthesis.tmpl
40
+ DESC
41
+ option :format, type: :string, default: 'template', desc: 'Output format (template, json, yaml)'
42
+ option :type, type: :string, default: 'agent', desc: 'Template type (agent, persona)'
43
+ option :with_schema, type: :boolean, default: false, desc: 'Include DSL schema in output'
44
+ option :validate, type: :boolean, default: false, desc: 'Validate template syntax'
45
+ def synthesis_template
46
+ handle_command_error('load template') do
47
+ # Validate type
48
+ template_type = options[:type].downcase
49
+ unless %w[agent persona].include?(template_type)
50
+ Formatters::ProgressFormatter.error("Invalid template type: #{template_type}")
51
+ puts
52
+ puts 'Supported types: agent, persona'
53
+ exit 1
54
+ end
55
+
56
+ # Load template
57
+ template_content = load_template(template_type)
58
+
59
+ # Validate if requested
60
+ if options[:validate]
61
+ validation_result = validate_template_content(template_content, template_type)
62
+
63
+ # Display warnings if any
64
+ unless validation_result[:warnings].empty?
65
+ Formatters::ProgressFormatter.warn('Template validation warnings:')
66
+ validation_result[:warnings].each do |warning|
67
+ puts " ⚠ #{warning}"
68
+ end
69
+ puts
70
+ end
71
+
72
+ # Display errors and exit if validation failed
73
+ if validation_result[:valid]
74
+ Formatters::ProgressFormatter.success('Template validation passed')
75
+ return
76
+ else
77
+ Formatters::ProgressFormatter.error('Template validation failed:')
78
+ validation_result[:errors].each do |error|
79
+ puts " ✗ #{error}"
80
+ end
81
+ exit 1
82
+ end
83
+ end
84
+
85
+ # Generate output based on format
86
+ format = options[:format].downcase
87
+ case format
88
+ when 'template'
89
+ output_template_format(template_content)
90
+ when 'json'
91
+ output_json_format(template_content, template_type)
92
+ when 'yaml'
93
+ output_yaml_format(template_content, template_type)
94
+ else
95
+ Formatters::ProgressFormatter.error("Invalid format: #{format}")
96
+ puts
97
+ puts 'Supported formats: template, json, yaml'
98
+ exit 1
99
+ end
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ # Output raw template format
106
+ def output_template_format(content)
107
+ puts content
108
+ end
109
+
110
+ # Output JSON format with metadata
111
+ def output_json_format(content, type)
112
+ data = {
113
+ version: Dsl::Schema.version,
114
+ template_type: type,
115
+ template: content
116
+ }
117
+
118
+ if options[:with_schema]
119
+ data[:schema] = Dsl::Schema.to_json_schema
120
+ data[:safe_agent_methods] = Dsl::Schema.safe_agent_methods
121
+ data[:safe_tool_methods] = Dsl::Schema.safe_tool_methods
122
+ data[:safe_helper_methods] = Dsl::Schema.safe_helper_methods
123
+ end
124
+
125
+ puts JSON.pretty_generate(data)
126
+ end
127
+
128
+ # Output YAML format with metadata
129
+ def output_yaml_format(content, type)
130
+ data = {
131
+ 'version' => Dsl::Schema.version,
132
+ 'template_type' => type,
133
+ 'template' => content
134
+ }
135
+
136
+ if options[:with_schema]
137
+ data['schema'] = Dsl::Schema.to_json_schema.transform_keys(&:to_s)
138
+ data['safe_agent_methods'] = Dsl::Schema.safe_agent_methods
139
+ data['safe_tool_methods'] = Dsl::Schema.safe_tool_methods
140
+ data['safe_helper_methods'] = Dsl::Schema.safe_helper_methods
141
+ end
142
+
143
+ puts YAML.dump(data)
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ module CLI
5
+ module Commands
6
+ module System
7
+ # Agent code synthesis command
8
+ module Synthesize
9
+ def self.included(base)
10
+ base.class_eval do
11
+ desc 'synthesize [INSTRUCTIONS]', 'Synthesize agent code from natural language instructions'
12
+ long_desc <<-DESC
13
+ Synthesize agent code by converting natural language instructions
14
+ into Ruby DSL code without creating an actual agent.
15
+
16
+ This command uses a LanguageModel resource from your cluster to generate
17
+ agent code. If --model is not specified, the first available model will
18
+ be auto-selected.
19
+
20
+ Instructions can be provided either as a command argument or via STDIN.
21
+ If no argument is provided, the command will read from STDIN.
22
+
23
+ This command helps you validate your instructions and understand how the
24
+ synthesis engine interprets them. Use --dry-run to see the prompt that
25
+ would be sent to the LLM, or run without it to generate actual code.
26
+
27
+ Examples:
28
+ # Test with dry-run (show prompt only)
29
+ aictl system synthesize "Monitor GitHub issues daily" --dry-run
30
+
31
+ # Generate code from instructions (auto-selects first available model)
32
+ aictl system synthesize "Send daily reports to Slack"
33
+
34
+ # Use a specific cluster model
35
+ aictl system synthesize "Process webhooks from GitHub" --model my-claude
36
+
37
+ # Output raw code without formatting (useful for piping to files)
38
+ aictl system synthesize "Monitor logs" --raw > agent.rb
39
+
40
+ # Read instructions from STDIN
41
+ cat instructions.txt | aictl system synthesize > agent.rb
42
+
43
+ # Read from STDIN with pipe
44
+ echo "Monitor GitHub issues" | aictl system synthesize --raw
45
+
46
+ # Specify custom agent name and tools
47
+ aictl system synthesize "Process webhooks from GitHub" \\
48
+ --agent-name github-processor \\
49
+ --tools github,slack \\
50
+ --model my-gpt4
51
+ DESC
52
+ option :agent_name, type: :string, default: 'test-agent', desc: 'Name for the test agent'
53
+ option :tools, type: :string, desc: 'Comma-separated list of available tools'
54
+ option :models, type: :string, desc: 'Comma-separated list of available models (from cluster)'
55
+ option :model, type: :string, desc: 'Model to use for synthesis (defaults to first available in cluster)'
56
+ option :dry_run, type: :boolean, default: false, desc: 'Show prompt without calling LLM'
57
+ option :raw, type: :boolean, default: false, desc: 'Output only the raw code without formatting'
58
+
59
+ def synthesize(instructions = nil)
60
+ handle_command_error('synthesize agent') do
61
+ # Read instructions from STDIN if not provided as argument
62
+ if instructions.nil? || instructions.strip.empty?
63
+ if $stdin.tty?
64
+ Formatters::ProgressFormatter.error('No instructions provided')
65
+ puts
66
+ puts 'Provide instructions either as an argument or via STDIN:'
67
+ puts ' aictl system synthesize "Your instructions here"'
68
+ puts ' cat instructions.txt | aictl system synthesize'
69
+ exit 1
70
+ else
71
+ instructions = $stdin.read.strip
72
+ if instructions.empty?
73
+ Formatters::ProgressFormatter.error('No instructions provided')
74
+ puts
75
+ puts 'Provide instructions either as an argument or via STDIN:'
76
+ puts ' aictl system synthesize "Your instructions here"'
77
+ puts ' cat instructions.txt | aictl system synthesize'
78
+ exit 1
79
+ end
80
+ end
81
+ end
82
+ # Select model to use for synthesis
83
+ selected_model = select_synthesis_model
84
+
85
+ # Load synthesis template
86
+ template_content = load_bundled_template('agent')
87
+
88
+ # Detect temporal intent from instructions
89
+ temporal_intent = detect_temporal_intent(instructions)
90
+
91
+ # Prepare template data
92
+ template_data = {
93
+ 'Instructions' => instructions,
94
+ 'AgentName' => options[:agent_name],
95
+ 'ToolsList' => format_tools_list(options[:tools]),
96
+ 'ModelsList' => format_models_list(options[:models]),
97
+ 'TemporalIntent' => temporal_intent,
98
+ 'PersonaSection' => '',
99
+ 'ScheduleSection' => temporal_intent == 'scheduled' ? ' schedule "0 */1 * * *" # Example hourly schedule' : '',
100
+ 'ScheduleRules' => temporal_intent == 'scheduled' ? "\n2. Include schedule with cron expression\n3. Set mode to :scheduled\n4. " : "\n2. ",
101
+ 'ConstraintsSection' => '',
102
+ 'ErrorContext' => nil
103
+ }
104
+
105
+ # Render template (Go-style template syntax)
106
+ rendered_prompt = render_go_template(template_content, template_data)
107
+
108
+ if options[:dry_run]
109
+ # Show the prompt that would be sent
110
+ puts 'Synthesis Prompt Preview'
111
+ puts '=' * 80
112
+ puts
113
+ puts rendered_prompt
114
+ puts
115
+ puts '=' * 80
116
+ Formatters::ProgressFormatter.success('Dry-run complete - prompt displayed above')
117
+ return
118
+ end
119
+
120
+ # Call LLM to generate code (no output - just do it)
121
+ llm_response = call_llm_for_synthesis(rendered_prompt, selected_model)
122
+
123
+ # Extract Ruby code from response
124
+ generated_code = extract_ruby_code(llm_response)
125
+
126
+ if generated_code.nil?
127
+ Formatters::ProgressFormatter.error('Failed to extract Ruby code from LLM response')
128
+ puts
129
+ puts 'LLM Response:'
130
+ puts llm_response
131
+ exit 1
132
+ end
133
+
134
+ # Handle raw output
135
+ if options[:raw]
136
+ puts generated_code
137
+ return
138
+ end
139
+
140
+ # Display formatted code
141
+ highlighted_code = highlight_ruby_code(generated_code)
142
+
143
+ puts highlighted_code
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ # Detect temporal intent from instructions (scheduled vs autonomous)
150
+ def detect_temporal_intent(instructions)
151
+ temporal_keywords = {
152
+ scheduled: %w[daily weekly hourly monthly schedule cron every day week hour minute],
153
+ autonomous: %w[monitor watch continuously constantly always loop]
154
+ }
155
+
156
+ instructions_lower = instructions.downcase
157
+
158
+ # Check for scheduled keywords
159
+ scheduled_matches = temporal_keywords[:scheduled].count { |keyword| instructions_lower.include?(keyword) }
160
+ autonomous_matches = temporal_keywords[:autonomous].count { |keyword| instructions_lower.include?(keyword) }
161
+
162
+ scheduled_matches > autonomous_matches ? 'scheduled' : 'autonomous'
163
+ end
164
+
165
+ # Format tools list for template
166
+ def format_tools_list(tools_str)
167
+ return 'No tools specified' if tools_str.nil? || tools_str.strip.empty?
168
+
169
+ tools = tools_str.split(',').map(&:strip)
170
+ tools.map { |tool| "- #{tool}" }.join("\n")
171
+ end
172
+
173
+ # Format models list for template
174
+ def format_models_list(models_str)
175
+ # If not specified, try to detect from cluster
176
+ if models_str.nil? || models_str.strip.empty?
177
+ models = detect_available_models
178
+ return models.map { |model| "- #{model}" }.join("\n") unless models.empty?
179
+
180
+ return 'No models available (run: aictl model list)'
181
+ end
182
+
183
+ models = models_str.split(',').map(&:strip)
184
+ models.map { |model| "- #{model}" }.join("\n")
185
+ end
186
+
187
+ # Detect available models from cluster
188
+ def detect_available_models
189
+ models = ctx.client.list_resources('LanguageModel', namespace: ctx.namespace)
190
+ models.map { |m| m.dig('metadata', 'name') }
191
+ rescue StandardError => e
192
+ Formatters::ProgressFormatter.error("Failed to list models from cluster: #{e.message}")
193
+ []
194
+ end
195
+
196
+ # Select model to use for synthesis
197
+ def select_synthesis_model
198
+ # If --model option specified, use it
199
+ return options[:model] if options[:model]
200
+
201
+ # Otherwise, auto-select from available cluster models
202
+ available_models = detect_available_models
203
+
204
+ if available_models.empty?
205
+ Formatters::ProgressFormatter.error('No models available in cluster')
206
+ puts
207
+ puts 'Please create a model first:'
208
+ puts ' aictl model create'
209
+ puts
210
+ puts 'Or list existing models:'
211
+ puts ' aictl model list'
212
+ exit 1
213
+ end
214
+
215
+ # Auto-select first available model (silently)
216
+ available_models.first
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ module CLI
5
+ module Commands
6
+ module System
7
+ # Template validation command
8
+ module ValidateTemplate
9
+ def self.included(base)
10
+ base.class_eval do
11
+ desc 'validate_template', 'Validate synthesis template against DSL schema'
12
+ long_desc <<-DESC
13
+ Validate a synthesis template file against the DSL schema.
14
+
15
+ Extracts Ruby code examples from the template and validates each example
16
+ against the Language Operator Agent DSL schema. Checks for dangerous
17
+ methods, syntax errors, and compliance with safe coding practices.
18
+
19
+ Examples:
20
+ # Validate a custom template file
21
+ aictl system validate_template --template /path/to/template.tmpl
22
+
23
+ # Validate the bundled agent template (default)
24
+ aictl system validate_template
25
+
26
+ # Validate the bundled persona template
27
+ aictl system validate_template --type persona
28
+
29
+ # Verbose output with all violations
30
+ aictl system validate_template --template mytemplate.tmpl --verbose
31
+ DESC
32
+ option :template, type: :string, desc: 'Path to template file (defaults to bundled template)'
33
+ option :type, type: :string, default: 'agent', desc: 'Template type if using bundled template (agent, persona)'
34
+ option :verbose, type: :boolean, default: false, desc: 'Show detailed violation information'
35
+ def validate_template
36
+ handle_command_error('validate template') do
37
+ # Determine template source
38
+ if options[:template]
39
+ # Load custom template from file
40
+ unless File.exist?(options[:template])
41
+ Formatters::ProgressFormatter.error("Template file not found: #{options[:template]}")
42
+ exit 1
43
+ end
44
+ template_content = File.read(options[:template])
45
+ template_name = File.basename(options[:template])
46
+ else
47
+ # Load bundled template
48
+ template_type = options[:type].downcase
49
+ unless %w[agent persona].include?(template_type)
50
+ Formatters::ProgressFormatter.error("Invalid template type: #{template_type}")
51
+ puts
52
+ puts 'Supported types: agent, persona'
53
+ exit 1
54
+ end
55
+ template_content = load_bundled_template(template_type)
56
+ template_name = "bundled #{template_type} template"
57
+ end
58
+
59
+ # Display header
60
+ puts "Validating template: #{template_name}"
61
+ puts '=' * 60
62
+ puts
63
+
64
+ # Extract code examples
65
+ code_examples = extract_code_examples(template_content)
66
+
67
+ if code_examples.empty?
68
+ Formatters::ProgressFormatter.warn('No Ruby code examples found in template')
69
+ puts
70
+ puts 'Templates should contain Ruby code blocks like:'
71
+ puts '```ruby'
72
+ puts 'agent "my-agent" do'
73
+ puts ' # ...'
74
+ puts 'end'
75
+ puts '```'
76
+ exit 1
77
+ end
78
+
79
+ puts "Found #{code_examples.size} code example(s)"
80
+ puts
81
+
82
+ # Validate each example
83
+ all_valid = true
84
+ code_examples.each_with_index do |example, idx|
85
+ puts "Example #{idx + 1} (starting at line #{example[:start_line]}):"
86
+ puts '-' * 40
87
+
88
+ result = validate_code_against_schema(example[:code])
89
+
90
+ if result[:valid] && result[:warnings].empty?
91
+ Formatters::ProgressFormatter.success('Valid - No issues found')
92
+ elsif result[:valid]
93
+ Formatters::ProgressFormatter.success('Valid - With warnings')
94
+ result[:warnings].each do |warn|
95
+ line = example[:start_line] + (warn[:location] || 0)
96
+ puts " ⚠ Line #{line}: #{warn[:message]}"
97
+ end
98
+ else
99
+ Formatters::ProgressFormatter.error('Invalid - Violations detected')
100
+ result[:errors].each do |err|
101
+ line = example[:start_line] + (err[:location] || 0)
102
+ puts " ✗ Line #{line}: #{err[:message]}"
103
+ puts " Type: #{err[:type]}" if options[:verbose]
104
+ end
105
+ all_valid = false
106
+ end
107
+
108
+ puts
109
+ end
110
+
111
+ # Final summary
112
+ puts '=' * 60
113
+ if all_valid
114
+ Formatters::ProgressFormatter.success('All examples are valid')
115
+ exit 0
116
+ else
117
+ Formatters::ProgressFormatter.error('Validation failed')
118
+ puts
119
+ puts 'Fix the violations above and run validation again.'
120
+ exit 1
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end