monkey_lens 0.1.0 → 0.4.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 +22 -0
- data/README.md +11 -1
- data/lib/monkey_lens/capture.rb +27 -3
- data/lib/monkey_lens/cli.rb +15 -6
- data/lib/monkey_lens/diff.rb +8 -2
- data/lib/monkey_lens/policy.rb +125 -0
- data/lib/monkey_lens/provenance.rb +85 -0
- data/lib/monkey_lens/version.rb +1 -1
- data/lib/monkey_lens.rb +8 -2
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 731b73d48ea7d97b0d3ce1f66a5f246fee22298053e4011c2f1e3a8fd8060f57
|
|
4
|
+
data.tar.gz: a3ddb66de6351e173ee7db2b785949e1ea602487192c0a899396ef345efe745f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5939da5fffbbb4c203da4f5d2168a3a04e03b578c67e323ef80b54bacbf7265ceec4916bea7cf87502b8977e5d60907e52c1306eb3ec35373f8dae701efb5ab9
|
|
7
|
+
data.tar.gz: 22c9b607eb22bf61e6455346fc10b07eae60d3ef3d8186fcb8e7b238c3996a689381c3fc0b503b1df890db617b48e74a4109f5e31d557af26d0923f488b37d80
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to MonkeyLens are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.4.0 — 2026-08-14
|
|
6
|
+
|
|
7
|
+
- Record `original_name`, `aliases`, and `super_owner` on every captured method.
|
|
8
|
+
- Diff `alias_changed` and `super_owner_changed` so alias_method and prepend-layer shifts are visible.
|
|
9
|
+
- Inspect output includes aliases and the super-method owner.
|
|
10
|
+
|
|
11
|
+
## 0.3.0 — 2026-08-11
|
|
12
|
+
|
|
13
|
+
- Added `MonkeyLens::Provenance` to attribute method `source_location` to gem, app, stdlib, or eval origins.
|
|
14
|
+
- Opt-in provenance capture via `provenance: true` on `MonkeyLens.capture` and `--provenance` on CLI capture/inspect.
|
|
15
|
+
- Included provenance in inspect output and snapshot method records when enabled.
|
|
16
|
+
|
|
17
|
+
## 0.2.0 — 2026-08-04
|
|
18
|
+
|
|
19
|
+
- Added `MonkeyLens::Policy` for approving known runtime drift without deleting evidence from the baseline.
|
|
20
|
+
- Added wildcard waivers for change type, target, and method identifiers.
|
|
21
|
+
- Required a written reason for every waiver.
|
|
22
|
+
- Added optional ISO-date expiration for temporary approvals.
|
|
23
|
+
- Added YAML policy loading through `MonkeyLens::Policy.load`.
|
|
24
|
+
- Added `Policy::Decision` with effective and waived changes, threshold evaluation, and JSON-ready output.
|
|
25
|
+
- Added the `MonkeyLens.evaluate` convenience API.
|
|
26
|
+
|
|
5
27
|
## 0.1.0 — 2026-08-04
|
|
6
28
|
|
|
7
29
|
- Initial runtime snapshot and drift engine.
|
data/README.md
CHANGED
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
<a href="LICENSE.txt"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
|
+
<p align="center">
|
|
17
|
+
<a href="https://rubygems.org/gems/monkey_lens"><strong>Official RubyGems gem</strong></a>
|
|
18
|
+
·
|
|
19
|
+
<a href="https://theworker02.github.io/monkeylens/"><strong>Documentation website</strong></a>
|
|
20
|
+
·
|
|
21
|
+
<a href="https://github.com/theworker02/monkeylens/releases/latest"><strong>Latest GitHub release</strong></a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
16
24
|
MonkeyLens captures the effective Ruby method table for selected classes and modules, records who owns every method, tracks source locations, visibility, signatures, and ancestor order, then compares that runtime against a committed baseline.
|
|
17
25
|
|
|
18
26
|
It turns invisible runtime mutation into reviewable evidence.
|
|
@@ -43,7 +51,7 @@ Then run:
|
|
|
43
51
|
bundle install
|
|
44
52
|
```
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
Install directly from the official RubyGems release:
|
|
47
55
|
|
|
48
56
|
```bash
|
|
49
57
|
gem install monkey_lens
|
|
@@ -144,11 +152,13 @@ end
|
|
|
144
152
|
| Change | Default severity |
|
|
145
153
|
| --- | --- |
|
|
146
154
|
| Method owner changed | Critical |
|
|
155
|
+
| Super-method owner changed | High |
|
|
147
156
|
| Method source changed | High |
|
|
148
157
|
| Method removed | High |
|
|
149
158
|
| Prepend/ancestor order changed | High |
|
|
150
159
|
| Method signature changed | Medium |
|
|
151
160
|
| Visibility changed | Medium |
|
|
161
|
+
| Method aliases changed | Medium |
|
|
152
162
|
| Method added | Low |
|
|
153
163
|
|
|
154
164
|
The threshold is configured with `fail_on: low|medium|high|critical`.
|
data/lib/monkey_lens/capture.rb
CHANGED
|
@@ -6,9 +6,10 @@ module MonkeyLens
|
|
|
6
6
|
class Capture
|
|
7
7
|
VISIBILITIES = %i[public protected private].freeze
|
|
8
8
|
|
|
9
|
-
def initialize(targets:, ignore_methods: [])
|
|
9
|
+
def initialize(targets:, ignore_methods: [], provenance: false)
|
|
10
10
|
@targets = targets
|
|
11
11
|
@ignore_methods = ignore_methods.to_set
|
|
12
|
+
@provenance = provenance
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def call
|
|
@@ -54,14 +55,21 @@ module MonkeyLens
|
|
|
54
55
|
|
|
55
56
|
method = receiver.instance_method(method_name)
|
|
56
57
|
visibility = VISIBILITIES.find { |candidate| receiver.public_send("#{candidate}_method_defined?", method_name) }
|
|
58
|
+
aliases = aliases_for(receiver, method_name, method)
|
|
59
|
+
super_method = method.super_method
|
|
60
|
+
original = method.respond_to?(:original_name) ? method.original_name.to_s : method_name.to_s
|
|
57
61
|
|
|
58
62
|
[method_name.to_s, {
|
|
59
63
|
"owner" => constant_name(method.owner),
|
|
60
64
|
"visibility" => visibility.to_s,
|
|
61
65
|
"parameters" => method.parameters.map { |kind, parameter| [kind.to_s, parameter&.to_s] },
|
|
62
66
|
"arity" => method.arity,
|
|
63
|
-
"source_location" => normalize_source(method.source_location)
|
|
64
|
-
|
|
67
|
+
"source_location" => normalize_source(method.source_location),
|
|
68
|
+
"provenance" => provenance_for(method.source_location),
|
|
69
|
+
"original_name" => original,
|
|
70
|
+
"aliases" => aliases,
|
|
71
|
+
"super_owner" => super_method && constant_name(super_method.owner)
|
|
72
|
+
}.compact]
|
|
65
73
|
rescue NameError
|
|
66
74
|
nil
|
|
67
75
|
end.to_h
|
|
@@ -83,5 +91,21 @@ module MonkeyLens
|
|
|
83
91
|
cwd = File.expand_path(Dir.pwd)
|
|
84
92
|
absolute.start_with?("#{cwd}/") ? absolute.delete_prefix("#{cwd}/") : absolute
|
|
85
93
|
end
|
|
94
|
+
|
|
95
|
+
def provenance_for(location)
|
|
96
|
+
return nil unless @provenance
|
|
97
|
+
|
|
98
|
+
Provenance.for_source_location(location)&.to_h
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def aliases_for(receiver, method_name, method)
|
|
102
|
+
receiver.instance_methods(true).filter_map do |other|
|
|
103
|
+
next if other == method_name
|
|
104
|
+
|
|
105
|
+
other.to_s if receiver.instance_method(other) == method
|
|
106
|
+
rescue NameError
|
|
107
|
+
nil
|
|
108
|
+
end.sort
|
|
109
|
+
end
|
|
86
110
|
end
|
|
87
111
|
end
|
data/lib/monkey_lens/cli.rb
CHANGED
|
@@ -40,13 +40,14 @@ module MonkeyLens
|
|
|
40
40
|
private
|
|
41
41
|
|
|
42
42
|
def capture
|
|
43
|
-
options = common_options.merge(output: ".monkeylens.json")
|
|
43
|
+
options = common_options.merge(output: ".monkeylens.json", provenance: false)
|
|
44
44
|
parser = parser_for("capture", options) do |opts|
|
|
45
45
|
opts.on("-o", "--output PATH", "Snapshot output path") { |value| options[:output] = value }
|
|
46
|
+
opts.on("--provenance", "Attribute method source to gem/app/stdlib/eval") { options[:provenance] = true }
|
|
46
47
|
end
|
|
47
48
|
parser.parse!(@argv)
|
|
48
49
|
config = load_runtime(options)
|
|
49
|
-
snapshot = MonkeyLens.capture(targets: config.targets, ignore_methods: config.ignore_methods)
|
|
50
|
+
snapshot = MonkeyLens.capture(targets: config.targets, ignore_methods: config.ignore_methods, provenance: options[:provenance])
|
|
50
51
|
snapshot.write(options[:output])
|
|
51
52
|
@out.puts "Captured #{snapshot.targets.length} target(s) to #{options[:output]}"
|
|
52
53
|
0
|
|
@@ -87,18 +88,26 @@ module MonkeyLens
|
|
|
87
88
|
|
|
88
89
|
def inspect_target
|
|
89
90
|
options = common_options
|
|
90
|
-
parser = parser_for("inspect", options)
|
|
91
|
+
parser = parser_for("inspect", options) do |opts|
|
|
92
|
+
opts.on("--provenance", "Attribute method source to gem/app/stdlib/eval") { |value| options[:provenance] = true }
|
|
93
|
+
end
|
|
91
94
|
parser.parse!(@argv)
|
|
92
95
|
target = @argv.shift
|
|
93
96
|
raise OptionParser::MissingArgument, "TARGET is required" unless target
|
|
94
97
|
|
|
95
98
|
load_requires(options[:requires])
|
|
96
|
-
snapshot = MonkeyLens.capture(targets: [target])
|
|
99
|
+
snapshot = MonkeyLens.capture(targets: [target], provenance: options[:provenance])
|
|
97
100
|
record = snapshot.targets.fetch(target)
|
|
98
101
|
@out.puts "#{target} (#{record.fetch("kind")})"
|
|
99
102
|
@out.puts "Ancestors: #{record.fetch("ancestors").join(" -> ")}"
|
|
100
103
|
record.fetch("instance_methods").each do |name, method|
|
|
101
|
-
|
|
104
|
+
source = method.fetch("source_location")&.join(":") || "native"
|
|
105
|
+
provenance = method["provenance"]
|
|
106
|
+
provenance_text = provenance ? " provenance=#{provenance.fetch("kind")}" : ""
|
|
107
|
+
aliases = Array(method["aliases"])
|
|
108
|
+
alias_text = aliases.empty? ? "" : " aliases=#{aliases.join(",")}"
|
|
109
|
+
super_text = method["super_owner"] ? " super=#{method["super_owner"]}" : ""
|
|
110
|
+
@out.puts " ##{name} [#{method.fetch("visibility")}] owner=#{method.fetch("owner")} source=#{source}#{provenance_text}#{alias_text}#{super_text}"
|
|
102
111
|
end
|
|
103
112
|
0
|
|
104
113
|
end
|
|
@@ -137,7 +146,7 @@ module MonkeyLens
|
|
|
137
146
|
end
|
|
138
147
|
|
|
139
148
|
def common_options
|
|
140
|
-
{config: ".monkeylens.yml", requires: []}
|
|
149
|
+
{config: ".monkeylens.yml", requires: [], provenance: false}
|
|
141
150
|
end
|
|
142
151
|
|
|
143
152
|
def parser_for(command, options)
|
data/lib/monkey_lens/diff.rb
CHANGED
|
@@ -101,8 +101,8 @@ module MonkeyLens
|
|
|
101
101
|
if before.fetch("owner") != after.fetch("owner")
|
|
102
102
|
changes << change("owner_changed", "critical", target, method_id:, before: before.fetch("owner"), after: after.fetch("owner"), message: "effective method owner changed")
|
|
103
103
|
end
|
|
104
|
-
if before.fetch("source_location") != after.fetch("source_location")
|
|
105
|
-
changes << change("source_changed", "high", target, method_id:, before: before.fetch("source_location"), after: after.fetch("source_location"), message: "method implementation source changed")
|
|
104
|
+
if before.fetch("source_location", nil) != after.fetch("source_location", nil)
|
|
105
|
+
changes << change("source_changed", "high", target, method_id:, before: before.fetch("source_location", nil), after: after.fetch("source_location", nil), message: "method implementation source changed")
|
|
106
106
|
end
|
|
107
107
|
if before.fetch("parameters") != after.fetch("parameters") || before.fetch("arity") != after.fetch("arity")
|
|
108
108
|
changes << change("signature_changed", "medium", target, method_id:, before: signature(before), after: signature(after), message: "method signature changed")
|
|
@@ -110,6 +110,12 @@ module MonkeyLens
|
|
|
110
110
|
if before.fetch("visibility") != after.fetch("visibility")
|
|
111
111
|
changes << change("visibility_changed", "medium", target, method_id:, before: before.fetch("visibility"), after: after.fetch("visibility"), message: "method visibility changed")
|
|
112
112
|
end
|
|
113
|
+
if before.fetch("aliases", []) != after.fetch("aliases", [])
|
|
114
|
+
changes << change("alias_changed", "medium", target, method_id:, before: before.fetch("aliases", []), after: after.fetch("aliases", []), message: "method aliases changed")
|
|
115
|
+
end
|
|
116
|
+
if before.fetch("super_owner", nil) != after.fetch("super_owner", nil)
|
|
117
|
+
changes << change("super_owner_changed", "high", target, method_id:, before: before.fetch("super_owner", nil), after: after.fetch("super_owner", nil), message: "super-method owner changed")
|
|
118
|
+
end
|
|
113
119
|
end
|
|
114
120
|
|
|
115
121
|
def signature(method)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module MonkeyLens
|
|
7
|
+
class Policy
|
|
8
|
+
class Waiver
|
|
9
|
+
attr_reader :type, :target, :method_pattern, :reason, :expires_on
|
|
10
|
+
|
|
11
|
+
def initialize(type: "*", target: "*", method: "*", reason:, expires_on: nil)
|
|
12
|
+
raise ConfigurationError, "waiver reason is required" if reason.to_s.strip.empty?
|
|
13
|
+
|
|
14
|
+
@type = type.to_s
|
|
15
|
+
@target = target.to_s
|
|
16
|
+
@method_pattern = method.to_s
|
|
17
|
+
@reason = reason.to_s
|
|
18
|
+
@expires_on = expires_on && Date.iso8601(expires_on.to_s)
|
|
19
|
+
rescue Date::Error
|
|
20
|
+
raise ConfigurationError, "invalid waiver expiration #{expires_on.inspect}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def match?(change, today: Date.today)
|
|
24
|
+
return false if expired?(today:)
|
|
25
|
+
|
|
26
|
+
matches?(type, change.type) &&
|
|
27
|
+
matches?(target, change.target) &&
|
|
28
|
+
matches?(method_pattern, change.method_id.to_s)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def expired?(today: Date.today)
|
|
32
|
+
expires_on && expires_on < today
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_h
|
|
36
|
+
{
|
|
37
|
+
"type" => type,
|
|
38
|
+
"target" => target,
|
|
39
|
+
"method" => method_pattern,
|
|
40
|
+
"reason" => reason,
|
|
41
|
+
"expires_on" => expires_on&.iso8601
|
|
42
|
+
}.compact
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def matches?(pattern, value)
|
|
48
|
+
File.fnmatch?(pattern, value.to_s, File::FNM_EXTGLOB)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class Decision
|
|
53
|
+
attr_reader :result, :effective_changes, :waived_changes, :threshold
|
|
54
|
+
|
|
55
|
+
def initialize(result:, effective_changes:, waived_changes:, threshold:)
|
|
56
|
+
@result = result
|
|
57
|
+
@effective_changes = effective_changes.freeze
|
|
58
|
+
@waived_changes = waived_changes.freeze
|
|
59
|
+
@threshold = threshold.to_s
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def clean?
|
|
63
|
+
effective_changes.empty?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def failure?
|
|
67
|
+
minimum = Change::LEVELS.fetch(threshold)
|
|
68
|
+
effective_changes.any? { |change| change.level >= minimum }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def to_h
|
|
72
|
+
{
|
|
73
|
+
"schema" => 1,
|
|
74
|
+
"clean" => clean?,
|
|
75
|
+
"failure" => failure?,
|
|
76
|
+
"threshold" => threshold,
|
|
77
|
+
"summary" => Change::LEVELS.keys.to_h do |level|
|
|
78
|
+
[level, effective_changes.count { |change| change.severity == level }]
|
|
79
|
+
end,
|
|
80
|
+
"effective_changes" => effective_changes.map(&:to_h),
|
|
81
|
+
"waived_changes" => waived_changes.map do |item|
|
|
82
|
+
{"change" => item.fetch(:change).to_h, "waiver" => item.fetch(:waiver).to_h}
|
|
83
|
+
end
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
attr_reader :waivers
|
|
89
|
+
|
|
90
|
+
def self.load(path)
|
|
91
|
+
data = YAML.safe_load_file(path, permitted_classes: [Date], aliases: false) || {}
|
|
92
|
+
from_hash(data)
|
|
93
|
+
rescue Psych::Exception => error
|
|
94
|
+
raise ConfigurationError, "invalid policy file #{path}: #{error.message}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.from_hash(data)
|
|
98
|
+
values = data["waivers"] || data[:waivers] || []
|
|
99
|
+
new(waivers: values)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def initialize(waivers: [])
|
|
103
|
+
@waivers = waivers.map do |waiver|
|
|
104
|
+
waiver.is_a?(Waiver) ? waiver : Waiver.new(**symbolize(waiver))
|
|
105
|
+
end.freeze
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def evaluate(result, threshold: "high", today: Date.today)
|
|
109
|
+
Change::LEVELS.fetch(threshold.to_s)
|
|
110
|
+
effective = []
|
|
111
|
+
waived = []
|
|
112
|
+
result.changes.each do |change|
|
|
113
|
+
waiver = waivers.find { |candidate| candidate.match?(change, today:) }
|
|
114
|
+
waiver ? waived << {change:, waiver:} : effective << change
|
|
115
|
+
end
|
|
116
|
+
Decision.new(result:, effective_changes: effective, waived_changes: waived, threshold:)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def symbolize(value)
|
|
122
|
+
value.to_h.each_with_object({}) { |(key, item), result| result[key.to_sym] = item }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MonkeyLens
|
|
4
|
+
class Provenance
|
|
5
|
+
KINDS = %w[gem app stdlib eval unknown].freeze
|
|
6
|
+
|
|
7
|
+
attr_reader :kind, :gem, :path, :line
|
|
8
|
+
|
|
9
|
+
def initialize(kind:, gem: nil, path: nil, line: nil)
|
|
10
|
+
@kind = kind.to_s
|
|
11
|
+
@gem = gem
|
|
12
|
+
@path = path
|
|
13
|
+
@line = line
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.for_source_location(location)
|
|
17
|
+
return nil unless location
|
|
18
|
+
|
|
19
|
+
path, line = location
|
|
20
|
+
path = path.to_s
|
|
21
|
+
line = Integer(line)
|
|
22
|
+
kind, gem_name = classify(path)
|
|
23
|
+
new(kind:, gem: gem_name, path: normalize_path(path), line:)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.classify(path)
|
|
27
|
+
return ["eval", nil] if eval_path?(path)
|
|
28
|
+
|
|
29
|
+
expanded = File.expand_path(path)
|
|
30
|
+
gem_info = gem_for_path(expanded)
|
|
31
|
+
return ["gem", gem_info] if gem_info
|
|
32
|
+
|
|
33
|
+
if stdlib_path?(expanded)
|
|
34
|
+
["stdlib", nil]
|
|
35
|
+
elsif app_path?(expanded)
|
|
36
|
+
["app", nil]
|
|
37
|
+
else
|
|
38
|
+
["unknown", nil]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.eval_path?(path)
|
|
43
|
+
path.start_with?("(eval") || path == "eval"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.gem_for_path(path)
|
|
47
|
+
Gem.loaded_specs.each_value do |spec|
|
|
48
|
+
spec.full_require_paths.each do |require_path|
|
|
49
|
+
expanded = File.expand_path(require_path)
|
|
50
|
+
next unless path.start_with?("#{expanded}/") || path == expanded
|
|
51
|
+
|
|
52
|
+
return spec.name
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.stdlib_path?(path)
|
|
59
|
+
ruby_root = RbConfig::CONFIG["rubylibdir"]
|
|
60
|
+
return path.start_with?(ruby_root) if ruby_root
|
|
61
|
+
|
|
62
|
+
path.include?("/ruby/") && path.include?("/lib/")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.app_path?(path)
|
|
66
|
+
cwd = File.expand_path(Dir.pwd)
|
|
67
|
+
path.start_with?("#{cwd}/") || path == cwd
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.normalize_path(path)
|
|
71
|
+
absolute = File.expand_path(path)
|
|
72
|
+
cwd = File.expand_path(Dir.pwd)
|
|
73
|
+
absolute.start_with?("#{cwd}/") ? absolute.delete_prefix("#{cwd}/") : absolute
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def to_h
|
|
77
|
+
{
|
|
78
|
+
"kind" => kind,
|
|
79
|
+
"gem" => gem,
|
|
80
|
+
"path" => path,
|
|
81
|
+
"line" => line
|
|
82
|
+
}.compact
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/monkey_lens/version.rb
CHANGED
data/lib/monkey_lens.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "monkey_lens/config"
|
|
|
5
5
|
require_relative "monkey_lens/snapshot"
|
|
6
6
|
require_relative "monkey_lens/change"
|
|
7
7
|
require_relative "monkey_lens/capture"
|
|
8
|
+
require_relative "monkey_lens/provenance"
|
|
8
9
|
require_relative "monkey_lens/diff"
|
|
9
10
|
require_relative "monkey_lens/formatter"
|
|
10
11
|
|
|
@@ -15,13 +16,18 @@ module MonkeyLens
|
|
|
15
16
|
|
|
16
17
|
module_function
|
|
17
18
|
|
|
18
|
-
def capture(targets:, ignore_methods: [])
|
|
19
|
-
Capture.new(targets:, ignore_methods:).call
|
|
19
|
+
def capture(targets:, ignore_methods: [], provenance: false)
|
|
20
|
+
Capture.new(targets:, ignore_methods:, provenance:).call
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def diff(baseline, current)
|
|
23
24
|
Diff.new(baseline:, current:).call
|
|
24
25
|
end
|
|
26
|
+
|
|
27
|
+
def evaluate(result, policy:, threshold: "high", today: Date.today)
|
|
28
|
+
policy.evaluate(result, threshold:, today:)
|
|
29
|
+
end
|
|
25
30
|
end
|
|
26
31
|
|
|
32
|
+
require_relative "monkey_lens/policy"
|
|
27
33
|
require_relative "monkey_lens/railtie" if defined?(Rails::Railtie)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: monkey_lens
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthew Looney
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '6.0'
|
|
19
19
|
type: :development
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '6.0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: rake
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -63,6 +63,8 @@ files:
|
|
|
63
63
|
- lib/monkey_lens/formatter/json.rb
|
|
64
64
|
- lib/monkey_lens/formatter/sarif.rb
|
|
65
65
|
- lib/monkey_lens/formatter/text.rb
|
|
66
|
+
- lib/monkey_lens/policy.rb
|
|
67
|
+
- lib/monkey_lens/provenance.rb
|
|
66
68
|
- lib/monkey_lens/railtie.rb
|
|
67
69
|
- lib/monkey_lens/rake_task.rb
|
|
68
70
|
- lib/monkey_lens/snapshot.rb
|