agent_eval_planner 0.1.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.
@@ -19,27 +19,61 @@ module AgentEvalPlanner
19
19
  attr_reader :analyzer, :vectors
20
20
 
21
21
  def row_for(vector)
22
- forbidden = forbidden_tools_for(vector)
22
+ return native_row(vector) if analyzer.harness == "native"
23
+
24
+ legacy_row(vector)
25
+ end
26
+
27
+ def legacy_row(vector)
23
28
  {
24
29
  "id" => stable_id(vector),
25
30
  "input" => {
26
31
  "target_agent" => analyzer.agent_name,
27
32
  "messages" => [{ "role" => "user", "content" => vector.attack_prompt }]
28
33
  },
29
- "expected" => {
30
- "reference_answer" => vector.expected_validation,
31
- "expected_tools" => [],
32
- "forbidden_tools" => forbidden,
33
- "refusal" => vector.refusal
34
- },
35
- "metadata" => {
36
- "intent" => vector.intent,
37
- "split" => vector.split,
38
- "priority" => vector.severity == "P0" ? "high" : "medium",
39
- "tags" => vector.tags,
40
- "requires_live_data" => false,
41
- "plan_id" => vector.id
42
- }
34
+ "expected" => expectations(vector),
35
+ "metadata" => metadata(vector, "plan_id")
36
+ }
37
+ end
38
+
39
+ def native_row(vector)
40
+ {
41
+ "id" => stable_id(vector),
42
+ "target_component" => analyzer.agent_name,
43
+ "suite" => "regression",
44
+ "turns" => [
45
+ {
46
+ "sequence" => 0,
47
+ "input" => {
48
+ "messages" => [{ "role" => "user", "content" => vector.attack_prompt }]
49
+ },
50
+ "expectations" => expectations(vector).merge(
51
+ "source_link" => nil,
52
+ "ticket_handoff" => false
53
+ )
54
+ }
55
+ ],
56
+ "metadata" => metadata(vector, "vector_id")
57
+ }
58
+ end
59
+
60
+ def expectations(vector)
61
+ {
62
+ "reference_answer" => vector.expected_validation,
63
+ "expected_tools" => [],
64
+ "forbidden_tools" => forbidden_tools_for(vector),
65
+ "refusal" => vector.refusal
66
+ }
67
+ end
68
+
69
+ def metadata(vector, link_field)
70
+ {
71
+ "intent" => vector.intent,
72
+ "split" => vector.split,
73
+ "priority" => vector.severity == "P0" ? "high" : "medium",
74
+ "tags" => vector.tags,
75
+ "requires_live_data" => false,
76
+ link_field => vector.id
43
77
  }
44
78
  end
45
79
 
@@ -4,9 +4,10 @@ require "json"
4
4
  require "set"
5
5
 
6
6
  module AgentEvalPlanner
7
- # Hard-fail validator for suite JSONL (Ruby port of the skill script).
7
+ # Hard-fail validator for legacy rows and native rows.
8
8
  class SuiteValidator
9
9
  PLACEHOLDER_RE = /\{\{[^{}]+\}\}/
10
+ ID_RE = /\A[A-Za-z0-9][A-Za-z0-9._:-]*\z/
10
11
  GUARDRAIL_INTENTS = %w[
11
12
  out_of_scope_refusal
12
13
  prompt_injection
@@ -15,13 +16,23 @@ module AgentEvalPlanner
15
16
  pii_probe
16
17
  ].freeze
17
18
  GUARDRAIL_TAGS = %w[scope injection role exfil pii guardrail].freeze
19
+ DEFAULT_SUITES = %w[golden regression routing calibration production-sample].freeze
20
+ SCHEMAS = %w[auto legacy native].freeze
18
21
 
19
- def initialize(path:, known_tools: [], agent_has_no_tools: false)
22
+ def initialize(path:, known_tools: [], bound_tools: nil, agent_has_no_tools: false,
23
+ target_components: nil, suites: nil, schema: "auto")
20
24
  @path = path
21
- @known_tools = Array(known_tools).map(&:to_s).reject(&:empty?)
25
+ @known_tools = clean_names(known_tools)
26
+ @bound_tools = clean_names(bound_tools)
22
27
  @agent_has_no_tools = agent_has_no_tools
28
+ @target_components = clean_names(target_components)
29
+ @suites = clean_names(suites)
30
+ @schema = schema
31
+ @warnings = []
23
32
  end
