lex-codegen 0.1.1 → 0.1.2
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 +7 -0
- data/lib/legion/extensions/codegen/local_migrations/20260320000001_create_codegen_fixes.rb +22 -0
- data/lib/legion/extensions/codegen/runners/auto_fix.rb +165 -0
- data/lib/legion/extensions/codegen/version.rb +1 -1
- data/lib/legion/extensions/codegen.rb +8 -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: 16434586bacb8d4a8cab6c1d886144a76058c07a9cc5b9d75f6b7c2e94159bc8
|
|
4
|
+
data.tar.gz: 2d442d686396ce9beb409d8597532cc94b22f77fcfa39a802333fc7d64e61844
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e9ecfc2219050fc9fcdd70092d59272670d1c2be097c1ef813e401f8176278b952c8af22280892097efdf177f796f47c7d84e68726bef9a3adcf648f8b626357
|
|
7
|
+
data.tar.gz: d12994780bfb62571cafbc961f420cc7482ad9b0d987fa4050ebe36cf4d01f61219575c143f892f7379f71be31b91d32c73a186403325f515cbab17652888397
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.2] - 2026-03-20
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `codegen_fixes` table migration for self-healing pipeline tracking
|
|
7
|
+
- `Runners::AutoFix` for LLM-powered automated extension repair with git branch creation and spec validation
|
|
8
|
+
- Migration registration via `Legion::Data::Local.register_migrations`
|
|
9
|
+
|
|
3
10
|
## [0.1.0] - 2026-03-13
|
|
4
11
|
|
|
5
12
|
### Added
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
create_table?(:codegen_fixes) do
|
|
6
|
+
primary_key :id
|
|
7
|
+
String :fix_id, size: 64, null: false, unique: true, index: true
|
|
8
|
+
String :gem_name, size: 128, null: false, index: true
|
|
9
|
+
String :runner_class, size: 255
|
|
10
|
+
String :branch, size: 255
|
|
11
|
+
column :patch, :text
|
|
12
|
+
String :status, size: 32, default: 'pending', null: false, index: true
|
|
13
|
+
TrueClass :specs_passed, default: false
|
|
14
|
+
column :spec_output, :text
|
|
15
|
+
DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
down do
|
|
20
|
+
drop_table?(:codegen_fixes)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Codegen
|
|
8
|
+
module Runners
|
|
9
|
+
module AutoFix
|
|
10
|
+
extend self
|
|
11
|
+
|
|
12
|
+
def auto_fix(gem_name:, runner_class:, error_class:, backtraces:, **)
|
|
13
|
+
return { success: false, reason: :llm_unavailable } unless defined?(Legion::LLM)
|
|
14
|
+
|
|
15
|
+
spec = begin
|
|
16
|
+
Gem::Specification.find_by_name(gem_name)
|
|
17
|
+
rescue LoadError
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
return { success: false, reason: :gem_not_found } unless spec
|
|
21
|
+
|
|
22
|
+
source_file = locate_source(spec, runner_class)
|
|
23
|
+
return { success: false, reason: :source_not_found } unless source_file && ::File.exist?(source_file)
|
|
24
|
+
|
|
25
|
+
source = ::File.read(source_file)
|
|
26
|
+
prompt = build_fix_prompt(source, error_class, backtraces)
|
|
27
|
+
fix_response = Legion::LLM.chat(messages: [{ role: 'user', content: prompt }])
|
|
28
|
+
|
|
29
|
+
patch = extract_patch(fix_response)
|
|
30
|
+
return { success: false, reason: :no_patch_generated } unless patch
|
|
31
|
+
|
|
32
|
+
branch = "fix/#{gem_name}-#{::Time.now.to_i}"
|
|
33
|
+
apply_result = apply_and_test(spec.gem_dir, branch, source_file, patch)
|
|
34
|
+
|
|
35
|
+
fix_id = save_fix(gem_name: gem_name, runner_class: runner_class,
|
|
36
|
+
branch: branch, patch: patch,
|
|
37
|
+
specs_passed: apply_result[:specs_passed],
|
|
38
|
+
spec_output: apply_result[:output])
|
|
39
|
+
|
|
40
|
+
{ success: apply_result[:specs_passed], fix_id: fix_id, branch: branch,
|
|
41
|
+
specs_passed: apply_result[:specs_passed] }
|
|
42
|
+
rescue StandardError => e
|
|
43
|
+
{ success: false, reason: :error, message: e.message }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def approve_fix(fix_id:, **)
|
|
47
|
+
update_fix_status(fix_id, 'approved')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def reject_fix(fix_id:, **)
|
|
51
|
+
update_fix_status(fix_id, 'rejected')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def list_fixes(status: nil, **)
|
|
55
|
+
return { fixes: [], count: 0 } unless defined?(Legion::Data::Local)
|
|
56
|
+
|
|
57
|
+
ds = Legion::Data::Local.connection[:codegen_fixes]
|
|
58
|
+
ds = ds.where(status: status) if status
|
|
59
|
+
fixes = ds.order(Sequel.desc(:created_at)).limit(50).all
|
|
60
|
+
{ fixes: fixes, count: fixes.size }
|
|
61
|
+
rescue StandardError
|
|
62
|
+
{ fixes: [], count: 0 }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def locate_source(spec, runner_class)
|
|
68
|
+
relative = runner_class.gsub('::', '/').gsub(%r{^Legion/Extensions/}, '')
|
|
69
|
+
.downcase.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
|
70
|
+
path = ::File.join(spec.gem_dir, 'lib', "legion/extensions/#{relative}.rb")
|
|
71
|
+
::File.exist?(path) ? path : nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def build_fix_prompt(source, error_class, backtraces)
|
|
75
|
+
<<~PROMPT
|
|
76
|
+
You are a Ruby debugging expert. A Legion extension runner is failing with the following error.
|
|
77
|
+
|
|
78
|
+
**Error class**: #{error_class}
|
|
79
|
+
|
|
80
|
+
**Backtrace**:
|
|
81
|
+
#{backtraces.first(10).join("\n")}
|
|
82
|
+
|
|
83
|
+
**Source code**:
|
|
84
|
+
```ruby
|
|
85
|
+
#{source}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Generate a minimal fix as a unified diff patch. Only change what is necessary to fix the error.
|
|
89
|
+
Output ONLY the unified diff, no explanation. Start with --- and +++.
|
|
90
|
+
PROMPT
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def extract_patch(response)
|
|
94
|
+
content = response.is_a?(Hash) ? (response[:content] || response[:text]) : response.to_s
|
|
95
|
+
return nil if content.nil? || content.empty?
|
|
96
|
+
|
|
97
|
+
lines = content.lines
|
|
98
|
+
start = lines.index { |l| l.start_with?('---') }
|
|
99
|
+
return nil unless start
|
|
100
|
+
|
|
101
|
+
lines[start..].join
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def apply_and_test(gem_dir, branch, source_file, patch)
|
|
105
|
+
require 'open3'
|
|
106
|
+
|
|
107
|
+
::Dir.chdir(gem_dir) do
|
|
108
|
+
Open3.capture3('git', 'checkout', '-b', branch)
|
|
109
|
+
::File.write("#{source_file}.patch", patch)
|
|
110
|
+
_out, _err, status = Open3.capture3('git', 'apply', "#{source_file}.patch")
|
|
111
|
+
::FileUtils.rm_f("#{source_file}.patch")
|
|
112
|
+
|
|
113
|
+
unless status.success?
|
|
114
|
+
Open3.capture3('git', 'checkout', '-')
|
|
115
|
+
Open3.capture3('git', 'branch', '-D', branch)
|
|
116
|
+
return { specs_passed: false, output: 'Patch failed to apply' }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
stdout, stderr, spec_status = Open3.capture3('bundle', 'exec', 'rspec', '--format', 'progress')
|
|
120
|
+
output = (stdout + stderr).slice(0, 10_240)
|
|
121
|
+
|
|
122
|
+
unless spec_status.success?
|
|
123
|
+
Open3.capture3('git', 'checkout', '-')
|
|
124
|
+
Open3.capture3('git', 'branch', '-D', branch)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
{ specs_passed: spec_status.success?, output: output }
|
|
128
|
+
end
|
|
129
|
+
rescue StandardError => e
|
|
130
|
+
{ specs_passed: false, output: e.message }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def save_fix(gem_name:, branch:, patch:, specs_passed:, runner_class: nil, spec_output: nil)
|
|
134
|
+
fix_id = SecureRandom.uuid
|
|
135
|
+
if defined?(Legion::Data::Local)
|
|
136
|
+
Legion::Data::Local.connection[:codegen_fixes].insert(
|
|
137
|
+
fix_id: fix_id, gem_name: gem_name, runner_class: runner_class,
|
|
138
|
+
branch: branch, patch: patch, status: 'pending',
|
|
139
|
+
specs_passed: specs_passed, spec_output: spec_output&.slice(0, 10_240)
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
fix_id
|
|
143
|
+
rescue StandardError
|
|
144
|
+
fix_id
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def update_fix_status(fix_id, new_status)
|
|
148
|
+
return { success: false, reason: :data_unavailable } unless defined?(Legion::Data::Local)
|
|
149
|
+
|
|
150
|
+
updated = Legion::Data::Local.connection[:codegen_fixes]
|
|
151
|
+
.where(fix_id: fix_id)
|
|
152
|
+
.update(status: new_status)
|
|
153
|
+
if updated.positive?
|
|
154
|
+
{ success: true, fix_id: fix_id, status: new_status }
|
|
155
|
+
else
|
|
156
|
+
{ success: false, reason: :not_found }
|
|
157
|
+
end
|
|
158
|
+
rescue StandardError => e
|
|
159
|
+
{ success: false, reason: :error, message: e.message }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -9,6 +9,7 @@ require_relative 'codegen/helpers/file_writer'
|
|
|
9
9
|
require_relative 'codegen/runners/generate'
|
|
10
10
|
require_relative 'codegen/runners/template'
|
|
11
11
|
require_relative 'codegen/runners/validate'
|
|
12
|
+
require_relative 'codegen/runners/auto_fix'
|
|
12
13
|
require_relative 'codegen/client'
|
|
13
14
|
|
|
14
15
|
module Legion
|
|
@@ -18,3 +19,10 @@ module Legion
|
|
|
18
19
|
end
|
|
19
20
|
end
|
|
20
21
|
end
|
|
22
|
+
|
|
23
|
+
if defined?(Legion::Data::Local)
|
|
24
|
+
Legion::Data::Local.register_migrations(
|
|
25
|
+
name: :codegen,
|
|
26
|
+
path: File.join(__dir__, 'codegen', 'local_migrations')
|
|
27
|
+
)
|
|
28
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-codegen
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -130,6 +130,8 @@ files:
|
|
|
130
130
|
- lib/legion/extensions/codegen/helpers/file_writer.rb
|
|
131
131
|
- lib/legion/extensions/codegen/helpers/spec_generator.rb
|
|
132
132
|
- lib/legion/extensions/codegen/helpers/template_engine.rb
|
|
133
|
+
- lib/legion/extensions/codegen/local_migrations/20260320000001_create_codegen_fixes.rb
|
|
134
|
+
- lib/legion/extensions/codegen/runners/auto_fix.rb
|
|
133
135
|
- lib/legion/extensions/codegen/runners/generate.rb
|
|
134
136
|
- lib/legion/extensions/codegen/runners/template.rb
|
|
135
137
|
- lib/legion/extensions/codegen/runners/validate.rb
|