swarm_memory 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/lib/claude_swarm/base_executor.rb +133 -0
- data/lib/claude_swarm/claude_code_executor.rb +349 -0
- data/lib/claude_swarm/claude_mcp_server.rb +77 -0
- data/lib/claude_swarm/cli.rb +712 -0
- data/lib/claude_swarm/commands/ps.rb +216 -0
- data/lib/claude_swarm/commands/show.rb +139 -0
- data/lib/claude_swarm/configuration.rb +363 -0
- data/lib/claude_swarm/hooks/session_start_hook.rb +42 -0
- data/lib/claude_swarm/json_handler.rb +91 -0
- data/lib/claude_swarm/mcp_generator.rb +248 -0
- data/lib/claude_swarm/openai/chat_completion.rb +264 -0
- data/lib/claude_swarm/openai/executor.rb +254 -0
- data/lib/claude_swarm/openai/responses.rb +338 -0
- data/lib/claude_swarm/orchestrator.rb +879 -0
- data/lib/claude_swarm/process_tracker.rb +78 -0
- data/lib/claude_swarm/session_cost_calculator.rb +209 -0
- data/lib/claude_swarm/session_path.rb +42 -0
- data/lib/claude_swarm/settings_generator.rb +77 -0
- data/lib/claude_swarm/system_utils.rb +46 -0
- data/lib/claude_swarm/templates/generation_prompt.md.erb +230 -0
- data/lib/claude_swarm/tools/reset_session_tool.rb +24 -0
- data/lib/claude_swarm/tools/session_info_tool.rb +24 -0
- data/lib/claude_swarm/tools/task_tool.rb +63 -0
- data/lib/claude_swarm/version.rb +5 -0
- data/lib/claude_swarm/worktree_manager.rb +475 -0
- data/lib/claude_swarm/yaml_loader.rb +22 -0
- data/lib/claude_swarm.rb +69 -0
- data/lib/swarm_cli/cli.rb +201 -0
- data/lib/swarm_cli/command_registry.rb +61 -0
- data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
- data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
- data/lib/swarm_cli/commands/migrate.rb +55 -0
- data/lib/swarm_cli/commands/run.rb +173 -0
- data/lib/swarm_cli/config_loader.rb +97 -0
- data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
- data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
- data/lib/swarm_cli/interactive_repl.rb +918 -0
- data/lib/swarm_cli/mcp_serve_options.rb +44 -0
- data/lib/swarm_cli/mcp_tools_options.rb +59 -0
- data/lib/swarm_cli/migrate_options.rb +54 -0
- data/lib/swarm_cli/migrator.rb +132 -0
- data/lib/swarm_cli/options.rb +151 -0
- data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
- data/lib/swarm_cli/ui/components/content_block.rb +120 -0
- data/lib/swarm_cli/ui/components/divider.rb +57 -0
- data/lib/swarm_cli/ui/components/panel.rb +62 -0
- data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
- data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
- data/lib/swarm_cli/ui/formatters/number.rb +58 -0
- data/lib/swarm_cli/ui/formatters/text.rb +77 -0
- data/lib/swarm_cli/ui/formatters/time.rb +73 -0
- data/lib/swarm_cli/ui/icons.rb +59 -0
- data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
- data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
- data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
- data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
- data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
- data/lib/swarm_cli/version.rb +5 -0
- data/lib/swarm_cli.rb +45 -0
- data/lib/swarm_memory/adapters/base.rb +140 -0
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +789 -0
- data/lib/swarm_memory/chat_extension.rb +34 -0
- data/lib/swarm_memory/cli/commands.rb +306 -0
- data/lib/swarm_memory/core/entry.rb +37 -0
- data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
- data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
- data/lib/swarm_memory/core/path_normalizer.rb +75 -0
- data/lib/swarm_memory/core/semantic_index.rb +244 -0
- data/lib/swarm_memory/core/storage.rb +286 -0
- data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
- data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
- data/lib/swarm_memory/dsl/memory_config.rb +113 -0
- data/lib/swarm_memory/embeddings/embedder.rb +36 -0
- data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
- data/lib/swarm_memory/errors.rb +21 -0
- data/lib/swarm_memory/integration/cli_registration.rb +30 -0
- data/lib/swarm_memory/integration/configuration.rb +43 -0
- data/lib/swarm_memory/integration/registration.rb +31 -0
- data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
- data/lib/swarm_memory/optimization/analyzer.rb +244 -0
- data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
- data/lib/swarm_memory/prompts/memory.md.erb +109 -0
- data/lib/swarm_memory/prompts/memory_assistant.md.erb +139 -0
- data/lib/swarm_memory/prompts/memory_researcher.md.erb +201 -0
- data/lib/swarm_memory/prompts/memory_retrieval.md.erb +76 -0
- data/lib/swarm_memory/search/semantic_search.rb +112 -0
- data/lib/swarm_memory/search/text_search.rb +40 -0
- data/lib/swarm_memory/search/text_similarity.rb +80 -0
- data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
- data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
- data/lib/swarm_memory/tools/load_skill.rb +313 -0
- data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
- data/lib/swarm_memory/tools/memory_delete.rb +99 -0
- data/lib/swarm_memory/tools/memory_edit.rb +185 -0
- data/lib/swarm_memory/tools/memory_glob.rb +145 -0
- data/lib/swarm_memory/tools/memory_grep.rb +209 -0
- data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
- data/lib/swarm_memory/tools/memory_read.rb +123 -0
- data/lib/swarm_memory/tools/memory_write.rb +215 -0
- data/lib/swarm_memory/utils.rb +50 -0
- data/lib/swarm_memory/version.rb +5 -0
- data/lib/swarm_memory.rb +166 -0
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
- data/lib/swarm_sdk/agent/builder.rb +461 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
- data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
- data/lib/swarm_sdk/agent/chat.rb +1144 -0
- data/lib/swarm_sdk/agent/context.rb +112 -0
- data/lib/swarm_sdk/agent/context_manager.rb +309 -0
- data/lib/swarm_sdk/agent/definition.rb +556 -0
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
- data/lib/swarm_sdk/configuration.rb +296 -0
- data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
- data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
- data/lib/swarm_sdk/context_compactor.rb +340 -0
- data/lib/swarm_sdk/hooks/adapter.rb +359 -0
- data/lib/swarm_sdk/hooks/context.rb +197 -0
- data/lib/swarm_sdk/hooks/definition.rb +80 -0
- data/lib/swarm_sdk/hooks/error.rb +29 -0
- data/lib/swarm_sdk/hooks/executor.rb +146 -0
- data/lib/swarm_sdk/hooks/registry.rb +147 -0
- data/lib/swarm_sdk/hooks/result.rb +150 -0
- data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
- data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
- data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
- data/lib/swarm_sdk/log_collector.rb +51 -0
- data/lib/swarm_sdk/log_stream.rb +69 -0
- data/lib/swarm_sdk/markdown_parser.rb +75 -0
- data/lib/swarm_sdk/model_aliases.json +5 -0
- data/lib/swarm_sdk/models.json +1 -0
- data/lib/swarm_sdk/models.rb +120 -0
- data/lib/swarm_sdk/node/agent_config.rb +49 -0
- data/lib/swarm_sdk/node/builder.rb +439 -0
- data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
- data/lib/swarm_sdk/node_context.rb +170 -0
- data/lib/swarm_sdk/node_orchestrator.rb +384 -0
- data/lib/swarm_sdk/permissions/config.rb +239 -0
- data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
- data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
- data/lib/swarm_sdk/permissions/validator.rb +173 -0
- data/lib/swarm_sdk/permissions_builder.rb +122 -0
- data/lib/swarm_sdk/plugin.rb +147 -0
- data/lib/swarm_sdk/plugin_registry.rb +101 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
- data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
- data/lib/swarm_sdk/result.rb +97 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
- data/lib/swarm_sdk/swarm/builder.rb +586 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +416 -0
- data/lib/swarm_sdk/swarm.rb +982 -0
- data/lib/swarm_sdk/tools/bash.rb +274 -0
- data/lib/swarm_sdk/tools/clock.rb +44 -0
- data/lib/swarm_sdk/tools/delegate.rb +164 -0
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
- data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
- data/lib/swarm_sdk/tools/edit.rb +150 -0
- data/lib/swarm_sdk/tools/glob.rb +158 -0
- data/lib/swarm_sdk/tools/grep.rb +228 -0
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
- data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
- data/lib/swarm_sdk/tools/read.rb +251 -0
- data/lib/swarm_sdk/tools/registry.rb +93 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
- data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
- data/lib/swarm_sdk/tools/think.rb +95 -0
- data/lib/swarm_sdk/tools/todo_write.rb +216 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
- data/lib/swarm_sdk/tools/write.rb +117 -0
- data/lib/swarm_sdk/utils.rb +50 -0
- data/lib/swarm_sdk/version.rb +5 -0
- data/lib/swarm_sdk.rb +167 -0
- metadata +313 -0
|
@@ -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
|