claude_swarm 1.0.1 → 1.0.4

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 (267) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/release.md +1 -1
  3. data/.claude/hooks/lint-code-files.rb +65 -0
  4. data/.rubocop.yml +22 -2
  5. data/CHANGELOG.md +14 -1
  6. data/CLAUDE.md +1 -1
  7. data/CONTRIBUTING.md +69 -0
  8. data/README.md +27 -2
  9. data/Rakefile +71 -3
  10. data/analyze_coverage.rb +94 -0
  11. data/docs/v2/CHANGELOG.swarm_cli.md +43 -0
  12. data/docs/v2/CHANGELOG.swarm_memory.md +379 -0
  13. data/docs/v2/CHANGELOG.swarm_sdk.md +362 -0
  14. data/docs/v2/README.md +308 -0
  15. data/docs/v2/guides/claude-code-agents.md +262 -0
  16. data/docs/v2/guides/complete-tutorial.md +3088 -0
  17. data/docs/v2/guides/getting-started.md +1456 -0
  18. data/docs/v2/guides/memory-adapters.md +998 -0
  19. data/docs/v2/guides/plugins.md +816 -0
  20. data/docs/v2/guides/quick-start-cli.md +1745 -0
  21. data/docs/v2/guides/rails-integration.md +1902 -0
  22. data/docs/v2/guides/swarm-memory.md +599 -0
  23. data/docs/v2/reference/cli.md +729 -0
  24. data/docs/v2/reference/ruby-dsl.md +2154 -0
  25. data/docs/v2/reference/yaml.md +1835 -0
  26. data/docs-team-swarm.yml +2222 -0
  27. data/examples/learning-assistant/assistant.md +7 -0
  28. data/examples/learning-assistant/example-memories/concept-example.md +90 -0
  29. data/examples/learning-assistant/example-memories/experience-example.md +66 -0
  30. data/examples/learning-assistant/example-memories/fact-example.md +76 -0
  31. data/examples/learning-assistant/example-memories/memory-index.md +78 -0
  32. data/examples/learning-assistant/example-memories/skill-example.md +168 -0
  33. data/examples/learning-assistant/learning_assistant.rb +34 -0
  34. data/examples/learning-assistant/learning_assistant.yml +20 -0
  35. data/examples/v2/dsl/01_basic.rb +44 -0
  36. data/examples/v2/dsl/02_core_parameters.rb +59 -0
  37. data/examples/v2/dsl/03_capabilities.rb +71 -0
  38. data/examples/v2/dsl/04_llm_parameters.rb +56 -0
  39. data/examples/v2/dsl/05_advanced_flags.rb +73 -0
  40. data/examples/v2/dsl/06_permissions.rb +80 -0
  41. data/examples/v2/dsl/07_mcp_server.rb +62 -0
  42. data/examples/v2/dsl/08_swarm_hooks.rb +53 -0
  43. data/examples/v2/dsl/09_agent_hooks.rb +67 -0
  44. data/examples/v2/dsl/10_all_agents_hooks.rb +67 -0
  45. data/examples/v2/dsl/11_delegation.rb +60 -0
  46. data/examples/v2/dsl/12_complete_integration.rb +137 -0
  47. data/examples/v2/file_tools_swarm.yml +102 -0
  48. data/examples/v2/hooks/01_basic_hooks.rb +133 -0
  49. data/examples/v2/hooks/02_usage_tracking.rb +201 -0
  50. data/examples/v2/hooks/03_production_monitoring.rb +429 -0
  51. data/examples/v2/hooks/agent_stop_exit_0.yml +21 -0
  52. data/examples/v2/hooks/agent_stop_exit_1.yml +21 -0
  53. data/examples/v2/hooks/agent_stop_exit_2.yml +26 -0
  54. data/examples/v2/hooks/multiple_hooks_all_pass.yml +37 -0
  55. data/examples/v2/hooks/multiple_hooks_first_fails.yml +37 -0
  56. data/examples/v2/hooks/multiple_hooks_second_fails.yml +37 -0
  57. data/examples/v2/hooks/multiple_hooks_warnings.yml +37 -0
  58. data/examples/v2/hooks/post_tool_use_exit_0.yml +24 -0
  59. data/examples/v2/hooks/post_tool_use_exit_1.yml +24 -0
  60. data/examples/v2/hooks/post_tool_use_exit_2.yml +24 -0
  61. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_0.yml +26 -0
  62. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_1.yml +26 -0
  63. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_2.yml +26 -0
  64. data/examples/v2/hooks/pre_tool_use_exit_0.yml +24 -0
  65. data/examples/v2/hooks/pre_tool_use_exit_1.yml +24 -0
  66. data/examples/v2/hooks/pre_tool_use_exit_2.yml +24 -0
  67. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_0.yml +26 -0
  68. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_1.yml +26 -0
  69. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_2.yml +27 -0
  70. data/examples/v2/hooks/swarm_summary.sh +44 -0
  71. data/examples/v2/hooks/user_prompt_exit_0.yml +21 -0
  72. data/examples/v2/hooks/user_prompt_exit_1.yml +21 -0
  73. data/examples/v2/hooks/user_prompt_exit_2.yml +21 -0
  74. data/examples/v2/hooks/validate_bash.rb +59 -0
  75. data/examples/v2/multi_directory_permissions.yml +221 -0
  76. data/examples/v2/node_context_demo.rb +127 -0
  77. data/examples/v2/node_workflow.rb +173 -0
  78. data/examples/v2/path_resolution_demo.rb +216 -0
  79. data/examples/v2/simple-swarm-v2.rb +90 -0
  80. data/examples/v2/simple-swarm-v2.yml +62 -0
  81. data/examples/v2/swarm.yml +71 -0
  82. data/examples/v2/swarm_with_hooks.yml +61 -0
  83. data/examples/v2/swarm_with_hooks_simple.yml +25 -0
  84. data/examples/v2/think_tool_demo.rb +62 -0
  85. data/exe/swarm +6 -0
  86. data/lib/claude_swarm/claude_mcp_server.rb +0 -6
  87. data/lib/claude_swarm/cli.rb +10 -3
  88. data/lib/claude_swarm/commands/ps.rb +19 -20
  89. data/lib/claude_swarm/commands/show.rb +1 -1
  90. data/lib/claude_swarm/configuration.rb +10 -12
  91. data/lib/claude_swarm/mcp_generator.rb +10 -1
  92. data/lib/claude_swarm/orchestrator.rb +73 -49
  93. data/lib/claude_swarm/system_utils.rb +37 -11
  94. data/lib/claude_swarm/version.rb +1 -1
  95. data/lib/claude_swarm/worktree_manager.rb +1 -0
  96. data/lib/claude_swarm/yaml_loader.rb +22 -0
  97. data/lib/claude_swarm.rb +7 -2
  98. data/lib/swarm_cli/cli.rb +201 -0
  99. data/lib/swarm_cli/command_registry.rb +61 -0
  100. data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
  101. data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
  102. data/lib/swarm_cli/commands/migrate.rb +55 -0
  103. data/lib/swarm_cli/commands/run.rb +173 -0
  104. data/lib/swarm_cli/config_loader.rb +97 -0
  105. data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
  106. data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
  107. data/lib/swarm_cli/interactive_repl.rb +918 -0
  108. data/lib/swarm_cli/mcp_serve_options.rb +44 -0
  109. data/lib/swarm_cli/mcp_tools_options.rb +59 -0
  110. data/lib/swarm_cli/migrate_options.rb +54 -0
  111. data/lib/swarm_cli/migrator.rb +132 -0
  112. data/lib/swarm_cli/options.rb +151 -0
  113. data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
  114. data/lib/swarm_cli/ui/components/content_block.rb +120 -0
  115. data/lib/swarm_cli/ui/components/divider.rb +57 -0
  116. data/lib/swarm_cli/ui/components/panel.rb +62 -0
  117. data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
  118. data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
  119. data/lib/swarm_cli/ui/formatters/number.rb +58 -0
  120. data/lib/swarm_cli/ui/formatters/text.rb +77 -0
  121. data/lib/swarm_cli/ui/formatters/time.rb +73 -0
  122. data/lib/swarm_cli/ui/icons.rb +59 -0
  123. data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
  124. data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
  125. data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
  126. data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
  127. data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
  128. data/lib/swarm_cli/version.rb +5 -0
  129. data/lib/swarm_cli.rb +44 -0
  130. data/lib/swarm_memory/adapters/base.rb +141 -0
  131. data/lib/swarm_memory/adapters/filesystem_adapter.rb +845 -0
  132. data/lib/swarm_memory/chat_extension.rb +34 -0
  133. data/lib/swarm_memory/cli/commands.rb +306 -0
  134. data/lib/swarm_memory/core/entry.rb +37 -0
  135. data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
  136. data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
  137. data/lib/swarm_memory/core/path_normalizer.rb +75 -0
  138. data/lib/swarm_memory/core/semantic_index.rb +244 -0
  139. data/lib/swarm_memory/core/storage.rb +288 -0
  140. data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
  141. data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
  142. data/lib/swarm_memory/dsl/memory_config.rb +113 -0
  143. data/lib/swarm_memory/embeddings/embedder.rb +36 -0
  144. data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
  145. data/lib/swarm_memory/errors.rb +21 -0
  146. data/lib/swarm_memory/integration/cli_registration.rb +30 -0
  147. data/lib/swarm_memory/integration/configuration.rb +43 -0
  148. data/lib/swarm_memory/integration/registration.rb +31 -0
  149. data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
  150. data/lib/swarm_memory/optimization/analyzer.rb +244 -0
  151. data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
  152. data/lib/swarm_memory/prompts/memory.md.erb +109 -0
  153. data/lib/swarm_memory/prompts/memory_assistant.md.erb +181 -0
  154. data/lib/swarm_memory/prompts/memory_researcher.md.erb +281 -0
  155. data/lib/swarm_memory/prompts/memory_retrieval.md.erb +78 -0
  156. data/lib/swarm_memory/search/semantic_search.rb +112 -0
  157. data/lib/swarm_memory/search/text_search.rb +42 -0
  158. data/lib/swarm_memory/search/text_similarity.rb +80 -0
  159. data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
  160. data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
  161. data/lib/swarm_memory/tools/load_skill.rb +313 -0
  162. data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
  163. data/lib/swarm_memory/tools/memory_delete.rb +99 -0
  164. data/lib/swarm_memory/tools/memory_edit.rb +185 -0
  165. data/lib/swarm_memory/tools/memory_glob.rb +160 -0
  166. data/lib/swarm_memory/tools/memory_grep.rb +247 -0
  167. data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
  168. data/lib/swarm_memory/tools/memory_read.rb +123 -0
  169. data/lib/swarm_memory/tools/memory_write.rb +231 -0
  170. data/lib/swarm_memory/utils.rb +50 -0
  171. data/lib/swarm_memory/version.rb +5 -0
  172. data/lib/swarm_memory.rb +166 -0
  173. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
  174. data/lib/swarm_sdk/agent/builder.rb +461 -0
  175. data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
  176. data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
  177. data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
  178. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
  179. data/lib/swarm_sdk/agent/chat.rb +1159 -0
  180. data/lib/swarm_sdk/agent/context.rb +112 -0
  181. data/lib/swarm_sdk/agent/context_manager.rb +309 -0
  182. data/lib/swarm_sdk/agent/definition.rb +556 -0
  183. data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
  184. data/lib/swarm_sdk/configuration.rb +296 -0
  185. data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
  186. data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
  187. data/lib/swarm_sdk/context_compactor.rb +340 -0
  188. data/lib/swarm_sdk/hooks/adapter.rb +359 -0
  189. data/lib/swarm_sdk/hooks/context.rb +197 -0
  190. data/lib/swarm_sdk/hooks/definition.rb +80 -0
  191. data/lib/swarm_sdk/hooks/error.rb +29 -0
  192. data/lib/swarm_sdk/hooks/executor.rb +146 -0
  193. data/lib/swarm_sdk/hooks/registry.rb +147 -0
  194. data/lib/swarm_sdk/hooks/result.rb +150 -0
  195. data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
  196. data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
  197. data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
  198. data/lib/swarm_sdk/log_collector.rb +51 -0
  199. data/lib/swarm_sdk/log_stream.rb +69 -0
  200. data/lib/swarm_sdk/markdown_parser.rb +75 -0
  201. data/lib/swarm_sdk/model_aliases.json +5 -0
  202. data/lib/swarm_sdk/models.json +1 -0
  203. data/lib/swarm_sdk/models.rb +120 -0
  204. data/lib/swarm_sdk/node/agent_config.rb +49 -0
  205. data/lib/swarm_sdk/node/builder.rb +439 -0
  206. data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
  207. data/lib/swarm_sdk/node_context.rb +170 -0
  208. data/lib/swarm_sdk/node_orchestrator.rb +384 -0
  209. data/lib/swarm_sdk/permissions/config.rb +239 -0
  210. data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
  211. data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
  212. data/lib/swarm_sdk/permissions/validator.rb +173 -0
  213. data/lib/swarm_sdk/permissions_builder.rb +122 -0
  214. data/lib/swarm_sdk/plugin.rb +147 -0
  215. data/lib/swarm_sdk/plugin_registry.rb +101 -0
  216. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
  217. data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
  218. data/lib/swarm_sdk/result.rb +97 -0
  219. data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
  220. data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
  221. data/lib/swarm_sdk/swarm/builder.rb +586 -0
  222. data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
  223. data/lib/swarm_sdk/swarm/tool_configurator.rb +419 -0
  224. data/lib/swarm_sdk/swarm.rb +982 -0
  225. data/lib/swarm_sdk/tools/bash.rb +274 -0
  226. data/lib/swarm_sdk/tools/clock.rb +44 -0
  227. data/lib/swarm_sdk/tools/delegate.rb +164 -0
  228. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
  229. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
  230. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
  231. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
  232. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
  233. data/lib/swarm_sdk/tools/edit.rb +150 -0
  234. data/lib/swarm_sdk/tools/glob.rb +158 -0
  235. data/lib/swarm_sdk/tools/grep.rb +228 -0
  236. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
  237. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
  238. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
  239. data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
  240. data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
  241. data/lib/swarm_sdk/tools/read.rb +251 -0
  242. data/lib/swarm_sdk/tools/registry.rb +93 -0
  243. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
  244. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
  245. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
  246. data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
  247. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
  248. data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
  249. data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
  250. data/lib/swarm_sdk/tools/think.rb +95 -0
  251. data/lib/swarm_sdk/tools/todo_write.rb +216 -0
  252. data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
  253. data/lib/swarm_sdk/tools/write.rb +117 -0
  254. data/lib/swarm_sdk/utils.rb +50 -0
  255. data/lib/swarm_sdk/version.rb +5 -0
  256. data/lib/swarm_sdk.rb +157 -0
  257. data/llm.v2.txt +13407 -0
  258. data/rubocop/cop/security/no_reflection_methods.rb +47 -0
  259. data/rubocop/cop/security/no_ruby_llm_logger.rb +32 -0
  260. data/swarm_cli.gemspec +57 -0
  261. data/swarm_memory.gemspec +28 -0
  262. data/swarm_sdk.gemspec +41 -0
  263. data/team.yml +1 -1
  264. data/team_full.yml +1875 -0
  265. data/{team_v2.yml → team_sdk.yml} +121 -52
  266. metadata +249 -6
  267. data/EXAMPLES.md +0 -164
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ module Optimization
5
+ # Analyzes memory health and generates statistics
6
+ #
7
+ # Provides insights about memory state, quality, and organization.
8
+ class Analyzer
9
+ # Initialize analyzer
10
+ #
11
+ # @param adapter [Adapters::Base] Storage adapter
12
+ def initialize(adapter:)
13
+ @adapter = adapter
14
+ end
15
+
16
+ # Analyze overall memory health
17
+ #
18
+ # @return [Hash] Comprehensive statistics and health metrics
19
+ #
20
+ # @example
21
+ # stats = analyzer.analyze
22
+ # stats[:health_score] # => 75
23
+ # stats[:total_entries] # => 42
24
+ def analyze
25
+ # List entries (handle errors gracefully)
26
+ entries = @adapter.list
27
+ return empty_analysis if entries.empty?
28
+
29
+ stats = {
30
+ total_entries: entries.size,
31
+ total_size: @adapter.total_size,
32
+ by_type: Hash.new(0),
33
+ by_confidence: Hash.new(0),
34
+ with_frontmatter: 0,
35
+ with_embeddings: 0,
36
+ with_tags: 0,
37
+ with_links: 0,
38
+ quality_scores: [],
39
+ }
40
+
41
+ # Analyze each entry
42
+ entries.each do |entry_info|
43
+ analyze_entry(entry_info[:path], stats)
44
+ end
45
+
46
+ # Calculate averages
47
+ stats[:average_quality] = if stats[:quality_scores].empty?
48
+ 0
49
+ else
50
+ stats[:quality_scores].sum / stats[:quality_scores].size
51
+ end
52
+
53
+ # Calculate health score
54
+ stats[:health_score] = calculate_health_score(stats)
55
+
56
+ stats.except(:quality_scores) # Remove internal array
57
+ end
58
+
59
+ # Generate formatted health report
60
+ #
61
+ # @return [String] Human-readable health report
62
+ def health_report
63
+ stats = analyze
64
+ format_health_report(stats)
65
+ end
66
+
67
+ private
68
+
69
+ def empty_analysis
70
+ {
71
+ total_entries: 0,
72
+ total_size: 0,
73
+ by_type: {},
74
+ by_confidence: {},
75
+ with_frontmatter: 0,
76
+ with_embeddings: 0,
77
+ with_tags: 0,
78
+ with_links: 0,
79
+ average_quality: 0,
80
+ health_score: 0,
81
+ message: "Memory is empty. No entries to analyze.",
82
+ }
83
+ end
84
+
85
+ def analyze_entry(path, stats)
86
+ entry = @adapter.read_entry(file_path: path)
87
+
88
+ # Get metadata from entry (always has string keys from .yml file)
89
+ metadata = entry.metadata || {}
90
+
91
+ # All keys are strings (guaranteed by FilesystemAdapter stringification)
92
+ type = metadata["type"]
93
+ confidence = metadata["confidence"]
94
+ tags = metadata["tags"] || []
95
+ related = metadata["related"] || []
96
+
97
+ # Count by type
98
+ if type
99
+ stats[:by_type][type] += 1
100
+ stats[:with_frontmatter] += 1 # Has metadata
101
+ end
102
+
103
+ # Count by confidence
104
+ stats[:by_confidence][confidence] += 1 if confidence
105
+
106
+ # Count embeddings
107
+ stats[:with_embeddings] += 1 if entry.embedded?
108
+
109
+ # Count tags and links
110
+ stats[:with_tags] += 1 unless tags.empty?
111
+ stats[:with_links] += 1 unless related.empty?
112
+
113
+ # Track quality scores (calculate from metadata, not content)
114
+ quality = calculate_quality_from_metadata(metadata)
115
+ stats[:quality_scores] << quality
116
+ end
117
+
118
+ def calculate_health_score(stats)
119
+ total = stats[:total_entries]
120
+ return 0 if total.zero?
121
+
122
+ score = 0
123
+
124
+ # Frontmatter coverage (30 points)
125
+ frontmatter_pct = (stats[:with_frontmatter].to_f / total * 100).round
126
+ score += 30 if frontmatter_pct > 80
127
+ score += 20 if frontmatter_pct > 50 && frontmatter_pct <= 80
128
+ score += 10 if frontmatter_pct > 20 && frontmatter_pct <= 50
129
+
130
+ # Tags coverage (20 points)
131
+ tags_pct = (stats[:with_tags].to_f / total * 100).round
132
+ score += 20 if tags_pct > 60
133
+ score += 10 if tags_pct > 30 && tags_pct <= 60
134
+
135
+ # Links coverage (20 points)
136
+ links_pct = (stats[:with_links].to_f / total * 100).round
137
+ score += 20 if links_pct > 40
138
+ score += 10 if links_pct > 20 && links_pct <= 40
139
+
140
+ # Embedding coverage (15 points)
141
+ embedding_pct = (stats[:with_embeddings].to_f / total * 100).round
142
+ score += 15 if embedding_pct > 80
143
+ score += 8 if embedding_pct > 50 && embedding_pct <= 80
144
+
145
+ # High confidence ratio (15 points)
146
+ high_confidence = stats[:by_confidence]["high"] || 0
147
+ high_confidence_pct = (high_confidence.to_f / total * 100).round
148
+ score += 15 if high_confidence_pct > 50
149
+ score += 8 if high_confidence_pct > 25 && high_confidence_pct <= 50
150
+
151
+ score
152
+ end
153
+
154
+ def format_health_report(stats)
155
+ report = []
156
+ report << "# Memory Health Report"
157
+ report << ""
158
+ report << "## Overview"
159
+ report << "- Total entries: #{stats[:total_entries]}"
160
+ report << "- Total size: #{format_bytes(stats[:total_size])}"
161
+ report << "- Entries with frontmatter: #{stats[:with_frontmatter]} (#{percentage(stats[:with_frontmatter], stats[:total_entries])}%)"
162
+ report << "- Entries with embeddings: #{stats[:with_embeddings]} (#{percentage(stats[:with_embeddings], stats[:total_entries])}%)"
163
+ report << "- Entries with tags: #{stats[:with_tags]} (#{percentage(stats[:with_tags], stats[:total_entries])}%)"
164
+ report << "- Entries with related links: #{stats[:with_links]} (#{percentage(stats[:with_links], stats[:total_entries])}%)"
165
+ report << "- Average quality score: #{stats[:average_quality]}/100"
166
+ report << ""
167
+
168
+ unless stats[:by_type].empty?
169
+ report << "## By Type"
170
+ stats[:by_type].sort_by { |_, count| -count }.each do |type, count|
171
+ report << "- #{type}: #{count} (#{percentage(count, stats[:total_entries])}%)"
172
+ end
173
+ report << ""
174
+ end
175
+
176
+ unless stats[:by_confidence].empty?
177
+ report << "## By Confidence"
178
+ confidence_order = { "high" => 0, "medium" => 1, "low" => 2 }
179
+ stats[:by_confidence].sort_by { |k, _| confidence_order[k] || 999 }.each do |conf, count|
180
+ report << "- #{conf}: #{count} (#{percentage(count, stats[:total_entries])}%)"
181
+ end
182
+ report << ""
183
+ end
184
+
185
+ report << "## Health Score: #{stats[:health_score]}/100"
186
+ report << health_score_interpretation(stats[:health_score])
187
+
188
+ report.join("\n")
189
+ end
190
+
191
+ def percentage(part, total)
192
+ return 0 if total.zero?
193
+
194
+ ((part.to_f / total) * 100).round
195
+ end
196
+
197
+ def health_score_interpretation(score)
198
+ case score
199
+ when 80..100
200
+ "Excellent - Memory is well-organized and high-quality"
201
+ when 60..79
202
+ "Good - Memory is decent but could use some improvements"
203
+ when 40..59
204
+ "Fair - Consider running defrag to improve organization"
205
+ when 20..39
206
+ "Poor - Memory needs significant cleanup and reorganization"
207
+ else
208
+ "Critical - Memory is poorly organized and needs immediate attention"
209
+ end
210
+ end
211
+
212
+ def format_bytes(bytes)
213
+ if bytes >= 1_000_000
214
+ "#{(bytes.to_f / 1_000_000).round(1)}MB"
215
+ elsif bytes >= 1_000
216
+ "#{(bytes.to_f / 1_000).round(1)}KB"
217
+ else
218
+ "#{bytes}B"
219
+ end
220
+ end
221
+
222
+ # Calculate quality score from metadata (not content parsing)
223
+ #
224
+ # @param metadata [Hash] Metadata hash from .yml file (string keys)
225
+ # @return [Integer] Quality score 0-100
226
+ def calculate_quality_from_metadata(metadata)
227
+ return 0 if metadata.nil? || metadata.empty?
228
+
229
+ score = 0
230
+
231
+ # All keys are strings (no defensive || checks needed)
232
+ score += 20 if metadata["type"]
233
+ score += 20 if metadata["confidence"]
234
+ score += 15 unless (metadata["tags"] || []).empty?
235
+ score += 15 unless (metadata["related"] || []).empty?
236
+ score += 10 if metadata["domain"]
237
+ score += 10 if metadata["last_verified"]
238
+ score += 10 if metadata["confidence"] == "high"
239
+
240
+ score
241
+ end
242
+ end
243
+ end
244
+ end