lemans 1.4.0 → 1.5.1

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.
@@ -10,7 +10,7 @@ module Lemans
10
10
  # Verifies a trial in the sandbox the agent worked in, after Trial has closed
11
11
  # its network. The tests are uploaded fresh at verification time, never before.
12
12
  class Verifier
13
- Verification = Data.define(:reward, :credit, :logs)
13
+ Verification = Data.define(:reward, :credit, :features, :logs)
14
14
 
15
15
  REWARD_RANGE = (0.0..1.0)
16
16
 
@@ -22,6 +22,13 @@ module Lemans
22
22
 
23
23
  VERIFY_BIN = "verify"
24
24
 
25
+ # A feature passes when all its checks passed; one with a check the run
26
+ # never reported is left out.
27
+ def self.features_from(statuses, features)
28
+ graded = features.to_h.select { |_, checks| checks.all? { statuses.key?(it) } }
29
+ graded.transform_values { |checks| checks.all? { statuses[it] == "pass" } }.sort.to_h unless graded.empty?
30
+ end
31
+
25
32
  private attr_reader :task, :environment, :snapshot, :timeout
26
33
 
27
34
  def initialize(task, environment, snapshot)
@@ -94,7 +101,9 @@ module Lemans
94
101
  result = environment.exec(command, timeout:, env:)
95
102
 
96
103
  reward = read_reward(result)
97
- Verification.new(reward:, credit: read_credit(reward), logs: result.output.to_s)
104
+ checks = read_checks
105
+ features = (self.class.features_from(checks.fetch("checks", {}), TestScanner.for(task)&.features) if checks && task.final_step?)
106
+ Verification.new(reward:, credit: credit_from(checks, reward), features:, logs: result.output.to_s)
98
107
  end
99
108
 
100
109
  def verifier_script
@@ -120,20 +129,20 @@ module Lemans
120
129
  value
121
130
  end
122
131
 
123
- def read_credit(reward)
132
+ def read_checks
124
133
  path = File.join(task.verifier.logs_dir, "checks.json")
125
- return reward unless environment.exec("test -e #{Shellwords.escape(path)}").success?
134
+ return nil unless environment.exec("test -e #{Shellwords.escape(path)}").success?
126
135
 
127
136
  result = environment.exec("cat #{Shellwords.escape(path)}")
128
137
  raise VerifierError, "could not read #{path}: #{result.output.to_s[0, 500]}" unless result.success?
129
138
 
130
- checks = begin
131
- JSON.parse(result.output.to_s)
132
- rescue JSON::ParserError => e
133
- raise VerifierError, "#{path} is not JSON: #{e.message[0, 500]}"
134
- end
139
+ JSON.parse(result.output.to_s)
140
+ rescue JSON::ParserError => e
141
+ raise VerifierError, "#{path} is not JSON: #{e.message[0, 500]}"
142
+ end
135
143
 
136
- grading = checks["grading"]
144
+ def credit_from(checks, reward)
145
+ grading = checks&.dig("grading")
137
146
  return reward unless grading && (base_credit = grading["base_credit"])
138
147
  return 0.0 if reward.zero?
139
148
 
data/lib/lemans/trial.rb CHANGED
@@ -12,13 +12,15 @@ module Lemans
12
12
  class Trial
13
13
  attr_reader :task, :config, :model, :agent_name, :environment, :result
14
14
 
15
- private attr_reader :agent, :store, :snapshot, :patch, :current_step_index
15
+ private attr_reader :agent, :store, :restart_from, :restart_mode, :snapshot, :patch, :current_step_index
16
16
 
17
- def initialize(task, model = nil, result: nil, store: nil, agent: nil, environment: nil)
17
+ def initialize(task, model = nil, result: nil, store: nil, agent: nil, environment: nil, restart_from: nil, restart_mode: nil)
18
18
  @task = task
19
19
  @config = task.config
20
20
  @model = model || config.models.first
21
21
  @store = store
22
+ @restart_from = restart_from
23
+ @restart_mode = restart_mode
22
24
 
