solid_agent 0.0.0 → 0.2.0
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 +4 -4
- data/CHANGELOG.md +68 -0
- data/LICENSE +21 -0
- data/README.md +321 -0
- data/Rakefile +32 -0
- data/docs/agent-md-spec.md +803 -0
- data/docs/parser-design.md +1369 -0
- data/docs/registry-api.md +882 -0
- data/examples/README.md +60 -0
- data/examples/manifests/changelog_writer.agent.md +81 -0
- data/examples/manifests/usage.rb +96 -0
- data/examples/memory_handoff/app/agents/researcher_agent.rb +36 -0
- data/examples/memory_handoff/app/agents/writer_agent.rb +41 -0
- data/examples/memory_handoff/usage.rb +45 -0
- data/examples/persistent_conversation/app/agents/support_agent.rb +59 -0
- data/examples/persistent_conversation/app/controllers/support_conversations_controller.rb +24 -0
- data/examples/persistent_conversation/app/views/agents/support/instructions.md.erb +8 -0
- data/examples/persistent_conversation/usage.rb +51 -0
- data/examples/reasoning/app/agents/analysis_agent.rb +52 -0
- data/examples/reasoning/usage.rb +52 -0
- data/examples/run_tracking/app/agents/report_agent.rb +30 -0
- data/examples/run_tracking/app/controllers/agent_runs_controller.rb +43 -0
- data/examples/run_tracking/app/jobs/document_analysis_job.rb +17 -0
- data/examples/run_tracking/app/services/document_analysis_run.rb +68 -0
- data/examples/run_tracking/usage.rb +85 -0
- data/examples/tool_streaming/app/agents/browser_agent.rb +65 -0
- data/examples/tool_streaming/app/channels/tool_status_channel.rb +24 -0
- data/examples/tool_streaming/app/views/browser_agent/tools/fetch_url.json.erb +15 -0
- data/examples/tool_streaming/usage.rb +47 -0
- data/lib/generators/solid_agent/agent/agent_generator.rb +95 -0
- data/lib/generators/solid_agent/agent/templates/action.text.erb +10 -0
- data/lib/generators/solid_agent/agent/templates/agent.rb.erb +93 -0
- data/lib/generators/solid_agent/context/context_generator.rb +124 -0
- data/lib/generators/solid_agent/context/templates/context_model.rb.erb +134 -0
- data/lib/generators/solid_agent/context/templates/create_context.rb.erb +32 -0
- data/lib/generators/solid_agent/context/templates/create_generations.rb.erb +46 -0
- data/lib/generators/solid_agent/context/templates/create_messages.rb.erb +37 -0
- data/lib/generators/solid_agent/context/templates/generation_model.rb.erb +51 -0
- data/lib/generators/solid_agent/context/templates/message_model.rb.erb +47 -0
- data/lib/generators/solid_agent/install/install_generator.rb +92 -0
- data/lib/generators/solid_agent/install/templates/agent_context.rb.erb +171 -0
- data/lib/generators/solid_agent/install/templates/agent_generation.rb.erb +76 -0
- data/lib/generators/solid_agent/install/templates/agent_memory.rb.erb +51 -0
- data/lib/generators/solid_agent/install/templates/agent_memory_entry.rb.erb +12 -0
- data/lib/generators/solid_agent/install/templates/agent_message.rb.erb +76 -0
- data/lib/generators/solid_agent/install/templates/agent_run.rb.erb +122 -0
- data/lib/generators/solid_agent/install/templates/create_agent_contexts.rb.erb +32 -0
- data/lib/generators/solid_agent/install/templates/create_agent_generations.rb.erb +51 -0
- data/lib/generators/solid_agent/install/templates/create_agent_memories.rb.erb +35 -0
- data/lib/generators/solid_agent/install/templates/create_agent_messages.rb.erb +38 -0
- data/lib/generators/solid_agent/install/templates/create_agent_runs.rb.erb +46 -0
- data/lib/generators/solid_agent/install/templates/initializer.rb.erb +51 -0
- data/lib/generators/solid_agent/manifest/manifest_generator.rb +209 -0
- data/lib/generators/solid_agent/manifest/templates/agent.md.erb +39 -0
- data/lib/generators/solid_agent/manifest/templates/prompt.erb +13 -0
- data/lib/generators/solid_agent/reasons/reasons_generator.rb +83 -0
- data/lib/generators/solid_agent/reasons/templates/add_reasoning_columns.rb.erb +12 -0
- data/lib/generators/solid_agent/tool/templates/tool.json.erb +19 -0
- data/lib/generators/solid_agent/tool/tool_generator.rb +117 -0
- data/lib/solid_agent/agent_manifest/agent_builder.rb +323 -0
- data/lib/solid_agent/agent_manifest/errors.rb +26 -0
- data/lib/solid_agent/agent_manifest/exporter_registry.rb +117 -0
- data/lib/solid_agent/agent_manifest/exporters/agent_md_exporter.rb +115 -0
- data/lib/solid_agent/agent_manifest/exporters/base_exporter.rb +152 -0
- data/lib/solid_agent/agent_manifest/exporters/crewai_exporter.rb +125 -0
- data/lib/solid_agent/agent_manifest/exporters/dotprompt_exporter.rb +92 -0
- data/lib/solid_agent/agent_manifest/input_schema.rb +154 -0
- data/lib/solid_agent/agent_manifest/manifest.rb +306 -0
- data/lib/solid_agent/agent_manifest/parser_registry.rb +185 -0
- data/lib/solid_agent/agent_manifest/parsers/agent_md_parser.rb +87 -0
- data/lib/solid_agent/agent_manifest/parsers/base_parser.rb +223 -0
- data/lib/solid_agent/agent_manifest/parsers/crewai_parser.rb +201 -0
- data/lib/solid_agent/agent_manifest/parsers/dotprompt_parser.rb +122 -0
- data/lib/solid_agent/agent_manifest/parsers/github_prompt_parser.rb +143 -0
- data/lib/solid_agent/agent_manifest/picoschema.rb +254 -0
- data/lib/solid_agent/agent_manifest/registry/auth.rb +103 -0
- data/lib/solid_agent/agent_manifest/registry/client.rb +384 -0
- data/lib/solid_agent/agent_manifest/resource.rb +103 -0
- data/lib/solid_agent/agent_manifest/tool.rb +160 -0
- data/lib/solid_agent/agent_manifest/validator.rb +368 -0
- data/lib/solid_agent/agent_manifest.rb +381 -0
- data/lib/solid_agent/engine.rb +16 -0
- data/lib/solid_agent/has_context.rb +670 -0
- data/lib/solid_agent/has_memory.rb +136 -0
- data/lib/solid_agent/has_reasons.rb +230 -0
- data/lib/solid_agent/has_tools.rb +257 -0
- data/lib/solid_agent/model_naming.rb +42 -0
- data/lib/solid_agent/model_pricing.rb +93 -0
- data/lib/solid_agent/reasonable/reason.rb +205 -0
- data/lib/solid_agent/reasonable.rb +181 -0
- data/lib/solid_agent/records/agent.rb +520 -0
- data/lib/solid_agent/records/agent_run.rb +520 -0
- data/lib/solid_agent/records/agent_template.rb +142 -0
- data/lib/solid_agent/records/agent_version.rb +141 -0
- data/lib/solid_agent/records/ownable.rb +130 -0
- data/lib/solid_agent/records.rb +152 -0
- data/lib/solid_agent/run_fingerprint.rb +51 -0
- data/lib/solid_agent/streams_tool_updates.rb +178 -0
- data/lib/solid_agent/tool_cache.rb +91 -0
- data/lib/solid_agent/version.rb +5 -0
- data/lib/solid_agent.rb +95 -0
- data/sig/solid_agent.rbs +4 -0
- metadata +174 -14
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgent
|
|
4
|
+
module AgentManifest
|
|
5
|
+
# Validator provides comprehensive validation for Manifests.
|
|
6
|
+
#
|
|
7
|
+
# Goes beyond ActiveModel validations to check semantic correctness,
|
|
8
|
+
# tool definitions, schema validity, and cross-field consistency.
|
|
9
|
+
#
|
|
10
|
+
# @example Validate a manifest
|
|
11
|
+
# errors = Validator.validate(manifest)
|
|
12
|
+
# if errors.empty?
|
|
13
|
+
# puts "Manifest is valid!"
|
|
14
|
+
# else
|
|
15
|
+
# errors.each { |e| puts "Error: #{e}" }
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# @example Check validity
|
|
19
|
+
# Validator.valid?(manifest) # => true/false
|
|
20
|
+
#
|
|
21
|
+
# @example Validate and raise
|
|
22
|
+
# Validator.validate!(manifest) # raises ValidationError if invalid
|
|
23
|
+
#
|
|
24
|
+
class Validator
|
|
25
|
+
# Known model providers for validation
|
|
26
|
+
KNOWN_PROVIDERS = %w[anthropic openai google meta cohere mistral].freeze
|
|
27
|
+
|
|
28
|
+
# Reserved names that cannot be used
|
|
29
|
+
RESERVED_NAMES = %w[agent manifest config settings default system].freeze
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
# Validate a manifest and return array of error messages
|
|
33
|
+
#
|
|
34
|
+
# @param manifest [Manifest] Manifest to validate
|
|
35
|
+
# @param strict [Boolean] Enable strict validation
|
|
36
|
+
# @return [Array<String>] Error messages (empty if valid)
|
|
37
|
+
def validate(manifest, strict: false)
|
|
38
|
+
errors = []
|
|
39
|
+
|
|
40
|
+
errors.concat(validate_meta(manifest))
|
|
41
|
+
errors.concat(validate_model(manifest))
|
|
42
|
+
errors.concat(validate_tools(manifest))
|
|
43
|
+
errors.concat(validate_resources(manifest))
|
|
44
|
+
errors.concat(validate_schemas(manifest))
|
|
45
|
+
errors.concat(validate_extensions(manifest))
|
|
46
|
+
errors.concat(validate_content(manifest))
|
|
47
|
+
|
|
48
|
+
errors.concat(validate_strict(manifest)) if strict
|
|
49
|
+
|
|
50
|
+
errors
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Check if a manifest is valid
|
|
54
|
+
#
|
|
55
|
+
# @param manifest [Manifest] Manifest to validate
|
|
56
|
+
# @param strict [Boolean] Enable strict validation
|
|
57
|
+
# @return [Boolean]
|
|
58
|
+
def valid?(manifest, strict: false)
|
|
59
|
+
validate(manifest, strict: strict).empty?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Validate and raise if invalid
|
|
63
|
+
#
|
|
64
|
+
# @param manifest [Manifest] Manifest to validate
|
|
65
|
+
# @param strict [Boolean] Enable strict validation
|
|
66
|
+
# @raise [ValidationError] if manifest is invalid
|
|
67
|
+
# @return [Manifest] The validated manifest
|
|
68
|
+
def validate!(manifest, strict: false)
|
|
69
|
+
errors = validate(manifest, strict: strict)
|
|
70
|
+
raise ValidationError, "Invalid manifest: #{errors.join('; ')}" if errors.any?
|
|
71
|
+
|
|
72
|
+
manifest
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# Validate meta fields
|
|
78
|
+
def validate_meta(manifest)
|
|
79
|
+
errors = []
|
|
80
|
+
|
|
81
|
+
# Name is required
|
|
82
|
+
if manifest.name.blank?
|
|
83
|
+
errors << "name is required"
|
|
84
|
+
else
|
|
85
|
+
# Name format
|
|
86
|
+
unless manifest.name =~ /\A[a-z][a-z0-9\-]*\z/
|
|
87
|
+
errors << "name must start with lowercase letter and contain only lowercase letters, numbers, and hyphens"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Name length
|
|
91
|
+
if manifest.name.length > 100
|
|
92
|
+
errors << "name must be 100 characters or less"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Reserved names
|
|
96
|
+
if RESERVED_NAMES.include?(manifest.name)
|
|
97
|
+
errors << "name '#{manifest.name}' is reserved"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Version format
|
|
102
|
+
if manifest.version.present?
|
|
103
|
+
unless manifest.version =~ /\A\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?\z/
|
|
104
|
+
errors << "version must follow semantic versioning (e.g., 1.0.0, 1.0.0-beta.1)"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Tags
|
|
109
|
+
if manifest.tags.is_a?(Array)
|
|
110
|
+
manifest.tags.each do |tag|
|
|
111
|
+
unless tag =~ /\A[a-z][a-z0-9\-]*\z/
|
|
112
|
+
errors << "tag '#{tag}' must be lowercase with hyphens only"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Repository URL
|
|
118
|
+
if manifest.repository.present?
|
|
119
|
+
unless manifest.repository =~ %r{\Ahttps?://}
|
|
120
|
+
errors << "repository must be a valid URL"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
errors
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Validate model configuration
|
|
128
|
+
def validate_model(manifest)
|
|
129
|
+
errors = []
|
|
130
|
+
|
|
131
|
+
return errors if manifest.model.blank?
|
|
132
|
+
|
|
133
|
+
# Model format - should be provider/model
|
|
134
|
+
if manifest.model.include?("/")
|
|
135
|
+
provider, model_name = manifest.model.split("/", 2)
|
|
136
|
+
|
|
137
|
+
# Warn about unknown providers (not an error)
|
|
138
|
+
unless KNOWN_PROVIDERS.include?(provider)
|
|
139
|
+
# This is just informational, not an error
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
if model_name.blank?
|
|
143
|
+
errors << "model identifier must include model name after provider"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Config validation
|
|
148
|
+
if manifest.config.present?
|
|
149
|
+
# Temperature range
|
|
150
|
+
temp = manifest.config[:temperature] || manifest.config["temperature"]
|
|
151
|
+
if temp && (temp.to_f < 0 || temp.to_f > 2)
|
|
152
|
+
errors << "temperature must be between 0 and 2"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Max tokens
|
|
156
|
+
max_tokens = manifest.config[:max_tokens] || manifest.config["max_tokens"]
|
|
157
|
+
if max_tokens && max_tokens.to_i <= 0
|
|
158
|
+
errors << "max_tokens must be a positive integer"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Top P
|
|
162
|
+
top_p = manifest.config[:top_p] || manifest.config["top_p"]
|
|
163
|
+
if top_p && (top_p.to_f < 0 || top_p.to_f > 1)
|
|
164
|
+
errors << "top_p must be between 0 and 1"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
errors
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Validate tool definitions
|
|
172
|
+
def validate_tools(manifest)
|
|
173
|
+
errors = []
|
|
174
|
+
|
|
175
|
+
return errors unless manifest.tools.is_a?(Array)
|
|
176
|
+
|
|
177
|
+
tool_names = Set.new
|
|
178
|
+
|
|
179
|
+
manifest.tools.each_with_index do |tool, index|
|
|
180
|
+
prefix = "tools[#{index}]"
|
|
181
|
+
|
|
182
|
+
if tool.reference?
|
|
183
|
+
# Reference validation
|
|
184
|
+
if tool.ref.blank?
|
|
185
|
+
errors << "#{prefix}: $ref cannot be blank"
|
|
186
|
+
end
|
|
187
|
+
else
|
|
188
|
+
# Inline tool validation
|
|
189
|
+
if tool.name.blank?
|
|
190
|
+
errors << "#{prefix}: name is required for inline tools"
|
|
191
|
+
else
|
|
192
|
+
# Check for duplicates
|
|
193
|
+
if tool_names.include?(tool.name)
|
|
194
|
+
errors << "#{prefix}: duplicate tool name '#{tool.name}'"
|
|
195
|
+
end
|
|
196
|
+
tool_names.add(tool.name)
|
|
197
|
+
|
|
198
|
+
# Name format
|
|
199
|
+
unless tool.name =~ /\A[a-zA-Z][a-zA-Z0-9_]*\z/
|
|
200
|
+
errors << "#{prefix}: name '#{tool.name}' must start with letter and contain only alphanumeric and underscore"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Input schema validation
|
|
205
|
+
if tool.input_schema.present?
|
|
206
|
+
unless tool.input_schema.is_a?(Hash)
|
|
207
|
+
errors << "#{prefix}: inputSchema must be an object"
|
|
208
|
+
else
|
|
209
|
+
unless tool.input_schema["type"] == "object"
|
|
210
|
+
errors << "#{prefix}: inputSchema type should be 'object'"
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
errors
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Validate resource definitions
|
|
221
|
+
def validate_resources(manifest)
|
|
222
|
+
errors = []
|
|
223
|
+
|
|
224
|
+
return errors unless manifest.resources.is_a?(Array)
|
|
225
|
+
|
|
226
|
+
manifest.resources.each_with_index do |resource, index|
|
|
227
|
+
prefix = "resources[#{index}]"
|
|
228
|
+
|
|
229
|
+
if resource.uri.blank?
|
|
230
|
+
errors << "#{prefix}: uri is required"
|
|
231
|
+
else
|
|
232
|
+
# Basic URI format check
|
|
233
|
+
unless resource.uri =~ %r{\A[a-z][a-z0-9+.-]*://}i
|
|
234
|
+
errors << "#{prefix}: uri must be a valid URI with scheme"
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# MIME type format
|
|
239
|
+
if resource.mime_type.present?
|
|
240
|
+
unless resource.mime_type =~ %r{\A[a-z]+/[a-z0-9.+-]+\z}i
|
|
241
|
+
errors << "#{prefix}: mimeType format is invalid"
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
errors
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Validate schemas
|
|
250
|
+
def validate_schemas(manifest)
|
|
251
|
+
errors = []
|
|
252
|
+
|
|
253
|
+
# Input schema
|
|
254
|
+
if manifest.input_schema.present?
|
|
255
|
+
begin
|
|
256
|
+
manifest.input_schema.to_json_schema
|
|
257
|
+
rescue StandardError => e
|
|
258
|
+
errors << "input schema is invalid: #{e.message}"
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Output schema
|
|
263
|
+
if manifest.output_schema.present?
|
|
264
|
+
unless manifest.output_schema.is_a?(Hash)
|
|
265
|
+
errors << "output schema must be an object"
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
errors
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Validate framework extensions
|
|
273
|
+
def validate_extensions(manifest)
|
|
274
|
+
errors = []
|
|
275
|
+
|
|
276
|
+
return errors unless manifest.extensions.is_a?(Hash)
|
|
277
|
+
|
|
278
|
+
# ActiveAgent extensions
|
|
279
|
+
aa_ext = manifest.extensions[:activeagent]
|
|
280
|
+
if aa_ext.is_a?(Hash)
|
|
281
|
+
# Class name format
|
|
282
|
+
if aa_ext[:class_name].present?
|
|
283
|
+
unless aa_ext[:class_name] =~ /\A[A-Z][a-zA-Z0-9]*Agent\z/
|
|
284
|
+
errors << "activeagent.class_name should follow Rails naming convention (e.g., MyAgent)"
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Concerns validation
|
|
289
|
+
if aa_ext[:concerns].is_a?(Array)
|
|
290
|
+
aa_ext[:concerns].each do |concern|
|
|
291
|
+
concern_name = concern.is_a?(Hash) ? concern.keys.first : concern
|
|
292
|
+
unless concern_name.to_s =~ /\Ahas_[a-z_]+\z/
|
|
293
|
+
errors << "activeagent.concerns: '#{concern_name}' should follow has_* naming pattern"
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
errors
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Validate content (instructions, template)
|
|
303
|
+
def validate_content(manifest)
|
|
304
|
+
errors = []
|
|
305
|
+
|
|
306
|
+
# Must have some form of content
|
|
307
|
+
if manifest.instructions.blank? && manifest.template.blank?
|
|
308
|
+
errors << "manifest must have instructions or template content"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Template syntax check (basic Liquid validation)
|
|
312
|
+
if manifest.template.present?
|
|
313
|
+
# Check for unclosed tags
|
|
314
|
+
open_tags = manifest.template.scan(/\{%\s*(\w+)/).flatten
|
|
315
|
+
close_tags = manifest.template.scan(/\{%\s*end(\w+)/).flatten
|
|
316
|
+
|
|
317
|
+
# Simple balance check for common tags
|
|
318
|
+
%w[if for unless case].each do |tag|
|
|
319
|
+
opens = open_tags.count(tag)
|
|
320
|
+
closes = close_tags.count(tag)
|
|
321
|
+
if opens != closes
|
|
322
|
+
errors << "template has unbalanced {% #{tag} %} tags (#{opens} opens, #{closes} closes)"
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Check for unclosed variable tags
|
|
327
|
+
if manifest.template.count("{{") != manifest.template.count("}}")
|
|
328
|
+
errors << "template has unclosed {{ }} variable tags"
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
errors
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Strict validation (additional checks)
|
|
336
|
+
def validate_strict(manifest)
|
|
337
|
+
errors = []
|
|
338
|
+
|
|
339
|
+
# Description required in strict mode
|
|
340
|
+
if manifest.description.blank?
|
|
341
|
+
errors << "description is required in strict mode"
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Model required in strict mode
|
|
345
|
+
if manifest.model.blank?
|
|
346
|
+
errors << "model is required in strict mode"
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# All tools must have descriptions
|
|
350
|
+
manifest.tools&.each_with_index do |tool, index|
|
|
351
|
+
next if tool.reference?
|
|
352
|
+
|
|
353
|
+
if tool.description.blank?
|
|
354
|
+
errors << "tools[#{index}]: description is required in strict mode"
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Version required in strict mode
|
|
359
|
+
if manifest.version.blank?
|
|
360
|
+
errors << "version is required in strict mode"
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
errors
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|