llmed 0.2.11 → 0.3.1

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: 0a90029f77fd962cbec07534801ea73ed440c9e1f2e2d87a1d62827af44baf3e
4
+ data.tar.gz: 1e4d31b1dae583581d8fc9716419490a2901180862e03618cd09f189945a2384
5
5
  SHA512:
6
- metadata.gz: d765dabbd6b724f8d37d21b36f8dcf7bf9a75b3ba73e3c635701f6ce5d716e6977447b8584291bb79ec97074519118cf3746a9be865fe4572f4d71387ba6295e
7
- data.tar.gz: 0611b8c5c945ab1ec79252275646b11f8699ea22edfee23c5ba5ba09166732676fa4f45832d43035896377e0ccaaece993d32e2cccdbf2387c94abd36ddbd280
6
+ metadata.gz: 7d39d323d49f5952335372842244d532cf31129cf3bda5ebd19e2a4abe46510f6479927036a523aad50ec1373808311290f41a1da43940e4ab2b30bfbd16db53
7
+ data.tar.gz: e9b52e1af2fceca9d6ae84cc7882b9a34ca281f7558546e959f6cdac9ff2a95e93262e211c086151194613392620795cf5b781473d60db6f20a44923f5660199
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).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,37 @@ 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
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?
170
+ elsif release_context.digest? && !ctx.same_digest?(release_context.digest)
171
+ update_rest = true
172
+ update_context_digest << release_context.digest
173
+ elsif release_context.digest?
177
174
  # maybe the context is not connected to the source code
178
175
  next
179
176
  end
180
-
181
- if update_rest
182
- update_context_digest << release_context_digest
183
- next
184
- end
185
- next if ctx.same_digest?(release_context_digest)
186
-
187
- update_rest = true
188
- update_context_digest << release_context_digest
189
177
  end
190
178
  end
191
179
 
@@ -211,7 +199,15 @@ class LLMed
211
199
 
212
200
  return {} unless File.exist?(release_source_code)
213
201
 
214
- File.read(release_source_code).scan(/context='(.+)' digest='(.+)'/).to_h
202
+ File.read(release_source_code).scan(/context='(.+?)' digest='(.+?)'/).to_h
203
+ end
204
+
205
+ def release_instance
206
+ if File.exist?(release_source_code)
207
+ Release.load(File.read(release_source_code), code_comment)
208
+ else
209
+ Release.empty
210
+ end
215
211
  end
216
212
  end
217
213
  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,119 @@
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 = ''
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)
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
+ @contexts = contexts
101
+ self
102
+ end
103
+
104
+ private
105
+
106
+ def initialize(origin, code_comment)
107
+ @origin = origin
108
+ @content = ''
109
+ @changes = []
110
+ @code_comment = code_comment
111
+ @contexts = []
112
+
113
+ @origin.scan(%r{<llmed-code context='(.+?)' digest='(.+?)' after='(.*?)'>(.+?)#{@code_comment}+\s*</llmed-code>}im).each do |match|
114
+ name, digest, after, code = match
115
+ @contexts << ContextCode.new(name, digest, code, after)
116
+ end
117
+ end
118
+ end
119
+ 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
@@ -83,4 +83,5 @@ end
83
83
  require_relative 'llm'
84
84
  require_relative 'llmed/configuration'
85
85
  require_relative 'llmed/context'
86
+ require_relative 'llmed/release'
86
87
  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.1
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