claude_swarm 1.0.0 → 1.0.2

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 +21 -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 -3
  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 +247 -4
  267. data/EXAMPLES.md +0 -164
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ # Extension module for SwarmSDK::Agent::Chat
5
+ #
6
+ # Adds individual tool removal capability needed for:
7
+ # 1. Mode-based tool filtering (retrieval/interactive/researcher)
8
+ # 2. LoadSkill's fine-grained tool swapping
9
+ #
10
+ # This is injected into SwarmSDK::Agent::Chat when SwarmMemory is loaded.
11
+ module ChatExtension
12
+ # Remove a specific tool by name
13
+ #
14
+ # Used by SwarmMemory to filter tools based on memory mode.
15
+ # Unlike remove_mutable_tools (which removes ALL mutable tools),
16
+ # this removes a single tool by name.
17
+ #
18
+ # @param tool_name [String, Symbol] Tool name to remove
19
+ # @return [void]
20
+ def remove_tool(tool_name)
21
+ tool_sym = tool_name.to_sym
22
+ tool_str = tool_name.to_s
23
+
24
+ # Remove from @tools hash (tools are keyed by symbol)
25
+ @tools.delete(tool_sym)
26
+ @tools.delete(tool_str)
27
+ end
28
+ end
29
+ end
30
+
31
+ # Inject into SwarmSDK when both gems are loaded
32
+ if defined?(SwarmSDK::Agent::Chat)
33
+ SwarmSDK::Agent::Chat.include(SwarmMemory::ChatExtension)
34
+ end
@@ -0,0 +1,306 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ module CLI
5
+ # CLI commands for managing SwarmMemory embeddings
6
+ #
7
+ # Registers with SwarmCLI to provide:
8
+ # swarm memory setup - Download embedding model
9
+ # swarm memory status - Check model cache status
10
+ # swarm memory model-path - Show cache location
11
+ # swarm memory defrag - Optimize memory storage
12
+ # swarm memory rebuild - Rebuild all embeddings
13
+ class Commands
14
+ class << self
15
+ # Execute memory command (called by SwarmCLI)
16
+ #
17
+ # @param args [Array<String>] Command arguments (e.g., ["defrag", ".swarm/memory"])
18
+ # @return [void]
19
+ def execute(args)
20
+ subcommand = args.first
21
+ subcommand_args = args[1..] # Remaining args after subcommand
22
+
23
+ case subcommand
24
+ when "setup"
25
+ setup_embeddings
26
+ when "status"
27
+ show_status
28
+ when "model-path"
29
+ show_model_path
30
+ when "defrag"
31
+ defrag_memory(subcommand_args)
32
+ when "rebuild"
33
+ rebuild_embeddings(subcommand_args)
34
+ else
35
+ show_help
36
+ exit(1)
37
+ end
38
+ rescue StandardError => e
39
+ $stderr.puts "Error: #{e.message}"
40
+ exit(1)
41
+ end
42
+
43
+ private
44
+
45
+ def setup_embeddings
46
+ puts "Setting up SwarmMemory embeddings..."
47
+ puts
48
+
49
+ begin
50
+ embedder = SwarmMemory::Embeddings::InformersEmbedder.new
51
+ rescue StandardError => e
52
+ $stderr.puts "Error: #{e.message}"
53
+ $stderr.puts
54
+ $stderr.puts "Make sure the 'informers' gem is installed:"
55
+ $stderr.puts " gem install informers"
56
+ exit(1)
57
+ end
58
+
59
+ model_name = ENV["SWARM_MEMORY_EMBEDDING_MODEL"] || "sentence-transformers/multi-qa-MiniLM-L6-cos-v1"
60
+
61
+ if embedder.cached?
62
+ puts "✓ Model already cached!"
63
+ puts " Model: #{model_name}"
64
+ puts " Location: #{Informers.cache_dir}/#{model_name}/"
65
+ puts
66
+ puts "No download needed. Embeddings ready to use."
67
+ else
68
+ puts "Model not cached. Downloading..."
69
+ puts " Model: #{model_name}"
70
+ puts " Size: ~90MB (unquantized ONNX)"
71
+ puts " Location: #{Informers.cache_dir}"
72
+ puts
73
+ puts "This is a one-time download. Please wait..."
74
+ puts
75
+
76
+ embedder.preload!
77
+
78
+ puts
79
+ puts "✓ Setup complete!"
80
+ puts " Model: #{model_name}"
81
+ puts " Model cached and ready to use."
82
+ puts " Semantic search is now available."
83
+ end
84
+
85
+ exit(0)
86
+ end
87
+
88
+ def show_status
89
+ begin
90
+ embedder = SwarmMemory::Embeddings::InformersEmbedder.new
91
+ rescue StandardError => e
92
+ $stderr.puts "Error: #{e.message}"
93
+ $stderr.puts
94
+ $stderr.puts "Make sure the 'informers' gem is installed:"
95
+ $stderr.puts " gem install informers"
96
+ exit(1)
97
+ end
98
+
99
+ model_name = ENV["SWARM_MEMORY_EMBEDDING_MODEL"] || "sentence-transformers/multi-qa-MiniLM-L6-cos-v1"
100
+
101
+ puts "SwarmMemory Embedding Status"
102
+ puts "=" * 50
103
+ puts
104
+
105
+ if embedder.cached?
106
+ puts "Status: ✓ Model cached"
107
+ puts "Model: #{model_name}"
108
+ puts "Dimensions: #{embedder.dimensions}"
109
+ puts "Cache: #{Informers.cache_dir}"
110
+ puts
111
+ puts "Semantic search is available for memory defragmentation."
112
+ else
113
+ puts "Status: ✗ Model not cached"
114
+ puts "Model: #{model_name}"
115
+ puts "Dimensions: #{embedder.dimensions}"
116
+ puts
117
+ puts "Run 'swarm memory setup' to download the model."
118
+ puts "Or it will download automatically on first use."
119
+ end
120
+
121
+ exit(0)
122
+ end
123
+
124
+ def show_model_path
125
+ puts Informers.cache_dir
126
+ exit(0)
127
+ end
128
+
129
+ def defrag_memory(args)
130
+ # Expect directory path as argument
131
+ directory = args&.first
132
+
133
+ unless directory && !directory.empty?
134
+ $stderr.puts "Error: Memory directory path required"
135
+ $stderr.puts
136
+ $stderr.puts "Usage: swarm memory defrag DIRECTORY"
137
+ $stderr.puts
138
+ $stderr.puts "Example:"
139
+ $stderr.puts " swarm memory defrag .swarm/assistant-memory"
140
+ exit(1)
141
+ end
142
+
143
+ unless Dir.exist?(directory)
144
+ $stderr.puts "Error: Directory not found: #{directory}"
145
+ exit(1)
146
+ end
147
+
148
+ puts "Defragmenting memory at: #{directory}"
149
+ puts "=" * 70
150
+ puts
151
+
152
+ # Create storage
153
+ adapter = SwarmMemory::Adapters::FilesystemAdapter.new(directory: directory)
154
+ storage = SwarmMemory::Core::Storage.new(adapter: adapter)
155
+
156
+ # Create defrag tool
157
+ defrag = SwarmMemory::Tools::MemoryDefrag.new(storage: storage)
158
+
159
+ # Run full analysis
160
+ puts "Running full defrag analysis..."
161
+ puts
162
+ result = defrag.execute(action: "full", dry_run: false)
163
+ puts result
164
+
165
+ exit(0)
166
+ end
167
+
168
+ def rebuild_embeddings(args)
169
+ # Expect directory path as argument
170
+ directory = args&.first
171
+
172
+ unless directory && !directory.empty?
173
+ $stderr.puts "Error: Memory directory path required"
174
+ $stderr.puts
175
+ $stderr.puts "Usage: swarm memory rebuild DIRECTORY"
176
+ $stderr.puts
177
+ $stderr.puts "Example:"
178
+ $stderr.puts " swarm memory rebuild .swarm/assistant-memory"
179
+ exit(1)
180
+ end
181
+
182
+ unless Dir.exist?(directory)
183
+ $stderr.puts "Error: Directory not found: #{directory}"
184
+ exit(1)
185
+ end
186
+
187
+ puts "Rebuilding embeddings for memory at: #{directory}"
188
+ puts "=" * 70
189
+ puts
190
+
191
+ # Initialize embedder
192
+ begin
193
+ embedder = SwarmMemory::Embeddings::InformersEmbedder.new
194
+ rescue StandardError => e
195
+ $stderr.puts "Error: #{e.message}"
196
+ $stderr.puts
197
+ $stderr.puts "Make sure the 'informers' gem is installed:"
198
+ $stderr.puts " gem install informers"
199
+ exit(1)
200
+ end
201
+
202
+ # Ensure model is cached
203
+ unless embedder.cached?
204
+ puts "Model not cached. Downloading..."
205
+ puts " Model: sentence-transformers/all-MiniLM-L6-v2"
206
+ puts " Size: ~90MB (unquantized ONNX)"
207
+ puts
208
+ embedder.preload!
209
+ puts
210
+ end
211
+
212
+ # Create storage with embedder
213
+ adapter = SwarmMemory::Adapters::FilesystemAdapter.new(directory: directory)
214
+ storage = SwarmMemory::Core::Storage.new(adapter: adapter, embedder: embedder)
215
+
216
+ # Get all entries
217
+ all_entries = adapter.all_entries
218
+ total_count = all_entries.size
219
+
220
+ if total_count.zero?
221
+ puts "No entries found in #{directory}"
222
+ exit(0)
223
+ end
224
+
225
+ puts "Found #{total_count} entries to rebuild"
226
+ puts
227
+
228
+ # Rebuild each entry
229
+ processed = 0
230
+ errors = 0
231
+
232
+ all_entries.each do |path, entry|
233
+ # Re-write the entry to regenerate embedding
234
+ # The storage.write() method will automatically generate the embedding
235
+ storage.write(
236
+ file_path: path,
237
+ content: entry.content,
238
+ title: entry.title,
239
+ metadata: entry.metadata,
240
+ generate_embedding: true,
241
+ )
242
+
243
+ processed += 1
244
+ print("\rProcessed: #{processed}/#{total_count} (#{errors} errors)")
245
+ rescue StandardError => e
246
+ errors += 1
247
+ print("\rProcessed: #{processed}/#{total_count} (#{errors} errors)")
248
+ warn("\nError rebuilding #{path}: #{e.message}")
249
+ end
250
+
251
+ puts
252
+ puts
253
+ puts "Rebuild complete!"
254
+ puts " Total entries: #{total_count}"
255
+ puts " Successfully rebuilt: #{processed}"
256
+ puts " Errors: #{errors}" if errors.positive?
257
+ puts
258
+
259
+ exit(0)
260
+ end
261
+
262
+ def show_help
263
+ puts
264
+ puts "Usage: swarm memory SUBCOMMAND"
265
+ puts
266
+ puts "Subcommands:"
267
+ puts " setup Setup embeddings (download model ~90MB, one-time)"
268
+ puts " status Check if embeddings are ready"
269
+ puts " model-path Show embedding model cache path"
270
+ puts " defrag DIRECTORY Defrag memory at given directory"
271
+ puts " rebuild DIRECTORY Rebuild all embeddings for memory at directory"
272
+ puts
273
+ puts "Environment Variables:"
274
+ puts " SWARM_MEMORY_EMBEDDING_MODEL Model to use (default: all-MiniLM-L6-v2)"
275
+ puts " Options: all-MiniLM-L6-v2, multi-qa-MiniLM-L6-cos-v1"
276
+ puts " SWARM_MEMORY_EMBEDDING_MAX_CHARS Max chars to embed (default: 300, -1: unlimited)"
277
+ puts
278
+ puts " Adaptive Thresholds (short queries use lower threshold):"
279
+ puts " SWARM_MEMORY_DISCOVERY_THRESHOLD Normal query threshold (default: 0.35)"
280
+ puts " SWARM_MEMORY_DISCOVERY_THRESHOLD_SHORT Short query threshold (default: 0.25)"
281
+ puts " SWARM_MEMORY_ADAPTIVE_WORD_CUTOFF Word count cutoff (default: 10)"
282
+ puts " Queries < 10 words use short threshold"
283
+ puts
284
+ puts " SWARM_MEMORY_SEMANTIC_WEIGHT Semantic weight (default: 0.5)"
285
+ puts " SWARM_MEMORY_KEYWORD_WEIGHT Keyword weight (default: 0.5)"
286
+ puts
287
+ puts "Examples:"
288
+ puts " swarm memory setup # Download model"
289
+ puts " swarm memory status # Check if ready"
290
+ puts " swarm memory model-path # Show model location"
291
+ puts " swarm memory defrag .swarm/assistant-memory # Optimize memory"
292
+ puts " swarm memory rebuild .swarm/assistant-memory # Rebuild embeddings"
293
+ puts
294
+ puts " # Use Q&A-optimized model"
295
+ puts " SWARM_MEMORY_EMBEDDING_MODEL=sentence-transformers/multi-qa-MiniLM-L6-cos-v1 \\"
296
+ puts " swarm memory setup"
297
+ puts
298
+ puts " # Rebuild with more content (850 chars)"
299
+ puts " SWARM_MEMORY_EMBEDDING_MAX_CHARS=850 \\"
300
+ puts " swarm memory rebuild .swarm/assistant-memory"
301
+ puts
302
+ end
303
+ end
304
+ end
305
+ end
306
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ module Core
5
+ # Represents a single memory entry with metadata and optional embedding
6
+ #
7
+ # @attr content [String] The actual content stored
8
+ # @attr title [String] Brief description of the content
9
+ # @attr updated_at [Time] Last modification timestamp
10
+ # @attr size [Integer] Content size in bytes
11
+ # @attr embedding [Array<Float>, nil] Optional 384-dim embedding vector
12
+ # @attr metadata [Hash, nil] Optional parsed frontmatter metadata
13
+ Entry = Struct.new(
14
+ :content,
15
+ :title,
16
+ :updated_at,
17
+ :size,
18
+ :embedding,
19
+ :metadata,
20
+ keyword_init: true,
21
+ ) do
22
+ # Check if entry has an embedding
23
+ #
24
+ # @return [Boolean]
25
+ def embedded?
26
+ !embedding.nil? && !embedding.empty?
27
+ end
28
+
29
+ # Check if entry has metadata
30
+ #
31
+ # @return [Boolean]
32
+ def has_metadata?
33
+ !metadata.nil? && !metadata.empty?
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ module Core
5
+ # Parser for YAML frontmatter in memory entries
6
+ #
7
+ # Parses markdown content with YAML frontmatter:
8
+ # ---
9
+ # type: concept
10
+ # confidence: high
11
+ # tags: [ruby, testing]
12
+ # ---
13
+ #
14
+ # # Title
15
+ # Content here...
16
+ class FrontmatterParser
17
+ # Regex pattern to match frontmatter (same as MarkdownParser)
18
+ FRONTMATTER_PATTERN = /\A---\s*\n(.*?)\n---\s*\n(.*)\z/m
19
+
20
+ class << self
21
+ # Parse content and extract frontmatter
22
+ #
23
+ # @param content [String] Full entry content
24
+ # @return [Hash] { frontmatter: Hash, body: String, error: nil|String }
25
+ #
26
+ # @example
27
+ # parsed = FrontmatterParser.parse("---\ntype: fact\n---\nContent")
28
+ # parsed[:frontmatter] # => { type: "fact" }
29
+ # parsed[:body] # => "Content"
30
+ def parse(content)
31
+ return { frontmatter: {}, body: content, error: nil } if content.nil? || content.empty?
32
+
33
+ if content =~ FRONTMATTER_PATTERN
34
+ frontmatter_yaml = Regexp.last_match(1)
35
+ body = Regexp.last_match(2)
36
+
37
+ begin
38
+ frontmatter = YAML.safe_load(frontmatter_yaml, permitted_classes: [Symbol, Date, Time], aliases: true)
39
+ frontmatter = symbolize_keys(frontmatter) if frontmatter.is_a?(Hash)
40
+ { frontmatter: frontmatter || {}, body: body, error: nil }
41
+ rescue StandardError => e
42
+ # If YAML parsing fails, treat as body without frontmatter
43
+ { frontmatter: {}, body: content, error: e.message }
44
+ end
45
+ else
46
+ # No frontmatter
47
+ { frontmatter: {}, body: content, error: nil }
48
+ end
49
+ end
50
+
51
+ # Extract specific metadata fields from frontmatter
52
+ #
53
+ # @param content [String] Full entry content
54
+ # @return [Hash] Extracted metadata fields
55
+ #
56
+ # @example
57
+ # metadata = FrontmatterParser.extract_metadata(content)
58
+ # metadata[:confidence] # => "high"
59
+ # metadata[:type] # => "concept"
60
+ def extract_metadata(content)
61
+ parsed = parse(content)
62
+ fm = parsed[:frontmatter]
63
+
64
+ {
65
+ confidence: fm[:confidence]&.to_s&.downcase, # "high", "medium", "low"
66
+ type: fm[:type]&.to_s&.downcase, # "concept", "fact", "skill", "experience"
67
+ tags: Array(fm[:tags] || []),
68
+ last_verified: parse_date(fm[:last_verified]),
69
+ related: Array(fm[:related] || []),
70
+ domain: fm[:domain]&.to_s,
71
+ source: fm[:source]&.to_s,
72
+ }
73
+ end
74
+
75
+ private
76
+
77
+ # Parse date from various formats
78
+ #
79
+ # @param value [String, Date, Time, nil] Date value
80
+ # @return [Date, nil]
81
+ def parse_date(value)
82
+ return if value.nil?
83
+ return value.to_date if value.is_a?(Time)
84
+ return value if value.is_a?(Date)
85
+
86
+ Date.parse(value.to_s)
87
+ rescue ArgumentError
88
+ nil
89
+ end
90
+
91
+ # Recursively symbolize hash keys
92
+ #
93
+ # @param obj [Object] Object to symbolize
94
+ # @return [Object] Object with symbolized keys
95
+ def symbolize_keys(obj)
96
+ case obj
97
+ when Hash
98
+ obj.transform_keys(&:to_sym).transform_values { |v| symbolize_keys(v) }
99
+ when Array
100
+ obj.map { |item| symbolize_keys(item) }
101
+ else
102
+ obj
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ module Core
5
+ # Extracts structured metadata from memory entries
6
+ #
7
+ # This class wraps FrontmatterParser and provides additional
8
+ # metadata extraction logic specific to the memory system.
9
+ class MetadataExtractor
10
+ class << self
11
+ # Extract all metadata from a memory entry
12
+ #
13
+ # @param content [String] Full entry content
14
+ # @return [Hash] Extracted metadata
15
+ #
16
+ # @example
17
+ # metadata = MetadataExtractor.extract(content)
18
+ # metadata[:confidence] # => "high"
19
+ # metadata[:type] # => "concept"
20
+ # metadata[:tags] # => ["ruby", "testing"]
21
+ def extract(content)
22
+ FrontmatterParser.extract_metadata(content)
23
+ end
24
+
25
+ # Check if entry has required frontmatter for quality
26
+ #
27
+ # @param content [String] Full entry content
28
+ # @return [Boolean] True if entry has basic required fields
29
+ def has_required_frontmatter?(content)
30
+ metadata = extract(content)
31
+ !metadata[:type].nil? && !metadata[:confidence].nil?
32
+ end
33
+
34
+ # Calculate entry quality score (0-100)
35
+ #
36
+ # @param content [String] Full entry content
37
+ # @return [Integer] Quality score
38
+ def quality_score(content)
39
+ metadata = extract(content)
40
+ score = 0
41
+
42
+ # Has type (20 points)
43
+ score += 20 if metadata[:type]
44
+
45
+ # Has confidence (20 points)
46
+ score += 20 if metadata[:confidence]
47
+
48
+ # Has tags (15 points)
49
+ score += 15 unless metadata[:tags].empty?
50
+
51
+ # Has related links (15 points)
52
+ score += 15 unless metadata[:related].empty?
53
+
54
+ # Has domain (10 points)
55
+ score += 10 if metadata[:domain]
56
+
57
+ # Has last_verified (10 points)
58
+ score += 10 if metadata[:last_verified]
59
+
60
+ # High confidence bonus (10 points)
61
+ score += 10 if metadata[:confidence] == "high"
62
+
63
+ score
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ module Core
5
+ # Validates and normalizes memory paths
6
+ #
7
+ # Ensures paths are safe, hierarchical, and follow conventions.
8
+ class PathNormalizer
9
+ # Invalid path patterns
10
+ INVALID_PATTERNS = [
11
+ %r{\A/}, # Absolute paths
12
+ /\.\./, # Parent directory references
13
+ %r{//}, # Double slashes
14
+ /\A\s/, # Leading whitespace
15
+ /\s\z/, # Trailing whitespace
16
+ /[<>:"|?*]/, # Invalid filesystem characters
17
+ ].freeze
18
+
19
+ class << self
20
+ # Normalize and validate a memory path
21
+ #
22
+ # @param path [String] Path to normalize
23
+ # @return [String] Normalized path
24
+ # @raise [ArgumentError] If path is invalid
25
+ #
26
+ # @example
27
+ # PathNormalizer.normalize("concepts/ruby/classes.md")
28
+ # # => "concepts/ruby/classes.md"
29
+ #
30
+ # PathNormalizer.normalize("../secrets")
31
+ # # => ArgumentError: Path cannot contain '..'
32
+ def normalize(path)
33
+ raise ArgumentError, "path is required" if path.nil? || path.to_s.strip.empty?
34
+
35
+ original_path = path.to_s.strip
36
+
37
+ # Check for absolute paths and parent references FIRST (before normalization)
38
+ if original_path.start_with?("/")
39
+ raise ArgumentError, "Invalid path: #{original_path}. Paths must be relative, hierarchical, and safe."
40
+ end
41
+
42
+ if original_path.include?("..")
43
+ raise ArgumentError, "Invalid path: #{original_path}. Paths must be relative, hierarchical, and safe."
44
+ end
45
+
46
+ # Normalize (remove leading/trailing slashes, collapse doubles)
47
+ path = original_path
48
+ path = path.sub(%r{\A/+}, "") # Remove leading slashes
49
+ path = path.sub(%r{/+\z}, "") # Remove trailing slashes
50
+ path = path.gsub(%r{/+}, "/") # Collapse multiple slashes
51
+
52
+ # Check for other invalid characters
53
+ if path.match?(/[<>:"|?*]/)
54
+ raise ArgumentError, "Invalid path: #{original_path}. Paths must be relative, hierarchical, and safe."
55
+ end
56
+
57
+ raise ArgumentError, "Normalized path is empty" if path.empty?
58
+
59
+ path
60
+ end
61
+
62
+ # Check if a path is valid without raising an exception
63
+ #
64
+ # @param path [String] Path to validate
65
+ # @return [Boolean] True if path is valid
66
+ def valid?(path)
67
+ normalize(path)
68
+ true
69
+ rescue ArgumentError
70
+ false
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end