lex-swarm-github 0.3.2 → 0.3.4
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/Gemfile +1 -0
- data/lib/legion/extensions/swarm_github/actors/lifecycle_subscriber.rb +2 -6
- data/lib/legion/extensions/swarm_github/actors/stale_issues.rb +1 -1
- data/lib/legion/extensions/swarm_github/runners/extension_lifecycle.rb +32 -9
- data/lib/legion/extensions/swarm_github/runners/github_swarm.rb +2 -2
- data/lib/legion/extensions/swarm_github/runners/pr_pipeline.rb +2 -1
- data/lib/legion/extensions/swarm_github/runners/pull_request_reviewer.rb +6 -6
- data/lib/legion/extensions/swarm_github/runners/review_notifier.rb +1 -1
- data/lib/legion/extensions/swarm_github/runners/review_poster.rb +1 -1
- data/lib/legion/extensions/swarm_github/version.rb +1 -1
- data/lib/legion/extensions/swarm_github.rb +2 -2
- data/spec/legion/extensions/swarm_github/actors/lifecycle_subscriber_spec.rb +1 -1
- data/spec/legion/extensions/swarm_github/runners/extension_lifecycle_spec.rb +35 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 872fde87c10b7bf7e68f2aab5b56d05d9e66e552511061463e75fcabe714ca62
|
|
4
|
+
data.tar.gz: 72ccbe57f57adabf3ecce969ce4fb721485e9478aabe934e56ed159d6ca31959
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4331730e59dafc94477cc02309afd0b04f2c56d5556570fe69169c490b62921e31ac6cc4e49606d5f93082dff93d652cfc68992f7a71ac150b1f764098acdffb
|
|
7
|
+
data.tar.gz: 39de677ac295aa04c728f47e16433b7a99c6e6baff36353d48702643b34f15d7dd204f70c1a25003342cc48d6352ac3c58d6ac0dab7439948eb4dce6d2dae4d2
|
data/Gemfile
CHANGED
|
@@ -9,7 +9,7 @@ module Legion
|
|
|
9
9
|
class LifecycleSubscriber < Legion::Extensions::Actors::Subscription
|
|
10
10
|
def runner_class = self.class
|
|
11
11
|
def runner_function = 'action'
|
|
12
|
-
def check_subtask? =
|
|
12
|
+
def check_subtask? = true
|
|
13
13
|
def generate_task? = false
|
|
14
14
|
|
|
15
15
|
def action(payload)
|
|
@@ -54,11 +54,7 @@ module Legion
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def log
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@log ||= Object.new.tap do |nl|
|
|
60
|
-
%i[debug info warn error fatal].each { |m| nl.define_singleton_method(m) { |*| nil } }
|
|
61
|
-
end
|
|
57
|
+
Legion::Logging
|
|
62
58
|
end
|
|
63
59
|
end
|
|
64
60
|
end
|
|
@@ -6,7 +6,7 @@ module Legion
|
|
|
6
6
|
module Extensions
|
|
7
7
|
module SwarmGithub
|
|
8
8
|
module Actor
|
|
9
|
-
class StaleIssues < Legion::Extensions::Actors::Every
|
|
9
|
+
class StaleIssues < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/EveryActorRequiresTime
|
|
10
10
|
def runner_class
|
|
11
11
|
Legion::Extensions::SwarmGithub::Runners::GithubSwarm
|
|
12
12
|
end
|
|
@@ -7,7 +7,9 @@ module Legion
|
|
|
7
7
|
module ExtensionLifecycle
|
|
8
8
|
extend self
|
|
9
9
|
|
|
10
|
-
def run_lifecycle(generation
|
|
10
|
+
def run_lifecycle(generation: nil, review: nil, review_k: nil, review_models: nil, **payload)
|
|
11
|
+
generation ||= build_generation(payload)
|
|
12
|
+
review ||= build_review(payload)
|
|
11
13
|
config = github_config
|
|
12
14
|
return { success: false, error: :github_not_enabled } unless config[:enabled]
|
|
13
15
|
return { success: false, error: :target_repo_missing } unless config[:target_repo]
|
|
@@ -64,7 +66,7 @@ module Legion
|
|
|
64
66
|
github_client.commit_files(owner: owner, repo: repo, branch: branch, files: files, message: message)
|
|
65
67
|
end
|
|
66
68
|
|
|
67
|
-
def open_pull_request(owner:, repo:, branch:, base:, generation:, review:)
|
|
69
|
+
def open_pull_request(owner:, repo:, branch:, base:, generation:, review:)
|
|
68
70
|
return { success: false, error: :github_runner_unavailable } unless github_runner_available?
|
|
69
71
|
|
|
70
72
|
body = build_pr_body(generation: generation, review: review)
|
|
@@ -84,7 +86,7 @@ module Legion
|
|
|
84
86
|
return { success: true } unless pull_number && labels&.any?
|
|
85
87
|
return { success: false, error: :github_runner_unavailable } unless github_runner_available?
|
|
86
88
|
|
|
87
|
-
github_client.
|
|
89
|
+
github_client.add_labels_to_issue(owner: owner, repo: repo, issue_number: pull_number, labels: labels)
|
|
88
90
|
rescue StandardError => e
|
|
89
91
|
log.warn("Label failed: #{e.message}")
|
|
90
92
|
{ success: false, error: e.message }
|
|
@@ -172,8 +174,9 @@ module Legion
|
|
|
172
174
|
Legion::Extensions::SwarmGithub::Runners::PullRequestReviewer.review_pull_request(**kwargs)
|
|
173
175
|
end
|
|
174
176
|
|
|
177
|
+
blocker_severities = %w[error critical].freeze
|
|
175
178
|
approvals = reviews.count do |r|
|
|
176
|
-
r[:status] == 'reviewed' && (r[:comments] || []).none? { |c|
|
|
179
|
+
r[:status] == 'reviewed' && (r[:comments] || []).none? { |c| blocker_severities.include?(c[:severity]&.to_s) }
|
|
177
180
|
end
|
|
178
181
|
rejections = k - approvals
|
|
179
182
|
|
|
@@ -190,7 +193,7 @@ module Legion
|
|
|
190
193
|
{ success: true, skipped: true, reason: :review_error }
|
|
191
194
|
end
|
|
192
195
|
|
|
193
|
-
def handle_auto_merge(owner:, repo:, pull_number:, config:, review:, review_result: nil)
|
|
196
|
+
def handle_auto_merge(owner:, repo:, pull_number:, config:, review:, review_result: nil)
|
|
194
197
|
return unless config[:auto_merge] && review[:verdict]&.to_sym == :approve
|
|
195
198
|
return if review_result && review_result[:consensus] == :request_changes
|
|
196
199
|
|
|
@@ -275,11 +278,31 @@ module Legion
|
|
|
275
278
|
end
|
|
276
279
|
|
|
277
280
|
def log
|
|
278
|
-
|
|
281
|
+
Legion::Logging
|
|
282
|
+
end
|
|
279
283
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
284
|
+
def build_generation(payload)
|
|
285
|
+
{
|
|
286
|
+
name: payload[:name],
|
|
287
|
+
generation_id: payload[:generation_id],
|
|
288
|
+
gap_id: payload[:gap_id],
|
|
289
|
+
gap_type: payload[:gap_type],
|
|
290
|
+
tier: payload[:tier],
|
|
291
|
+
code: payload[:code],
|
|
292
|
+
spec_code: payload[:spec_code],
|
|
293
|
+
file_path: payload[:file_path],
|
|
294
|
+
spec_path: payload[:spec_path]
|
|
295
|
+
}.compact
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def build_review(payload)
|
|
299
|
+
{
|
|
300
|
+
verdict: payload[:verdict],
|
|
301
|
+
confidence: payload[:confidence],
|
|
302
|
+
stages: payload[:stages],
|
|
303
|
+
issues: payload[:issues],
|
|
304
|
+
passed: payload[:passed]
|
|
305
|
+
}.compact
|
|
283
306
|
end
|
|
284
307
|
end
|
|
285
308
|
end
|
|
@@ -5,8 +5,8 @@ module Legion
|
|
|
5
5
|
module SwarmGithub
|
|
6
6
|
module Runners
|
|
7
7
|
module GithubSwarm
|
|
8
|
-
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
-
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
10
10
|
|
|
11
11
|
def ingest_issue(repo:, issue_number:, title:, labels: [], **)
|
|
12
12
|
key = issue_tracker.track(repo: repo, issue_number: issue_number, title: title, labels: labels)
|
|
@@ -43,7 +43,8 @@ module Legion
|
|
|
43
43
|
return unless issue_number
|
|
44
44
|
|
|
45
45
|
review_comments = review_result.dig(:review, :comments) || []
|
|
46
|
-
|
|
46
|
+
blocker_severities = %w[critical high].freeze
|
|
47
|
+
approved = review_comments.none? { |c| blocker_severities.include?(c[:severity]&.to_s&.downcase) }
|
|
47
48
|
@issue_tracker&.record_validation(
|
|
48
49
|
"#{repo}##{issue_number}",
|
|
49
50
|
validator: :code_review,
|
|
@@ -31,7 +31,7 @@ module Legion
|
|
|
31
31
|
owner: owner, repo: repo, pull_number: pull_number
|
|
32
32
|
)[:result] || []
|
|
33
33
|
rescue StandardError => e
|
|
34
|
-
log.warn(e.message) if respond_to?(:log, true)
|
|
34
|
+
log.warn(e.message) if respond_to?(:log, true) # rubocop:disable Legion/HelperMigration/LoggingGuard
|
|
35
35
|
[]
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -43,18 +43,18 @@ module Legion
|
|
|
43
43
|
llm_kwargs = { message: prompt, caller: { extension: 'lex-swarm-github' } }
|
|
44
44
|
llm_kwargs[:model] = model if model
|
|
45
45
|
llm_kwargs[:provider] = provider if provider
|
|
46
|
-
response = Legion::LLM.chat(**llm_kwargs)
|
|
46
|
+
response = Legion::LLM.chat(**llm_kwargs) # rubocop:disable Legion/HelperMigration/DirectLlm
|
|
47
47
|
parse_review_response(response)
|
|
48
48
|
end
|
|
49
49
|
merge_chunk_reviews(reviews)
|
|
50
50
|
rescue StandardError => e
|
|
51
|
-
log.warn("Review generation failed: #{e.message}") if respond_to?(:log, true)
|
|
51
|
+
log.warn("Review generation failed: #{e.message}") if respond_to?(:log, true) # rubocop:disable Legion/HelperMigration/LoggingGuard
|
|
52
52
|
{ summary: 'Review generation failed', comments: [] }
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def merge_chunk_reviews(reviews)
|
|
56
56
|
merged_comments = reviews.flat_map { |r| r[:comments] || [] }
|
|
57
|
-
summaries = reviews.
|
|
57
|
+
summaries = reviews.filter_map { |r| r[:summary] }
|
|
58
58
|
{
|
|
59
59
|
summary: summaries.join("\n\n"),
|
|
60
60
|
comments: merged_comments
|
|
@@ -62,9 +62,9 @@ module Legion
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def parse_review_response(response)
|
|
65
|
-
|
|
65
|
+
json_parse(response)
|
|
66
66
|
rescue StandardError => e
|
|
67
|
-
log.warn(e.message) if respond_to?(:log, true)
|
|
67
|
+
log.warn(e.message) if respond_to?(:log, true) # rubocop:disable Legion/HelperMigration/LoggingGuard
|
|
68
68
|
{ summary: response.to_s, comments: [] }
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -17,7 +17,7 @@ module Legion
|
|
|
17
17
|
|
|
18
18
|
{ notified: result[:ok] == true, channel: channel, ts: result[:ts] }
|
|
19
19
|
rescue StandardError => e
|
|
20
|
-
log.warn(e.message) if respond_to?(:log, true)
|
|
20
|
+
log.warn(e.message) if respond_to?(:log, true) # rubocop:disable Legion/HelperMigration/LoggingGuard
|
|
21
21
|
{ notified: false, reason: "slack error: #{e.message}" }
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -22,7 +22,7 @@ module Legion
|
|
|
22
22
|
review_id = result.dig(:result, 'id') || result.dig(:result, :id)
|
|
23
23
|
{ posted: true, review_id: review_id, comments_count: inline_comments.size }
|
|
24
24
|
rescue StandardError => e
|
|
25
|
-
log.warn(e.message) if respond_to?(:log, true)
|
|
25
|
+
log.warn(e.message) if respond_to?(:log, true) # rubocop:disable Legion/HelperMigration/LoggingGuard
|
|
26
26
|
{ posted: false, reason: "post failed: #{e.message}" }
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -11,12 +11,12 @@ require 'legion/extensions/swarm_github/runners/review_poster'
|
|
|
11
11
|
require 'legion/extensions/swarm_github/runners/review_notifier'
|
|
12
12
|
require 'legion/extensions/swarm_github/runners/pr_pipeline'
|
|
13
13
|
require 'legion/extensions/swarm_github/runners/extension_lifecycle'
|
|
14
|
-
require 'legion/extensions/swarm_github/actors/lifecycle_subscriber' if defined?(Legion::Extensions::Actors::Subscription)
|
|
14
|
+
require 'legion/extensions/swarm_github/actors/lifecycle_subscriber' if defined?(Legion::Extensions::Actors::Subscription) # rubocop:disable Legion/HelperMigration/RequireDefinedGuard
|
|
15
15
|
|
|
16
16
|
module Legion
|
|
17
17
|
module Extensions
|
|
18
18
|
module SwarmGithub
|
|
19
|
-
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
19
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core, false
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -22,7 +22,7 @@ RSpec.describe Legion::Extensions::SwarmGithub::Actor::LifecycleSubscriber do
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
describe '#check_subtask?' do
|
|
25
|
-
it { expect(actor.check_subtask?).to be
|
|
25
|
+
it { expect(actor.check_subtask?).to be true }
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
describe '#generate_task?' do
|
|
@@ -78,7 +78,7 @@ RSpec.describe Legion::Extensions::SwarmGithub::Runners::ExtensionLifecycle do
|
|
|
78
78
|
allow(github_client).to receive(:create_pull_request).and_return(
|
|
79
79
|
{ result: { 'number' => 42, 'html_url' => 'https://github.com/org/repo/pull/42' } }
|
|
80
80
|
)
|
|
81
|
-
allow(github_client).to receive(:
|
|
81
|
+
allow(github_client).to receive(:add_labels_to_issue).and_return({ success: true })
|
|
82
82
|
allow(runner).to receive(:pr_reviewer_available?).and_return(false)
|
|
83
83
|
end
|
|
84
84
|
|
|
@@ -109,14 +109,46 @@ RSpec.describe Legion::Extensions::SwarmGithub::Runners::ExtensionLifecycle do
|
|
|
109
109
|
)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
-
it 'calls
|
|
112
|
+
it 'calls add_labels_to_issue' do
|
|
113
113
|
runner.run_lifecycle(generation: generation, review: review)
|
|
114
|
-
expect(github_client).to have_received(:
|
|
114
|
+
expect(github_client).to have_received(:add_labels_to_issue).with(
|
|
115
115
|
hash_including(labels: %w[auto-generated needs-review])
|
|
116
116
|
)
|
|
117
117
|
end
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
+
context 'when called with a flat payload (no generation/review keys)' do
|
|
121
|
+
let(:github_client) { double('Github::Client') }
|
|
122
|
+
|
|
123
|
+
before do
|
|
124
|
+
runner.instance_variable_set(:@github_client, nil)
|
|
125
|
+
allow(runner).to receive(:github_config).and_return(
|
|
126
|
+
{ enabled: true, target_repo: 'org/repo', target_branch: 'main',
|
|
127
|
+
auto_merge: false, pr_labels: [], branch_prefix: 'feature/auto-generated' }
|
|
128
|
+
)
|
|
129
|
+
stub_const('Legion::Extensions::Github::Client', Class.new)
|
|
130
|
+
allow(Legion::Extensions::Github::Client).to receive(:new).and_return(github_client)
|
|
131
|
+
allow(github_client).to receive(:create_branch).and_return({ success: true })
|
|
132
|
+
allow(github_client).to receive(:commit_files).and_return({ success: true })
|
|
133
|
+
allow(github_client).to receive(:create_pull_request).and_return(
|
|
134
|
+
{ result: { 'number' => 99, 'html_url' => 'https://github.com/org/repo/pull/99' } }
|
|
135
|
+
)
|
|
136
|
+
allow(runner).to receive(:pr_reviewer_available?).and_return(false)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'builds generation and review from flat keys and returns success' do
|
|
140
|
+
result = runner.run_lifecycle(
|
|
141
|
+
name: 'lex-bar', generation_id: 'gen-flat', gap_id: 'gap-002',
|
|
142
|
+
gap_type: 'missing_runner', tier: 'utility',
|
|
143
|
+
code: '# code', spec_code: '# spec', file_path: 'lib/foo.rb', spec_path: 'spec/foo_spec.rb',
|
|
144
|
+
verdict: 'approve', stages: {}
|
|
145
|
+
)
|
|
146
|
+
expect(result[:success]).to be true
|
|
147
|
+
expect(result[:pull_number]).to eq(99)
|
|
148
|
+
expect(result[:generation_id]).to eq('gen-flat')
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
120
152
|
context 'when branch creation fails' do
|
|
121
153
|
before do
|
|
122
154
|
runner.instance_variable_set(:@github_client, nil)
|