scout-ai 0.2.0 → 1.0.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/.vimproject +91 -10
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/bin/scout-ai +2 -0
- data/lib/scout/llm/agent/chat.rb +24 -0
- data/lib/scout/llm/agent.rb +13 -13
- data/lib/scout/llm/ask.rb +26 -16
- data/lib/scout/llm/backends/bedrock.rb +129 -0
- data/lib/scout/llm/backends/huggingface.rb +6 -21
- data/lib/scout/llm/backends/ollama.rb +69 -36
- data/lib/scout/llm/backends/openai.rb +85 -35
- data/lib/scout/llm/backends/openwebui.rb +1 -1
- data/lib/scout/llm/backends/relay.rb +3 -2
- data/lib/scout/llm/backends/responses.rb +272 -0
- data/lib/scout/llm/chat.rb +547 -0
- data/lib/scout/llm/parse.rb +70 -13
- data/lib/scout/llm/tools.rb +126 -5
- data/lib/scout/llm/utils.rb +17 -10
- data/lib/scout/model/base.rb +19 -0
- data/lib/scout/model/python/base.rb +25 -0
- data/lib/scout/model/python/huggingface/causal/next_token.rb +23 -0
- data/lib/scout/model/python/huggingface/causal.rb +29 -0
- data/lib/scout/model/python/huggingface/classification +0 -0
- data/lib/scout/model/python/huggingface/classification.rb +50 -0
- data/lib/scout/model/python/huggingface.rb +112 -0
- data/lib/scout/model/python/torch/dataloader.rb +57 -0
- data/lib/scout/model/python/torch/helpers.rb +84 -0
- data/lib/scout/model/python/torch/introspection.rb +34 -0
- data/lib/scout/model/python/torch/load_and_save.rb +47 -0
- data/lib/scout/model/python/torch.rb +94 -0
- data/lib/scout/model/util/run.rb +181 -0
- data/lib/scout/model/util/save.rb +81 -0
- data/lib/scout-ai.rb +3 -1
- data/python/scout_ai/__init__.py +35 -0
- data/python/scout_ai/__pycache__/__init__.cpython-310.pyc +0 -0
- data/python/scout_ai/__pycache__/__init__.cpython-311.pyc +0 -0
- data/python/scout_ai/__pycache__/huggingface.cpython-310.pyc +0 -0
- data/python/scout_ai/__pycache__/huggingface.cpython-311.pyc +0 -0
- data/python/scout_ai/__pycache__/util.cpython-310.pyc +0 -0
- data/python/scout_ai/__pycache__/util.cpython-311.pyc +0 -0
- data/python/scout_ai/atcold/__init__.py +0 -0
- data/python/scout_ai/atcold/plot_lib.py +141 -0
- data/python/scout_ai/atcold/spiral.py +27 -0
- data/python/scout_ai/huggingface/data.py +48 -0
- data/python/scout_ai/huggingface/eval.py +60 -0
- data/python/scout_ai/huggingface/model.py +29 -0
- data/python/scout_ai/huggingface/rlhf.py +83 -0
- data/python/scout_ai/huggingface/train/__init__.py +34 -0
- data/python/scout_ai/huggingface/train/__pycache__/__init__.cpython-310.pyc +0 -0
- data/python/scout_ai/huggingface/train/__pycache__/next_token.cpython-310.pyc +0 -0
- data/python/scout_ai/huggingface/train/next_token.py +315 -0
- data/python/scout_ai/language_model.py +70 -0
- data/python/scout_ai/util.py +32 -0
- data/scout-ai.gemspec +130 -0
- data/scout_commands/agent/ask +133 -15
- data/scout_commands/agent/kb +15 -0
- data/scout_commands/llm/ask +71 -12
- data/scout_commands/llm/process +4 -2
- data/test/data/cat.jpg +0 -0
- data/test/scout/llm/agent/test_chat.rb +14 -0
- data/test/scout/llm/backends/test_bedrock.rb +60 -0
- data/test/scout/llm/backends/test_huggingface.rb +3 -3
- data/test/scout/llm/backends/test_ollama.rb +48 -10
- data/test/scout/llm/backends/test_openai.rb +96 -11
- data/test/scout/llm/backends/test_responses.rb +115 -0
- data/test/scout/llm/test_ask.rb +1 -0
- data/test/scout/llm/test_chat.rb +214 -0
- data/test/scout/llm/test_parse.rb +81 -2
- data/test/scout/model/python/huggingface/causal/test_next_token.rb +59 -0
- data/test/scout/model/python/huggingface/test_causal.rb +33 -0
- data/test/scout/model/python/huggingface/test_classification.rb +30 -0
- data/test/scout/model/python/test_base.rb +44 -0
- data/test/scout/model/python/test_huggingface.rb +9 -0
- data/test/scout/model/python/test_torch.rb +71 -0
- data/test/scout/model/python/torch/test_helpers.rb +14 -0
- data/test/scout/model/test_base.rb +117 -0
- data/test/scout/model/util/test_save.rb +31 -0
- metadata +72 -5
- data/questions/coach +0 -2
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
|
2
|
+
require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
|
3
|
+
|
4
|
+
class TestPythonModel < Test::Unit::TestCase
|
5
|
+
def test_linear
|
6
|
+
model = nil
|
7
|
+
|
8
|
+
TmpFile.with_path do |dir|
|
9
|
+
|
10
|
+
dir['model.py'].write <<-EOF
|
11
|
+
class TestModel:
|
12
|
+
def __init__(self, delta):
|
13
|
+
self.delta = delta
|
14
|
+
|
15
|
+
def eval(self, x):
|
16
|
+
return [e + self.delta for e in x]
|
17
|
+
EOF
|
18
|
+
|
19
|
+
model = PythonModel.new dir, 'TestModel', :model, delta: 1
|
20
|
+
|
21
|
+
model.eval do |sample,list=nil|
|
22
|
+
init unless state
|
23
|
+
if list
|
24
|
+
state.eval(list)
|
25
|
+
else
|
26
|
+
state.eval([sample])[0]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
assert_equal 2, model.eval(1)
|
31
|
+
assert_equal [4, 6], model.eval_list([3, 5])
|
32
|
+
|
33
|
+
model.save
|
34
|
+
|
35
|
+
model = ScoutModel.new dir
|
36
|
+
|
37
|
+
assert_equal 2, model.eval(1)
|
38
|
+
|
39
|
+
model = ScoutModel.new dir, delta: 2
|
40
|
+
|
41
|
+
assert_equal 3, model.eval(1)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
|
2
|
+
require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
|
3
|
+
|
4
|
+
class TestTorch < Test::Unit::TestCase
|
5
|
+
def test_linear
|
6
|
+
model = nil
|
7
|
+
|
8
|
+
TmpFile.with_dir do |dir|
|
9
|
+
|
10
|
+
# Create model
|
11
|
+
|
12
|
+
TorchModel.init_python
|
13
|
+
|
14
|
+
model = TorchModel.new dir
|
15
|
+
model.state = ScoutPython.torch.nn.Linear.new(1, 1)
|
16
|
+
model.criterion = ScoutPython.torch.nn.MSELoss.new()
|
17
|
+
|
18
|
+
model.extract_features do |f|
|
19
|
+
[f]
|
20
|
+
end
|
21
|
+
|
22
|
+
model.post_process do |v,list|
|
23
|
+
list ? list.collect{|vv| vv.first } : v.first
|
24
|
+
end
|
25
|
+
|
26
|
+
# Train model
|
27
|
+
|
28
|
+
model.add 5.0, [10.0]
|
29
|
+
model.add 10.0, [20.0]
|
30
|
+
|
31
|
+
model.options[:training_args][:epochs] = 1000
|
32
|
+
model.train
|
33
|
+
|
34
|
+
w = model.get_weights.to_ruby.first.first
|
35
|
+
|
36
|
+
assert w > 1.8
|
37
|
+
assert w < 2.2
|
38
|
+
|
39
|
+
# Load the model again
|
40
|
+
|
41
|
+
sss 0
|
42
|
+
model.save
|
43
|
+
|
44
|
+
model = ScoutModel.new dir
|
45
|
+
|
46
|
+
# Test model
|
47
|
+
|
48
|
+
y = model.eval_list([100.0, 200.0]).first
|
49
|
+
|
50
|
+
assert(y > 150.0)
|
51
|
+
assert(y < 250.0)
|
52
|
+
|
53
|
+
y = model.eval(100.0)
|
54
|
+
|
55
|
+
assert(y > 150.0)
|
56
|
+
assert(y < 250.0)
|
57
|
+
|
58
|
+
test = [1.0, 5.0, 10.0, 20.0]
|
59
|
+
input_sum = Misc.sum(test)
|
60
|
+
sum = Misc.sum(model.eval_list(test))
|
61
|
+
assert sum > 0.8 * input_sum * 2
|
62
|
+
assert sum < 1.2 * input_sum * 2
|
63
|
+
|
64
|
+
w = TorchModel.get_weights(model.state).to_ruby.first.first
|
65
|
+
|
66
|
+
assert w > 1.8
|
67
|
+
assert w < 2.2
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
|
2
|
+
require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
|
3
|
+
|
4
|
+
require 'scout/model/python/base'
|
5
|
+
class TestTorchHelpers < Test::Unit::TestCase
|
6
|
+
def test_del
|
7
|
+
ScoutPython.init_scout
|
8
|
+
ScoutPython.pyimport :torch
|
9
|
+
batch = [[100.0]]
|
10
|
+
tensor = TorchModel.tensor(batch, 'cuda', @dtype)
|
11
|
+
tensor.del
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
|
2
|
+
require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
|
3
|
+
|
4
|
+
class TestClass < Test::Unit::TestCase
|
5
|
+
def test_trivial_model
|
6
|
+
model = ScoutModel.new
|
7
|
+
model.eval do |sample,list=nil|
|
8
|
+
if list
|
9
|
+
list.collect{|sample|
|
10
|
+
sample * 2
|
11
|
+
}
|
12
|
+
else
|
13
|
+
sample * 2
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
assert_equal 2, model.eval(1)
|
18
|
+
assert_equal [2, 4], model.eval_list([1, 2])
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_trivial_model_options
|
22
|
+
model = ScoutModel.new nil, factor: 4
|
23
|
+
model.eval do |sample,list=nil|
|
24
|
+
if list
|
25
|
+
list.collect{|sample|
|
26
|
+
sample * @options[:factor]
|
27
|
+
}
|
28
|
+
else
|
29
|
+
sample * @options[:factor]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
assert_equal 4, model.eval(1)
|
34
|
+
assert_equal [4, 8], model.eval_list([1, 2])
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_R_model
|
38
|
+
require 'rbbt-util'
|
39
|
+
require 'rbbt/util/R'
|
40
|
+
|
41
|
+
text =<<-EOF
|
42
|
+
1 0;1;1
|
43
|
+
1 1;0;1
|
44
|
+
1 1;1;1
|
45
|
+
1 0;1;1
|
46
|
+
1 1;;1
|
47
|
+
0 0;1;0
|
48
|
+
0 1;0;0
|
49
|
+
0 0;1;0
|
50
|
+
0 1;0;0
|
51
|
+
EOF
|
52
|
+
|
53
|
+
TmpFile.with_file do |dir|
|
54
|
+
Open.mkdir dir
|
55
|
+
model = ScoutModel.new dir
|
56
|
+
|
57
|
+
model.extract_features do |sample|
|
58
|
+
sample.split(";")
|
59
|
+
end
|
60
|
+
|
61
|
+
model.train do |list,labels|
|
62
|
+
TmpFile.with_file do |feature_file|
|
63
|
+
Open.write(feature_file, list.collect{|feats| feats * "\t"} * "\n")
|
64
|
+
Open.write(feature_file + '.class', labels * "\n")
|
65
|
+
R.run <<-EOF
|
66
|
+
features = read.table("#{ feature_file }", sep ="\\t", stringsAsFactors=FALSE);
|
67
|
+
labels = scan("#{ feature_file }.class", what=numeric());
|
68
|
+
features = cbind(features, class = labels);
|
69
|
+
rbbt.require('e1071')
|
70
|
+
model = svm(class ~ ., data = features)
|
71
|
+
save(model, file="#{ state_file }");
|
72
|
+
EOF
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
model.eval do |features|
|
77
|
+
TmpFile.with_file do |feature_file|
|
78
|
+
TmpFile.with_file do |results|
|
79
|
+
Open.write(feature_file, features * "\t")
|
80
|
+
R.run <<-EOF
|
81
|
+
features = read.table("#{ feature_file }", sep ="\\t", stringsAsFactors=FALSE);
|
82
|
+
library(e1071)
|
83
|
+
load(file="#{ state_file }")
|
84
|
+
label = predict(model, features);
|
85
|
+
cat(label, file="#{results}");
|
86
|
+
EOF
|
87
|
+
|
88
|
+
Open.read(results)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
text.split(/\n/).each do |line|
|
94
|
+
label, sample = line.split(" ")
|
95
|
+
model.add(sample, label)
|
96
|
+
end
|
97
|
+
|
98
|
+
model.train
|
99
|
+
|
100
|
+
assert model.eval("1;1;1").to_f > 0.5
|
101
|
+
assert model.eval("0;0;0").to_f < 0.5
|
102
|
+
|
103
|
+
model.save
|
104
|
+
|
105
|
+
model = ScoutModel.new dir
|
106
|
+
assert model.eval("1;1;1").to_f > 0.5
|
107
|
+
assert model.eval("0;0;0").to_f < 0.5
|
108
|
+
|
109
|
+
model.post_process do |result|
|
110
|
+
result.to_f < 0.5 ? :bad : :good
|
111
|
+
end
|
112
|
+
|
113
|
+
assert_equal :bad, model.eval("0;0;0")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
|
2
|
+
require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
|
3
|
+
|
4
|
+
require 'scout/model/base'
|
5
|
+
|
6
|
+
class TestClass < Test::Unit::TestCase
|
7
|
+
def test_trivial_model_save
|
8
|
+
|
9
|
+
TmpFile.with_file do |dir|
|
10
|
+
model = ScoutModel.new dir
|
11
|
+
|
12
|
+
model.eval do |sample,list=nil|
|
13
|
+
if list
|
14
|
+
list.collect{|sample|
|
15
|
+
sample * 2
|
16
|
+
}
|
17
|
+
else
|
18
|
+
sample * 2
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
model.save
|
23
|
+
|
24
|
+
model = ScoutModel.new dir
|
25
|
+
|
26
|
+
assert_equal 2, model.eval(1)
|
27
|
+
assert_equal [2, 4], model.eval_list([1, 2])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
metadata
CHANGED
@@ -1,14 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scout-ai
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miguel Vazquez
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
11
|
-
dependencies:
|
10
|
+
date: 2025-06-05 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: scout-rig
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
12
26
|
description: assorted functionalities to help scouts use AI
|
13
27
|
email: mikisvaz@gmail.com
|
14
28
|
executables:
|
@@ -29,38 +43,91 @@ files:
|
|
29
43
|
- bin/scout-ai
|
30
44
|
- lib/scout-ai.rb
|
31
45
|
- lib/scout/llm/agent.rb
|
46
|
+
- lib/scout/llm/agent/chat.rb
|
32
47
|
- lib/scout/llm/ask.rb
|
48
|
+
- lib/scout/llm/backends/bedrock.rb
|
33
49
|
- lib/scout/llm/backends/huggingface.rb
|
34
50
|
- lib/scout/llm/backends/ollama.rb
|
35
51
|
- lib/scout/llm/backends/openai.rb
|
36
52
|
- lib/scout/llm/backends/openwebui.rb
|
37
53
|
- lib/scout/llm/backends/relay.rb
|
54
|
+
- lib/scout/llm/backends/responses.rb
|
55
|
+
- lib/scout/llm/chat.rb
|
38
56
|
- lib/scout/llm/embed.rb
|
39
57
|
- lib/scout/llm/parse.rb
|
40
58
|
- lib/scout/llm/rag.rb
|
41
59
|
- lib/scout/llm/tools.rb
|
42
60
|
- lib/scout/llm/utils.rb
|
43
|
-
-
|
61
|
+
- lib/scout/model/base.rb
|
62
|
+
- lib/scout/model/python/base.rb
|
63
|
+
- lib/scout/model/python/huggingface.rb
|
64
|
+
- lib/scout/model/python/huggingface/causal.rb
|
65
|
+
- lib/scout/model/python/huggingface/causal/next_token.rb
|
66
|
+
- lib/scout/model/python/huggingface/classification
|
67
|
+
- lib/scout/model/python/huggingface/classification.rb
|
68
|
+
- lib/scout/model/python/torch.rb
|
69
|
+
- lib/scout/model/python/torch/dataloader.rb
|
70
|
+
- lib/scout/model/python/torch/helpers.rb
|
71
|
+
- lib/scout/model/python/torch/introspection.rb
|
72
|
+
- lib/scout/model/python/torch/load_and_save.rb
|
73
|
+
- lib/scout/model/util/run.rb
|
74
|
+
- lib/scout/model/util/save.rb
|
75
|
+
- python/scout_ai/__init__.py
|
76
|
+
- python/scout_ai/__pycache__/__init__.cpython-310.pyc
|
77
|
+
- python/scout_ai/__pycache__/__init__.cpython-311.pyc
|
78
|
+
- python/scout_ai/__pycache__/huggingface.cpython-310.pyc
|
79
|
+
- python/scout_ai/__pycache__/huggingface.cpython-311.pyc
|
80
|
+
- python/scout_ai/__pycache__/util.cpython-310.pyc
|
81
|
+
- python/scout_ai/__pycache__/util.cpython-311.pyc
|
82
|
+
- python/scout_ai/atcold/__init__.py
|
83
|
+
- python/scout_ai/atcold/plot_lib.py
|
84
|
+
- python/scout_ai/atcold/spiral.py
|
85
|
+
- python/scout_ai/huggingface/data.py
|
86
|
+
- python/scout_ai/huggingface/eval.py
|
87
|
+
- python/scout_ai/huggingface/model.py
|
88
|
+
- python/scout_ai/huggingface/rlhf.py
|
89
|
+
- python/scout_ai/huggingface/train/__init__.py
|
90
|
+
- python/scout_ai/huggingface/train/__pycache__/__init__.cpython-310.pyc
|
91
|
+
- python/scout_ai/huggingface/train/__pycache__/next_token.cpython-310.pyc
|
92
|
+
- python/scout_ai/huggingface/train/next_token.py
|
93
|
+
- python/scout_ai/language_model.py
|
94
|
+
- python/scout_ai/util.py
|
95
|
+
- scout-ai.gemspec
|
44
96
|
- scout_commands/agent/ask
|
97
|
+
- scout_commands/agent/kb
|
45
98
|
- scout_commands/llm/ask
|
46
99
|
- scout_commands/llm/process
|
47
100
|
- scout_commands/llm/template
|
101
|
+
- test/data/cat.jpg
|
48
102
|
- test/data/person/brothers
|
49
103
|
- test/data/person/identifiers
|
50
104
|
- test/data/person/marriages
|
51
105
|
- test/data/person/parents
|
106
|
+
- test/scout/llm/agent/test_chat.rb
|
107
|
+
- test/scout/llm/backends/test_bedrock.rb
|
52
108
|
- test/scout/llm/backends/test_huggingface.rb
|
53
109
|
- test/scout/llm/backends/test_ollama.rb
|
54
110
|
- test/scout/llm/backends/test_openai.rb
|
55
111
|
- test/scout/llm/backends/test_openwebui.rb
|
56
112
|
- test/scout/llm/backends/test_relay.rb
|
113
|
+
- test/scout/llm/backends/test_responses.rb
|
57
114
|
- test/scout/llm/test_agent.rb
|
58
115
|
- test/scout/llm/test_ask.rb
|
116
|
+
- test/scout/llm/test_chat.rb
|
59
117
|
- test/scout/llm/test_embed.rb
|
60
118
|
- test/scout/llm/test_parse.rb
|
61
119
|
- test/scout/llm/test_rag.rb
|
62
120
|
- test/scout/llm/test_tools.rb
|
63
121
|
- test/scout/llm/test_utils.rb
|
122
|
+
- test/scout/model/python/huggingface/causal/test_next_token.rb
|
123
|
+
- test/scout/model/python/huggingface/test_causal.rb
|
124
|
+
- test/scout/model/python/huggingface/test_classification.rb
|
125
|
+
- test/scout/model/python/test_base.rb
|
126
|
+
- test/scout/model/python/test_huggingface.rb
|
127
|
+
- test/scout/model/python/test_torch.rb
|
128
|
+
- test/scout/model/python/torch/test_helpers.rb
|
129
|
+
- test/scout/model/test_base.rb
|
130
|
+
- test/scout/model/util/test_save.rb
|
64
131
|
- test/test_helper.rb
|
65
132
|
homepage: http://github.com/mikisvaz/scout-ai
|
66
133
|
licenses:
|
@@ -80,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
147
|
- !ruby/object:Gem::Version
|
81
148
|
version: '0'
|
82
149
|
requirements: []
|
83
|
-
rubygems_version: 3.6.
|
150
|
+
rubygems_version: 3.6.6
|
84
151
|
specification_version: 4
|
85
152
|
summary: AI gear for scouts
|
86
153
|
test_files: []
|
data/questions/coach
DELETED