23
25
  @agent = agent.is_a?(Agent) ? agent : Agents.build(agent || config.agent_name, profile: config.agent, model: @model)
24
26
  @agent_name = @agent.name
@@ -47,9 +49,12 @@ module Lemans
47
49
  @snapshot = nil
48
50
  @patch = nil
49
51
  @current_step_index = nil
52
+ @recovered_since = nil
50
53
  end
51
54
 
52
55
  def run
56
+ restart! if restart_from
57
+
53
58
  phase(:environment_setup) do
54
59
  environment.start
55
60
 
@@ -64,45 +69,55 @@ module Lemans
64
69
  # Seal the git state to collect the agent's patch later
65
70
  @patch = Patch.new(task, environment)
66
71
  patch.seal!
72
+ patch.replay!(settled_patches, (restart_artifact("agent.patch") if restart_mode)) if restart_from
67
73
 
68
- agent.install(task, environment)
74
+ # A reverification of the last step runs no agent
75
+ agent.install(task, environment) unless restart_mode == :reverify && restart_step == task.steps
69
76
 
70
77
  environment.switch_network_policy!(config.agent.environment.network)
71
78
  end
72
79
 
73
- each_step do |step_task|
74
- response =
75
- phase(:agent) do
76
- agent.run(step_task, environment)
77
- rescue InfrastructureError, ::Miniswen::InfrastructureError => e
78
- # Mark the failure here, where the agent phase is still known
79
- result.failed!(:agent_error, e.message)
80
- collect_patch!
81
- raise
82
- rescue ::Miniswen::AccountingError
83
- # Classified by the outer rescue; the work is still on disk
80
+ adopt_phases! if restart_from
81
+
82
+ pending = restart_mode
83
+ each_step(from: restart_from ? restart_step : 1) do |step_task|
84
+ mode, pending = pending, nil
85
+
86
+ unless mode == :reverify
87
+ history = restart_artifact("agent.result.json") if mode == :recover
88
+ response =
89
+ phase(:agent, started_at: (@recovered_since if mode == :recover)) do
90
+ agent.run(step_task, environment, history:)
91
+ rescue InfrastructureError, ::Miniswen::InfrastructureError => e
92
+ # Mark the failure here, where the agent phase is still known
93
+ result.failed!(:agent_error, e.message)
94
+ collect_patch!
95
+ raise
96
+ rescue ::Miniswen::AccountingError
97
+ # Classified by the outer rescue; the work is still on disk
98
+ collect_patch!
99
+ raise
100
+ end
101
+
102
+ # Whatever the agent brought back is evidence, a failed run's included
103
+ save_trajectory!(response.trajectory)
104
+ store&.save_artifact(result, response.raw_result, path: with_step_index("agent.result.json")) if response.raw_result
105
+
106
+ if response.error?
107
+ result.failed!(:agent_error, response.error)
84
108
  collect_patch!
85
- raise
109
+ return result
86
110
  end
87
111
 
88
- # Whatever the agent brought back is evidence, a failed run's included
89
- save_trajectory!(response.trajectory)
90
- store&.save_artifact(result, response.raw_result, path: with_step_index("agent.result.json")) if response.raw_result
91
-
92
- if response.error?
93
- result.failed!(:agent_error, response.error)
94
- collect_patch!
95
- return result
96
- end
112
+ if task.multistep?
113
+ result.step_completed!(response.outcome, response.usage, duration: result.phases.last.duration)
114
+ else
115
+ result.completed!(response.outcome, response.usage)
116
+ end
97
117
 
98
- if task.multistep?
99
- result.step_completed!(response.outcome, response.usage, duration: result.phases.last.duration)
100
- else
101
- result.completed!(response.outcome, response.usage)
118
+ check_cost_limit!
102
119
  end
103
120
 
104
- check_cost_limit!
105
-
106
121
  collect_patch!
107
122
  if step_task.final_step?
108
123
  patch.compile!(result, store) if task.multistep? && store
@@ -124,7 +139,7 @@ module Lemans
124
139
  store&.save_artifact(result, verification.logs, path: with_step_index("verifier.log"))
125
140
 
126
141
  if step_task.final_step?