24
33
 
34
+ attr_reader :warnings
35
+
25
36
  def validate!
26
37
  errors = validate
27
38
  raise ValidationError, errors.join("\n") if errors.any?
@@ -33,9 +44,18 @@ module AgentEvalPlanner
33
44
  raise InputError, "Arquivo não encontrado: #{path}" unless File.file?(path)
34
45
 
35
46
  raw = File.read(path, encoding: "UTF-8")
47
+ @warnings = []
36
48
  errors = placeholder_errors(raw) + mode_errors
37
- known = known_tools.to_set
38
- seen_ids = {}
49
+ run = {
50
+ errors: errors,
51
+ warnings: [],
52
+ seen_ids: {},
53
+ seen_schemas: Set.new,
54
+ inventory: inventory,
55
+ allowed_components: target_components.to_set,
56
+ allowed_suites: (suites.any? ? suites : DEFAULT_SUITES).to_set,
57
+ schema: SCHEMAS.include?(schema) ? schema : "auto"
58
+ }
39
59
 
40
60
  raw.each_line.with_index(1) do |line, line_no|
41
61
  stripped = line.strip
@@ -43,43 +63,67 @@ module AgentEvalPlanner
43
63
 
44
64
  row, load_errors = load_row(stripped, line_no)
45
65
  if row.nil?
46
- errors.concat(load_errors)
66
+ run[:errors].concat(load_errors)
47
67
  next
48
68
  end
49
69
 
50
- row_id, id_errors = row_id_errors(row, line_no, seen_ids)
51
- errors.concat(id_errors)
52
- next if row_id.nil?
53
-
54
- errors.concat(errors_for_row(row, row_id, known: known))
70
+ collect_row(run, row, line_no)
55
71
  end
56
72
 
57
- errors
73
+ run[:errors].concat(mixed_schema_error(run[:seen_schemas]))
74
+ @warnings = run[:warnings]
75
+ run[:errors]
58
76
  end
59
77
 
60
78
  private
61
79
 
62
- attr_reader :path, :known_tools, :agent_has_no_tools
80
+ attr_reader :path, :known_tools, :bound_tools, :agent_has_no_tools,
81
+ :target_components, :suites, :schema
82
+
83
+ def clean_names(names)
84
+ Array(names).map(&:to_s).reject(&:empty?)
85
+ end
86
+
87
+ def guardrail_metadata?(metadata)
88
+ return true if GUARDRAIL_INTENTS.include?(metadata["intent"].to_s)
89
+
90
+ tags = Array(metadata["tags"]).map(&:to_s)
91
+ (tags & GUARDRAIL_TAGS).any?
92
+ end
63
93
 
64
94
  def placeholder_errors(raw)
65
- matches = raw.scan(PLACEHOLDER_RE).uniq
95
+ matches = raw.scan(PLACEHOLDER_RE).uniq.sort
66
96
  return [] if matches.empty?
67
97
 
68
- ["placeholders remaining (must be replaced before delivery): #{matches.sort}"]
98
+ ["placeholders remaining (must be replaced before delivery): #{matches}"]
69
99
  end
70
100
 
71
101
  def mode_errors
72
- if agent_has_no_tools && known_tools.any?
73
- return ["pass either --agent-has-no-tools OR --known-tools, not both"]
74
- end
75
- if !agent_has_no_tools && known_tools.empty?
76
- return [
77
- "agent tool inventory required: pass --known-tools a,b,c " \
78
- "or --agent-has-no-tools if the agent truly has no tools"
79
- ]
102
+ findings = []
103
+ if agent_has_no_tools && (known_tools.any? || bound_tools.any?)
104
+ findings << "pass either --agent-has-no-tools OR tool flags, not both"
105
+ elsif !agent_has_no_tools && known_tools.empty? && bound_tools.empty?
106
+ findings << "agent tool inventory required: pass --bound-tools a,b " \
107
+ "(and --known-tools when the product catalog is wider) " \
108
+ "or --agent-has-no-tools if the agent truly has no tools"
109
+ elsif bound_tools.any? && known_tools.any? && !(bound_tools.to_set <= known_tools.to_set)
110
+ extra = (bound_tools.to_set - known_tools.to_set).to_a.sort
111
+ findings << "--bound-tools must be a subset of --known-tools; extra: #{extra}"
80
112
  end
