hokipoki 0.3.4 โ 0.5.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 +4 -4
- data/lib/generators/hive_mind/install_generator.rb +18 -2
- data/lib/generators/hive_mind/start_generator.rb +582 -0
- data/lib/generators/hive_mind/templates/hokipoki_claude.rb +45 -0
- data/lib/generators/hokipoki/attach_parasite_generator.rb +355 -0
- data/lib/generators/hokipoki/install_generator.rb +515 -0
- data/lib/generators/hokipoki/scan_project_generator.rb +279 -0
- data/lib/generators/parasite/install_generator.rb +458 -0
- data/lib/hokipoki/atomic_fact_extractor.rb +524 -0
- data/lib/hokipoki/claude/parasite.rb +62 -10
- data/lib/hokipoki/claude/thought_interceptor.rb +385 -0
- data/lib/hokipoki/claude_auto_loader.rb +28 -11
- data/lib/hokipoki/template_store.rb +425 -0
- data/lib/hokipoki/vector_engine.rb +525 -0
- data/lib/hokipoki/version.rb +1 -1
- data/lib/hokipoki.rb +260 -6
- metadata +81 -1
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hokipoki
|
|
4
|
+
# Template Store - Dynamic content generation from compressed templates
|
|
5
|
+
# Implements the "SECRET SAUCE" 75% compression breakthrough
|
|
6
|
+
class TemplateStore
|
|
7
|
+
|
|
8
|
+
# Content templates for dynamic generation
|
|
9
|
+
TEMPLATES = {
|
|
10
|
+
# Ruby/Rails templates
|
|
11
|
+
'active_record_model' => <<~TEMPLATE,
|
|
12
|
+
ActiveRecord model context: {{model_name}} with associations and validations.
|
|
13
|
+
Key methods: {{methods}}. Attributes: {{attributes}}.
|
|
14
|
+
Best practices: Use strong parameters, validate input, follow Rails conventions.
|
|
15
|
+
Common patterns: belongs_to, has_many, validates presence/uniqueness.
|
|
16
|
+
TEMPLATE
|
|
17
|
+
|
|
18
|
+
'rails_controller' => <<~TEMPLATE,
|
|
19
|
+
Rails controller context: {{controller_name}} handling {{actions}}.
|
|
20
|
+
Key methods: {{methods}}. Security: Use strong parameters, authenticate users.
|
|
21
|
+
RESTful actions: index, show, new, create, edit, update, destroy.
|
|
22
|
+
Response formats: render, redirect_to, respond_with.
|
|
23
|
+
TEMPLATE
|
|
24
|
+
|
|
25
|
+
'ruby_class' => <<~TEMPLATE,
|
|
26
|
+
Ruby class context: {{class_name}} with methods {{methods}}.
|
|
27
|
+
Object-oriented design: Use single responsibility principle.
|
|
28
|
+
Error handling: Rescue specific exceptions, provide meaningful messages.
|
|
29
|
+
Testing: Write unit tests, use dependency injection.
|
|
30
|
+
TEMPLATE
|
|
31
|
+
|
|
32
|
+
# Frontend templates
|
|
33
|
+
'javascript_code' => <<~TEMPLATE,
|
|
34
|
+
JavaScript context: {{js_concepts}}. Modern practices: use const/let, arrow functions.
|
|
35
|
+
Async patterns: Promises, async/await for API calls.
|
|
36
|
+
DOM manipulation: querySelector, addEventListener for interactions.
|
|
37
|
+
Debugging: Use console.log, browser dev tools, proper error handling.
|
|
38
|
+
TEMPLATE
|
|
39
|
+
|
|
40
|
+
'css_styles' => <<~TEMPLATE,
|
|
41
|
+
CSS styling context: {{css_classes}} for {{elements}}.
|
|
42
|
+
Layout: Flexbox for 1D, Grid for 2D layouts. Responsive design with media queries.
|
|
43
|
+
Performance: Minimize specificity, avoid !important, use CSS custom properties.
|
|
44
|
+
Modern CSS: Use logical properties, container queries, CSS Grid.
|
|
45
|
+
TEMPLATE
|
|
46
|
+
|
|
47
|
+
'erb_template' => <<~TEMPLATE,
|
|
48
|
+
ERB template context: {{template_type}} rendering {{data}}.
|
|
49
|
+
Security: Use <%= %> for output, <% %> for logic, sanitize user input.
|
|
50
|
+
Helpers: Use Rails helpers, extract complex logic to helpers.
|
|
51
|
+
Performance: Avoid N+1 queries, use partials for reusable components.
|
|
52
|
+
TEMPLATE
|
|
53
|
+
|
|
54
|
+
# Testing templates
|
|
55
|
+
'test_spec' => <<~TEMPLATE,
|
|
56
|
+
Testing context: {{test_type}} for {{subject}}.
|
|
57
|
+
Structure: Arrange-Act-Assert pattern. Use descriptive test names.
|
|
58
|
+
Mocking: Stub external dependencies, test behavior not implementation.
|
|
59
|
+
Coverage: Test happy path, edge cases, error conditions.
|
|
60
|
+
TEMPLATE
|
|
61
|
+
|
|
62
|
+
# Documentation templates
|
|
63
|
+
'documentation' => <<~TEMPLATE,
|
|
64
|
+
Documentation context: {{doc_type}} for {{topic}}.
|
|
65
|
+
Structure: Clear headings, code examples, usage instructions.
|
|
66
|
+
Content: What it does, how to use it, common gotchas.
|
|
67
|
+
Maintenance: Keep examples current, explain the why not just what.
|
|
68
|
+
TEMPLATE
|
|
69
|
+
|
|
70
|
+
# Database templates
|
|
71
|
+
'database_migration' => <<~TEMPLATE,
|
|
72
|
+
Database migration context: {{migration_type}} for {{table_name}}.
|
|
73
|
+
Safety: Use reversible migrations, test rollbacks.
|
|
74
|
+
Performance: Add indexes for queries, avoid large data changes.
|
|
75
|
+
Best practices: One concept per migration, use change method when possible.
|
|
76
|
+
TEMPLATE
|
|
77
|
+
|
|
78
|
+
# Configuration templates
|
|
79
|
+
'configuration' => <<~TEMPLATE,
|
|
80
|
+
Configuration context: {{config_type}} settings for {{environment}}.
|
|
81
|
+
Security: Use environment variables for secrets, validate configuration.
|
|
82
|
+
Organization: Group related settings, document required values.
|
|
83
|
+
Deployment: Different configs per environment, version control safe values.
|
|
84
|
+
TEMPLATE
|
|
85
|
+
|
|
86
|
+
# Error/debugging templates
|
|
87
|
+
'error_analysis' => <<~TEMPLATE,
|
|
88
|
+
Error analysis: {{error_type}} in {{context}}.
|
|
89
|
+
Common causes: {{likely_causes}}.
|
|
90
|
+
Debugging steps: Check logs, verify configuration, test incrementally.
|
|
91
|
+
Prevention: Input validation, error handling, monitoring.
|
|
92
|
+
TEMPLATE
|
|
93
|
+
|
|
94
|
+
# Implementation guidance templates
|
|
95
|
+
'implementation_guide' => <<~TEMPLATE,
|
|
96
|
+
Implementation guide for {{feature}} using {{technologies}}.
|
|
97
|
+
Approach: Start simple, iterate, test early and often.
|
|
98
|
+
Architecture: Follow MVC pattern, separate concerns, use design patterns.
|
|
99
|
+
Code quality: Readable code, meaningful names, appropriate abstractions.
|
|
100
|
+
TEMPLATE
|
|
101
|
+
}.freeze
|
|
102
|
+
|
|
103
|
+
def initialize
|
|
104
|
+
@generation_cache = {}
|
|
105
|
+
@template_usage_stats = Hash.new(0)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Generate content from template with keywords and context
|
|
109
|
+
def generate_content(template_type, keywords:, intent:, params: {})
|
|
110
|
+
$stdout.puts " ๐ญ TEMPLATE: Generating #{template_type} content"
|
|
111
|
+
|
|
112
|
+
# Track template usage
|
|
113
|
+
@template_usage_stats[template_type] += 1
|
|
114
|
+
|
|
115
|
+
# Check cache first
|
|
116
|
+
cache_key = build_cache_key(template_type, keywords, intent, params)
|
|
117
|
+
cached_content = @generation_cache[cache_key]
|
|
118
|
+
|
|
119
|
+
if cached_content
|
|
120
|
+
$stdout.puts " ๐จ Cache hit for #{template_type}"
|
|
121
|
+
return cached_content
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Generate new content
|
|
125
|
+
generated_content = case template_type
|
|
126
|
+
when 'active_record_model'
|
|
127
|
+
generate_activerecord_content(keywords, intent, params)
|
|
128
|
+
when 'rails_controller'
|
|
129
|
+
generate_controller_content(keywords, intent, params)
|
|
130
|
+
when 'ruby_class'
|
|
131
|
+
generate_ruby_class_content(keywords, intent, params)
|
|
132
|
+
when 'javascript_code'
|
|
133
|
+
generate_javascript_content(keywords, intent, params)
|
|
134
|
+
when 'css_styles'
|
|
135
|
+
generate_css_content(keywords, intent, params)
|
|
136
|
+
when 'erb_template'
|
|
137
|
+
generate_erb_content(keywords, intent, params)
|
|
138
|
+
when 'test_spec'
|
|
139
|
+
generate_test_content(keywords, intent, params)
|
|
140
|
+
when 'documentation'
|
|
141
|
+
generate_documentation_content(keywords, intent, params)
|
|
142
|
+
when 'database_migration'
|
|
143
|
+
generate_migration_content(keywords, intent, params)
|
|
144
|
+
when 'configuration'
|
|
145
|
+
generate_configuration_content(keywords, intent, params)
|
|
146
|
+
else
|
|
147
|
+
generate_generic_content(template_type, keywords, intent, params)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Cache generated content
|
|
151
|
+
@generation_cache[cache_key] = generated_content
|
|
152
|
+
|
|
153
|
+
# Clean cache if it gets too large
|
|
154
|
+
clean_cache_if_needed
|
|
155
|
+
|
|
156
|
+
$stdout.puts " โ
Generated #{template_type} (#{generated_content.length} chars)"
|
|
157
|
+
generated_content
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Get template statistics
|
|
161
|
+
def statistics
|
|
162
|
+
{
|
|
163
|
+
templates_available: TEMPLATES.size,
|
|
164
|
+
cache_size: @generation_cache.size,
|
|
165
|
+
most_used_template: @template_usage_stats.max_by { |k, v| v }&.first,
|
|
166
|
+
total_generations: @template_usage_stats.values.sum
|
|
167
|
+
}
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
def generate_activerecord_content(keywords, intent, params)
|
|
173
|
+
model_keywords = keywords.select { |k| k.start_with?('class_', 'method_') }
|
|
174
|
+
model_name = extract_model_name(model_keywords)
|
|
175
|
+
methods = extract_methods(model_keywords)
|
|
176
|
+
|
|
177
|
+
template = TEMPLATES['active_record_model']
|
|
178
|
+
template.gsub('{{model_name}}', model_name)
|
|
179
|
+
.gsub('{{methods}}', methods.join(', '))
|
|
180
|
+
.gsub('{{attributes}}', infer_attributes(keywords).join(', '))
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def generate_controller_content(keywords, intent, params)
|
|
184
|
+
controller_keywords = keywords.select { |k| k.start_with?('class_', 'method_') }
|
|
185
|
+
controller_name = extract_controller_name(controller_keywords)
|
|
186
|
+
actions = extract_controller_actions(controller_keywords)
|
|
187
|
+
|
|
188
|
+
template = TEMPLATES['rails_controller']
|
|
189
|
+
template.gsub('{{controller_name}}', controller_name)
|
|
190
|
+
.gsub('{{actions}}', actions.join(', '))
|
|
191
|
+
.gsub('{{methods}}', actions.join(', '))
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def generate_ruby_class_content(keywords, intent, params)
|
|
195
|
+
class_keywords = keywords.select { |k| k.start_with?('class_', 'method_') }
|
|
196
|
+
class_name = extract_class_name(class_keywords)
|
|
197
|
+
methods = extract_methods(class_keywords)
|
|
198
|
+
|
|
199
|
+
template = TEMPLATES['ruby_class']
|
|
200
|
+
template.gsub('{{class_name}}', class_name)
|
|
201
|
+
.gsub('{{methods}}', methods.join(', '))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def generate_javascript_content(keywords, intent, params)
|
|
205
|
+
js_concepts = keywords.select { |k| k.include?('js') || k.include?('javascript') || k.include?('function') }
|
|
206
|
+
|
|
207
|
+
template = TEMPLATES['javascript_code']
|
|
208
|
+
template.gsub('{{js_concepts}}', js_concepts.join(', '))
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def generate_css_content(keywords, intent, params)
|
|
212
|
+
css_keywords = keywords.select { |k| k.start_with?('css_') }
|
|
213
|
+
css_classes = css_keywords.map { |k| k.sub('css_', '') }
|
|
214
|
+
elements = infer_css_elements(css_classes)
|
|
215
|
+
|
|
216
|
+
template = TEMPLATES['css_styles']
|
|
217
|
+
template.gsub('{{css_classes}}', css_classes.join(', '))
|
|
218
|
+
.gsub('{{elements}}', elements.join(', '))
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def generate_erb_content(keywords, intent, params)
|
|
222
|
+
template_type = infer_erb_template_type(keywords)
|
|
223
|
+
data_keywords = keywords.reject { |k| k.start_with?('css_', 'tech_') }
|
|
224
|
+
|
|
225
|
+
template = TEMPLATES['erb_template']
|
|
226
|
+
template.gsub('{{template_type}}', template_type)
|
|
227
|
+
.gsub('{{data}}', data_keywords.join(', '))
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def generate_test_content(keywords, intent, params)
|
|
231
|
+
test_type = infer_test_type(keywords)
|
|
232
|
+
subject = infer_test_subject(keywords)
|
|
233
|
+
|
|
234
|
+
template = TEMPLATES['test_spec']
|
|
235
|
+
template.gsub('{{test_type}}', test_type)
|
|
236
|
+
.gsub('{{subject}}', subject)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def generate_documentation_content(keywords, intent, params)
|
|
240
|
+
doc_type = infer_documentation_type(keywords)
|
|
241
|
+
topic = keywords.reject { |k| k.include?('tech_') }.join(' ')
|
|
242
|
+
|
|
243
|
+
template = TEMPLATES['documentation']
|
|
244
|
+
template.gsub('{{doc_type}}', doc_type)
|
|
245
|
+
.gsub('{{topic}}', topic)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def generate_migration_content(keywords, intent, params)
|
|
249
|
+
migration_type = infer_migration_type(keywords)
|
|
250
|
+
table_name = infer_table_name(keywords)
|
|
251
|
+
|
|
252
|
+
template = TEMPLATES['database_migration']
|
|
253
|
+
template.gsub('{{migration_type}}', migration_type)
|
|
254
|
+
.gsub('{{table_name}}', table_name)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def generate_configuration_content(keywords, intent, params)
|
|
258
|
+
config_type = infer_config_type(keywords)
|
|
259
|
+
environment = infer_environment(keywords)
|
|
260
|
+
|
|
261
|
+
template = TEMPLATES['configuration']
|
|
262
|
+
template.gsub('{{config_type}}', config_type)
|
|
263
|
+
.gsub('{{environment}}', environment)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def generate_generic_content(template_type, keywords, intent, params)
|
|
267
|
+
# Fallback for unknown template types
|
|
268
|
+
relevant_keywords = keywords.first(5)
|
|
269
|
+
|
|
270
|
+
case intent
|
|
271
|
+
when :implementation
|
|
272
|
+
"Implementation guidance for #{template_type}: #{relevant_keywords.join(', ')}. Follow established patterns, write tests, consider edge cases."
|
|
273
|
+
when :debugging
|
|
274
|
+
"Debugging #{template_type}: Check #{relevant_keywords.join(', ')}. Verify configuration, check logs, test incrementally."
|
|
275
|
+
when :learning
|
|
276
|
+
"Learning about #{template_type}: Key concepts include #{relevant_keywords.join(', ')}. Start with documentation, practice with examples."
|
|
277
|
+
when :optimization
|
|
278
|
+
"Optimizing #{template_type}: Focus on #{relevant_keywords.join(', ')}. Profile first, optimize bottlenecks, measure results."
|
|
279
|
+
else
|
|
280
|
+
"Context for #{template_type}: #{relevant_keywords.join(', ')}. Apply best practices and follow conventions."
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Helper methods for extracting semantic information from keywords
|
|
285
|
+
|
|
286
|
+
def extract_model_name(keywords)
|
|
287
|
+
model_keyword = keywords.find { |k| k.start_with?('class_') }
|
|
288
|
+
model_keyword ? model_keyword.sub('class_', '').capitalize : 'Model'
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def extract_controller_name(keywords)
|
|
292
|
+
controller_keyword = keywords.find { |k| k.start_with?('class_') && k.include?('controller') }
|
|
293
|
+
controller_keyword ? controller_keyword.sub('class_', '').capitalize : 'Controller'
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def extract_class_name(keywords)
|
|
297
|
+
class_keyword = keywords.find { |k| k.start_with?('class_') }
|
|
298
|
+
class_keyword ? class_keyword.sub('class_', '').capitalize : 'Class'
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def extract_methods(keywords)
|
|
302
|
+
method_keywords = keywords.select { |k| k.start_with?('method_') }
|
|
303
|
+
method_keywords.map { |k| k.sub('method_', '') }.first(5)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def extract_controller_actions(keywords)
|
|
307
|
+
method_keywords = extract_methods(keywords)
|
|
308
|
+
rest_actions = %w[index show new create edit update destroy]
|
|
309
|
+
actions = method_keywords & rest_actions
|
|
310
|
+
actions.any? ? actions : method_keywords.first(3)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def infer_attributes(keywords)
|
|
314
|
+
# Infer model attributes from keywords
|
|
315
|
+
attribute_hints = keywords.select { |k|
|
|
316
|
+
k.match?(/name|title|email|description|content|status|type|id/)
|
|
317
|
+
}
|
|
318
|
+
attribute_hints.any? ? attribute_hints.first(3) : ['name', 'description']
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def infer_css_elements(css_classes)
|
|
322
|
+
# Map CSS classes to likely HTML elements
|
|
323
|
+
element_mapping = {
|
|
324
|
+
'btn' => 'button',
|
|
325
|
+
'card' => 'div',
|
|
326
|
+
'container' => 'div',
|
|
327
|
+
'nav' => 'nav',
|
|
328
|
+
'header' => 'header',
|
|
329
|
+
'footer' => 'footer',
|
|
330
|
+
'form' => 'form',
|
|
331
|
+
'input' => 'input',
|
|
332
|
+
'text' => 'span'
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
css_classes.map { |cls|
|
|
336
|
+
element_mapping.find { |key, _| cls.include?(key) }&.last || 'div'
|
|
337
|
+
}.uniq
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def infer_erb_template_type(keywords)
|
|
341
|
+
return 'layout' if keywords.any? { |k| k.include?('layout') }
|
|
342
|
+
return 'partial' if keywords.any? { |k| k.include?('partial') }
|
|
343
|
+
return 'form' if keywords.any? { |k| k.include?('form') }
|
|
344
|
+
'view'
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def infer_test_type(keywords)
|
|
348
|
+
return 'unit test' if keywords.any? { |k| k.include?('unit') }
|
|
349
|
+
return 'integration test' if keywords.any? { |k| k.include?('integration') }
|
|
350
|
+
return 'system test' if keywords.any? { |k| k.include?('system') }
|
|
351
|
+
'spec'
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def infer_test_subject(keywords)
|
|
355
|
+
class_keywords = keywords.select { |k| k.start_with?('class_') }
|
|
356
|
+
method_keywords = keywords.select { |k| k.start_with?('method_') }
|
|
357
|
+
|
|
358
|
+
if class_keywords.any?
|
|
359
|
+
class_keywords.first.sub('class_', '')
|
|
360
|
+
elsif method_keywords.any?
|
|
361
|
+
method_keywords.first.sub('method_', '')
|
|
362
|
+
else
|
|
363
|
+
'component'
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def infer_documentation_type(keywords)
|
|
368
|
+
return 'API documentation' if keywords.any? { |k| k.include?('api') }
|
|
369
|
+
return 'user guide' if keywords.any? { |k| k.include?('guide') }
|
|
370
|
+
return 'tutorial' if keywords.any? { |k| k.include?('tutorial') }
|
|
371
|
+
'technical documentation'
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def infer_migration_type(keywords)
|
|
375
|
+
return 'create table' if keywords.any? { |k| k.include?('create') }
|
|
376
|
+
return 'add column' if keywords.any? { |k| k.include?('add') }
|
|
377
|
+
return 'modify table' if keywords.any? { |k| k.include?('change') }
|
|
378
|
+
'database change'
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def infer_table_name(keywords)
|
|
382
|
+
table_hints = keywords.select { |k|
|
|
383
|
+
k.match?(/users|posts|articles|products|orders|customers/)
|
|
384
|
+
}
|
|
385
|
+
table_hints.first || 'table'
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def infer_config_type(keywords)
|
|
389
|
+
return 'database config' if keywords.any? { |k| k.include?('database') }
|
|
390
|
+
return 'application config' if keywords.any? { |k| k.include?('application') }
|
|
391
|
+
return 'environment config' if keywords.any? { |k| k.include?('environment') }
|
|
392
|
+
'configuration'
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def infer_environment(keywords)
|
|
396
|
+
return 'production' if keywords.any? { |k| k.include?('production') }
|
|
397
|
+
return 'development' if keywords.any? { |k| k.include?('development') }
|
|
398
|
+
return 'test' if keywords.any? { |k| k.include?('test') }
|
|
399
|
+
'environment'
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def build_cache_key(template_type, keywords, intent, params)
|
|
403
|
+
# Create a stable cache key
|
|
404
|
+
key_parts = [
|
|
405
|
+
template_type,
|
|
406
|
+
keywords.sort.join('_'),
|
|
407
|
+
intent.to_s,
|
|
408
|
+
params.sort_by { |k, v| k.to_s }.to_h.to_s
|
|
409
|
+
]
|
|
410
|
+
|
|
411
|
+
Digest::MD5.hexdigest(key_parts.join('|'))
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def clean_cache_if_needed
|
|
415
|
+
# Keep cache size reasonable
|
|
416
|
+
if @generation_cache.size > 500
|
|
417
|
+
# Remove oldest entries (simple LRU approximation)
|
|
418
|
+
keys_to_remove = @generation_cache.keys.first(100)
|
|
419
|
+
keys_to_remove.each { |key| @generation_cache.delete(key) }
|
|
420
|
+
|
|
421
|
+
$stdout.puts " ๐งน Cache cleaned: removed #{keys_to_remove.size} old entries"
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
end
|