llmed 0.2.11 → 0.3.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/README.md +1 -1
- data/lib/llmed/application.rb +39 -42
- data/lib/llmed/configuration.rb +2 -1
- data/lib/llmed/release.rb +127 -0
- data/lib/llmed/release.rb~ +31 -0
- data/lib/llmed.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cf2738765232f0257b21a09fcb1ed050c559b0d03089648b73a77c93e4e7c9e
|
4
|
+
data.tar.gz: 929c9c957b1d0302a8f6ea9cbee4c39caca1ad24d0bc9550d13d7538d1af6918
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3da39f1f46eb18a4d9cc301e70c5a8a998b81a5af039cbc1133f186c693bb5c2ba5cca0b92434f908addd354649a5cfe0d36b9ef4e2472cf64a8fbcdf0b8d116
|
7
|
+
data.tar.gz: 23b557c3a624f5f96a9af664272a7f2a92500d6d87ab30e5041a0c2d4a12f98cd4d68879570954b58f260a816dab1e226c0359039d0a9fe42693d85b6f63c35d
|
data/README.md
CHANGED
@@ -16,7 +16,7 @@ In classic terms the LLM is the Compiler, Source Code is the Binary, the Program
|
|
16
16
|
```ruby
|
17
17
|
set_llm provider: :openai, api_key: ENV['OPENAI_API_KEY'], model: 'gpt-4o-mini'
|
18
18
|
|
19
|
-
application "hola mundo ruby", language: :ruby, output_file: "holamundo-ruby.ollmed" do
|
19
|
+
application "hola mundo ruby", release: nil, language: :ruby, output_file: "holamundo-ruby.ollmed" do
|
20
20
|
# More stable context: if this changes, all subsequent context will be recompiled.
|
21
21
|
context "variables" do
|
22
22
|
<<-LLM
|
data/lib/llmed/application.rb
CHANGED
@@ -39,11 +39,16 @@ class LLMed
|
|
39
39
|
return unless @release
|
40
40
|
|
41
41
|
output_file = Pathname.new(@output_dir) + @output_file
|
42
|
-
|
42
|
+
|
43
|
+
if @release && File.exist?(output_file) && !File.exist?(release_source_code)
|
43
44
|
FileUtils.cp(output_file, release_source_code)
|
44
45
|
FileUtils.cp(output_file, release_main_source_code)
|
45
46
|
@logger.info("APPLICATION #{@name} RELEASE FILE #{release_source_code}")
|
47
|
+
elsif @release && !File.exist?(output_file) && File.exist?(release_main_source_code)
|
48
|
+
FileUtils.cp(release_main_source_code, output_file)
|
49
|
+
return
|
46
50
|
end
|
51
|
+
|
47
52
|
@logger.info("APPLICATION #{@name} INPUT RELEASE FILE #{release_main_source_code}")
|
48
53
|
end
|
49
54
|
|
@@ -70,34 +75,18 @@ class LLMed
|
|
70
75
|
output_content = output
|
71
76
|
|
72
77
|
if @release && File.exist?(release_source_code) && !release_contexts.empty?
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
release_source_code_content = release_source_code_content.sub(%r{(.*?)(<llmed-code context='#{name}' digest='.*?'>)(.+?)(</llmed-code>)(.*?)}m) do
|
87
|
-
"#{::Regexp.last_match(1)}<llmed-code context='#{name}' digest='#{new_digest}'>#{new_code}#{::Regexp.last_match(4)}#{::Regexp.last_match(5)}"
|
88
|
-
end
|
89
|
-
|
90
|
-
if release_contexts[name].nil?
|
91
|
-
@logger.info("APPLICATION #{@name} ADDING NEW CONTEXT #{name}")
|
92
|
-
release_source_code_content += "<llmed-code context='#{name}' digest='#{new_digest}'>
|
93
|
-
#{new_code}
|
94
|
-
#{code_comment}</llmed-code>"
|
95
|
-
else
|
96
|
-
@logger.info("APPLICATION #{@name} PATCHING CONTEXT #{name} \n\tFROM #{digest}\n\tTO DIGEST #{new_digest}")
|
78
|
+
output_release = Release.load(File.read(release_source_code), code_comment)
|
79
|
+
input_release = Release.load(output, code_comment)
|
80
|
+
output_content = output_release.merge!(input_release, user_contexts).content
|
81
|
+
output_release.changes do |change|
|
82
|
+
action, ctx = change
|
83
|
+
case action
|
84
|
+
when :added
|
85
|
+
@logger.info("APPLICATION #{@name} PATCH ADDING NEW CONTEXT #{ctx.name}")
|
86
|
+
when :updated
|
87
|
+
@logger.info("APPLICATION #{@name} PATCH UPDATING CONTEXT #{ctx.name} TO DIGEST #{ctx.digest}")
|
97
88
|
end
|
98
89
|
end
|
99
|
-
|
100
|
-
output_content = release_source_code_content
|
101
90
|
end
|
102
91
|
|
103
92
|
output_file(@output_dir) do |file|
|
@@ -154,38 +143,38 @@ class LLMed
|
|
154
143
|
private
|
155
144
|
|
156
145
|
def code_comment
|
157
|
-
{ ruby: '#' }.fetch(@language.to_sym)
|
146
|
+
{ ruby: '#', node: '//' }.fetch(@language.to_sym)
|
158
147
|
end
|
159
148
|
|
160
149
|
def digests_of_context_to_update
|
161
150
|
update_context_digest = []
|
162
151
|
|
163
|
-
unless
|
152
|
+
unless release_instance.empty?
|
164
153
|
# rebuild context from top to down
|
165
154
|
# we are expecting:
|
166
155
|
# - top the most stable concepts
|
167
156
|
# - buttom the most inestable concepts
|
168
157
|
update_rest = false
|
169
158
|
@contexts.each do |ctx|
|
170
|
-
|
159
|
+
release_context = release_instance.context_by(ctx.name)
|
160
|
+
|
161
|
+
if update_rest && release_context.digest?
|
162
|
+
update_context_digest << release_context.digest
|
163
|
+
next
|
164
|
+
end
|
171
165
|
|
172
166
|
# added new context
|
173
|
-
if
|
167
|
+
if !release_context.digest? && !user_contexts[ctx.name].nil?
|
174
168
|
update_context_digest << user_contexts[ctx.name]
|
175
169
|
next
|
176
|
-
elsif
|
177
|
-
|
170
|
+
elsif release_context.digest? && !ctx.same_digest?(release_context.digest)
|
171
|
+
update_rest = true
|
172
|
+
update_context_digest << release_context.digest
|
178
173
|
next
|
179
|
-
|
180
|
-
|
181
|
-
if update_rest
|
182
|
-
update_context_digest << release_context_digest
|
174
|
+
elsif release_context.digest?
|
175
|
+
# maybe the context is not connected to the source code
|
183
176
|
next
|
184
177
|
end
|
185
|
-
next if ctx.same_digest?(release_context_digest)
|
186
|
-
|
187
|
-
update_rest = true
|
188
|
-
update_context_digest << release_context_digest
|
189
178
|
end
|
190
179
|
end
|
191
180
|
|
@@ -211,7 +200,15 @@ class LLMed
|
|
211
200
|
|
212
201
|
return {} unless File.exist?(release_source_code)
|
213
202
|
|
214
|
-
File.read(release_source_code).scan(/context='(
|
203
|
+
File.read(release_source_code).scan(/context='(.+?)' digest='(.+?)'/).to_h
|
204
|
+
end
|
205
|
+
|
206
|
+
def release_instance
|
207
|
+
if File.exist?(release_source_code)
|
208
|
+
Release.load(File.read(release_source_code), code_comment)
|
209
|
+
else
|
210
|
+
Release.empty
|
211
|
+
end
|
215
212
|
end
|
216
213
|
end
|
217
214
|
end
|
data/lib/llmed/configuration.rb
CHANGED
@@ -7,6 +7,7 @@ class LLMed
|
|
7
7
|
@prompt = LLMed::LLM::Template.build(template: "
|
8
8
|
You are a software developer with knowledge only of the programming language {language}, following the SOLID principles strictly, you always use only imperative and functional programming, and design highly isolated components.
|
9
9
|
The contexts are declarations of how the source code will be, ensure to follow this always.
|
10
|
+
The contexts are connected as a linked list.
|
10
11
|
Your response must contain only the generated source code, with no additional text.
|
11
12
|
All source code must be written in a single file, and you must ensure it runs correctly on the first attempt.
|
12
13
|
There is always a one-to-one correspondence between context and source code.
|
@@ -18,7 +19,7 @@ You must only modify the following source code:
|
|
18
19
|
Only generate source code of the context who digest belongs to {update_context_digests} or a is a new context.
|
19
20
|
|
20
21
|
Wrap with comment every code that belongs to the indicated context, example in ruby:
|
21
|
-
#<llmed-code context='context name' digest='....'>
|
22
|
+
#<llmed-code context='context name' digest='....' after='digest next context'>
|
22
23
|
...
|
23
24
|
#</llmed-code>
|
24
25
|
", input_variables: %w[language source_code update_context_digests])
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class LLMed
|
5
|
+
class Release
|
6
|
+
ContextCode = Struct.new(:name, :digest, :code, :after) do
|
7
|
+
def to_llmed_code(code_comment)
|
8
|
+
"#{code_comment}<llmed-code context='#{name}' digest='#{digest}' after='#{after}'>#{code}#{code_comment}</llmed-code>"
|
9
|
+
end
|
10
|
+
|
11
|
+
def digest?
|
12
|
+
return false if digest.nil?
|
13
|
+
return false if digest.empty?
|
14
|
+
|
15
|
+
true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.load(origin, code_comment)
|
20
|
+
new(origin, code_comment)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.empty
|
24
|
+
new('', '')
|
25
|
+
end
|
26
|
+
|
27
|
+
def content
|
28
|
+
out = String.new()
|
29
|
+
|
30
|
+
@contexts.each do |ctx|
|
31
|
+
out += ctx.to_llmed_code(@code_comment)
|
32
|
+
out += "\n"
|
33
|
+
end
|
34
|
+
|
35
|
+
out.strip!
|
36
|
+
|
37
|
+
out
|
38
|
+
end
|
39
|
+
|
40
|
+
def empty?
|
41
|
+
@origin.empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :contexts
|
45
|
+
|
46
|
+
def changes
|
47
|
+
changes = @changes.dup
|
48
|
+
@changes.clear
|
49
|
+
changes
|
50
|
+
end
|
51
|
+
|
52
|
+
def context_by(name)
|
53
|
+
@contexts.each do |ctx|
|
54
|
+
return ctx if ctx.name == name
|
55
|
+
end
|
56
|
+
|
57
|
+
ContextCode.new('', '', '', '')
|
58
|
+
end
|
59
|
+
|
60
|
+
def has_context?(name)
|
61
|
+
context_by(name).digest?
|
62
|
+
end
|
63
|
+
|
64
|
+
def merge!(release, user_contexts)
|
65
|
+
contexts = []
|
66
|
+
|
67
|
+
# updates
|
68
|
+
@contexts.each do |ctx|
|
69
|
+
new_ctx = release.context_by(ctx.name)
|
70
|
+
if release.has_context?(ctx.name)
|
71
|
+
contexts << new_ctx
|
72
|
+
@changes << [:updated, new_ctx]
|
73
|
+
else
|
74
|
+
contexts << ctx
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# insertions
|
79
|
+
insertions = []
|
80
|
+
release.contexts.each do |new_ctx|
|
81
|
+
next if has_context?(new_ctx.name)
|
82
|
+
|
83
|
+
contexts.each_with_index do |current_ctx, idx|
|
84
|
+
if current_ctx.digest == new_ctx.after
|
85
|
+
insertions << [idx, current_ctx.digest, new_ctx]
|
86
|
+
break
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
insertions.each do |action|
|
92
|
+
idx, old_digest, new_ctx = action
|
93
|
+
contexts.each do |ctx|
|
94
|
+
ctx.after = new_ctx.digest if ctx.after == old_digest
|
95
|
+
end
|
96
|
+
contexts.insert(idx, new_ctx)
|
97
|
+
@changes << [:added, new_ctx]
|
98
|
+
end
|
99
|
+
|
100
|
+
# fix user contexts digest
|
101
|
+
contexts.each do |ctx|
|
102
|
+
user_context_digest = user_contexts[ctx.name]
|
103
|
+
if !user_context_digest.nil?
|
104
|
+
ctx.digest = user_context_digest
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
@contexts = contexts
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def initialize(origin, code_comment)
|
115
|
+
@origin = origin
|
116
|
+
@content = ''
|
117
|
+
@changes = []
|
118
|
+
@code_comment = code_comment
|
119
|
+
@contexts = []
|
120
|
+
|
121
|
+
@origin.scan(%r{<llmed-code context='(.+?)' digest='(.+?)' after='(.*?)'>(.+?)#{@code_comment}+\s*</llmed-code>}im).each do |match|
|
122
|
+
name, digest, after, code = match
|
123
|
+
@contexts << ContextCode.new(name, digest, code, after)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class LLMed
|
5
|
+
class Release
|
6
|
+
|
7
|
+
ContextCode = Struct.new(:name, :digest, :code)
|
8
|
+
|
9
|
+
def read(path)
|
10
|
+
@origin = File.read(path)
|
11
|
+
@changes = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def contexts
|
15
|
+
contexts = []
|
16
|
+
@origin.scan(%r{<llmed-code context='(.+?)' digest='(.+?)'>(.+?)</llmed-code>}im).each do |match|
|
17
|
+
name, digest, code = match
|
18
|
+
contexts << ContextCode.new(name, digest, code)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def merge(release)
|
23
|
+
content = ""
|
24
|
+
release.contexts.each do |ctx|
|
25
|
+
content = content.sub(%r{(.*?)(<llmed-code context='#{ctx.name}' digest='.*?'>)(.+?)(</llmed-code>)(.*?)}m) do
|
26
|
+
"#{::Regexp.last_match(1)}<llmed-code context='#{ctx.name}' digest='#{ctx.digest}'>#{ctx.code}#{::Regexp.last_match(4)}#{::Regexp.last_match(5)}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/llmed.rb
CHANGED
@@ -71,6 +71,7 @@ class LLMed
|
|
71
71
|
|
72
72
|
llm_response = llm.chat(messages: messages)
|
73
73
|
@logger.info("APPLICATION #{app.name} TOTAL TOKENS #{llm_response.total_tokens}")
|
74
|
+
|
74
75
|
app.patch_or_create(llm_response.source_code)
|
75
76
|
app.write_statistics(llm_response)
|
76
77
|
app.notify("COMPILE DONE #{llm_response.duration_seconds}")
|
@@ -83,4 +84,5 @@ end
|
|
83
84
|
require_relative 'llm'
|
84
85
|
require_relative 'llmed/configuration'
|
85
86
|
require_relative 'llmed/context'
|
87
|
+
require_relative 'llmed/release'
|
86
88
|
require_relative 'llmed/application'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: llmed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jovany Leandro G.C
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-06-
|
11
|
+
date: 2025-06-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: langchainrb
|
@@ -102,6 +102,8 @@ files:
|
|
102
102
|
- lib/llmed/configuration.rb~
|
103
103
|
- lib/llmed/context.rb
|
104
104
|
- lib/llmed/context.rb~
|
105
|
+
- lib/llmed/release.rb
|
106
|
+
- lib/llmed/release.rb~
|
105
107
|
homepage: https://github.com/bit4bit/llmed
|
106
108
|
licenses:
|
107
109
|
- GPL-3.0
|