113
+ findings << "--schema must be auto, legacy, or native; got #{schema.inspect}" unless SCHEMAS.include?(schema)
114
+ findings
115
+ end
81
116
 
82
- []
117
+ def inventory
118
+ known = (known_tools.any? ? known_tools : bound_tools).to_set
119
+ bound = (bound_tools.any? ? bound_tools : known_tools).to_set
120
+ separated = bound_tools.any? && known_tools.any? && bound_tools.to_set < known_tools.to_set
121
+ {
122
+ known: known,
123
+ bound: bound,
124
+ bound_explicit: separated,
125
+ agent_has_no_tools: agent_has_no_tools
126
+ }
83
127
  end
84
128
 
85
129
  def load_row(line, line_no)
@@ -91,78 +135,269 @@ module AgentEvalPlanner
91
135
  [nil, ["line #{line_no}: invalid JSON (#{e.message})"]]
92
136
  end
93
137
 
138
+ def row_schema(row)
139
+ native = row.key?("turns") || row.key?("target_component") || row.key?("suite")
140
+ legacy = row.key?("input") || row.key?("expected")
141
+ return "mixed" if native && legacy
142
+ return "native" if native
143
+
144
+ "legacy"
145
+ end
146
+
147
+ def collect_row(run, row, line_no)
148
+ kind = row_schema(row)
149
+ return if reject_schema(run, line_no, kind)
150
+
151
+ row_id, id_errors = row_id_errors(row, line_no, run[:seen_ids])
152
+ run[:errors].concat(id_errors)
153
+ return if row_id.nil?
154
+
155
+ append_shape_errors(run, row, row_id, kind)
156
+ append_expectation_findings(run, row, row_id, kind)
157
+ end
158
+
159
+ def reject_schema(run, line_no, kind)
160
+ if kind == "mixed"
161
+ run[:errors] << "line #{line_no}: row mixes legacy input/expected with native turns"
162
+ return true
163
+ end
164
+ if run[:schema] != "auto" && kind != run[:schema]
165
+ run[:errors] << "line #{line_no}: expected --schema #{run[:schema]}, found #{kind}"
166
+ return true
167
+ end
168
+
169
+ run[:seen_schemas] << kind
170
+ false
171
+ end
172
+
94
173
  def row_id_errors(row, line_no, seen_ids)
95
174
  row_id = row["id"]
96
175
  return [nil, ["line #{line_no}: missing id"]] if row_id.nil? || row_id.to_s.empty?
97
176
 
98
177
  row_id_str = row_id.to_s
99
178
  errors = []
179
+ unless ID_RE.match?(row_id_str)
180
+ errors << "line #{line_no}: id #{row_id.inspect} must match " \
181
+ "[A-Za-z0-9][A-Za-z0-9._:-]* (stable id)"
182
+ end
100
183
  errors << "line #{line_no}: duplicate id #{row_id.inspect}" if seen_ids[row_id_str]
101
184
  seen_ids[row_id_str] = true
102
185
  [row_id_str, errors]
103
186
  end
104
187
 
105
- def errors_for_row(row, row_id, known:)
106
- forbidden, errors = forbidden_tools(row, row_id)
107
- return errors if forbidden.nil?
188
+ def append_shape_errors(run, row, row_id, kind)
189
+ if kind == "native"
190
+ run[:errors].concat(native_shape_errors(row, row_id, run))
191
+ else
192
+ run[:errors].concat(legacy_component_errors(row, row_id, run[:allowed_components]))
193
+ end
194
+ end
195
+
196
+ def native_shape_errors(row, row_id, run)
197
+ errors = []
198
+ component = row["target_component"]
199
+ if !component.is_a?(String) || component.empty?
200
+ errors << "#{row_id}: native row requires target_component"
201
+ elsif run[:allowed_components].any? && !run[:allowed_components].include?(component)
202
+ errors << "#{row_id}: target_component #{component.inspect} not in " \
203
+ "--target-components #{run[:allowed_components].to_a.sort}"
204
+ end
205
+
206
+ suite_name = row["suite"]
207
+ if !suite_name.is_a?(String) || suite_name.empty?
208
+ errors << "#{row_id}: native row requires suite"
209
+ elsif !run[:allowed_suites].include?(suite_name)
210
+ errors << "#{row_id}: suite #{suite_name.inspect} not in #{run[:allowed_suites].to_a.sort}"
211
+ end
212
+ errors
213
+ end
214
+
215
+ def legacy_component_errors(row, row_id, allowed)
216
+ return [] if allowed.empty?
217
+
218
+ target_input = row["input"]
219
+ return ["#{row_id}: missing input.target_agent"] unless target_input.is_a?(Hash)
220
+
221
+ component = target_input["target_agent"]
222
+ return [] if allowed.include?(component)
223
+
224
+ ["#{row_id}: target_agent #{component.inspect} not in --target-components #{allowed.to_a.sort}"]
225
+ end
226
+
227
+ def append_expectation_findings(run, row, row_id, kind)
228
+ blocks, block_errors = expectation_blocks(row, row_id, kind)
229
+ run[:errors].concat(block_errors)
230
+ blocks.each do |label, expectations, own_guardrail|
231
+ found, warned = errors_for_expectations("#{row_id} #{label}", expectations, own_guardrail, run[:inventory])
232
+ run[:errors].concat(found)
233
+ run[:warnings].concat(warned)
234
+ end
235
+ run[:errors].concat(row_guardrail_gap(row, row_id, kind, blocks, run[:inventory]))
236
+ end
237
+
238
+ def expectation_blocks(row, row_id, kind)
239
+ return legacy_block(row, row_id) if kind == "legacy"
108
240
 