127
- result.graded!(verification.reward, credit: verification.credit)
142
+ result.graded!(verification.reward, credit: verification.credit, features: verification.features)
128
143
  elsif verification.reward.zero?
129
144
  result.graded!(0.0)
130
145
  throw :halt
@@ -166,31 +181,84 @@ module Lemans
166
181
  store.save_artifact(result, JSON.pretty_generate(trajectory.to_atif), path:)
167
182
  end
168
183
 
169
- def each_step
184
+ def each_step(from: 1)
170
185
  return yield task unless task.multistep?
171
186
 
172
187
  catch(:halt) do
173
- 1.upto(task.steps) do |index|
174
- resume_agent! if index > 1
188
+ from.upto(task.steps) do |index|
189
+ # The first step of a restart finds a fresh tree, already replayed
190
+ resume_agent! if index > from
175
191
  @current_step_index = index
176
192
  yield task.for_step(index)
177
193
  end
178
194
  end
179
195
  end
180
196
 
197
+ # The step a restart begins at: the one a reverification grades again, or
198
+ # the first one not settled (run afresh, or recovered from its history).
199
+ def restart_step
200
+ @restart_step ||= restart_mode == :reverify ? restart_from.verified_step : restart_from.settled_steps + 1
201
+ end
202
+
203
+ # The new run carries the settled steps' evidence, and the agent's part of
204
+ # a step it grades again: the patch is collected anew.
205
+ def restart!
206
+ result.restart!(restart_from, step: restart_step, mode: restart_mode)
207
+ carried = restart_mode == :reverify ? %w[trajectory.json agent.result.json].map { restart_path(it) } : []
208
+
209
+ store.artifacts(restart_from).each do |path|
210
+ next unless path[%r{\.(\d+)(?:\.[^./]+)?\z}, 1].to_i.between?(1, restart_step - 1) || carried.include?(path)
211
+
212
+ store.save_artifact(result, store.read_artifact(restart_from, path), path:)
213
+ end
214
+ end
215
+
216
+ def settled_patches
217
+ (1...restart_step).map do |index|
218
+ store.read_artifact(restart_from, "agent.#{index}.patch") ||
219
+ raise(InfrastructureError, "#{restart_from.id} has no agent.#{index}.patch to replay")
220
+ end
221
+ end
222
+
223
+ def restart_artifact(name)
224
+ path = restart_path(name)
225
+ store.read_artifact(restart_from, path) || raise(InfrastructureError, "#{restart_from.id} has no #{path} to restart from")
226
+ end
227
+
228
+ def restart_path(name) = task.multistep? ? with_step_index(name, restart_step) : name
229
+
230
+ # A recovered step's agent phase reaches back over the part the source
231
+ # run already spent, so the step lasts as long as both parts together.
232
+ def adopt_phases!
233
+ names = restart_from.phases.map(&:name).select { it.to_s[/\.(\d+)\z/, 1].to_i.between?(1, restart_step - 1) }
234
+ agent_phase = restart_path("agent").to_sym
235
+ now = Time.now.utc
236
+
237
+ case restart_mode
238
+ when :reverify
239
+ result.adopt_phases!(restart_from, names + [ agent_phase ], now:)
240
+ when :recover
241
+ result.adopt_phases!(restart_from, names, through: agent_phase, now:)
242
+ partial = restart_from.phases.find { it.name == agent_phase }
243
+ @recovered_since = now - (partial.finished_at - partial.started_at)
244
+ else
245
+ result.adopt_phases!(restart_from, names, now:)
246
+ end
247
+ end
248
+
181
249
  def resume_agent!
182
250
  patch.restore!
183
251
  environment.exec!("rm -rf #{Verifier::TESTS_DIR} #{Shellwords.escape(task.verifier.logs_dir)}")
184
252
  environment.switch_network_policy!(config.agent.environment.network)
185
253
  end
186
254
 
187
- def with_step_index(path)
188
- return path unless current_step_index
255
+ def with_step_index(path, index = current_step_index)
256
+ return path unless index
189
257
 
190
258
  *pre, last = path.to_s.split(".")
