claude_swarm 1.0.1 → 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 +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 +6 -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 +247 -4
  267. data/EXAMPLES.md +0 -164
@@ -0,0 +1,711 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmCLI
4
+ module Formatters
5
+ # HumanFormatter creates beautiful, detailed real-time output using reusable UI components.
6
+ # Shows everything: agent thinking, tool calls with arguments, results, responses.
7
+ # Uses clean component architecture for maintainability and testability.
8
+ #
9
+ # Modes:
10
+ # - :non_interactive - Full headers, task prompt, complete summary (for single execution)
11
+ # - :interactive - Minimal output for REPL (headers shown in welcome screen)
12
+ class HumanFormatter
13
+ attr_reader :spinner_manager
14
+
15
+ def initialize(output: $stdout, quiet: false, truncate: false, verbose: false, mode: :non_interactive)
16
+ @output = output
17
+ @quiet = quiet
18
+ @truncate = truncate
19
+ @verbose = verbose
20
+ @mode = mode
21
+
22
+ # Initialize Pastel with TTY detection
23
+ @pastel = Pastel.new(enabled: output.tty?)
24
+
25
+ # Initialize state managers
26
+ @color_cache = SwarmCLI::UI::State::AgentColorCache.new
27
+ @depth_tracker = SwarmCLI::UI::State::DepthTracker.new
28
+ @usage_tracker = SwarmCLI::UI::State::UsageTracker.new
29
+ @spinner_manager = SwarmCLI::UI::State::SpinnerManager.new
30
+
31
+ # Initialize components
32
+ @divider = SwarmCLI::UI::Components::Divider.new(pastel: @pastel, terminal_width: TTY::Screen.width)
33
+ @agent_badge = SwarmCLI::UI::Components::AgentBadge.new(pastel: @pastel, color_cache: @color_cache)
34
+ @content_block = SwarmCLI::UI::Components::ContentBlock.new(pastel: @pastel)
35
+ @panel = SwarmCLI::UI::Components::Panel.new(pastel: @pastel)
36
+
37
+ # Initialize event renderer
38
+ @event_renderer = SwarmCLI::UI::Renderers::EventRenderer.new(
39
+ pastel: @pastel,
40
+ agent_badge: @agent_badge,
41
+ depth_tracker: @depth_tracker,
42
+ )
43
+
44
+ # Track last context percentage for warnings
45
+ @last_context_percentage = {}
46
+
47
+ # Start time tracking
48
+ @start_time = nil
49
+ end
50
+
51
+ # Called when swarm execution starts
52
+ def on_start(config_path:, swarm_name:, lead_agent:, prompt:)
53
+ @start_time = Time.now
54
+
55
+ # Only show headers in non-interactive mode
56
+ if @mode == :non_interactive
57
+ print_header(swarm_name, lead_agent)
58
+ print_prompt(prompt)
59
+ @output.puts @divider.full
60
+ @output.puts @pastel.bold("#{SwarmCLI::UI::Icons::INFO} Execution Log:")
61
+ @output.puts
62
+ end
63
+ end
64
+
65
+ # Called for each log entry from SwarmSDK
66
+ def on_log(entry)
67
+ return if @quiet
68
+
69
+ case entry[:type]
70
+ when "user_prompt"
71
+ handle_user_request(entry)
72
+ when "agent_step"
73
+ handle_agent_step(entry)
74
+ when "agent_stop"
75
+ handle_agent_stop(entry)
76
+ when "tool_call"
77
+ handle_tool_call(entry)
78
+ when "tool_result"
79
+ handle_tool_result(entry)
80
+ when "agent_delegation"
81
+ handle_agent_delegation(entry)
82
+ when "delegation_result"
83
+ handle_delegation_result(entry)
84
+ when "context_limit_warning"
85
+ handle_context_warning(entry)
86
+ when "model_lookup_warning"
87
+ handle_model_lookup_warning(entry)
88
+ when "compression_started"
89
+ handle_compression_started(entry)
90
+ when "compression_completed"
91
+ handle_compression_completed(entry)
92
+ when "hook_executed"
93
+ handle_hook_executed(entry)
94
+ when "breakpoint_enter"
95
+ handle_breakpoint_enter(entry)
96
+ when "breakpoint_exit"
97
+ handle_breakpoint_exit(entry)
98
+ end
99
+ end
100
+
101
+ # Called when swarm execution completes successfully
102
+ def on_success(result:)
103
+ if @mode == :non_interactive
104
+ # Full result display with summary
105
+ @output.puts
106
+ @output.puts @divider.full
107
+ end
108
+
109
+ # Print result (handles mode internally)
110
+ print_result(result)
111
+
112
+ # Only print summary in non-interactive mode
113
+ print_summary(result) if @mode == :non_interactive
114
+ end
115
+
116
+ # Called when swarm execution fails
117
+ def on_error(error:, duration: nil)
118
+ @output.puts
119
+ @output.puts @divider.full
120
+ print_error(error)
121
+ @output.puts @divider.full
122
+ end
123
+
124
+ private
125
+
126
+ def handle_user_request(entry)
127
+ agent = entry[:agent]
128
+ @usage_tracker.track_agent(agent)
129
+ @usage_tracker.track_llm_request(entry[:usage])
130
+
131
+ # Stop any delegation waiting spinner (in case this agent was delegated to)
132
+ unless @quiet
133
+ delegation_spinner = "delegation_#{agent}".to_sym
134
+ @spinner_manager.stop(delegation_spinner) if @spinner_manager.active?(delegation_spinner)
135
+ end
136
+
137
+ # Render agent thinking line
138
+ @output.puts @event_renderer.agent_thinking(
139
+ agent: agent,
140
+ model: entry[:model],
141
+ timestamp: entry[:timestamp],
142
+ )
143
+
144
+ # Show tools available
145
+ if entry[:tools]&.any?
146
+ @output.puts @event_renderer.tools_available(entry[:tools], indent: @depth_tracker.get(agent))
147
+ end
148
+
149
+ # Show delegation options
150
+ if entry[:delegates_to]&.any?
151
+ @output.puts @event_renderer.delegates_to(
152
+ entry[:delegates_to],
153
+ indent: @depth_tracker.get(agent),
154
+ color_cache: @color_cache,
155
+ )
156
+ end
157
+
158
+ @output.puts
159
+
160
+ # Start spinner for agent thinking
161
+ unless @quiet
162
+ spinner_key = "agent_#{agent}".to_sym
163
+ @spinner_manager.start(spinner_key, "#{agent} is thinking...")
164
+ end
165
+ end
166
+
167
+ def handle_agent_step(entry)
168
+ agent = entry[:agent]
169
+ indent_level = @depth_tracker.get(agent)
170
+
171
+ # Stop agent thinking spinner
172
+ unless @quiet
173
+ spinner_key = "agent_#{agent}".to_sym
174
+ @spinner_manager.stop(spinner_key)
175
+ end
176
+
177
+ # Track usage
178
+ if entry[:usage]
179
+ @usage_tracker.track_llm_request(entry[:usage])
180
+ @last_context_percentage[agent] = entry[:usage][:tokens_used_percentage]
181
+
182
+ # Render usage stats
183
+ @output.puts @event_renderer.usage_stats(
184
+ tokens: entry[:usage][:total_tokens] || 0,
185
+ cost: entry[:usage][:total_cost] || 0.0,
186
+ context_pct: entry[:usage][:tokens_used_percentage],
187
+ remaining: entry[:usage][:tokens_remaining],
188
+ cumulative: entry[:usage][:cumulative_total_tokens],
189
+ indent: indent_level,
190
+ )
191
+ end
192
+
193
+ # Display thinking text (if present)
194
+ if entry[:content] && !entry[:content].empty?
195
+ thinking = @event_renderer.thinking_text(entry[:content], indent: indent_level)
196
+ @output.puts thinking unless thinking.empty?
197
+ @output.puts if thinking && !thinking.empty?
198
+ end
199
+
200
+ # Show tool request summary
201
+ tool_count = entry[:tool_calls]&.size || 0
202
+ if tool_count > 0
203
+ indent = @depth_tracker.indent(agent)
204
+ @output.puts "#{indent} #{@pastel.dim("→ Requesting #{tool_count} tool#{"s" if tool_count > 1}...")}"
205
+ end
206
+
207
+ @output.puts
208
+ @output.puts @divider.event(indent: indent_level)
209
+ end
210
+
211
+ def handle_agent_stop(entry)
212
+ agent = entry[:agent]
213
+ indent_level = @depth_tracker.get(agent)
214
+
215
+ # Stop agent thinking spinner with success
216
+ unless @quiet
217
+ spinner_key = "agent_#{agent}".to_sym
218
+ @spinner_manager.success(spinner_key, "completed")
219
+ end
220
+
221
+ # Track usage
222
+ if entry[:usage]
223
+ @usage_tracker.track_llm_request(entry[:usage])
224
+ @last_context_percentage[agent] = entry[:usage][:tokens_used_percentage]
225
+
226
+ # Render usage stats
227
+ @output.puts @event_renderer.usage_stats(
228
+ tokens: entry[:usage][:total_tokens] || 0,
229
+ cost: entry[:usage][:total_cost] || 0.0,
230
+ context_pct: entry[:usage][:tokens_used_percentage],
231
+ remaining: entry[:usage][:tokens_remaining],
232
+ cumulative: entry[:usage][:cumulative_total_tokens],
233
+ indent: indent_level,
234
+ )
235
+ end
236
+
237
+ # Display final response (only for top-level agent in non-interactive mode)
238
+ # In interactive mode, response is shown by print_result to avoid duplication
239
+ if entry[:content] && !entry[:content].empty? && indent_level.zero? && @mode == :non_interactive
240
+ @output.puts @event_renderer.agent_response(
241
+ agent: agent,
242
+ timestamp: entry[:timestamp],
243
+ )
244
+
245
+ # Render response content
246
+ indent = @depth_tracker.indent(agent)
247
+ response_lines = entry[:content].split("\n")
248
+
249
+ if @truncate && response_lines.length > 12
250
+ response_lines.first(12).each { |line| @output.puts "#{indent} #{line}" }
251
+ @output.puts "#{indent} #{@pastel.dim("... (#{response_lines.length - 12} more lines)")}"
252
+ else
253
+ response_lines.each { |line| @output.puts "#{indent} #{line}" }
254
+ end
255
+ end
256
+
257
+ @output.puts
258
+ @output.puts @event_renderer.agent_completed(agent: agent)
259
+ @output.puts
260
+ @output.puts @divider.event(indent: indent_level)
261
+ end
262
+
263
+ def handle_tool_call(entry)
264
+ agent = entry[:agent]
265
+ @usage_tracker.track_tool_call(tool_call_id: entry[:tool_call_id], tool_name: entry[:tool])
266
+
267
+ # Special handling for Think tool - show as thoughts, not as a tool call
268
+ if entry[:tool] == "Think" && entry[:arguments] && entry[:arguments]["thoughts"]
269
+ thoughts = entry[:arguments]["thoughts"]
270
+ thinking = @event_renderer.thinking_text(thoughts, indent: @depth_tracker.get(agent))
271
+ @output.puts thinking unless thinking.empty?
272
+ @output.puts
273
+ # Don't show spinner for Think tool
274
+ return
275
+ end
276
+
277
+ # Render tool call event
278
+ @output.puts @event_renderer.tool_call(
279
+ agent: agent,
280
+ tool: entry[:tool],
281
+ timestamp: entry[:timestamp],
282
+ )
283
+
284
+ # Show arguments (skip TodoWrite unless verbose)
285
+ args = entry[:arguments]
286
+ show_args = args && !args.empty?
287
+ show_args &&= entry[:tool] != "TodoWrite" || @verbose
288
+
289
+ if show_args
290
+ @output.puts @event_renderer.tool_arguments(
291
+ args,
292
+ indent: @depth_tracker.get(agent),
293
+ truncate: @truncate,
294
+ )
295
+ end
296
+
297
+ @output.puts
298
+
299
+ # Start spinner for tool execution
300
+ unless @quiet || entry[:tool] == "TodoWrite"
301
+ spinner_key = "tool_#{entry[:tool_call_id]}".to_sym
302
+ @spinner_manager.start(spinner_key, "Executing #{entry[:tool]}...")
303
+ end
304
+ end
305
+
306
+ def handle_tool_result(entry)
307
+ agent = entry[:agent]
308
+ tool_name = entry[:tool] || @usage_tracker.tool_name_for(entry[:tool_call_id])
309
+
310
+ # Special handling for Think tool - skip showing result (already shown as thoughts)
311
+ if tool_name == "Think"
312
+ # Don't show anything - thoughts were already displayed in handle_tool_call
313
+ # Start spinner for agent processing
314
+ unless @quiet
315
+ spinner_key = "agent_#{agent}".to_sym
316
+ indent = @depth_tracker.indent(agent)
317
+ @spinner_manager.start(spinner_key, "#{indent}#{agent} is processing...")
318
+ end
319
+ return
320
+ end
321
+
322
+ # Stop tool spinner with success
323
+ unless @quiet || tool_name == "TodoWrite"
324
+ spinner_key = "tool_#{entry[:tool_call_id]}".to_sym
325
+ @spinner_manager.success(spinner_key, "completed")
326
+ end
327
+
328
+ # Special handling for TodoWrite
329
+ if tool_name == "TodoWrite"
330
+ display_todo_list(agent, entry[:timestamp])
331
+ else
332
+ @output.puts @event_renderer.tool_result(
333
+ agent: agent,
334
+ timestamp: entry[:timestamp],
335
+ tool: tool_name,
336
+ )
337
+
338
+ # Render result content
339
+ if entry[:result].is_a?(String) && !entry[:result].empty?
340
+ result_text = @event_renderer.tool_result_content(
341
+ entry[:result],
342
+ indent: @depth_tracker.get(agent),
343
+ truncate: !@verbose,
344
+ )
345
+ @output.puts result_text unless result_text.empty?
346
+ end
347
+ end
348
+
349
+ @output.puts
350
+ @output.puts @divider.event(indent: @depth_tracker.get(agent))
351
+
352
+ # Start spinner for agent processing tool result
353
+ # The agent will determine what to do next (more tools or finish)
354
+ # This spinner will be stopped by the next agent_step or agent_stop event
355
+ unless @quiet
356
+ spinner_key = "agent_#{agent}".to_sym
357
+ indent = @depth_tracker.indent(agent)
358
+ @spinner_manager.start(spinner_key, "#{indent}#{agent} is processing...")
359
+ end
360
+ end
361
+
362
+ def handle_agent_delegation(entry)
363
+ @usage_tracker.track_tool_call
364
+
365
+ @output.puts @event_renderer.delegation(
366
+ from: entry[:agent],
367
+ to: entry[:delegate_to],
368
+ timestamp: entry[:timestamp],
369
+ )
370
+ @output.puts
371
+
372
+ # Show arguments if present
373
+ if entry[:arguments] && !entry[:arguments].empty?
374
+ @output.puts @event_renderer.tool_arguments(
375
+ entry[:arguments],
376
+ indent: @depth_tracker.get(entry[:agent]),
377
+ truncate: @truncate,
378
+ )
379
+ end
380
+
381
+ @output.puts
382
+
383
+ # Start spinner waiting for delegated agent
384
+ unless @quiet
385
+ spinner_key = "delegation_#{entry[:delegate_to]}".to_sym
386
+ indent = @depth_tracker.indent(entry[:agent])
387
+ @spinner_manager.start(spinner_key, "#{indent}Waiting for #{entry[:delegate_to]}...")
388
+ end
389
+ end
390
+
391
+ def handle_delegation_result(entry)
392
+ @output.puts @event_renderer.delegation_result(
393
+ from: entry[:delegate_from],
394
+ to: entry[:agent],
395
+ timestamp: entry[:timestamp],
396
+ )
397
+
398
+ # Render result content
399
+ if entry[:result].is_a?(String) && !entry[:result].empty?
400
+ result_text = @event_renderer.tool_result_content(
401
+ entry[:result],
402
+ indent: @depth_tracker.get(entry[:agent]),
403
+ truncate: !@verbose,
404
+ )
405
+ @output.puts result_text unless result_text.empty?
406
+ end
407
+
408
+ @output.puts
409
+ @output.puts @divider.event(indent: @depth_tracker.get(entry[:agent]))
410
+
411
+ # Start spinner for agent processing delegation result
412
+ unless @quiet
413
+ spinner_key = "agent_#{entry[:agent]}".to_sym
414
+ indent = @depth_tracker.indent(entry[:agent])
415
+ @spinner_manager.start(spinner_key, "#{indent}#{entry[:agent]} is processing...")
416
+ end
417
+ end
418
+
419
+ def handle_context_warning(entry)
420
+ agent = entry[:agent]
421
+ threshold = entry[:threshold]
422
+ current_usage = entry[:current_usage]
423
+ tokens_remaining = entry[:tokens_remaining]
424
+
425
+ # Determine warning severity
426
+ type = threshold == "90%" ? :error : :warning
427
+
428
+ @output.puts @panel.render(
429
+ type: type,
430
+ title: "CONTEXT WARNING #{@agent_badge.render(agent)}",
431
+ lines: [
432
+ @pastel.public_send((type == :error ? :red : :yellow), "Context usage: #{current_usage} (threshold: #{threshold})"),
433
+ @pastel.dim("Tokens remaining: #{SwarmCLI::UI::Formatters::Number.format(tokens_remaining)}"),
434
+ ],
435
+ indent: @depth_tracker.get(agent),
436
+ )
437
+ end
438
+
439
+ def handle_model_lookup_warning(entry)
440
+ agent = entry[:agent]
441
+ model = entry[:model]
442
+ error_message = entry[:error_message]
443
+ suggestions = entry[:suggestions] || []
444
+
445
+ lines = [
446
+ @pastel.yellow("Model '#{model}' not found in registry"),
447
+ ]
448
+
449
+ if suggestions.any?
450
+ lines << @pastel.dim("Did you mean one of these?")
451
+ suggestions.each do |suggestion|
452
+ model_id = suggestion[:id] || suggestion["id"]
453
+ context = suggestion[:context_window] || suggestion["context_window"]
454
+ context_display = context ? " (#{SwarmCLI::UI::Formatters::Number.format(context)} tokens)" : ""
455
+ lines << " #{@pastel.cyan("•")} #{@pastel.white(model_id)}#{@pastel.dim(context_display)}"
456
+ end
457
+ else
458
+ lines << @pastel.dim("Error: #{error_message}")
459
+ end
460
+
461
+ lines << @pastel.dim("Context tracking unavailable for this model.")
462
+
463
+ @output.puts @panel.render(
464
+ type: :warning,
465
+ title: "MODEL WARNING #{@agent_badge.render(agent)}",
466
+ lines: lines,
467
+ indent: 0, # Always at root level (warnings shown at boot, not during execution)
468
+ )
469
+ end
470
+
471
+ def handle_compression_started(entry)
472
+ agent = entry[:agent]
473
+ message_count = entry[:message_count]
474
+ estimated_tokens = entry[:estimated_tokens]
475
+
476
+ @output.puts @panel.render(
477
+ type: :info,
478
+ title: "CONTEXT COMPRESSION #{@agent_badge.render(agent)}",
479
+ lines: [
480
+ @pastel.dim("Compressing #{message_count} messages (~#{SwarmCLI::UI::Formatters::Number.format(estimated_tokens)} tokens)..."),
481
+ ],
482
+ indent: @depth_tracker.get(agent),
483
+ )
484
+ end
485
+
486
+ def handle_compression_completed(entry)
487
+ agent = entry[:agent]
488
+ original_messages = entry[:original_message_count]
489
+ compressed_messages = entry[:compressed_message_count]
490
+ messages_removed = entry[:messages_removed]
491
+ original_tokens = entry[:original_tokens]
492
+ compressed_tokens = entry[:compressed_tokens]
493
+ compression_ratio = entry[:compression_ratio]
494
+ time_taken = entry[:time_taken]
495
+
496
+ @output.puts @panel.render(
497
+ type: :success,
498
+ title: "COMPRESSION COMPLETE #{@agent_badge.render(agent)}",
499
+ lines: [
500
+ "#{@pastel.dim("Messages:")} #{original_messages} → #{compressed_messages} #{@pastel.green("(-#{messages_removed})")}",
501
+ "#{@pastel.dim("Tokens:")} #{SwarmCLI::UI::Formatters::Number.format(original_tokens)} → #{SwarmCLI::UI::Formatters::Number.format(compressed_tokens)} #{@pastel.green("(#{(compression_ratio * 100).round(1)}%)")}",
502
+ "#{@pastel.dim("Time taken:")} #{SwarmCLI::UI::Formatters::Time.duration(time_taken)}",
503
+ ],
504
+ indent: @depth_tracker.get(agent),
505
+ )
506
+ end
507
+
508
+ def handle_hook_executed(entry)
509
+ hook_event = entry[:hook_event]
510
+ agent = entry[:agent]
511
+ success = entry[:success]
512
+ blocked = entry[:blocked]
513
+ stderr = entry[:stderr]
514
+ exit_code = entry[:exit_code]
515
+
516
+ @output.puts @event_renderer.hook_executed(
517
+ hook_event: hook_event,
518
+ agent: agent,
519
+ timestamp: entry[:timestamp],
520
+ success: success,
521
+ blocked: blocked,
522
+ )
523
+
524
+ # Show stderr if present
525
+ if stderr && !stderr.empty?
526
+ indent = @depth_tracker.indent(agent)
527
+
528
+ if blocked && hook_event == "user_prompt"
529
+ @output.puts
530
+ @output.puts "#{indent} #{@pastel.bold.red("⛔ Prompt Blocked by Hook:")}"
531
+ stderr.lines.each { |line| @output.puts "#{indent} #{@pastel.red(line.chomp)}" }
532
+ @output.puts "#{indent} #{@pastel.dim("(Prompt was not sent to the agent)")}"
533
+ elsif blocked
534
+ @output.puts "#{indent} #{@pastel.red("Blocked:")} #{@pastel.red(stderr)}"
535
+ else
536
+ @output.puts "#{indent} #{@pastel.yellow("Message:")} #{@pastel.dim(stderr)}"
537
+ end
538
+ end
539
+
540
+ # Show exit code in verbose mode
541
+ if @verbose && exit_code
542
+ indent = @depth_tracker.indent(agent)
543
+ code_color = if exit_code.zero?
544
+ :green
545
+ else
546
+ (exit_code == 2 ? :red : :yellow)
547
+ end
548
+ @output.puts "#{indent} #{@pastel.dim("Exit code:")} #{@pastel.public_send(code_color, exit_code)}"
549
+ end
550
+
551
+ @output.puts
552
+ end
553
+
554
+ def handle_breakpoint_enter(entry)
555
+ agent = entry[:agent]
556
+ event = entry[:event]
557
+
558
+ # Pause all spinners to allow clean interactive debugging
559
+ @spinner_manager.pause_all
560
+
561
+ # Show debugging notice
562
+ @output.puts
563
+ @output.puts @pastel.yellow("#{SwarmCLI::UI::Icons::THINKING} Breakpoint: Entering interactive debugging (#{event} hook)")
564
+ @output.puts @pastel.dim(" Agent: #{agent}")
565
+ @output.puts @pastel.dim(" Type 'exit' to continue execution")
566
+ @output.puts
567
+ end
568
+
569
+ def handle_breakpoint_exit(entry)
570
+ # Resume all spinners after debugging
571
+ @spinner_manager.resume_all
572
+
573
+ @output.puts
574
+ @output.puts @pastel.green("#{SwarmCLI::UI::Icons::SUCCESS} Breakpoint: Resuming execution")
575
+ @output.puts
576
+ end
577
+
578
+ def display_todo_list(agent, timestamp)
579
+ todos = SwarmSDK::Tools::Stores::TodoManager.get_todos(agent.to_sym)
580
+ indent = @depth_tracker.indent(agent)
581
+ time = SwarmCLI::UI::Formatters::Time.timestamp(timestamp)
582
+
583
+ if todos.empty?
584
+ @output.puts "#{indent}#{@pastel.dim(time)} #{@pastel.cyan("#{SwarmCLI::UI::Icons::BULLET} Todo list")} updated (empty)"
585
+ return
586
+ end
587
+
588
+ @output.puts "#{indent}#{@pastel.dim(time)} #{@pastel.cyan("#{SwarmCLI::UI::Icons::BULLET} Todo list")} updated:"
589
+ @output.puts
590
+
591
+ todos.each_with_index do |todo, index|
592
+ status = todo[:status] || todo["status"]
593
+ content = todo[:content] || todo["content"]
594
+ num = index + 1
595
+
596
+ line = case status
597
+ when "completed"
598
+ "#{indent} #{@pastel.dim("#{num}.")} #{@pastel.dim.strikethrough(content)}"
599
+ when "in_progress"
600
+ "#{indent} #{@pastel.bold.yellow("#{num}.")} #{@pastel.bold(content)}"
601
+ when "pending"
602
+ "#{indent} #{@pastel.white("#{num}.")} #{content}"
603
+ else
604
+ "#{indent} #{num}. #{content}"
605
+ end
606
+
607
+ @output.puts line
608
+ end
609
+ end
610
+
611
+ def print_header(swarm_name, lead_agent)
612
+ @output.puts
613
+ @output.puts @pastel.bold.bright_cyan("#{SwarmCLI::UI::Icons::SPARKLES} SwarmSDK - AI Agent Orchestration #{SwarmCLI::UI::Icons::SPARKLES}")
614
+ @output.puts @divider.full
615
+ @output.puts "#{@pastel.bold("Swarm:")} #{@pastel.cyan(swarm_name)}"
616
+ @output.puts "#{@pastel.bold("Lead Agent:")} #{@pastel.cyan(lead_agent)}"
617
+ @output.puts
618
+ end
619
+
620
+ def print_prompt(prompt)
621
+ @output.puts @pastel.bold("#{SwarmCLI::UI::Icons::THINKING} Task Prompt:")
622
+ @output.puts @pastel.bright_white(prompt)
623
+ @output.puts
624
+ end
625
+
626
+ def print_result(result)
627
+ return unless result.content && !result.content.empty?
628
+
629
+ # Interactive mode: Just show the response content directly
630
+ if @mode == :interactive
631
+ # Render markdown if content looks like markdown
632
+ content_to_display = if looks_like_markdown?(result.content)
633
+ begin
634
+ TTY::Markdown.parse(result.content)
635
+ rescue StandardError
636
+ result.content
637
+ end
638
+ else
639
+ result.content
640
+ end
641
+
642
+ @output.puts content_to_display
643
+ @output.puts
644
+ return
645
+ end
646
+
647
+ # Non-interactive mode: Full result display with header and dividers
648
+ @output.puts
649
+ @output.puts @pastel.bold.green("#{SwarmCLI::UI::Icons::SUCCESS} Execution Complete")
650
+ @output.puts
651
+ @output.puts @pastel.bold("#{SwarmCLI::UI::Icons::RESPONSE} Final Response from #{@agent_badge.render(result.agent)}:")
652
+ @output.puts
653
+ @output.puts @divider.full
654
+
655
+ # Render markdown if content looks like markdown
656
+ content_to_display = if looks_like_markdown?(result.content)
657
+ begin
658
+ TTY::Markdown.parse(result.content)
659
+ rescue StandardError
660
+ result.content
661
+ end
662
+ else
663
+ result.content
664
+ end
665
+
666
+ @output.puts content_to_display
667
+ @output.puts @divider.full
668
+ @output.puts
669
+ end
670
+
671
+ def print_summary(result)
672
+ @output.puts @pastel.bold("#{SwarmCLI::UI::Icons::INFO} Execution Summary:")
673
+ @output.puts
674
+
675
+ # Agents used (colored list)
676
+ agents_display = @agent_badge.render_list(@usage_tracker.agents)
677
+ @output.puts " #{SwarmCLI::UI::Icons::AGENT} #{@pastel.bold("Agents used:")} #{agents_display}"
678
+
679
+ # Metrics
680
+ @output.puts " #{SwarmCLI::UI::Icons::LLM} #{@pastel.bold("LLM Requests:")} #{result.llm_requests}"
681
+ @output.puts " #{SwarmCLI::UI::Icons::TOOL} #{@pastel.bold("Tool Calls:")} #{result.tool_calls_count}"
682
+ @output.puts " #{SwarmCLI::UI::Icons::TOKENS} #{@pastel.bold("Total Tokens:")} #{SwarmCLI::UI::Formatters::Number.format(result.total_tokens)}"
683
+ @output.puts " #{SwarmCLI::UI::Icons::COST} #{@pastel.bold("Total Cost:")} #{SwarmCLI::UI::Formatters::Cost.format(result.total_cost, pastel: @pastel)}"
684
+ @output.puts " #{SwarmCLI::UI::Icons::TIME} #{@pastel.bold("Duration:")} #{SwarmCLI::UI::Formatters::Time.duration(result.duration)}"
685
+
686
+ @output.puts
687
+ end
688
+
689
+ def print_error(error)
690
+ @output.puts
691
+ @output.puts @pastel.bold.red("#{SwarmCLI::UI::Icons::ERROR} Execution Failed")
692
+ @output.puts
693
+ @output.puts @pastel.red("Error: #{error.class.name}")
694
+ @output.puts @pastel.red(error.message)
695
+ @output.puts
696
+
697
+ return unless error.backtrace
698
+
699
+ @output.puts @pastel.dim("Backtrace:")
700
+ error.backtrace.first(5).each do |line|
701
+ @output.puts @pastel.dim(" #{line}")
702
+ end
703
+ @output.puts
704
+ end
705
+
706
+ def looks_like_markdown?(text)
707
+ text.match?(/^#+\s|^\*\s|^-\s|^\d+\.\s|```|\[.+\]\(.+\)/)
708
+ end
709
+ end
710
+ end
711
+ end