hokipoki 0.1.2 → 0.1.3
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/LICENSE +26 -0
- data/lib/generators/hive_mind/install_generator.rb +828 -0
- data/lib/hokipoki/claude/auto_loader.rb +162 -0
- data/lib/hokipoki/claude/connection_manager.rb +382 -0
- data/lib/hokipoki/claude/parasite.rb +333 -0
- data/lib/hokipoki/configuration.rb +187 -0
- data/lib/hokipoki/engine.rb +122 -0
- data/lib/hokipoki/feedback/ascii_banners.rb +108 -0
- data/lib/hokipoki/feedback/display_manager.rb +436 -0
- data/lib/hokipoki/intelligence/smart_retrieval_engine.rb +401 -0
- data/lib/hokipoki/intelligence/unified_orchestrator.rb +395 -0
- data/lib/hokipoki/license_validator.rb +296 -0
- data/lib/hokipoki/parasites/universal_generator.rb +662 -0
- data/lib/hokipoki/railtie.rb +34 -0
- data/lib/hokipoki/version.rb +1 -1
- data/lib/hokipoki.rb +177 -0
- metadata +77 -18
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hokipoki
|
|
4
|
+
module Claude
|
|
5
|
+
# Claude Parasite - Intelligent Claude CLI integration with HiveMind
|
|
6
|
+
# Automatically injects vector intelligence context into Claude conversations
|
|
7
|
+
class Parasite
|
|
8
|
+
include Singleton
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@logger = Rails.logger
|
|
12
|
+
@feedback = Feedback::DisplayManager.instance
|
|
13
|
+
@connection_manager = ConnectionManager.instance
|
|
14
|
+
@injection_stats = {
|
|
15
|
+
total_injections: 0,
|
|
16
|
+
successful_injections: 0,
|
|
17
|
+
failed_injections: 0,
|
|
18
|
+
total_tokens_saved: 0
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Auto-load when Claude CLI starts
|
|
23
|
+
def self.auto_load!
|
|
24
|
+
instance.ensure_connection!
|
|
25
|
+
instance.display_startup_message
|
|
26
|
+
instance
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Check if should inject context for this request
|
|
30
|
+
def should_inject?(user_message, request_context = {})
|
|
31
|
+
return false unless @connection_manager.connected?
|
|
32
|
+
return false if simple_greeting?(user_message)
|
|
33
|
+
return false if user_message.length < 10
|
|
34
|
+
|
|
35
|
+
# Smart injection logic based on content analysis
|
|
36
|
+
programming_related?(user_message) ||
|
|
37
|
+
question_asking?(user_message) ||
|
|
38
|
+
implementation_request?(user_message)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Get injection strategy for this request
|
|
42
|
+
def injection_strategy(user_message, request_context = {})
|
|
43
|
+
intent = analyze_intent(user_message)
|
|
44
|
+
token_budget = calculate_token_budget(user_message, intent)
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
max_tokens: token_budget,
|
|
48
|
+
style: :contextual_intelligence,
|
|
49
|
+
intent: intent,
|
|
50
|
+
compression: true,
|
|
51
|
+
priority: determine_priority(intent),
|
|
52
|
+
bypass_cache: emergency_request?(user_message)
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Craft the injection content
|
|
57
|
+
def craft_injection(user_message, original_messages, strategy = {})
|
|
58
|
+
return original_messages unless @connection_manager.connected?
|
|
59
|
+
|
|
60
|
+
@feedback.debug_info('injection', "analyzing message for context injection")
|
|
61
|
+
|
|
62
|
+
begin
|
|
63
|
+
# Get vector intelligence for the query
|
|
64
|
+
@feedback.pulling_from_hive_mind('context')
|
|
65
|
+
facts = retrieve_vector_intelligence(user_message, strategy)
|
|
66
|
+
|
|
67
|
+
if facts.present?
|
|
68
|
+
# Create intelligence-enhanced system message
|
|
69
|
+
system_message = build_system_message(facts, strategy)
|
|
70
|
+
|
|
71
|
+
# Calculate token savings
|
|
72
|
+
tokens_saved = estimate_tokens_saved(facts, user_message)
|
|
73
|
+
|
|
74
|
+
# Track successful injection
|
|
75
|
+
track_injection_success(user_message, facts, strategy, tokens_saved)
|
|
76
|
+
|
|
77
|
+
@feedback.total_tokens_saved(tokens_saved, @injection_stats[:total_tokens_saved])
|
|
78
|
+
|
|
79
|
+
# Add system message to conversation
|
|
80
|
+
[system_message] + original_messages
|
|
81
|
+
else
|
|
82
|
+
# No relevant context found, return original
|
|
83
|
+
@feedback.debug_info('injection', 'no relevant context found')
|
|
84
|
+
track_injection_attempt(user_message, 'no_context_found')
|
|
85
|
+
original_messages
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
rescue => e
|
|
89
|
+
@feedback.operation_error('injection', e.message)
|
|
90
|
+
@logger.error "🦠 Claude Parasite injection failed: #{e.message}"
|
|
91
|
+
track_injection_failure(user_message, e)
|
|
92
|
+
original_messages
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Display parasite status
|
|
97
|
+
def display_status
|
|
98
|
+
puts "\n#{Pastel.new.cyan.bold('🦠 Claude Parasite Status')}"
|
|
99
|
+
puts " Status: #{connection_status_display}"
|
|
100
|
+
puts " Total Injections: #{@injection_stats[:total_injections]}"
|
|
101
|
+
puts " Success Rate: #{success_rate}%"
|
|
102
|
+
puts " Tokens Saved: #{@injection_stats[:total_tokens_saved]}"
|
|
103
|
+
puts " Connection: #{@connection_manager.connected? ? '🟢 Online' : '🔴 Offline'}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Get parasite statistics
|
|
107
|
+
def statistics
|
|
108
|
+
@injection_stats.merge({
|
|
109
|
+
success_rate: success_rate,
|
|
110
|
+
connection_status: @connection_manager.connected?,
|
|
111
|
+
last_injection: @last_injection_time,
|
|
112
|
+
uptime: calculate_uptime
|
|
113
|
+
})
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Ensure connection to HiveMind
|
|
117
|
+
def ensure_connection!
|
|
118
|
+
unless @connection_manager.connected?
|
|
119
|
+
@connection_manager.establish_connection!
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Display startup message when Claude loads
|
|
124
|
+
def display_startup_message
|
|
125
|
+
pastel = Pastel.new
|
|
126
|
+
|
|
127
|
+
puts "\n#{pastel.green.bold('🧠 HiveMind is now connected!')}"
|
|
128
|
+
puts pastel.cyan(" 🦠 Claude Parasite: Active")
|
|
129
|
+
puts pastel.cyan(" 📊 Vector Intelligence: Online")
|
|
130
|
+
puts pastel.cyan(" ⚡ Smart Context Injection: Enabled")
|
|
131
|
+
|
|
132
|
+
if @connection_manager.connected?
|
|
133
|
+
db_status = @connection_manager.connection_status
|
|
134
|
+
puts pastel.dim(" 📈 Vector DB: #{db_status[:vector_db_status][:document_count]} documents ready")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
puts pastel.yellow("\n💡 Your Claude responses are now enhanced with project-specific intelligence!")
|
|
138
|
+
puts pastel.dim(" The parasite will automatically inject relevant context from your vector database.\n")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def simple_greeting?(message)
|
|
144
|
+
greetings = %w[hi hello hey thanks thank you yes no ok okay sure]
|
|
145
|
+
message.downcase.strip.split.all? { |word| greetings.include?(word) }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def programming_related?(message)
|
|
149
|
+
programming_keywords = %w[
|
|
150
|
+
code function method class variable api database
|
|
151
|
+
implement create build debug fix error bug
|
|
152
|
+
css html javascript react vue angular rails ruby python
|
|
153
|
+
sql query table model controller view
|
|
154
|
+
git github commit push pull merge
|
|
155
|
+
deploy server docker kubernetes aws
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
message.downcase.split.any? { |word| programming_keywords.include?(word) }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def question_asking?(message)
|
|
162
|
+
question_patterns = [
|
|
163
|
+
/\bhow\s+(do|can|to)\b/i,
|
|
164
|
+
/\bwhat\s+(is|are|does|should)\b/i,
|
|
165
|
+
/\bwhere\s+(is|are|do|should)\b/i,
|
|
166
|
+
/\bwhy\s+(is|are|does|should)\b/i,
|
|
167
|
+
/\bwhen\s+(is|are|do|should)\b/i,
|
|
168
|
+
/\bwhich\s+(is|are|should)\b/i,
|
|
169
|
+
/\?$/
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
question_patterns.any? { |pattern| message.match?(pattern) }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def implementation_request?(message)
|
|
176
|
+
implementation_keywords = %w[
|
|
177
|
+
implement create build make generate add
|
|
178
|
+
show example tutorial guide step
|
|
179
|
+
help setup configure install
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
message.downcase.split.any? { |word| implementation_keywords.include?(word) }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def analyze_intent(message)
|
|
186
|
+
return :debugging if message.match?(/\b(error|bug|fix|debug|problem|issue|broken)\b/i)
|
|
187
|
+
return :implementation if message.match?(/\b(implement|create|build|make|add)\b/i)
|
|
188
|
+
return :learning if message.match?(/\b(how|what|why|explain|understand|learn)\b/i)
|
|
189
|
+
return :reference if message.match?(/\b(example|show|demo|sample|reference)\b/i)
|
|
190
|
+
return :optimization if message.match?(/\b(optimize|improve|better|faster|performance)\b/i)
|
|
191
|
+
|
|
192
|
+
:general
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def calculate_token_budget(message, intent)
|
|
196
|
+
base_budget = case intent
|
|
197
|
+
when :debugging then 2000 # More context for debugging
|
|
198
|
+
when :implementation then 1800 # Good context for implementation
|
|
199
|
+
when :learning then 1500 # Standard context for learning
|
|
200
|
+
when :reference then 1200 # Less context for references
|
|
201
|
+
when :optimization then 1600 # Moderate context for optimization
|
|
202
|
+
else 1000 # Minimal context for general
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Adjust based on message length
|
|
206
|
+
if message.length > 200
|
|
207
|
+
base_budget += 500
|
|
208
|
+
elsif message.length < 50
|
|
209
|
+
base_budget -= 300
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
[base_budget, 3000].min # Cap at 3000 tokens
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def determine_priority(intent)
|
|
216
|
+
case intent
|
|
217
|
+
when :debugging then :high
|
|
218
|
+
when :implementation then :high
|
|
219
|
+
when :optimization then :medium
|
|
220
|
+
when :learning then :medium
|
|
221
|
+
when :reference then :low
|
|
222
|
+
else :low
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def emergency_request?(message)
|
|
227
|
+
emergency_keywords = %w[urgent emergency critical broken down error fatal]
|
|
228
|
+
message.downcase.split.any? { |word| emergency_keywords.include?(word) }
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def retrieve_vector_intelligence(message, strategy)
|
|
232
|
+
token_budget = strategy[:max_tokens] || 1500
|
|
233
|
+
|
|
234
|
+
# Use HokiPoki's smart retrieval with intent analysis
|
|
235
|
+
Hokipoki.retrieve_facts(
|
|
236
|
+
message,
|
|
237
|
+
token_budget: token_budget,
|
|
238
|
+
intent: strategy[:intent] || 'auto'
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def build_system_message(facts, strategy)
|
|
243
|
+
intent = strategy[:intent] || :general
|
|
244
|
+
|
|
245
|
+
system_content = case intent
|
|
246
|
+
when :debugging
|
|
247
|
+
"Context for debugging: #{facts}\n\nFocus on identifying issues and providing solutions."
|
|
248
|
+
when :implementation
|
|
249
|
+
"Implementation context: #{facts}\n\nProvide concrete code examples and step-by-step guidance."
|
|
250
|
+
when :learning
|
|
251
|
+
"Learning context: #{facts}\n\nExplain concepts clearly with examples."
|
|
252
|
+
when :reference
|
|
253
|
+
"Reference information: #{facts}\n\nProvide accurate documentation and examples."
|
|
254
|
+
when :optimization
|
|
255
|
+
"Optimization context: #{facts}\n\nSuggest performance improvements and best practices."
|
|
256
|
+
else
|
|
257
|
+
"Context: #{facts}"
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
{
|
|
261
|
+
'role' => 'system',
|
|
262
|
+
'content' => system_content
|
|
263
|
+
}
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def track_injection_success(message, facts, strategy, tokens_saved = nil)
|
|
267
|
+
@injection_stats[:total_injections] += 1
|
|
268
|
+
@injection_stats[:successful_injections] += 1
|
|
269
|
+
|
|
270
|
+
# Use provided tokens saved or estimate
|
|
271
|
+
estimated_tokens = tokens_saved || (facts.length / 4)
|
|
272
|
+
@injection_stats[:total_tokens_saved] += estimated_tokens
|
|
273
|
+
|
|
274
|
+
@last_injection_time = Time.current
|
|
275
|
+
|
|
276
|
+
@logger.info "🦠 Claude Parasite: Context injected successfully (#{estimated_tokens} tokens)"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def estimate_tokens_saved(facts, user_message)
|
|
280
|
+
# Estimate tokens saved by providing specific context vs generic response
|
|
281
|
+
context_tokens = facts.length / 4
|
|
282
|
+
baseline_tokens = user_message.length * 0.6 # Rough estimate of generic response
|
|
283
|
+
savings = [baseline_tokens - context_tokens, 0].max.round
|
|
284
|
+
savings
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def track_injection_attempt(message, reason)
|
|
288
|
+
@injection_stats[:total_injections] += 1
|
|
289
|
+
@logger.debug "🦠 Claude Parasite: Injection attempted but skipped (#{reason})"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def track_injection_failure(message, error)
|
|
293
|
+
@injection_stats[:total_injections] += 1
|
|
294
|
+
@injection_stats[:failed_injections] += 1
|
|
295
|
+
@logger.error "🦠 Claude Parasite: Injection failed - #{error.message}"
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def success_rate
|
|
299
|
+
return 0 if @injection_stats[:total_injections] == 0
|
|
300
|
+
|
|
301
|
+
(@injection_stats[:successful_injections].to_f / @injection_stats[:total_injections] * 100).round(1)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def connection_status_display
|
|
305
|
+
if @connection_manager.connected?
|
|
306
|
+
Pastel.new.green('🟢 Connected')
|
|
307
|
+
else
|
|
308
|
+
Pastel.new.red('🔴 Disconnected')
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def calculate_uptime
|
|
313
|
+
return 'N/A' unless @connection_manager.instance_variable_get(:@connection_time)
|
|
314
|
+
|
|
315
|
+
uptime_seconds = Time.current - @connection_manager.instance_variable_get(:@connection_time)
|
|
316
|
+
format_uptime(uptime_seconds)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def format_uptime(seconds)
|
|
320
|
+
hours = (seconds / 3600).to_i
|
|
321
|
+
minutes = ((seconds % 3600) / 60).to_i
|
|
322
|
+
|
|
323
|
+
if hours > 0
|
|
324
|
+
"#{hours}h #{minutes}m"
|
|
325
|
+
elsif minutes > 0
|
|
326
|
+
"#{minutes}m"
|
|
327
|
+
else
|
|
328
|
+
"#{seconds.to_i}s"
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hokipoki
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :vector_dimensions,
|
|
6
|
+
:lm_studio_url,
|
|
7
|
+
:embedding_model,
|
|
8
|
+
:enable_parasites,
|
|
9
|
+
:enable_forge,
|
|
10
|
+
:enable_behavioral_analysis,
|
|
11
|
+
:enable_template_optimization,
|
|
12
|
+
:template_caching,
|
|
13
|
+
:token_budget_default,
|
|
14
|
+
:context_compression,
|
|
15
|
+
:offline_mode,
|
|
16
|
+
:api_authentication,
|
|
17
|
+
:audit_logging,
|
|
18
|
+
:encrypted_logging,
|
|
19
|
+
:rate_limiting,
|
|
20
|
+
:default_brain,
|
|
21
|
+
:auto_brain_switching,
|
|
22
|
+
:universal_parasite_generation,
|
|
23
|
+
:auto_optimization,
|
|
24
|
+
:cross_parasite_learning,
|
|
25
|
+
:workshop_interface,
|
|
26
|
+
:background_processing,
|
|
27
|
+
:multi_llm_orchestration,
|
|
28
|
+
:redis_url,
|
|
29
|
+
:cache_ttl,
|
|
30
|
+
:max_retries
|
|
31
|
+
|
|
32
|
+
def initialize
|
|
33
|
+
# === CORE SETTINGS ===
|
|
34
|
+
@vector_dimensions = 768
|
|
35
|
+
@lm_studio_url = ENV.fetch('LM_STUDIO_URL', 'http://localhost:1236')
|
|
36
|
+
@embedding_model = ENV.fetch('EMBEDDING_MODEL', 'text-embedding-nomic-embed-text-v1.5')
|
|
37
|
+
|
|
38
|
+
# === COMPONENT ACTIVATION ===
|
|
39
|
+
@enable_parasites = ENV.fetch('HOKIPOKI_ENABLE_PARASITES', 'true') == 'true'
|
|
40
|
+
@enable_forge = ENV.fetch('HOKIPOKI_ENABLE_FORGE', 'false') == 'true'
|
|
41
|
+
@enable_behavioral_analysis = ENV.fetch('HOKIPOKI_ENABLE_BEHAVIORAL_ANALYSIS', 'true') == 'true'
|
|
42
|
+
@enable_template_optimization = ENV.fetch('HOKIPOKI_ENABLE_TEMPLATE_OPTIMIZATION', 'true') == 'true'
|
|
43
|
+
|
|
44
|
+
# === PERFORMANCE ===
|
|
45
|
+
@template_caching = ENV.fetch('HOKIPOKI_TEMPLATE_CACHING', 'true') == 'true'
|
|
46
|
+
@token_budget_default = ENV.fetch('HOKIPOKI_TOKEN_BUDGET_DEFAULT', '1500').to_i
|
|
47
|
+
@context_compression = ENV.fetch('HOKIPOKI_CONTEXT_COMPRESSION', 'true') == 'true'
|
|
48
|
+
@offline_mode = ENV.fetch('HOKIPOKI_OFFLINE_MODE', 'false') == 'true'
|
|
49
|
+
@cache_ttl = ENV.fetch('HOKIPOKI_CACHE_TTL', '3600').to_i # 1 hour default
|
|
50
|
+
@max_retries = ENV.fetch('HOKIPOKI_MAX_RETRIES', '3').to_i
|
|
51
|
+
|
|
52
|
+
# === SECURITY ===
|
|
53
|
+
@api_authentication = ENV.fetch('HOKIPOKI_API_AUTHENTICATION', 'true') == 'true'
|
|
54
|
+
@audit_logging = ENV.fetch('HOKIPOKI_AUDIT_LOGGING', 'true') == 'true'
|
|
55
|
+
@encrypted_logging = ENV.fetch('HOKIPOKI_ENCRYPTED_LOGGING', 'false') == 'true'
|
|
56
|
+
@rate_limiting = ENV.fetch('HOKIPOKI_RATE_LIMITING', 'true') == 'true'
|
|
57
|
+
|
|
58
|
+
# === BRAINS CONFIGURATION ===
|
|
59
|
+
@default_brain = ENV.fetch('HOKIPOKI_DEFAULT_BRAIN', 'default_brain')
|
|
60
|
+
@auto_brain_switching = ENV.fetch('HOKIPOKI_AUTO_BRAIN_SWITCHING', 'true') == 'true'
|
|
61
|
+
|
|
62
|
+
# === PARASITE SYSTEM ===
|
|
63
|
+
@universal_parasite_generation = ENV.fetch('HOKIPOKI_UNIVERSAL_PARASITE_GENERATION', 'true') == 'true'
|
|
64
|
+
@auto_optimization = ENV.fetch('HOKIPOKI_AUTO_OPTIMIZATION', 'true') == 'true'
|
|
65
|
+
@cross_parasite_learning = ENV.fetch('HOKIPOKI_CROSS_PARASITE_LEARNING', 'true') == 'true'
|
|
66
|
+
|
|
67
|
+
# === FORGE CONFIGURATION ===
|
|
68
|
+
@workshop_interface = ENV.fetch('HOKIPOKI_WORKSHOP_INTERFACE', 'true') == 'true'
|
|
69
|
+
@background_processing = ENV.fetch('HOKIPOKI_BACKGROUND_PROCESSING', 'sidekiq').to_sym
|
|
70
|
+
@multi_llm_orchestration = ENV.fetch('HOKIPOKI_MULTI_LLM_ORCHESTRATION', 'true') == 'true'
|
|
71
|
+
|
|
72
|
+
# === REDIS CONFIGURATION ===
|
|
73
|
+
@redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def validate!
|
|
77
|
+
errors = []
|
|
78
|
+
|
|
79
|
+
errors << "vector_dimensions must be positive" if vector_dimensions <= 0
|
|
80
|
+
errors << "token_budget_default must be positive" if token_budget_default <= 0
|
|
81
|
+
errors << "lm_studio_url must be a valid URL" unless valid_url?(lm_studio_url)
|
|
82
|
+
errors << "redis_url must be a valid URL" unless valid_url?(redis_url)
|
|
83
|
+
errors << "cache_ttl must be positive" if cache_ttl <= 0
|
|
84
|
+
errors << "max_retries must be non-negative" if max_retries < 0
|
|
85
|
+
|
|
86
|
+
unless %i[sidekiq resque delayed_job].include?(background_processing)
|
|
87
|
+
errors << "background_processing must be one of: sidekiq, resque, delayed_job"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
raise ConfigurationError, "Configuration errors: #{errors.join(', ')}" if errors.any?
|
|
91
|
+
|
|
92
|
+
true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def redis_config
|
|
96
|
+
{
|
|
97
|
+
url: redis_url,
|
|
98
|
+
timeout: 5,
|
|
99
|
+
reconnect_attempts: max_retries
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def lm_studio_config
|
|
104
|
+
{
|
|
105
|
+
url: lm_studio_url,
|
|
106
|
+
timeout: 30,
|
|
107
|
+
retries: max_retries,
|
|
108
|
+
embedding_model: embedding_model
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def vector_config
|
|
113
|
+
{
|
|
114
|
+
dimensions: vector_dimensions,
|
|
115
|
+
distance_metric: 'cosine',
|
|
116
|
+
index_type: 'hnsw'
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def security_config
|
|
121
|
+
{
|
|
122
|
+
api_authentication: api_authentication,
|
|
123
|
+
audit_logging: audit_logging,
|
|
124
|
+
encrypted_logging: encrypted_logging,
|
|
125
|
+
rate_limiting: rate_limiting
|
|
126
|
+
}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def parasite_config
|
|
130
|
+
{
|
|
131
|
+
enabled: enable_parasites,
|
|
132
|
+
universal_generation: universal_parasite_generation,
|
|
133
|
+
auto_optimization: auto_optimization,
|
|
134
|
+
cross_learning: cross_parasite_learning,
|
|
135
|
+
behavioral_analysis: enable_behavioral_analysis
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def forge_config
|
|
140
|
+
{
|
|
141
|
+
enabled: enable_forge,
|
|
142
|
+
workshop_interface: workshop_interface,
|
|
143
|
+
background_processing: background_processing,
|
|
144
|
+
multi_llm_orchestration: multi_llm_orchestration
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def intelligence_config
|
|
149
|
+
{
|
|
150
|
+
template_optimization: enable_template_optimization,
|
|
151
|
+
template_caching: template_caching,
|
|
152
|
+
context_compression: context_compression,
|
|
153
|
+
token_budget_default: token_budget_default,
|
|
154
|
+
offline_mode: offline_mode
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def brain_config
|
|
159
|
+
{
|
|
160
|
+
default_brain: default_brain,
|
|
161
|
+
auto_switching: auto_brain_switching
|
|
162
|
+
}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Environment helpers
|
|
166
|
+
def development?
|
|
167
|
+
Rails.env.development? if defined?(Rails)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def production?
|
|
171
|
+
Rails.env.production? if defined?(Rails)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def test?
|
|
175
|
+
Rails.env.test? if defined?(Rails)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
def valid_url?(url)
|
|
181
|
+
uri = URI.parse(url)
|
|
182
|
+
uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
183
|
+
rescue URI::InvalidURIError
|
|
184
|
+
false
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hokipoki
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
isolate_namespace Hokipoki
|
|
6
|
+
|
|
7
|
+
config.generators do |g|
|
|
8
|
+
g.test_framework :rspec
|
|
9
|
+
g.fixture_replacement :factory_bot
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Auto-load paths
|
|
13
|
+
config.autoload_paths += Dir[root.join('app', 'models', 'hokipoki')]
|
|
14
|
+
config.autoload_paths += Dir[root.join('app', 'services', 'hokipoki')]
|
|
15
|
+
config.autoload_paths += Dir[root.join('app', 'controllers', 'hokipoki')]
|
|
16
|
+
config.autoload_paths += Dir[root.join('app', 'jobs', 'hokipoki')]
|
|
17
|
+
|
|
18
|
+
# Assets
|
|
19
|
+
config.assets.precompile += %w[hokipoki/dashboard.css hokipoki/workshop.css]
|
|
20
|
+
config.assets.precompile += %w[hokipoki/dashboard.js hokipoki/parasite_optimizer_controller.js hokipoki/template_manager_controller.js]
|
|
21
|
+
|
|
22
|
+
initializer "hokipoki.initialize" do |app|
|
|
23
|
+
# Initialize Hokipoki configuration
|
|
24
|
+
Hokipoki.configure unless Hokipoki.configuration
|
|
25
|
+
|
|
26
|
+
# Validate configuration in non-test environments
|
|
27
|
+
unless Rails.env.test?
|
|
28
|
+
begin
|
|
29
|
+
Hokipoki.config.validate!
|
|
30
|
+
rescue Hokipoki::ConfigurationError => e
|
|
31
|
+
Rails.logger.error "❌ Hokipoki configuration error: #{e.message}"
|
|
32
|
+
raise e if Rails.env.production?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Set up Redis connection
|
|
37
|
+
if Hokipoki.config.redis_url.present?
|
|
38
|
+
begin
|
|
39
|
+
$redis ||= Redis.new(Hokipoki.config.redis_config)
|
|
40
|
+
Rails.logger.info "🔗 Hokipoki: Redis connected at #{Hokipoki.config.redis_url}"
|
|
41
|
+
rescue => e
|
|
42
|
+
Rails.logger.error "❌ Hokipoki: Redis connection failed: #{e.message}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Initialize core services
|
|
47
|
+
Rails.application.config.after_initialize do
|
|
48
|
+
if Hokipoki.config.enable_parasites
|
|
49
|
+
begin
|
|
50
|
+
Hokipoki::Parasites::UniversalGenerator.instance
|
|
51
|
+
Rails.logger.info "🦠 Hokipoki: Universal Parasite Generator initialized"
|
|
52
|
+
rescue => e
|
|
53
|
+
Rails.logger.error "❌ Hokipoki: Parasite system initialization failed: #{e.message}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if Hokipoki.config.enable_template_optimization
|
|
58
|
+
begin
|
|
59
|
+
Hokipoki::Intelligence::UnifiedOrchestrator.instance
|
|
60
|
+
Rails.logger.info "🧠 Hokipoki: Unified Intelligence Orchestrator initialized"
|
|
61
|
+
rescue => e
|
|
62
|
+
Rails.logger.error "❌ Hokipoki: Intelligence system initialization failed: #{e.message}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if Hokipoki.config.enable_forge
|
|
67
|
+
begin
|
|
68
|
+
require 'hokipoki/forge/generator_forge'
|
|
69
|
+
Rails.logger.info "🏭 Hokipoki: Generator Forge initialized"
|
|
70
|
+
rescue LoadError => e
|
|
71
|
+
Rails.logger.warn "⚠️ Hokipoki: Forge components not available: #{e.message}"
|
|
72
|
+
rescue => e
|
|
73
|
+
Rails.logger.error "❌ Hokipoki: Forge initialization failed: #{e.message}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Initialize security audit service
|
|
78
|
+
if Hokipoki.config.audit_logging
|
|
79
|
+
begin
|
|
80
|
+
Hokipoki::Security::AuditService.instance
|
|
81
|
+
Rails.logger.info "🔒 Hokipoki: Security Audit Service initialized"
|
|
82
|
+
rescue => e
|
|
83
|
+
Rails.logger.error "❌ Hokipoki: Security service initialization failed: #{e.message}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
Rails.logger.info "✅ Hokipoki Engine initialized successfully (v#{Hokipoki::VERSION})"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Add middleware for API authentication
|
|
92
|
+
initializer "hokipoki.middleware" do |app|
|
|
93
|
+
if Hokipoki.config.api_authentication
|
|
94
|
+
require 'hokipoki/security/api_authentication'
|
|
95
|
+
app.middleware.use Hokipoki::Security::ApiAuthentication
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Configure Sidekiq for background processing
|
|
100
|
+
initializer "hokipoki.sidekiq" do |app|
|
|
101
|
+
if defined?(Sidekiq) && Hokipoki.config.background_processing == :sidekiq
|
|
102
|
+
Sidekiq.configure_server do |config|
|
|
103
|
+
config.redis = Hokipoki.config.redis_config
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
Sidekiq.configure_client do |config|
|
|
107
|
+
config.redis = Hokipoki.config.redis_config
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
Rails.logger.info "⚙️ Hokipoki: Sidekiq configured for background processing"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Add custom rake tasks
|
|
115
|
+
rake_tasks do
|
|
116
|
+
load 'tasks/hokipoki.rake'
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Generator paths
|
|
120
|
+
config.generators.templates.unshift File.expand_path('generators/templates', __dir__)
|
|
121
|
+
end
|
|
122
|
+
end
|