llmed 0.4.3 → 0.6.0
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/lib/llm.rb +1 -1
- data/lib/llmed/application.rb +139 -12
- data/lib/llmed/configuration.rb +34 -20
- data/lib/llmed/context.rb +45 -2
- data/lib/llmed/release.rb +43 -20
- data/lib/llmed.rb +3 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c47ebb44059bae3a1ffc2d27dd064098036ebd390cb677a488761845e13d4b6b
|
|
4
|
+
data.tar.gz: 1442dec8d3c594b8610af95d7edfbdbdee8881ac00a046001cc6fd4eb9c2e68c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 80a7c93eed1106305b848bae99b0fcce744c55bc933538465041e19329ed1e6fe01f398c38ec849f2ae4981ec260024d3a9ded96f1465224a0b9f60cc8924b53
|
|
7
|
+
data.tar.gz: 7c3df54bec3ee2e2f02bd7108c92004e6969bb8019ab5209bda32327cc916492ac2946948b2ce835325b93d33238292187870e68bc35bfa50feefb0b38164263
|
data/lib/llm.rb
CHANGED
data/lib/llmed/application.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require 'set'
|
|
5
|
+
|
|
4
6
|
class LLMed
|
|
5
7
|
class Application
|
|
6
8
|
attr_reader :contexts, :name, :language
|
|
@@ -29,6 +31,8 @@ class LLMed
|
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def initialize(name:, language:, output_file:, block:, logger:, release:, release_dir:, output_dir:)
|
|
34
|
+
snapshot_file = Pathname.new(release_dir) + "#{output_file}.snapshot"
|
|
35
|
+
|
|
32
36
|
@name = name
|
|
33
37
|
@output_file = output_file
|
|
34
38
|
@language = language.to_sym
|
|
@@ -39,6 +43,7 @@ class LLMed
|
|
|
39
43
|
@release = release
|
|
40
44
|
@release_dir = release_dir
|
|
41
45
|
@output_dir = output_dir
|
|
46
|
+
@snapshot = Snapshot.new(snapshot_file)
|
|
42
47
|
end
|
|
43
48
|
|
|
44
49
|
# Example:
|
|
@@ -56,7 +61,113 @@ class LLMed
|
|
|
56
61
|
instance_eval(&@block)
|
|
57
62
|
end
|
|
58
63
|
|
|
59
|
-
|
|
64
|
+
class Snapshot
|
|
65
|
+
attr_reader :snapshot_file
|
|
66
|
+
|
|
67
|
+
def initialize(snapshot_file)
|
|
68
|
+
@snapshot_file = snapshot_file
|
|
69
|
+
@contexts = []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def sync(default)
|
|
73
|
+
load(default)
|
|
74
|
+
dump
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def refresh(contexts)
|
|
78
|
+
@contexts = contexts.map{ |ctx| [ctx.name, {'name' => ctx.name, 'message' => ctx.raw}]}.to_h
|
|
79
|
+
dump
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def diff(other_contexts)
|
|
83
|
+
diffs = {}
|
|
84
|
+
other_contexts.each do |other_ctx|
|
|
85
|
+
current_ctx = @contexts[other_ctx.name]
|
|
86
|
+
result = line_diff(current_ctx['message'], other_ctx.raw)
|
|
87
|
+
# omit not changes
|
|
88
|
+
if !result.all?{|op, line| op == '=:'}
|
|
89
|
+
diffs[other_ctx.name] = result
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
diffs
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def line_diff(text1, text2)
|
|
99
|
+
lines1 = text1.split("\n")
|
|
100
|
+
lines2 = text2.split("\n")
|
|
101
|
+
|
|
102
|
+
result = []
|
|
103
|
+
|
|
104
|
+
i1 = 0
|
|
105
|
+
i2 = 0
|
|
106
|
+
|
|
107
|
+
while i1 < lines1.size || i2 < lines2.size
|
|
108
|
+
line1 = lines1[i1]
|
|
109
|
+
line2 = lines2[i2]
|
|
110
|
+
|
|
111
|
+
if i1 < lines1.size && i2 < lines2.size && line1 == line2
|
|
112
|
+
result << ["=:", line1]
|
|
113
|
+
i1 += 1
|
|
114
|
+
i2 += 1
|
|
115
|
+
elsif i1 < lines1.size && (i2 >= lines2.size || !lines2[i2..-1].include?(line1))
|
|
116
|
+
result << ["-:", line1]
|
|
117
|
+
i1 += 1
|
|
118
|
+
elsif i2 < lines2.size && (i1 >= lines1.size || !lines1[i1..-1].include?(line2))
|
|
119
|
+
result << ["+:", line2]
|
|
120
|
+
i2 += 1
|
|
121
|
+
else
|
|
122
|
+
# Try to find if one of the lines matches later
|
|
123
|
+
idx1 = lines1[i1+1..-1]&.index(line2)
|
|
124
|
+
idx2 = lines2[i2+1..-1]&.index(line1)
|
|
125
|
+
|
|
126
|
+
if !idx1.nil? && (idx2.nil? || idx1 <= idx2)
|
|
127
|
+
result << ["-:", line1]
|
|
128
|
+
i1 += 1
|
|
129
|
+
elsif !idx2.nil?
|
|
130
|
+
result << ["+:", line2]
|
|
131
|
+
i2 += 1
|
|
132
|
+
else
|
|
133
|
+
# Lines differ, treat both as deleted and added
|
|
134
|
+
result << ["-:", line1]
|
|
135
|
+
result << ["+:", line2]
|
|
136
|
+
i1 += 1
|
|
137
|
+
i2 += 1
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
result
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def load(default)
|
|
146
|
+
if File.exist?(@snapshot_file)
|
|
147
|
+
File.open(@snapshot_file, 'r') do |f|
|
|
148
|
+
@contexts = JSON.load(f.read)['contexts']
|
|
149
|
+
end
|
|
150
|
+
else
|
|
151
|
+
@contexts = default.map{ |ctx| [ctx.name, {'name' => ctx.name, 'message' => ctx.raw}]}.to_h
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def dump
|
|
156
|
+
File.open(@snapshot_file, 'w') do |file|
|
|
157
|
+
file.write(JSON.dump({'contexts' => @contexts}))
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def prepare_snapshot
|
|
163
|
+
raise "snapshot preparation require contexts" if @contexts.empty?
|
|
164
|
+
|
|
165
|
+
@logger.info("APPLICATION #{@name} PREPARING SNAPSHOT #{@snapshot.snapshot_file}")
|
|
166
|
+
|
|
167
|
+
@snapshot.sync(@contexts)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def prepare_release
|
|
60
171
|
@logger.info("APPLICATION #{@name} COMPILING FOR #{@language} RELEASE #{@release}")
|
|
61
172
|
return unless @output_file.is_a?(String)
|
|
62
173
|
return unless @release
|
|
@@ -64,9 +175,7 @@ class LLMed
|
|
|
64
175
|
output_file = Pathname.new(@output_dir) + @output_file
|
|
65
176
|
|
|
66
177
|
if @release && File.exist?(output_file) && !File.exist?(release_source_code)
|
|
67
|
-
|
|
68
|
-
FileUtils.cp(output_file, release_main_source_code)
|
|
69
|
-
@logger.info("APPLICATION #{@name} RELEASE FILE #{release_source_code}")
|
|
178
|
+
|
|
70
179
|
elsif @release && !File.exist?(output_file) && File.exist?(release_main_source_code)
|
|
71
180
|
FileUtils.mkdir_p(File.dirname(output_file))
|
|
72
181
|
FileUtils.cp(release_main_source_code, output_file)
|
|
@@ -117,14 +226,35 @@ class LLMed
|
|
|
117
226
|
output_file(@output_dir) do |file|
|
|
118
227
|
file.write(output_content)
|
|
119
228
|
end
|
|
229
|
+
|
|
230
|
+
# only update snapshot if changes are made
|
|
231
|
+
if !File.exist?(release_source_code)
|
|
232
|
+
@snapshot.refresh(@contexts)
|
|
233
|
+
@logger.info("APPLICATION #{@name} SNAPSHOT REFRESHED")
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
output_file = Pathname.new(@output_dir) + @output_file
|
|
237
|
+
FileUtils.cp(output_file, release_source_code)
|
|
238
|
+
FileUtils.cp(output_file, release_main_source_code)
|
|
239
|
+
@logger.info("APPLICATION #{@name} RELEASE FILE #{release_source_code}")
|
|
120
240
|
end
|
|
121
241
|
|
|
122
242
|
def system_prompt(configuration)
|
|
243
|
+
contexts_diffs = @snapshot.diff(contexts)
|
|
244
|
+
changes_of_contexts = ''
|
|
245
|
+
if contexts_diffs.any?
|
|
246
|
+
contexts_diffs.each do |context_name, diffs|
|
|
247
|
+
changes_of_contexts += "# Context: #{context_name}\n"
|
|
248
|
+
changes_of_contexts += diffs.map { |op, line| "#{op} #{line}" }.join("\n")
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
123
252
|
configuration.prompt(language: language,
|
|
124
253
|
source_code: source_code,
|
|
125
254
|
code_comment_begin: @code_comment.begin,
|
|
126
255
|
code_comment_end: @code_comment.end,
|
|
127
|
-
update_context_digests: digests_of_context_to_update
|
|
256
|
+
update_context_digests: digests_of_context_to_update,
|
|
257
|
+
changes_of_contexts: changes_of_contexts)
|
|
128
258
|
end
|
|
129
259
|
|
|
130
260
|
def rebuild?
|
|
@@ -134,9 +264,8 @@ class LLMed
|
|
|
134
264
|
!digests_of_context_to_update.tap do |digests|
|
|
135
265
|
digests.each do |digest|
|
|
136
266
|
context_by_digest = release_contexts.invert
|
|
137
|
-
|
|
138
267
|
if context_by_digest[digest].nil?
|
|
139
|
-
@logger.info("APPLICATION #{@name} ADDING CONTEXT #{user_contexts.
|
|
268
|
+
@logger.info("APPLICATION #{@name} ADDING CONTEXT #{user_contexts.by_digest(digest).name}")
|
|
140
269
|
else
|
|
141
270
|
@logger.info("APPLICATION #{@name} REBUILDING CONTEXT #{context_by_digest[digest]}")
|
|
142
271
|
end
|
|
@@ -188,8 +317,8 @@ class LLMed
|
|
|
188
317
|
end
|
|
189
318
|
|
|
190
319
|
# added new context
|
|
191
|
-
if !release_context.digest? && !user_contexts
|
|
192
|
-
update_context_digest << user_contexts
|
|
320
|
+
if !release_context.digest? && !user_contexts.by_name(ctx.name).nil?
|
|
321
|
+
update_context_digest << user_contexts.by_name(ctx.name).digest
|
|
193
322
|
next
|
|
194
323
|
elsif release_context.digest? && !ctx.same_digest?(release_context.digest)
|
|
195
324
|
update_rest = true
|
|
@@ -214,9 +343,7 @@ class LLMed
|
|
|
214
343
|
end
|
|
215
344
|
|
|
216
345
|
def user_contexts
|
|
217
|
-
@contexts
|
|
218
|
-
[ctx.name, ctx.digest]
|
|
219
|
-
end.to_h
|
|
346
|
+
UserContexts.new(@contexts)
|
|
220
347
|
end
|
|
221
348
|
|
|
222
349
|
def release_contexts
|
data/lib/llmed/configuration.rb
CHANGED
|
@@ -7,36 +7,50 @@ class LLMed
|
|
|
7
7
|
@logger = logger
|
|
8
8
|
# Manual tested, pass 5 times execution
|
|
9
9
|
@prompt = LLMed::LLM::Template.build(template: "
|
|
10
|
-
You are a software developer
|
|
11
|
-
|
|
12
|
-
The contexts are declarations of how the source code will be (not a file) ensure to follow this always.
|
|
13
|
-
The contexts are connected as a flat linked list.
|
|
14
|
-
All the contexts represent one source code.
|
|
15
|
-
There is always a one-to-one correspondence between context and source code.
|
|
16
|
-
Always include the properly escaped comment: LLMED-COMPILED.
|
|
10
|
+
You are a software developer specialized in the programming language {language}. Follow SOLID principles strictly. Use only imperative and functional programming styles and design highly isolated components. You have full access to the standard library and third-party packages only if explicitly allowed.
|
|
11
|
+
Hard requirements: produce complete, executable, and compilable source code — no placeholders, no pseudo-código, no partial implementations, no explanations. If anything below cannot be satisfied, produce a runtime error implementation that clearly fails fast.
|
|
17
12
|
|
|
18
|
-
You must only
|
|
19
|
-
```
|
|
20
|
-
{source_code}
|
|
21
|
-
```
|
|
13
|
+
The input contexts are functional sections of a single large source file (not separate files). Contexts are linked as a flat linked list. There must be a one-to-one correspondence between each context and the code generated for that context. You must only generate source code for the context(s) whose digest is listed in {update_context_digests}.
|
|
22
14
|
|
|
23
|
-
|
|
15
|
+
Always include the escaped literal comment token LLMED-COMPILED somewhere in the generated code.
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
You must only modify the following source code that is provided between the code fences:
|
|
18
|
+
```{language}
|
|
19
|
+
{source_code}
|
|
20
|
+
```
|
|
29
21
|
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
Strict formatting for context wrappers: wrap every context implementation with the exact comment markers below. Use the literal placeholders {code_comment_begin} and {code_comment_end} replaced with the exact comment open/close strings for the target language. Example wrapper (replace placeholders when running the prompt):
|
|
23
|
+
{code_comment_begin}<llmed-code context='context name' digest='CURRENT_DIGEST' link-digest='NEXT_DIGEST' after='NEXT_DIGEST'>{code_comment_end}
|
|
24
|
+
... COMPLETE, RUNNABLE implementation for that context ...
|
|
25
|
+
{code_comment_begin}</llmed-code>{code_comment_end}
|
|
26
|
+
|
|
27
|
+
Behavioral rules (must be obeyed):
|
|
28
|
+
1. No comments-only outputs. If your natural answer would be comments, instead implement executable code that performs the described behavior. Do not output explanatory text outside code blocks — output only source code for the indicated contexts.
|
|
29
|
+
2. All functions/methods must have bodies implementing the intended behavior. If external information is missing, implement a reasonable, deterministic default rather than leaving a stub.
|
|
30
|
+
3. Fail-fast fallback: if the requested context genuinely cannot be implemented, include a clear runtime failure function implementation_impossible() that raises/prints a single machine-readable error (e.g. throws an exception with message IMPLEMENTATION-IMPOSSIBLE) and still compiles.
|
|
31
|
+
4. One-to-one mapping: produce exactly one code block per digest requested. Do not add unrelated helper contexts unless they are wrapped and linked to an indicated digest; if helpers are necessary, include them inside the same context wrapper.
|
|
32
|
+
5. Include the literal LLMED-COMPILED comment somewhere inside the code.
|
|
33
|
+
6. Do not output any text outside the source code. The assistant response must be only source code for the requested context(s).
|
|
34
|
+
|
|
35
|
+
All behavior described by contexts marked with '-:' in <changes> must be completely removed from the generated source code.
|
|
36
|
+
Do not leave any code, print statements, functions, or references implementing deleted contexts.
|
|
37
|
+
Each context listed in '+:' or '=:' must be implemented exactly according to its description.
|
|
38
|
+
Behavior from removed contexts must not appear anywhere in the output, even indirectly.
|
|
39
|
+
<changes>
|
|
40
|
+
{changes_of_contexts}
|
|
41
|
+
</changes>
|
|
42
|
+
|
|
43
|
+
Output requirement: your response must contain only the generated source code for the indicated context(s), with the required wrapper comments and the test harness; nothing else.
|
|
44
|
+
", input_variables: %w[language source_code code_comment_begin code_comment_end update_context_digests changes_of_contexts])
|
|
32
45
|
end
|
|
33
46
|
|
|
34
|
-
def prompt(language:, source_code:, code_comment_begin:, code_comment_end:, update_context_digests: [])
|
|
47
|
+
def prompt(language:, source_code:, code_comment_begin:, code_comment_end:, update_context_digests: [], changes_of_contexts: '')
|
|
35
48
|
@prompt.format(language: language,
|
|
36
49
|
source_code: source_code,
|
|
37
50
|
code_comment_begin: code_comment_begin,
|
|
38
51
|
code_comment_end: code_comment_end,
|
|
39
|
-
update_context_digests: update_context_digests.join(',')
|
|
52
|
+
update_context_digests: update_context_digests.join(','),
|
|
53
|
+
changes_of_contexts: changes_of_contexts)
|
|
40
54
|
end
|
|
41
55
|
|
|
42
56
|
# Change the default prompt, input variables: language, source_code
|
data/lib/llmed/context.rb
CHANGED
|
@@ -1,14 +1,53 @@
|
|
|
1
1
|
# Copyright 2025 Jovany Leandro G.C <bit4bit@riseup.net>
|
|
2
2
|
# frozen_string_literal: true
|
|
3
|
+
|
|
3
4
|
require 'erb'
|
|
4
5
|
|
|
5
6
|
class LLMed
|
|
7
|
+
class UserContexts
|
|
8
|
+
def initialize(contexts)
|
|
9
|
+
@contexts = contexts.dup
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def each(&block)
|
|
13
|
+
@contexts.each(&block)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def empty?
|
|
17
|
+
@contexts.empty?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def [](idx)
|
|
21
|
+
@contexts[idx]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def each_with_next(&block)
|
|
25
|
+
@contexts.each_with_index do |ctx, idx|
|
|
26
|
+
next_ctx = @contexts[idx + 1]
|
|
27
|
+
block.call(ctx, next_ctx)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def count
|
|
32
|
+
@contexts.count
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def by_name(name)
|
|
36
|
+
@contexts.find { |ctx| ctx.name == name }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def by_digest(digest)
|
|
40
|
+
@contexts.find { |ctx| ctx.same_digest?(digest) }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
6
44
|
class Context
|
|
7
45
|
attr_reader :name
|
|
8
46
|
|
|
9
|
-
def initialize(name:, options: {})
|
|
47
|
+
def initialize(name:, digest: nil, options: {})
|
|
10
48
|
@name = name
|
|
11
49
|
@skip = options[:skip] || false
|
|
50
|
+
@fixed_digest = digest || nil
|
|
12
51
|
@release_dir = options[:release_dir]
|
|
13
52
|
end
|
|
14
53
|
|
|
@@ -21,13 +60,17 @@ class LLMed
|
|
|
21
60
|
end
|
|
22
61
|
|
|
23
62
|
def digest
|
|
24
|
-
Digest::SHA256.hexdigest
|
|
63
|
+
@fixed_digest || Digest::SHA256.hexdigest("#{@name}.#{@message}")
|
|
25
64
|
end
|
|
26
65
|
|
|
27
66
|
def message
|
|
28
67
|
"# Context: \"#{@name}\" Digest: #{digest}\n\n#{@message}"
|
|
29
68
|
end
|
|
30
69
|
|
|
70
|
+
def raw
|
|
71
|
+
@message
|
|
72
|
+
end
|
|
73
|
+
|
|
31
74
|
def llm(message)
|
|
32
75
|
@message = message
|
|
33
76
|
end
|
data/lib/llmed/release.rb
CHANGED
|
@@ -5,7 +5,8 @@ class LLMed
|
|
|
5
5
|
class Release
|
|
6
6
|
ContextCode = Struct.new(:name, :digest, :code, :after) do
|
|
7
7
|
def to_llmed_code(code_comment)
|
|
8
|
-
|
|
8
|
+
close_newline = "\n" if code.strip.empty?
|
|
9
|
+
"#{code_comment.begin}<llmed-code context='#{name}' digest='#{digest}' after='#{after}'>#{code_comment.end}#{code}#{close_newline}#{code_comment.begin}</llmed-code>#{code_comment.end}"
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def digest?
|
|
@@ -99,42 +100,64 @@ class LLMed
|
|
|
99
100
|
|
|
100
101
|
# fix user contexts digest
|
|
101
102
|
contexts.each do |ctx|
|
|
102
|
-
|
|
103
|
-
ctx.digest =
|
|
103
|
+
user_context = user_contexts.by_name(ctx.name)
|
|
104
|
+
ctx.digest = user_context.digest unless user_context.nil?
|
|
104
105
|
end
|
|
105
106
|
|
|
106
107
|
# insertions missed user contexts
|
|
107
|
-
user_contexts.each do |
|
|
108
|
-
next if contexts.any? { |ctx| ctx.name == name }
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
user_contexts.each do |user_context|
|
|
109
|
+
next if contexts.any? { |ctx| ctx.name == user_context.name }
|
|
110
|
+
|
|
111
|
+
code = release.context_by(user_context.name).code
|
|
112
|
+
new_ctx = ContextCode.new(user_context.name, user_context.digest, code, '')
|
|
111
113
|
contexts.prepend(new_ctx)
|
|
112
114
|
@changes << [:added, new_ctx]
|
|
113
115
|
end
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
# code context must have the same order as user contexts
|
|
118
|
+
if user_contexts.empty?
|
|
119
|
+
contexts_sorted = contexts
|
|
120
|
+
else
|
|
121
|
+
user_contexts_iter = user_contexts.dup
|
|
122
|
+
contexts_iter = contexts.dup
|
|
123
|
+
order_digests = rewire_code_contexts(contexts_iter, user_contexts_iter)
|
|
124
|
+
|
|
125
|
+
contexts_on_digests = order_digests.map { |digest| contexts_iter.find { |ctx| ctx.digest == digest } }
|
|
126
|
+
contexts_missing_digests = contexts_iter.select { |ctx| !order_digests.include?(ctx.digest) }
|
|
127
|
+
contexts_sorted = contexts_on_digests + contexts_missing_digests
|
|
124
128
|
end
|
|
125
129
|
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
# Sort contexts so that the latest digest (the one whose 'after' is empty) comes last
|
|
131
|
+
@contexts = contexts_sorted.sort do |a, b|
|
|
132
|
+
if a.after.empty? && !b.after.empty?
|
|
128
133
|
1
|
|
134
|
+
elsif !a.after.empty? && b.after.empty?
|
|
135
|
+
-1
|
|
129
136
|
else
|
|
130
137
|
0
|
|
131
138
|
end
|
|
132
|
-
|
|
139
|
+
end
|
|
140
|
+
|
|
133
141
|
self
|
|
134
142
|
end
|
|
135
143
|
|
|
136
144
|
private
|
|
137
145
|
|
|
146
|
+
def rewire_code_contexts(code_contexts, user_contexts)
|
|
147
|
+
order_digests = []
|
|
148
|
+
user_contexts.each_with_next do |user_context, next_user_context|
|
|
149
|
+
ctx = code_contexts.find { |ctx| ctx.name == user_context.name }
|
|
150
|
+
if ctx
|
|
151
|
+
ctx.digest = user_context.digest
|
|
152
|
+
order_digests << user_context.digest
|
|
153
|
+
ctx.after = '' if user_contexts.count > 1
|
|
154
|
+
ctx.after = next_user_context.digest if next_user_context
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
order_digests
|
|
159
|
+
end
|
|
160
|
+
|
|
138
161
|
def initialize(origin, code_comment)
|
|
139
162
|
@origin = origin
|
|
140
163
|
@content = ''
|
|
@@ -142,7 +165,7 @@ class LLMed
|
|
|
142
165
|
@code_comment = code_comment
|
|
143
166
|
@contexts = []
|
|
144
167
|
|
|
145
|
-
@origin.scan(%r{<llmed-code context='(.+?)' digest='(.+?)'
|
|
168
|
+
@origin.scan(%r{<llmed-code context='(.+?)' digest='(.+?)'(?:\s+[^>]*?)?(after='.*?')?>#{@code_comment.end}(.+?)#{@code_comment.begin}+\s*<?/?llmed-code}im).each do |match|
|
|
146
169
|
name, digest, after_block, code = match
|
|
147
170
|
after = if after_block.nil?
|
|
148
171
|
''
|
data/lib/llmed.rb
CHANGED
|
@@ -66,10 +66,12 @@ class LLMed
|
|
|
66
66
|
def compile_application(app)
|
|
67
67
|
app.notify('COMPILE START')
|
|
68
68
|
|
|
69
|
-
app.
|
|
69
|
+
app.prepare_release
|
|
70
70
|
app.evaluate
|
|
71
|
+
app.prepare_snapshot
|
|
71
72
|
if app.rebuild?
|
|
72
73
|
llm = @configuration.llm
|
|
74
|
+
|
|
73
75
|
messages = [LLMed::LLM::Message::System.new(app.system_prompt(@configuration))]
|
|
74
76
|
app.contexts.each do |ctx|
|
|
75
77
|
next if ctx.skip?
|