191
- return "#{last}.#{current_step_index}" if pre.empty?
259
+ return "#{last}.#{index}" if pre.empty?
192
260
 
193
- [ *pre, current_step_index, last ].join(".")
261
+ [ *pre, index, last ].join(".")
194
262
  end
195
263
 
196
264
  def sandbox_ttl
@@ -209,8 +277,8 @@ module Lemans
209
277
  )
210
278
  end
211
279
 
212
- def phase(name)
213
- result.phase_started(with_step_index(name).to_sym)
280
+ def phase(name, started_at: nil)
281
+ result.phase_started(with_step_index(name).to_sym, started_at)
214
282
  yield
215
283
  ensure
216
284
  result.phase_finished(with_step_index(name).to_sym)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lemans
4
- VERSION = "1.4.0"
4
+ VERSION = "1.5.1"
5
5
  end
data/lib/lemans.rb CHANGED
@@ -11,6 +11,8 @@ loader.ignore("#{__dir__}/miniswen.rb", "#{__dir__}/miniswen")
11
11
  loader.ignore("#{__dir__}/lemans/trial/verifier/assets")
12
12
  # templates/ holds the files `lemans init` scaffolds, not Ruby the harness loads.
13
13
  loader.ignore("#{__dir__}/lemans/cli/templates")
14
+ # proxy/ holds the image the Docker backend runs for allowlists, not Ruby the harness loads.
15
+ loader.ignore("#{__dir__}/lemans/environments/docker/proxy")
14
16
  loader.setup
15
17
 
16
18
  require "miniswen"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require "time"
4
5
  require "miniswen/version"
5
6
  require "miniswen/ruby_llm"
6
7
 
@@ -261,25 +262,17 @@ module Miniswen
261
262
  @reporter = reporter
262
263
  end
263
264
 
264
- def run(instruction)
265
- uname = execute("uname -srvm").output.to_s.strip
266
- @messages = [
267
- { role: "system", content: SYSTEM_TEMPLATE },
268
- { role: "user", content: format(INSTANCE_TEMPLATE,
269
- instruction: instruction,
270
- system_information: uname,
271
- macos_sed_note: uname.start_with?("Darwin") ? "\n#{MACOS_SED_NOTE}" : "") }
272
- ]
273
-
274
- @steps = 0
275
- @cost = 0.0
276
-
277
- @totals = { input_tokens: 0, output_tokens: 0, cached_tokens: 0, thinking_tokens: 0 }
265
+ # With a `history` (the Result of an interrupted run), the session goes on
266
+ # where it stopped, under the budget the earlier part already spent.
267
+ def run(instruction = nil, history: nil)
268
+ if history
269
+ resume(history)
270
+ else
271
+ start(instruction)
272
+ end
278
273
 
279
- @cost_known = true
280
274
  @consecutive_format_errors = 0
281
275
  @refused_turns = 0
282
- @started_at = @clock.call
283
276
 
284
277
  loop do
285
278
  (status = limit_reached) and return finish(status)
@@ -329,6 +322,49 @@ module Miniswen
329
322
 
330
323
  private
331
324
 
