lex-detect 0.1.6 → 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 +12 -0
- data/CLAUDE.md +2 -2
- data/lib/legion/extensions/detect/formatters/markdown_pr.rb +61 -0
- data/lib/legion/extensions/detect/formatters/sarif.rb +82 -0
- data/lib/legion/extensions/detect/runners/task_observer.rb +5 -5
- data/lib/legion/extensions/detect/version.rb +1 -1
- data/lib/legion/extensions/detect.rb +11 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e40e042cb6111ce0d3708ed3de574e32baf92839704e2a22ed4c239bcaf4907
|
|
4
|
+
data.tar.gz: 4fdd6b3f2388a5aeb8a5a862e57cc29d57aa239793dbccdcabbdc1967267a031
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8d8a294a962b08b21d08d26b9189ab01e7194a244e04299b2094e3eecfd5b00f40cebc56d438ea71b5ed2a47aab10bb77bf465eb8b71eff5821fdb5e11cd9f55
|
|
7
|
+
data.tar.gz: 634257c97700a444f7a846ef249d69d718e5cf2d0202793bd38ba156bb48fae025410d98e7aeba56a8735647334d34c2f4bfd7cbdf53fc2c8870e50bf55297ce
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026-03-22
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- SARIF 2.1.0 formatter (`Formatters::Sarif`) for GitHub Code Scanning integration
|
|
7
|
+
- Markdown PR comment formatter (`Formatters::MarkdownPr`) for GitHub PR annotations
|
|
8
|
+
- `format_results(format:, detections:)` public API method supporting `:sarif`, `:markdown`, and `:json` output formats
|
|
9
|
+
|
|
10
|
+
## [0.1.7] - 2026-03-22
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `TaskObserver` queries now use `created` column instead of nonexistent `started_at` on the tasks table, fixing `PG::UndefinedColumn` errors on PostgreSQL
|
|
14
|
+
|
|
3
15
|
## [0.1.6] - 2026-03-20
|
|
4
16
|
|
|
5
17
|
### Fixed
|
data/CLAUDE.md
CHANGED
|
@@ -10,7 +10,7 @@ Legion Extension that scans the local environment and recommends which `lex-*` e
|
|
|
10
10
|
|
|
11
11
|
**GitHub**: https://github.com/LegionIO/lex-detect
|
|
12
12
|
**License**: MIT
|
|
13
|
-
**Version**: 0.
|
|
13
|
+
**Version**: 0.2.0
|
|
14
14
|
|
|
15
15
|
## Architecture
|
|
16
16
|
|
|
@@ -57,7 +57,7 @@ Legion::Extensions::Detect.catalog # raw CATALOG constant
|
|
|
57
57
|
|
|
58
58
|
## Testing
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
54 specs across 9 spec files.
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
63
|
bundle install
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Detect
|
|
6
|
+
module Formatters
|
|
7
|
+
module MarkdownPr
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def format(detections)
|
|
11
|
+
missing = collect_missing(detections)
|
|
12
|
+
installed = collect_installed(detections)
|
|
13
|
+
|
|
14
|
+
lines = ["## Legion Detect Findings\n"]
|
|
15
|
+
|
|
16
|
+
if missing.any?
|
|
17
|
+
lines << "### Missing Extensions (#{missing.size})\n"
|
|
18
|
+
missing.each do |m|
|
|
19
|
+
lines << "- **#{m[:name]}**: `#{m[:extension]}` not installed"
|
|
20
|
+
lines << " Detected by: #{m[:signals].join(', ')}"
|
|
21
|
+
lines << " Install: `gem install #{m[:extension]}`\n"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if installed.any?
|
|
26
|
+
lines << "### Installed (#{installed.size})\n"
|
|
27
|
+
installed.each do |i|
|
|
28
|
+
lines << "- #{i[:name]}: `#{i[:extension]}` :white_check_mark:"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
lines << "No extensions detected.\n" if missing.empty? && installed.empty?
|
|
33
|
+
|
|
34
|
+
lines << "\n---\n*Generated by legion-detect v#{VERSION}*"
|
|
35
|
+
lines.join("\n")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def collect_missing(detections)
|
|
39
|
+
detections.flat_map do |d|
|
|
40
|
+
d[:extensions].filter_map do |ext|
|
|
41
|
+
next if d[:installed][ext]
|
|
42
|
+
|
|
43
|
+
{ name: d[:name], extension: ext, signals: d[:matched_signals] }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def collect_installed(detections)
|
|
49
|
+
detections.flat_map do |d|
|
|
50
|
+
d[:extensions].filter_map do |ext|
|
|
51
|
+
next unless d[:installed][ext]
|
|
52
|
+
|
|
53
|
+
{ name: d[:name], extension: ext }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Detect
|
|
8
|
+
module Formatters
|
|
9
|
+
module Sarif
|
|
10
|
+
SCHEMA = 'https://json.schemastore.org/sarif-2.1.0.json'
|
|
11
|
+
SARIF_VERSION = '2.1.0'
|
|
12
|
+
|
|
13
|
+
SEVERITY_MAP = {
|
|
14
|
+
missing: 'warning',
|
|
15
|
+
installed: 'note'
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def format(detections)
|
|
21
|
+
rules = build_rules(detections)
|
|
22
|
+
results = build_results(detections)
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
'$schema' => SCHEMA,
|
|
26
|
+
'version' => SARIF_VERSION,
|
|
27
|
+
'runs' => [{
|
|
28
|
+
'tool' => {
|
|
29
|
+
'driver' => {
|
|
30
|
+
'name' => 'legion-detect',
|
|
31
|
+
'version' => VERSION,
|
|
32
|
+
'informationUri' => 'https://github.com/LegionIO/lex-detect',
|
|
33
|
+
'rules' => rules
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
'results' => results
|
|
37
|
+
}]
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_json(detections)
|
|
42
|
+
::JSON.pretty_generate(format(detections))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def build_rules(detections)
|
|
46
|
+
rules = detections.flat_map do |detection|
|
|
47
|
+
detection[:extensions].map do |ext|
|
|
48
|
+
{
|
|
49
|
+
'id' => "detect/#{ext}",
|
|
50
|
+
'name' => detection[:name],
|
|
51
|
+
'shortDescription' => { 'text' => "#{detection[:name]} detected — #{ext} recommended" },
|
|
52
|
+
'defaultConfiguration' => { 'level' => 'warning' }
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
rules.uniq { |r| r['id'] }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_results(detections)
|
|
60
|
+
detections.flat_map do |detection|
|
|
61
|
+
detection[:extensions].filter_map do |ext|
|
|
62
|
+
next if detection[:installed][ext]
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
'ruleId' => "detect/#{ext}",
|
|
66
|
+
'level' => 'warning',
|
|
67
|
+
'message' => {
|
|
68
|
+
'text' => "#{detection[:name]} detected (#{detection[:matched_signals].join(', ')}) but #{ext} is not installed"
|
|
69
|
+
},
|
|
70
|
+
'properties' => {
|
|
71
|
+
'matched_signals' => detection[:matched_signals],
|
|
72
|
+
'detection_name' => detection[:name]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -26,7 +26,7 @@ module Legion
|
|
|
26
26
|
|
|
27
27
|
since ||= Time.now - 60
|
|
28
28
|
db_tasks = Legion::Data.connection[:tasks]
|
|
29
|
-
.where {
|
|
29
|
+
.where { created > since }
|
|
30
30
|
.all
|
|
31
31
|
|
|
32
32
|
alerts = db_tasks.filter_map { |task| evaluate_rules(task) }
|
|
@@ -46,9 +46,9 @@ module Legion
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def check_timeout_risk(task, expected_duration: 120)
|
|
49
|
-
return nil unless task[:status] == 'running' && task[:
|
|
49
|
+
return nil unless task[:status] == 'running' && task[:created]
|
|
50
50
|
|
|
51
|
-
elapsed = Time.now - task[:
|
|
51
|
+
elapsed = Time.now - task[:created]
|
|
52
52
|
return nil unless elapsed > (expected_duration * 2)
|
|
53
53
|
|
|
54
54
|
{
|
|
@@ -66,7 +66,7 @@ module Legion
|
|
|
66
66
|
|
|
67
67
|
count = Legion::Data.connection[:tasks]
|
|
68
68
|
.where(runner_class: task[:runner_class], status: 'failed')
|
|
69
|
-
.where {
|
|
69
|
+
.where { created > Time.now - 600 }
|
|
70
70
|
.count
|
|
71
71
|
return nil unless count >= 3
|
|
72
72
|
|
|
@@ -109,7 +109,7 @@ module Legion
|
|
|
109
109
|
runner: task[:runner_class],
|
|
110
110
|
rule: alert&.dig(:rule),
|
|
111
111
|
severity: alert&.dig(:severity),
|
|
112
|
-
duration: task[:
|
|
112
|
+
duration: task[:created] ? (Time.now - task[:created]).round(2) : nil,
|
|
113
113
|
token_cost: nil,
|
|
114
114
|
observed_at: Time.now.utc
|
|
115
115
|
)
|
|
@@ -4,6 +4,8 @@ require 'legion/extensions/detect/version'
|
|
|
4
4
|
require 'legion/extensions/detect/catalog'
|
|
5
5
|
require 'legion/extensions/detect/scanner'
|
|
6
6
|
require 'legion/extensions/detect/installer'
|
|
7
|
+
require 'legion/extensions/detect/formatters/sarif'
|
|
8
|
+
require 'legion/extensions/detect/formatters/markdown_pr'
|
|
7
9
|
require_relative 'detect/runners/task_observer'
|
|
8
10
|
require_relative 'detect/runners/cancel_task'
|
|
9
11
|
|
|
@@ -41,6 +43,15 @@ module Legion
|
|
|
41
43
|
def catalog
|
|
42
44
|
CATALOG
|
|
43
45
|
end
|
|
46
|
+
|
|
47
|
+
def format_results(format: :json, detections: nil)
|
|
48
|
+
results = detections || scan
|
|
49
|
+
case format.to_sym
|
|
50
|
+
when :sarif then Formatters::Sarif.to_json(results)
|
|
51
|
+
when :markdown then Formatters::MarkdownPr.format(results)
|
|
52
|
+
else results
|
|
53
|
+
end
|
|
54
|
+
end
|
|
44
55
|
end
|
|
45
56
|
|
|
46
57
|
require_relative 'detect/actors/full_scan' if defined?(Legion::Extensions::Actors::Once)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-detect
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -31,6 +31,8 @@ files:
|
|
|
31
31
|
- lib/legion/extensions/detect/actors/full_scan.rb
|
|
32
32
|
- lib/legion/extensions/detect/actors/observer_tick.rb
|
|
33
33
|
- lib/legion/extensions/detect/catalog.rb
|
|
34
|
+
- lib/legion/extensions/detect/formatters/markdown_pr.rb
|
|
35
|
+
- lib/legion/extensions/detect/formatters/sarif.rb
|
|
34
36
|
- lib/legion/extensions/detect/installer.rb
|
|
35
37
|
- lib/legion/extensions/detect/local_migrations/20260319000001_create_detect_results.rb
|
|
36
38
|
- lib/legion/extensions/detect/local_migrations/20260320000001_create_observer_events.rb
|