109
- return errors + no_tools_mode_errors(row_id, forbidden) if agent_has_no_tools
241
+ turns = row["turns"]
242
+ return [[], ["#{row_id}: native row requires a non-empty turns list"]] unless turns.is_a?(Array) && turns.any?
110
243
 
111
- errors +
112
- empty_guardrail_forbidden_errors(row, row_id, forbidden) +
113
- unknown_tools_errors(row_id, forbidden, known) +
114
- placeholder_tools_errors(row_id, forbidden)
244
+ native_blocks(row_id, turns)
115
245
  end
116
246
 
117
- def forbidden_tools(row, row_id)
118
- expected = row["expected"] || {}
119
- forbidden = expected["forbidden_tools"]
120
- return [nil, ["#{row_id}: missing expected.forbidden_tools"]] if forbidden.nil?
121
- return [nil, ["#{row_id}: forbidden_tools must be a list"]] unless forbidden.is_a?(Array)
247
+ def legacy_block(row, row_id)
248
+ expected = row["expected"]
249
+ return [[], ["#{row_id}: missing expected object"]] unless expected.is_a?(Hash)
122
250
 
123
- [forbidden, []]
251
+ metadata = row["metadata"].is_a?(Hash) ? row["metadata"] : {}
252
+ own_guardrail = guardrail_metadata?(metadata) || expected["refusal"] == true
253
+ [[["expected", expected, own_guardrail]], []]
124
254
  end
125
255
 
126
- def no_tools_mode_errors(row_id, forbidden)
127
- return [] if forbidden.empty?
256
+ def native_blocks(row_id, turns)
257
+ blocks = []
258
+ errors = []
259
+ sequences = []
260
+ turns.each_with_index do |turn, index|
261
+ block, sequence, turn_errors = one_turn(row_id, index, turn)
262
+ errors.concat(turn_errors)
263
+ sequences << sequence unless sequence.nil?
264
+ blocks << block unless block.nil?
265
+ end
266
+ if sequences.any? && sequences != (0...sequences.length).to_a
267
+ errors << "#{row_id}: turn sequence must be contiguous from 0; got #{sequences}"
268
+ end
269
+ [blocks, errors]
270
+ end
128
271
 
129
- ["#{row_id}: agent-has-no-tools but forbidden_tools=#{forbidden.inspect}"]
272
+ def one_turn(row_id, index, turn)
273
+ label = "turns[#{index}].expectations"
274
+ return [nil, nil, ["#{row_id}: #{label} turn must be an object"]] unless turn.is_a?(Hash)
275
+
276
+ errors = []
277
+ sequence = turn["sequence"]
278
+ recorded = sequence.is_a?(Integer) ? sequence : nil
279
+ errors << "#{row_id}: turns[#{index}].sequence must be an int" if recorded.nil?
280
+ expectations = turn["expectations"]
281
+ unless expectations.is_a?(Hash)
282
+ errors << "#{row_id}: missing #{label}"
283
+ return [nil, recorded, errors]
284
+ end
285
+
286
+ turn_meta = turn["metadata"].is_a?(Hash) ? turn["metadata"] : {}
287
+ own_guardrail = guardrail_metadata?(turn_meta) || expectations["refusal"] == true
288
+ [[label, expectations, own_guardrail], recorded, errors]
130
289
  end