325
+ def start(instruction)
326
+ uname = execute("uname -srvm").output.to_s.strip
327
+ @messages = [
328
+ { role: "system", content: SYSTEM_TEMPLATE },
329
+ { role: "user", content: format(INSTANCE_TEMPLATE,
330
+ instruction: instruction,
331
+ system_information: uname,
332
+ macos_sed_note: uname.start_with?("Darwin") ? "\n#{MACOS_SED_NOTE}" : "") }
333
+ ]
334
+
335
+ @steps = 0
336
+ @cost = 0.0
337
+ @cost_known = true
338
+ @totals = { input_tokens: 0, output_tokens: 0, cached_tokens: 0, thinking_tokens: 0 }
339
+ @started_at = @clock.call
340
+ end
341
+
342
+ def resume(history)
343
+ @messages = answered_messages(history.messages)
344
+
345
+ @steps = history.steps.to_i
346
+ @cost = history.cost_usd.to_f
347
+ @cost_known = !history.cost_usd.nil?
348
+ @totals = { input_tokens: history.input_tokens.to_i, output_tokens: history.output_tokens.to_i,
349
+ cached_tokens: history.cached_tokens.to_i, thinking_tokens: history.thinking_tokens.to_i }
350
+ @started_at = @clock.call - elapsed(history.messages)
351
+ end
352
+
353
+ # A turn whose calls did not all get a result cannot go back to a provider:
354
+ # it is dropped, and the model decides again from the tree it left.
355
+ def answered_messages(messages)
356
+ turn = messages.rindex { it[:role] == "assistant" && it[:tool_calls] }
357
+ return messages.dup unless turn
358
+
359
+ answered = messages[(turn + 1)..].filter_map { it[:tool_call_id] if it[:role] == "tool" }
360
+ messages[turn][:tool_calls].all? { answered.include?(it[:id]) } ? messages.dup : messages[0...turn]
361
+ end
362
+
363
+ def elapsed(messages)
364
+ times = messages.filter_map { Time.parse(it[:timestamp]) if it[:role] == "assistant" && it[:timestamp] }
365
+ times.empty? ? 0.0 : times.max - times.min
366
+ end
367
+
332
368
  def execute(command)
333
369
  environment.exec(command, timeout: exec_timeout, env: EXEC_ENV)
334
370
  end
data/lib/miniswen/cli.rb CHANGED
@@ -25,6 +25,7 @@ module Miniswen
25
25
  @jail = false
26
26
  @allowed_hosts = nil
27
27
  @workdir = nil
28
+ @history = nil
28
29
  end
29
30
 
30
31
  def run
@@ -56,7 +57,7 @@ module Miniswen
56
57
  agent = Agent.new(model:, reporter:, environment:, **options)
57
58
 
58
59
  begin
59
- result = agent.run(instruction)
60
+ result = agent.run(instruction, history: @history && Agent::Result.from_h(JSON.parse(@history)))
60
61
  rescue StandardError => e
61
62
  write_results(agent.partial_result(error_message(e)))
62
63
  raise
@@ -105,7 +106,7 @@ module Miniswen
105
106
 
106
107
  def parse_args!
107
108
  parser = OptionParser.new do |opts|
108
- opts.banner = "Usage: miniswen -m MODEL -p INSTRUCTION [...options]"
109
+ opts.banner = "Usage: miniswen -m MODEL (-p INSTRUCTION | --continue-from PATH) [...options]"
109
110
 
110
111
  opts.on("-m MODEL", "--model=MODEL", String,
111
112
  "LLM to use (litellm format, e.g.: openrouter/openai/gpt-5.6-luna") do |v|
@@ -116,6 +117,10 @@ module Miniswen
116
117
  @instruction = File.file?(v) ? File.read(v) : v
117
118
  end
118
119
 
120
+ opts.on("--continue-from=PATH", String, "Continue the session saved by --results-path at PATH (no -p needed)") do |v|
121
+ @history = File.read(v)
122
+ end
123
+
119
124
  opts.on("--max-steps=STEPS", Integer, "Max steps count") do |v|
120
125
  options[:max_steps] = v
121
126
  end
@@ -197,7 +202,7 @@ module Miniswen
197
202
  return if @refresh_registry
198
203
 
199
204
  raise "Use -m to specify the model" unless @model
200
- raise "Please, provide instructions via -p option" unless @instruction
205
+ raise "Please, provide instructions via -p option" unless @instruction || @history
201
206
  end
202
207
  end
203
208
  end
@@ -20,7 +20,7 @@ module Miniswen
20
20
  env&.each { |key, value| argv += [ "--env", "#{key}=#{value}" ] }
21
21
  argv << id
22
22
  argv += [ "timeout", timeout.ceil.to_s ] if timeout&.positive?
23
- argv += [ "sh", "-c", command ]
23
+ argv += [ "bash", "-c", command ]
24
24
 
25
25
  Open3.popen2e(*argv) do |stdin, pipe, wait|
26
26
  stdin.close
data/lib/miniswen/jail.rb CHANGED
@@ -61,7 +61,7 @@ module Miniswen
61
61
  def spawn_arguments(command, env)
