kbs 0.0.1 → 0.1.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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +52 -0
  3. data/CHANGELOG.md +68 -2
  4. data/README.md +235 -334
  5. data/docs/DOCUMENTATION_STATUS.md +158 -0
  6. data/docs/advanced/custom-persistence.md +775 -0
  7. data/docs/advanced/debugging.md +726 -0
  8. data/docs/advanced/index.md +8 -0
  9. data/docs/advanced/performance.md +832 -0
  10. data/docs/advanced/testing.md +691 -0
  11. data/docs/api/blackboard.md +1157 -0
  12. data/docs/api/engine.md +978 -0
  13. data/docs/api/facts.md +1212 -0
  14. data/docs/api/index.md +12 -0
  15. data/docs/api/rules.md +1034 -0
  16. data/docs/architecture/blackboard.md +553 -0
  17. data/docs/architecture/index.md +277 -0
  18. data/docs/architecture/network-structure.md +343 -0
  19. data/docs/architecture/rete-algorithm.md +737 -0
  20. data/docs/assets/css/custom.css +83 -0
  21. data/docs/assets/images/blackboard-architecture.svg +136 -0
  22. data/docs/assets/images/compiled-network.svg +101 -0
  23. data/docs/assets/images/fact-assertion-flow.svg +117 -0
  24. data/docs/assets/images/kbs.jpg +0 -0
  25. data/docs/assets/images/pattern-matching-trace.svg +136 -0
  26. data/docs/assets/images/rete-network-layers.svg +96 -0
  27. data/docs/assets/images/system-layers.svg +69 -0
  28. data/docs/assets/images/trading-signal-network.svg +139 -0
  29. data/docs/assets/js/mathjax.js +17 -0
  30. data/docs/examples/expert-systems.md +1031 -0
  31. data/docs/examples/index.md +9 -0
  32. data/docs/examples/multi-agent.md +1335 -0
  33. data/docs/examples/stock-trading.md +488 -0
  34. data/docs/guides/blackboard-memory.md +558 -0
  35. data/docs/guides/dsl.md +1321 -0
  36. data/docs/guides/facts.md +652 -0
  37. data/docs/guides/getting-started.md +383 -0
  38. data/docs/guides/index.md +23 -0
  39. data/docs/guides/negation.md +529 -0
  40. data/docs/guides/pattern-matching.md +561 -0
  41. data/docs/guides/persistence.md +451 -0
  42. data/docs/guides/variable-binding.md +491 -0
  43. data/docs/guides/writing-rules.md +755 -0
  44. data/docs/index.md +157 -0
  45. data/docs/installation.md +156 -0
  46. data/docs/quick-start.md +228 -0
  47. data/examples/README.md +2 -2
  48. data/examples/advanced_example.rb +2 -2
  49. data/examples/advanced_example_dsl.rb +224 -0
  50. data/examples/ai_enhanced_kbs.rb +1 -1
  51. data/examples/ai_enhanced_kbs_dsl.rb +538 -0
  52. data/examples/blackboard_demo_dsl.rb +50 -0
  53. data/examples/car_diagnostic.rb +1 -1
  54. data/examples/car_diagnostic_dsl.rb +54 -0
  55. data/examples/concurrent_inference_demo.rb +5 -5
  56. data/examples/concurrent_inference_demo_dsl.rb +363 -0
  57. data/examples/csv_trading_system.rb +1 -1
  58. data/examples/csv_trading_system_dsl.rb +525 -0
  59. data/examples/knowledge_base.db +0 -0
  60. data/examples/portfolio_rebalancing_system.rb +2 -2
  61. data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
  62. data/examples/redis_trading_demo_dsl.rb +177 -0
  63. data/examples/run_all.rb +50 -0
  64. data/examples/run_all_dsl.rb +49 -0
  65. data/examples/stock_trading_advanced.rb +1 -1
  66. data/examples/stock_trading_advanced_dsl.rb +404 -0
  67. data/examples/temp.txt +7693 -0
  68. data/examples/temp_dsl.txt +8447 -0
  69. data/examples/timestamped_trading.rb +1 -1
  70. data/examples/timestamped_trading_dsl.rb +258 -0
  71. data/examples/trading_demo.rb +1 -1
  72. data/examples/trading_demo_dsl.rb +322 -0
  73. data/examples/working_demo.rb +1 -1
  74. data/examples/working_demo_dsl.rb +160 -0
  75. data/lib/kbs/blackboard/engine.rb +3 -3
  76. data/lib/kbs/blackboard/fact.rb +1 -1
  77. data/lib/kbs/condition.rb +1 -1
  78. data/lib/kbs/dsl/knowledge_base.rb +1 -1
  79. data/lib/kbs/dsl/variable.rb +1 -1
  80. data/lib/kbs/{rete_engine.rb → engine.rb} +1 -1
  81. data/lib/kbs/fact.rb +1 -1
  82. data/lib/kbs/version.rb +1 -1
  83. data/lib/kbs.rb +2 -2
  84. data/mkdocs.yml +181 -0
  85. metadata +66 -6
  86. data/examples/stock_trading_system.rb.bak +0 -563
