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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 410e07dba313fed006a63a77643c2cdf1e03dbe41c17f895725ceb710f10f8fb
4
- data.tar.gz: 45a9d37de9308cdfc113b3b83d0bd222b1f5879198d01fd8b8b152fbfafa83e0
3
+ metadata.gz: 16434586bacb8d4a8cab6c1d886144a76058c07a9cc5b9d75f6b7c2e94159bc8
4
+ data.tar.gz: 2d442d686396ce9beb409d8597532cc94b22f77fcfa39a802333fc7d64e61844
5
5
  SHA512:
6
- metadata.gz: f57fbf2f6f9f899ee3cadf673ba72f5c47b58de1ad789a560aa1aaca9dedc314d9d5991f9af795ea58075c38ed6b9dc135df6ada5677de35dcd2c0add19a8972
7
- data.tar.gz: 63a20b03008b9e1442b44170725615a3757222816c9ed983d33d88e6106c8a36c1d7ae3903a55e2588afaa3d1f0a5422ce19c274830843dfca4744b5bd1a273d
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
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Codegen
6
- VERSION = '0.1.1'
6
+ VERSION = '0.1.2'
7
7
  end
8
8
  end
9
9
  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.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