62
62
  [ command_env(env),
63
63
  "nsenter", "--target", @holder.pid.to_s, "--net", "--mount", "--pid=/proc/#{@holder.pid}/ns/pid_for_children", "--wd=#{@workdir}",
64
- "--", "setpriv", "--bounding-set=-all", "--inh-caps=-all", "--no-new-privs", "--", "sh", "-c", command ]
64
+ "--", "setpriv", "--bounding-set=-all", "--inh-caps=-all", "--no-new-privs", "--", "bash", "-c", command ]
65
65
  end
66
66
 
67
67
  def spawn_options = super.merge(unsetenv_others: true)
@@ -28,7 +28,7 @@ module Miniswen
28
28
 
29
29
  private
30
30
 
31
- def spawn_arguments(command, env) = [ env || {}, "sh", "-c", command ]
31
+ def spawn_arguments(command, env) = [ env || {}, "bash", "-c", command ]
32
32
 
33
33
  def spawn_options = { pgroup: true }
34
34
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Miniswen
4
- VERSION = "1.4.0"
4
+ VERSION = "1.5.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lemans
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov
@@ -95,6 +95,34 @@ dependencies:
95
95
  - - "~>"
96
96
  - !ruby/object:Gem::Version
97
97
  version: '1.1'
98
+ - !ruby/object:Gem::Dependency
99
+ name: rack
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '3.1'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '3.1'
112
+ - !ruby/object:Gem::Dependency
113
+ name: rackup
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '2.2'
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '2.2'
98
126
  - !ruby/object:Gem::Dependency
99
127
  name: rexml
100
128
  requirement: !ruby/object:Gem::Requirement
@@ -137,6 +165,20 @@ dependencies:
137
165
  - - "~>"
138
166
  - !ruby/object:Gem::Version
139
167
  version: '1.5'
168
+ - !ruby/object:Gem::Dependency
169
+ name: webrick
170
+ requirement: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - "~>"
173
+ - !ruby/object:Gem::Version
174
+ version: '1.9'
175
+ type: :runtime
176
+ prerelease: false
177
+ version_requirements: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - "~>"
180
+ - !ruby/object:Gem::Version
181
+ version: '1.9'
140
182
  - !ruby/object:Gem::Dependency
141
183
  name: zeitwerk
142
184
  requirement: !ruby/object:Gem::Requirement
@@ -159,6 +201,7 @@ email:
159
201
  executables:
160
202
  - lemans
161
203
  - lemans-remote
204
+ - lemans-viewer
162
205
  extensions: []
163
206
  extra_rdoc_files: []
164
207
  files:
@@ -167,6 +210,7 @@ files:
167
210
  - README.md
168
211
  - exe/lemans
169
212
  - exe/lemans-remote
213
+ - exe/lemans-viewer
170
214
  - lib/lemans.rb
171
215
  - lib/lemans/agent.rb
172
216
  - lib/lemans/agents.rb
@@ -213,6 +257,9 @@ files:
213
257
  - lib/lemans/environments/daytona/shell.rb
214
258
  - lib/lemans/environments/daytona/snapshot_store.rb
215
259
  - lib/lemans/environments/docker.rb
260
+ - lib/lemans/environments/docker/proxy/Dockerfile
261
+ - lib/lemans/environments/docker/proxy/README.md
262
+ - lib/lemans/environments/docker/proxy/server.rb
216
263
  - lib/lemans/ext/deep_merge.rb
217
264
  - lib/lemans/result.rb
218
265
  - lib/lemans/runner.rb
@@ -229,6 +276,7 @@ files:
229
276
  - lib/lemans/trial/verifier.rb
230
277
  - lib/lemans/trial/verifier/assets/eport-lemans.rb
231
278
  - lib/lemans/trial/verifier/assets/lemans_minitest_reporter.rb
279
+ - lib/lemans/trial/verifier/test_scanner.rb
232
280
  - lib/lemans/version.rb
233
281
  - lib/miniswen.rb
234
282
  - lib/miniswen/agent.rb