@@ -0,0 +1,726 @@
1
+ # Debugging
2
+
3
+ Debug KBS applications using network visualization, token tracing, fact inspection, and rule execution logging. This guide provides tools and techniques to understand rule behavior and diagnose issues.
4
+
5
+ ## Debugging Overview
6
+
7
+ Common debugging scenarios:
8
+
9
+ 1. **Rules not firing** - Conditions don't match expected facts
10
+ 2. **Unexpected rule firing** - Rules fire when they shouldn't
11
+ 3. **Performance issues** - Slow rule execution
12
+ 4. **Incorrect bindings** - Variables bound to wrong values
13
+ 5. **Network structure** - Understanding compilation
14
+
15
+ ## Enable Debug Output
16
+
17
+ ### Basic Logging
18
+
19
+ ```ruby
20
+ require 'kbs'
21
+
22
+ engine = KBS::Engine.new
23
+
24
+ # Enable debug output
25
+ engine.instance_variable_set(:@debug, true)
26
+
27
+ # Or create debug wrapper
28
+ class DebugEngine < KBS::Engine
29
+ def add_fact(type, attributes = {})
30
+ fact = super
31
+ puts "[FACT ADDED] #{fact.type}: #{fact.attributes.inspect}"
32
+ fact
33
+ end
34
+
35
+ def remove_fact(fact)
36
+ puts "[FACT REMOVED] #{fact.type}: #{fact.attributes.inspect}"
37
+ super
38
+ end
39
+ end
40
+ ```
41
+
42
+ ### Rule Execution Logging
43
+
44
+ ```ruby
45
+ class LoggingEngine < KBS::Engine
46
+ def initialize
47
+ super
48
+ @rule_log = []
49
+ end
50
+
51
+ def run
52
+ puts "\n=== Engine Run Started ==="
53
+ puts "Facts: #{facts.size}"
54
+ puts "Rules: #{@rules.size}"
55
+
56
+ result = super
57
+
58
+ puts "\n=== Engine Run Completed ==="
59
+ puts "Rules fired: #{@rule_log.size}"
60
+ @rule_log.each_with_index do |entry, i|
61
+ puts " #{i + 1}. #{entry[:rule]} (#{entry[:timestamp]})"
62
+ end
63
+
64
+ result
65
+ end
66
+
67
+ attr_reader :rule_log
68
+ end
69
+ ```
70
+
71
+ ## Fact Inspection
72
+
73
+ ### Inspect Current Facts
74
+
75
+ ```ruby
76
+ def inspect_facts(engine)
77
+ puts "\n=== Current Facts ==="
78
+
79
+ # Group by type
80
+ facts_by_type = engine.facts.group_by(&:type)
81
+
82
+ facts_by_type.each do |type, facts|
83
+ puts "\n#{type} (#{facts.size}):"
84
+ facts.each_with_index do |fact, i|
85
+ puts " #{i + 1}. #{fact.attributes.inspect}"
86
+ if fact.is_a?(KBS::Blackboard::Fact)
87
+ puts " ID: #{fact.id}"
88
+ puts " Created: #{fact.created_at}"
89
+ end
90
+ end
91
+ end
92
+
93
+ puts "\nTotal facts: #{engine.facts.size}"
94
+ end
95
+
96
+ # Usage
97
+ inspect_facts(engine)
98
+ ```
99
+
100
+ ### Query Fact History (Blackboard)
101
+
102
+ ```ruby
103
+ def inspect_fact_history(engine, fact_id)
104
+ return unless engine.is_a?(KBS::Blackboard::Engine)
105
+
106
+ puts "\n=== Fact History: #{fact_id} ==="
107
+
108
+ history = engine.fact_history(fact_id)
109
+
110
+ history.each do |entry|
111
+ puts "\n#{entry[:timestamp]}"
112
+ puts " Operation: #{entry[:operation]}"
113
+ puts " Attributes: #{entry[:attributes].inspect}"
114
+ end
115
+ end
116
+ ```
117
+
118
+ ### Find Facts by Criteria
119
+
120
+ ```ruby
121
+ def find_facts(engine, **criteria)
122
+ results = engine.facts.select do |fact|
123
+ criteria.all? do |key, value|
124
+ case key
125
+ when :type
126
+ fact.type == value
127
+ else
128
+ fact[key] == value
129
+ end
130
+ end
131
+ end
132
+
133
+ puts "\n=== Found #{results.size} facts ==="
134
+ results.each do |fact|
135
+ puts "#{fact.type}: #{fact.attributes.inspect}"
136
+ end
137
+
138
+ results
139
+ end
140
+
141
+ # Usage
142
+ find_facts(engine, type: :sensor, location: "bedroom")
143
+ find_facts(engine, type: :alert, severity: "critical")
144
+ ```
145
+
146
+ ## Rule Debugging
147
+
148
+ ### Trace Rule Execution
149
+
150
+ ```ruby
151
+ class RuleTracer
152
+ def initialize(engine)
153
+ @engine = engine
154
+ @traces = []
155
+ end
156
+
157
+ def wrap_rules
158
+ @engine.instance_variable_get(:@rules).each do |rule|
159
+ wrap_rule(rule)
160
+ end
161
+ end
162
+
163
+ def wrap_rule(rule)
164
+ original_action = rule.action
165
+
166
+ rule.action = lambda do |facts, bindings|
167
+ trace = {
168
+ rule: rule.name,
169
+ timestamp: Time.now,
170
+ facts: facts.map { |f| { type: f.type, attrs: f.attributes } },
171
+ bindings: bindings.dup
172
+ }
173
+
174
+ puts "\n[RULE FIRING] #{rule.name}"
175
+ puts " Facts: #{facts.map(&:type).join(', ')}"
176
+ puts " Bindings: #{bindings.inspect}"
177
+
178
+ result = original_action.call(facts, bindings)
179
+
180
+ trace[:duration] = (Time.now - trace[:timestamp])
181
+ @traces << trace
182
+
183
+ puts " Duration: #{trace[:duration]}s"
184
+
185
+ result
186
+ end
187
+ end
188
+
189
+ attr_reader :traces
190
+ end
191
+
192
+ # Usage
193
+ tracer = RuleTracer.new(engine)
194
+ tracer.wrap_rules
195
+ engine.run
196
+ puts "\nTotal rule firings: #{tracer.traces.size}"
197
+ ```
198
+
199
+ ### Test Individual Conditions
200
+
201
+ ```ruby
202
+ def test_condition(engine, condition)
203
+ puts "\n=== Testing Condition ==="
204
+ puts "Type: #{condition.pattern[:type]}"
205
+ puts "Pattern: #{condition.pattern.inspect}"
206
+
207
+ # Find matching facts
208
+ matches = engine.facts.select do |fact|
209
+ fact.matches?(condition.pattern)
210
+ end
211
+
212
+ puts "\nMatching facts: #{matches.size}"
213
+ matches.each do |fact|
214
+ puts " #{fact.attributes.inspect}"
215
+
216
+ # Test predicate if present
217
+ if condition.predicate
218
+ predicate_result = condition.predicate.call(fact)
219
+ puts " Predicate: #{predicate_result}"
220
+ end
221
+ end
222
+
223
+ matches
224
+ end
225
+
226
+ # Usage
227
+ condition = KBS::Condition.new(:sensor, {
228
+ type: "temperature",
229
+ value: :v?
230
+ }, predicate: lambda { |f| f[:value] > 25 })
231
+
232
+ test_condition(engine, condition)
233
+ ```
234
+
235
+ ### Why Did Rule Fire?
236
+
237
+ ```ruby
238
+ def why_rule_fired(engine, rule_name)
239
+ rule = engine.instance_variable_get(:@rules).find { |r| r.name == rule_name }
240
+
241
+ return unless rule
242
+
243
+ puts "\n=== Why '#{rule_name}' Fired ==="
244
+
245
+ # Check each condition
246
+ rule.conditions.each_with_index do |condition, i|
247
+ puts "\nCondition #{i + 1}: #{condition.pattern[:type]}"
248
+ puts " Pattern: #{condition.pattern.inspect}"
249
+ puts " Negated: #{condition.negated?}"
250
+
251
+ matches = engine.facts.select { |f| f.matches?(condition.pattern) }
252
+
253
+ if condition.predicate
254
+ matches = matches.select { |f| condition.predicate.call(f) }
255
+ end
256
+
257
+ puts " Matches: #{matches.size} facts"
258
+ matches.each do |fact|
259
+ puts " - #{fact.attributes.inspect}"
260
+ end
261
+ end
262
+ end
263
+ ```
264
+
265
+ ### Why Didn't Rule Fire?
266
+
267
+ ```ruby
268
+ def why_rule_didnt_fire(engine, rule_name)
269
+ rule = engine.instance_variable_get(:@rules).find { |r| r.name == rule_name }
270
+
271
+ return unless rule
272
+
273
+ puts "\n=== Why '#{rule_name}' Didn't Fire ==="
274
+
275
+ # Check each condition
276
+ failing_condition = nil
277
+
278
+ rule.conditions.each_with_index do |condition, i|
279
+ puts "\nCondition #{i + 1}: #{condition.pattern[:type]}"
280
+
281
+ matches = engine.facts.select { |f| f.matches?(condition.pattern) }
282
+
283
+ if condition.negated?
284
+ puts " Negated condition"
285
+ if matches.empty?
286
+ puts " ✓ PASSED (no matching facts)"
287
+ else
288
+ puts " ✗ FAILED (#{matches.size} matching facts found, but should be absent)"
289
+ failing_condition = i
290
+ matches.each do |fact|
291
+ puts " Blocking fact: #{fact.attributes.inspect}"
292
+ end
293
+ end
294
+ else
295
+ if matches.empty?
296
+ puts " ✗ FAILED (no matching facts)"
297
+ failing_condition = i
298
+
299
+ # Suggest similar facts
300
+ similar = engine.facts.select { |f| f.type == condition.pattern[:type] }
301
+ if similar.any?
302
+ puts " Similar facts (#{similar.size}):"
303
+ similar.first(3).each do |fact|
304
+ puts " - #{fact.attributes.inspect}"
305
+ end
306
+ end
307
+ else
308
+ # Check predicate
309
+ if condition.predicate
310
+ pred_matches = matches.select { |f| condition.predicate.call(f) }
311
+ if pred_matches.empty?
312
+ puts " ✗ FAILED (#{matches.size} facts match pattern, but predicate failed)"
313
+ failing_condition = i
314
+ matches.first(3).each do |fact|
315
+ puts " - #{fact.attributes.inspect} (predicate: false)"
316
+ end
317
+ else
318
+ puts " ✓ PASSED (#{pred_matches.size} facts)"
319
+ end
320
+ else
321
+ puts " ✓ PASSED (#{matches.size} facts)"
322
+ end
323
+ end
324
+ end
325
+
326
+ break if failing_condition
327
+ end
328
+
329
+ if failing_condition
330
+ puts "\n⚠️ Rule failed at condition #{failing_condition + 1}"
331
+ else
332
+ puts "\n✓ All conditions passed (rule should fire on next run)"
333
+ end
334
+ end
335
+
336
+ # Usage
337
+ why_rule_didnt_fire(engine, "detect_high_temperature")
338
+ ```
339
+
340
+ ## Network Visualization
341
+
342
+ ### Print Network Structure
343
+
344
+ ```ruby
345
+ def visualize_network(engine)
346
+ puts "\n=== RETE Network Structure ==="
347
+
348
+ # Alpha network
349
+ puts "\nALPHA NETWORK:"
350
+ alpha_memories = []
351
+
352
+ engine.instance_eval do
353
+ @alpha_network.each do |pattern, memory|
354
+ puts " #{pattern.inspect}"
355
+ puts " Items: #{memory.items.size}"
356
+ alpha_memories << memory
357
+ end
358
+ end
359
+
360
+ # Beta network
361
+ puts "\nBETA NETWORK:"
362
+ # Simplified - actual inspection depends on implementation
363
+
364
+ puts "\nSTATISTICS:"
365
+ puts " Alpha memories: #{alpha_memories.size}"
366
+ puts " Total facts: #{engine.facts.size}"
367
+ puts " Rules: #{engine.instance_variable_get(:@rules).size}"
368
+ end
369
+ ```
370
+
371
+ ### Graphviz Export
372
+
373
+ ```ruby
374
+ def export_to_graphviz(engine, filename = "network.dot")
375
+ File.open(filename, 'w') do |f|
376
+ f.puts "digraph RETE {"
377
+ f.puts " rankdir=TB;"
378
+ f.puts " node [shape=box];"
379
+
380
+ # Alpha nodes
381
+ f.puts "\n // Alpha Network"
382
+ engine.instance_eval do
383
+ @alpha_network.each_with_index do |(pattern, memory), i|
384
+ node_id = "alpha_#{i}"
385
+ label = "#{pattern[:type]}\\n#{memory.items.size} facts"
386
+ f.puts " #{node_id} [label=\"#{label}\", style=filled, fillcolor=lightblue];"
387
+ end
388
+ end
389
+
390
+ # Production nodes
391
+ f.puts "\n // Production Nodes"
392
+ engine.instance_variable_get(:@rules).each_with_index do |rule, i|
393
+ node_id = "rule_#{i}"
394
+ label = "#{rule.name}\\n#{rule.priority}"
395
+ f.puts " #{node_id} [label=\"#{label}\", style=filled, fillcolor=lightgreen];"
396
+ end
397
+
398
+ # Edges (simplified)
399
+ # ...
400
+
401
+ f.puts "}"
402
+ end
403
+
404
+ puts "Network exported to #{filename}"
405
+ puts "Render with: dot -Tpng #{filename} -o network.png"
406
+ end
407
+
408
+ # Usage
409
+ export_to_graphviz(engine)
410
+ ```
411
+
412
+ ## Token Tracing
413
+
414
+ ### Trace Token Propagation
415
+
416
+ ```ruby
417
+ class TokenTracer
418
+ def initialize
419
+ @trace = []
420
+ end
421
+
422
+ def log_activation(node_type, node_id, token)
423
+ @trace << {
424
+ timestamp: Time.now,
425
+ node_type: node_type,
426
+ node_id: node_id,
427
+ token: token.inspect
428
+ }
429
+
430
+ puts "[#{node_type}] #{node_id}: #{token.inspect}"
431
+ end
432
+
433
+ def print_trace
434
+ puts "\n=== Token Trace ==="
435
+ @trace.each_with_index do |entry, i|
436
+ puts "\n#{i + 1}. [#{entry[:node_type]}] #{entry[:node_id]}"
437
+ puts " Time: #{entry[:timestamp]}"
438
+ puts " Token: #{entry[:token]}"
439
+ end
440
+ end
441
+
442
+ attr_reader :trace
443
+ end
444
+
445
+ # Usage: Instrument nodes
446
+ tracer = TokenTracer.new
447
+
448
+ # Wrap alpha activation
449
+ alpha_memory.define_singleton_method(:right_activate) do |fact|
450
+ tracer.log_activation("AlphaMemory", object_id, fact)
451
+ super(fact)
452
+ end
453
+ ```
454
+
455
+ ## Interactive Debugging
456
+
457
+ ### Debug Console
458
+
459
+ ```ruby
460
+ class DebugConsole
461
+ def initialize(engine)
462
+ @engine = engine
463
+ end
464
+
465
+ def start
466
+ loop do
467
+ print "\nkbs> "
468
+ input = gets.chomp
469
+
470
+ break if input == "exit"
471
+
472
+ case input
473
+ when "facts"
474
+ inspect_facts(@engine)
475
+ when "rules"
476
+ list_rules
477
+ when "run"
478
+ @engine.run
479
+ puts "Engine ran successfully"
480
+ when /^add (\w+) (.+)$/
481
+ type = $1.to_sym
482
+ attrs = eval($2) # UNSAFE: eval user input (for demo only)
483
+ @engine.add_fact(type, attrs)
484
+ puts "Fact added"
485
+ when /^remove (\d+)$/
486
+ fact = @engine.facts[$1.to_i]
487
+ @engine.remove_fact(fact) if fact
488
+ puts "Fact removed"
489
+ when /^why (.+)$/
490
+ why_rule_didnt_fire(@engine, $1)
491
+ when "help"
492
+ print_help
493
+ else
494
+ puts "Unknown command: #{input}"
495
+ print_help
496
+ end
497
+ end
498
+ end
499
+
500
+ def list_rules
501
+ puts "\n=== Rules ==="
502
+ @engine.instance_variable_get(:@rules).each_with_index do |rule, i|
503
+ puts "#{i}. #{rule.name} (priority: #{rule.priority}, conditions: #{rule.conditions.size})"
504
+ end
505
+ end
506
+
507
+ def print_help
508
+ puts <<~HELP
509
+
510
+ Commands:
511
+ facts - List all facts
512
+ rules - List all rules
513
+ run - Run engine
514
+ add TYPE {ATTRS} - Add fact
515
+ remove INDEX - Remove fact
516
+ why RULE_NAME - Explain why rule didn't fire
517
+ exit - Exit console
518
+ help - Show this help
519
+
520
+ HELP
521
+ end
522
+ end
523
+
524
+ # Usage
525
+ console = DebugConsole.new(engine)
526
+ console.start
527
+ ```
528
+
529
+ ### Step-Through Debugger
530
+
531
+ ```ruby
532
+ class StepDebugger
533
+ def initialize(engine)
534
+ @engine = engine
535
+ @breakpoints = []
536
+ @step_mode = false
537
+ end
538
+
539
+ def add_breakpoint(rule_name)
540
+ @breakpoints << rule_name
541
+ puts "Breakpoint added: #{rule_name}"
542
+ end
543
+
544
+ def enable_step_mode
545
+ @step_mode = true
546
+
547
+ @engine.instance_variable_get(:@rules).each do |rule|
548
+ wrap_rule_with_breakpoint(rule)
549
+ end
550
+ end
551
+
552
+ def wrap_rule_with_breakpoint(rule)
553
+ original_action = rule.action
554
+
555
+ rule.action = lambda do |facts, bindings|
556
+ if @breakpoints.include?(rule.name) || @step_mode
557
+ puts "\n🔴 BREAKPOINT: #{rule.name}"
558
+ puts "Facts: #{facts.map { |f| { type: f.type, attrs: f.attributes } }}"
559
+ puts "Bindings: #{bindings.inspect}"
560
+
561
+ print "Continue? [y/n/i(nspect)] "
562
+ response = gets.chomp
563
+
564
+ case response
565
+ when 'n'
566
+ puts "Skipping rule"
567
+ return
568
+ when 'i'
569
+ inspect_rule_context(facts, bindings)
570
+ end
571
+ end
572
+
573
+ original_action.call(facts, bindings)
574
+ end
575
+ end
576
+
577
+ def inspect_rule_context(facts, bindings)
578
+ puts "\n=== Rule Context ==="
579
+ puts "Facts (#{facts.size}):"
580
+ facts.each_with_index do |fact, i|
581
+ puts " #{i}. #{fact.type}: #{fact.attributes.inspect}"
582
+ end
583
+
584
+ puts "\nBindings:"
585
+ bindings.each do |var, value|
586
+ puts " #{var} => #{value.inspect}"
587
+ end
588
+
589
+ print "\nPress Enter to continue..."
590
+ gets
591
+ end
592
+ end
593
+
594
+ # Usage
595
+ debugger = StepDebugger.new(engine)
596
+ debugger.add_breakpoint("high_temperature_alert")
597
+ debugger.enable_step_mode
598
+ engine.run
599
+ ```
600
+
601
+ ## Common Debugging Patterns
602
+
603
+ ### Verify Pattern Matching
604
+
605
+ ```ruby
606
+ def verify_pattern_match(fact, pattern)
607
+ puts "\n=== Pattern Match Verification ==="
608
+ puts "Fact: #{fact.attributes.inspect}"
609
+ puts "Pattern: #{pattern.inspect}"
610
+
611
+ result = fact.matches?(pattern)
612
+ puts "Result: #{result}"
613
+
614
+ # Detail each attribute
615
+ pattern.each do |key, expected|
616
+ next if key == :type
617
+
618
+ actual = fact[key]
619
+ match = (expected == actual || expected.is_a?(Symbol))
620
+
621
+ puts "\n #{key}:"
622
+ puts " Expected: #{expected.inspect}"
623
+ puts " Actual: #{actual.inspect}"
624
+ puts " Match: #{match ? '✓' : '✗'}"
625
+ end
626
+
627
+ result
628
+ end
629
+ ```
630
+
631
+ ### Diagnose Join Issues
632
+
633
+ ```ruby
634
+ def diagnose_join(engine, condition1, condition2)
635
+ puts "\n=== Join Diagnosis ==="
636
+
637
+ # Find matches for each condition
638
+ matches1 = engine.facts.select { |f| f.matches?(condition1.pattern) }
639
+ matches2 = engine.facts.select { |f| f.matches?(condition2.pattern) }
640
+
641
+ puts "\nCondition 1 matches: #{matches1.size}"
642
+ matches1.first(3).each { |f| puts " - #{f.attributes.inspect}" }
643
+
644
+ puts "\nCondition 2 matches: #{matches2.size}"
645
+ matches2.first(3).each { |f| puts " - #{f.attributes.inspect}" }
646
+
647
+ # Find join variables
648
+ vars1 = condition1.pattern.values.select { |v| v.is_a?(Symbol) && v.to_s.start_with?('?') }
649
+ vars2 = condition2.pattern.values.select { |v| v.is_a?(Symbol) && v.to_s.start_with?('?') }
650
+ join_vars = vars1 & vars2
651
+
652
+ puts "\nJoin variables: #{join_vars.inspect}"
653
+
654
+ if join_vars.empty?
655
+ puts "⚠️ No shared variables - conditions are independent"
656
+ else
657
+ # Check if any combinations match
658
+ combinations = 0
659
+ matches1.each do |f1|
660
+ matches2.each do |f2|
661
+ # Extract bindings
662
+ bindings1 = extract_bindings(f1, condition1.pattern)
663
+ bindings2 = extract_bindings(f2, condition2.pattern)
664
+
665
+ # Check join
666
+ if join_vars.all? { |v| bindings1[v] == bindings2[v] }
667
+ combinations += 1
668
+ end
669
+ end
670
+ end
671
+
672
+ puts "Valid combinations: #{combinations}"
673
+ end
674
+ end
675
+ ```
676
+
677
+ ### Track Memory Usage
678
+
679
+ ```ruby
680
+ require 'objspace'
681
+
682
+ def track_memory_usage(engine)
683
+ puts "\n=== Memory Usage ==="
684
+
685
+ # Facts
686
+ fact_size = engine.facts.sum { |f| ObjectSpace.memsize_of(f) }
687
+ puts "Facts: #{(fact_size / 1024.0).round(2)} KB (#{engine.facts.size} facts)"
688
+
689
+ # Alpha memories
690
+ alpha_size = 0
691
+ engine.instance_eval do
692
+ @alpha_network.each do |_, memory|
693
+ alpha_size += ObjectSpace.memsize_of(memory)
694
+ alpha_size += memory.items.sum { |f| ObjectSpace.memsize_of(f) }
695
+ end
696
+ end
697
+ puts "Alpha network: #{(alpha_size / 1024.0).round(2)} KB"
698
+
699
+ total = fact_size + alpha_size
700
+ puts "\nTotal: #{(total / 1024.0).round(2)} KB"
701
+ end
702
+ ```
703
+
704
+ ## Debugging Checklist
705
+
706
+ - [ ] Verify facts are added with correct types and attributes
707
+ - [ ] Check condition patterns match fact structure
708
+ - [ ] Test predicates independently
709
+ - [ ] Ensure variables are bound correctly across conditions
710
+ - [ ] Check negated conditions for blocking facts
711
+ - [ ] Verify rule priorities
712
+ - [ ] Inspect network structure
713
+ - [ ] Trace rule execution
714
+ - [ ] Monitor memory usage
715
+ - [ ] Check for infinite loops
716
+
717
+ ## Next Steps
718
+
719
+ - **[Testing Guide](testing.md)** - Write tests to prevent bugs
720
+ - **[Performance Guide](performance.md)** - Debug performance issues
721
+ - **[Architecture](../architecture/index.md)** - Understand network internals
722
+ - **[API Reference](../api/engine.md)** - Engine API documentation
723
+
724
+ ---
725
+
726
+ *Good debugging is about asking the right questions. Use these tools to understand what your rules are doing.*
@@ -0,0 +1,8 @@
1
+ # Advanced Topics
2
+
3
+ techniques and optimizations.
4
+
5
+ - **[Performance Tuning](performance.md)** - Optimize for production
6
+ - **[Custom Persistence](custom-persistence.md)** - Build your own backend
7
+ - **[Debugging](debugging.md)** - Network inspection and tracing
8
+ - **[Testing Rules](testing.md)** - Test strategies for rule-based systems