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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4479a97e9e36926e4f7af8677a06268824c84563e441d2b14c862c8c07f648f1
4
- data.tar.gz: daad9497943bb738f429fca0cb67a18858c9557657090af5878e803863c63fee
3
+ metadata.gz: 0cf2738765232f0257b21a09fcb1ed050c559b0d03089648b73a77c93e4e7c9e
4
+ data.tar.gz: 929c9c957b1d0302a8f6ea9cbee4c39caca1ad24d0bc9550d13d7538d1af6918
5
5
  SHA512:
6
- metadata.gz: d765dabbd6b724f8d37d21b36f8dcf7bf9a75b3ba73e3c635701f6ce5d716e6977447b8584291bb79ec97074519118cf3746a9be865fe4572f4d71387ba6295e
7
- data.tar.gz: 0611b8c5c945ab1ec79252275646b11f8699ea22edfee23c5ba5ba09166732676fa4f45832d43035896377e0ccaaece993d32e2cccdbf2387c94abd36ddbd280
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
@@ -39,11 +39,16 @@ class LLMed
39
39
  return unless @release
40
40
 
41
41
  output_file = Pathname.new(@output_dir) + @output_file
42
- if @release && !File.exist?(release_source_code)
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
- release_source_code_content = File.read(release_source_code)
74
- output_contexts = output.scan(%r{<llmed-code context='(.+?)' digest='(.+?)'>(.+?)</llmed-code>}im)
75
-
76
- output_contexts.each do |match|
77
- name, digest, new_code = match
78
- new_digest = digest
79
- @contexts.each do |ctx|
80
- if ctx.name == name
81
- new_digest = ctx.digest
82
- break
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 release_contexts.empty?
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
- release_context_digest = release_contexts[ctx.name]
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 release_context_digest.nil? and !user_contexts[ctx.name].nil?
167
+ if !release_context.digest? && !user_contexts[ctx.name].nil?
174
168
  update_context_digest << user_contexts[ctx.name]
175
169
  next
176
- elsif release_context_digest.nil?
177
- # maybe the context is not connected to the source code
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
- end
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='(.+)' digest='(.+)'/).to_h
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
@@ -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.11
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-08 00:00:00.000000000 Z
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