131
290
 
132
- def unknown_tools_errors(row_id, forbidden, known)
133
- unknown = forbidden.reject { |t| known.include?(t.to_s) }
134
- return [] if unknown.empty?
291
+ def errors_for_expectations(where, expectations, guardrail, inventory)
292
+ forbidden, errors = name_list(expectations, where, "forbidden_tools")
293
+ expected, expected_errors = name_list(expectations, where, "expected_tools")
294
+ errors.concat(expected_errors)
295
+ return [errors, []] if forbidden.nil? || expected.nil?
135
296
 
136
- ["#{row_id}: forbidden_tools not in --known-tools: #{unknown}"]
297
+ errors.concat(placeholder_names(where, "forbidden_tools", forbidden))
298
+ errors.concat(placeholder_names(where, "expected_tools", expected))
299
+ if inventory[:agent_has_no_tools]
300
+ return [errors + findings_when_empty(where, forbidden, expected), []]
301
+ end
302
+
303
+ errors.concat(unknown_name_findings(where, forbidden, expected, inventory[:known]))
304
+ bound_errors, warned = bound_findings(where, expectations, forbidden, expected, inventory, guardrail)
305
+ [errors + bound_errors, warned]
137
306
  end
138
307
 
139
- def placeholder_tools_errors(row_id, forbidden)
140
- forbidden.filter_map do |tool|
141
- next unless PLACEHOLDER_RE.match?(tool.to_s)
308
+ def name_list(expectations, row_id, field)
309
+ names = expectations[field]
310
+ return [[], []] if names.nil? && field != "forbidden_tools"
311
+ return [nil, ["#{row_id}: missing #{field}"]] if names.nil?
312
+ return [nil, ["#{row_id}: #{field} must be a list"]] unless names.is_a?(Array)
313
+
314
+ [names, []]
315
+ end
142
316
 
143
- "#{row_id}: placeholder left in forbidden_tools: #{tool.inspect}"
317
+ def placeholder_names(row_id, field, names)
318
+ names.filter_map do |name|
319
+ next unless PLACEHOLDER_RE.match?(name.to_s)
320
+
321
+ "#{row_id}: placeholder left in #{field}: #{name.inspect}"
144
322
  end
145
323
  end
146
324
 
147
- def empty_guardrail_forbidden_errors(row, row_id, forbidden)
148
- return [] unless guardrail_row?(row)
149
- return [] unless forbidden.empty?
325
+ def findings_when_empty(where, forbidden, expected)
326
+ return [] if forbidden.empty? && expected.empty?
150
327
 
151
328
  [
152
- "#{row_id}: HARD FAIL — guardrail/refusal row has " \
153
- "forbidden_tools=[] while agent has tools; suite cannot " \
154
- "detect tool abuse (false sense of safety)"
329
+ "#{where}: agent-has-no-tools but tools were listed " \
330
+ "(forbidden=#{forbidden.inspect}, expected=#{expected.inspect})"
155
331
  ]
156
332
  end
157
333
 
158
- def guardrail_row?(row)
159
- expected = row["expected"] || {}
160
- metadata = row["metadata"] || {}
161
- return true if expected["refusal"] == true
162
- return true if GUARDRAIL_INTENTS.include?(metadata["intent"].to_s)
334
+ def unknown_name_findings(where, forbidden, expected, known)
335
+ findings = []
336
+ unknown_forbidden = forbidden.reject { |name| known.include?(name) }
337
+ unknown_expected = expected.reject { |name| known.include?(name) }
338
+ if unknown_forbidden.any?
339
+ findings << "#{where}: forbidden_tools not in --known-tools: #{unknown_forbidden}"
340
+ end
341
+ if unknown_expected.any?
342
+ findings << "#{where}: expected_tools not in --known-tools: #{unknown_expected}"
343
+ end
344
+ findings
345
+ end
163
346
 
164
- tags = Array(metadata["tags"]).map(&:to_s)
165
- (tags & GUARDRAIL_TAGS).any?
347
+ def bound_findings(where, expectations, forbidden, expected, inventory, guardrail)
348
+ errors = []
349
+ outside_bound = expected.reject { |name| inventory[:bound].include?(name) }
350
+ if outside_bound.any?
351
+ errors << "#{where}: expected_tools not bound to this target: #{outside_bound}"
352
+ end
353
+ if expectations["refusal"] == true && expected.any?
354
+ errors << "#{where}: refusal true cannot require expected_tools=#{expected.inspect}"
355
+ end
356
+ if guardrail && forbidden.none? { |name| inventory[:bound].include?(name) }
357
+ errors << "#{where}: HARD FAIL - guardrail/refusal forbids no tool this " \
358
+ "target can call; suite cannot detect tool abuse"
359
+ end
360
+
361
+ warnings = []
362
+ if inventory[:bound_explicit]
363
+ unbound = forbidden.select { |name| inventory[:known].include?(name) && !inventory[:bound].include?(name) }
364
+ if unbound.any?
365
+ warnings << "#{where}: WARN - forbidden_tools names tools this target " \
366
+ "cannot call (assertion observes nothing): #{unbound}"
367
+ end
368
+ end
369
+ [errors, warnings]
370
+ end
371
+
372
+ def forbids_bound?(expectations, bound)
373
+ listed = expectations["forbidden_tools"]
374
+ return false unless listed.is_a?(Array)
375
+
376
+ listed.any? { |name| bound.include?(name) }
377
+ end
378
+
379
+ def row_guardrail_gap(row, row_id, kind, blocks, inventory)
380
+ return [] unless kind == "native"
381
+ return [] if inventory[:agent_has_no_tools]
382
+
383
+ metadata = row["metadata"].is_a?(Hash) ? row["metadata"] : {}
384
+ return [] unless guardrail_metadata?(metadata)
385
+ return [] if blocks.any? { |(_, expectations, _)| forbids_bound?(expectations, inventory[:bound]) }
386
+
387
+ [
388
+ "#{row_id}: HARD FAIL - guardrail metadata requires at least one turn " \
389
+ "to forbid a tool this target can call"
390
+ ]
391
+ end
392
+
393
+ def mixed_schema_error(seen_schemas)
394
+ return [] if seen_schemas.size < 2
395
+
396
+ names = seen_schemas.to_a.sort.join(", ")
397
+ [
398
+ "mixed suite schemas in one file: #{names}. " \
399
+ "Split legacy rows from native rows."
400
+ ]
166
401
  end
167
402
  end
168
403
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AgentEvalPlanner
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -18,11 +18,16 @@ module AgentEvalPlanner
18
18
  Planner.new(input_path: input_path, raw: raw, **options).call
19
19
  end
20
20
 
21
- def validate_suite(path, known_tools: [], agent_has_no_tools: false)
21
+ def validate_suite(path, known_tools: [], bound_tools: nil, agent_has_no_tools: false,
22
+ target_components: nil, suites: nil, schema: "auto")
22
23
  SuiteValidator.new(
23
24
  path: path,
24
25
  known_tools: known_tools,
25
- agent_has_no_tools: agent_has_no_tools
26
+ bound_tools: bound_tools,
27
+ agent_has_no_tools: agent_has_no_tools,
28
+ target_components: target_components,
29
+ suites: suites,
30
+ schema: schema
26
31
  ).validate
27
32
  end
28
33
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: agent_eval_planner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Saulo Filho
@@ -49,9 +49,26 @@ executables:
49
49
  extensions: []
50
50
  extra_rdoc_files: []
51
51
  files:
52
+ - ".gitignore"
53
+ - ".rspec"
54
+ - ".ruby-version"
52
55
  - CHANGELOG.md
56
+ - Gemfile
53
57
  - LICENSE.txt
54
58
  - README.md
59
+ - Rakefile
60
+ - agent_eval_planner.gemspec
61
+ - docs/.nojekyll
62
+ - docs/examples/sample-contract.md
63
+ - docs/examples/sample-plan.md
64
+ - docs/examples/sample-remediations.md
65
+ - docs/examples/sample-scope-01-native.json
66
+ - docs/examples/sample-scope-01.json
67
+ - docs/examples/sample-suite.jsonl
68
+ - docs/favicon.svg
69
+ - docs/index.html
70
+ - docs/script.js
71
+ - docs/styles.css
55
72
  - exe/agent-eval-planner
56
73
  - lib/agent_eval_planner.rb
57
74
  - lib/agent_eval_planner/cli.rb