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,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "json"
5
+ require "timeout"
6
+
7
+ module SwarmSDK
8
+ module Node
9
+ # Executes bash command transformers for node input/output transformation
10
+ #
11
+ # Transformers are shell commands that receive NodeContext data on STDIN as JSON
12
+ # and produce transformed content on STDOUT.
13
+ #
14
+ # ## Exit Code Behavior
15
+ #
16
+ # - **Exit 0**: Transform success
17
+ # - Use STDOUT as the transformed content
18
+ # - Node execution proceeds with transformed content
19
+ #
20
+ # - **Exit 1**: Skip node execution (pass-through)
21
+ # - STDOUT is IGNORED
22
+ # - Input transformer: Use current_input unchanged (no transformation)
23
+ # - Output transformer: Use result.content unchanged (no transformation)
24
+ # - For input transformer: Also skips the node's LLM execution
25
+ #
26
+ # - **Exit 2**: Halt entire workflow
27
+ # - STDOUT is IGNORED
28
+ # - STDERR is shown as error message
29
+ # - Workflow stops immediately with error
30
+ #
31
+ # ## JSON Input Format (STDIN)
32
+ #
33
+ # **Input transformer receives:**
34
+ # ```json
35
+ # {
36
+ # "event": "input",
37
+ # "node": "implementation",
38
+ # "original_prompt": "Build auth API",
39
+ # "content": "PLAN: Create endpoints...",
40
+ # "all_results": {
41
+ # "planning": {
42
+ # "content": "Create endpoints...",
43
+ # "agent": "planner",
44
+ # "duration": 2.5,
45
+ # "success": true
46
+ # }
47
+ # },
48
+ # "dependencies": ["planning"]
49
+ # }
50
+ # ```
51
+ #
52
+ # **Output transformer receives:**
53
+ # ```json
54
+ # {
55
+ # "event": "output",
56
+ # "node": "implementation",
57
+ # "original_prompt": "Build auth API",
58
+ # "content": "Implementation complete",
59
+ # "all_results": {
60
+ # "planning": {...},
61
+ # "implementation": {...}
62
+ # }
63
+ # }
64
+ # ```
65
+ #
66
+ # @example Input transformer that validates
67
+ # # validate.sh
68
+ # #!/bin/bash
69
+ # INPUT=$(cat)
70
+ # CONTENT=$(echo "$INPUT" | jq -r '.content')
71
+ #
72
+ # if [ ${#CONTENT} -gt 10000 ]; then
73
+ # echo "Content too long" >&2
74
+ # exit 2 # Halt workflow
75
+ # fi
76
+ #
77
+ # echo "$CONTENT"
78
+ # exit 0
79
+ #
80
+ # @example Input transformer that caches (skip execution)
81
+ # # cache_check.sh
82
+ # #!/bin/bash
83
+ # INPUT=$(cat)
84
+ # CONTENT=$(echo "$INPUT" | jq -r '.content')
85
+ #
86
+ # if cached "$CONTENT"; then
87
+ # exit 1 # Skip node execution, pass through unchanged
88
+ # fi
89
+ #
90
+ # echo "$CONTENT"
91
+ # exit 0
92
+ class TransformerExecutor
93
+ DEFAULT_TIMEOUT = 60
94
+
95
+ # Result object for transformer execution
96
+ TransformerResult = Struct.new(:success, :content, :skip_execution, :halt, :error_message, keyword_init: true) do
97
+ def skip_execution?
98
+ skip_execution
99
+ end
100
+
101
+ def halt?
102
+ halt
103
+ end
104
+ end
105
+
106
+ class << self
107
+ # Execute a transformer shell command
108
+ #
109
+ # @param command [String] Shell command to execute
110
+ # @param context [NodeContext] Node context for building JSON input
111
+ # @param event [String] Event type ("input" or "output")
112
+ # @param node_name [Symbol] Current node name
113
+ # @param fallback_content [String] Content to use if skip (exit 1)
114
+ # @param timeout [Integer] Timeout in seconds (default: 60)
115
+ # @return [TransformerResult] Result with transformed content or skip/halt flags
116
+ def execute(command:, context:, event:, node_name:, fallback_content:, timeout: DEFAULT_TIMEOUT)
117
+ # Build JSON input for transformer
118
+ input_json = build_transformer_input(context, event, node_name)
119
+
120
+ # Build environment variables
121
+ env = build_environment(node_name: node_name)
122
+
123
+ # Execute command with JSON stdin and timeout
124
+ stdout, stderr, status = Timeout.timeout(timeout) do
125
+ Open3.capture3(
126
+ env,
127
+ command,
128
+ stdin_data: JSON.generate(input_json),
129
+ )
130
+ end
131
+
132
+ # Handle exit code
133
+ # Exit 0: Transform success, use STDOUT
134
+ # Exit 1: Skip node execution, use fallback_content (IGNORE STDOUT)
135
+ # Exit 2: Halt workflow with error (IGNORE STDOUT)
136
+ case status.exitstatus
137
+ when 0
138
+ # Success: use STDOUT as transformed content (strip trailing newline)
139
+ TransformerResult.new(
140
+ success: true,
141
+ content: stdout.chomp, # Remove trailing newline from echo
142
+ skip_execution: false,
143
+ halt: false,
144
+ error_message: nil,
145
+ )
146
+ when 1
147
+ # Skip node execution: use fallback_content unchanged (IGNORE STDOUT)
148
+ # For input transformer: skip_execution = true (skip LLM call)
149
+ # For output transformer: skip_execution = false (just pass through)
150
+ TransformerResult.new(
151
+ success: true,
152
+ content: fallback_content,
153
+ skip_execution: (event == "input"), # Only skip LLM for input transformers
154
+ halt: false,
155
+ error_message: nil,
156
+ )
157
+ when 2
158
+ # Halt workflow: return error (IGNORE STDOUT)
159
+ error_msg = stderr.strip.empty? ? "Transformer halted workflow (exit 2)" : stderr.strip
160
+ TransformerResult.new(
161
+ success: false,
162
+ content: nil,
163
+ skip_execution: false,
164
+ halt: true,
165
+ error_message: error_msg,
166
+ )
167
+ else
168
+ # Unknown exit code: treat as error (halt)
169
+ error_msg = "Transformer exited with code #{status.exitstatus}\nSTDERR: #{stderr}"
170
+ TransformerResult.new(
171
+ success: false,
172
+ content: nil,
173
+ skip_execution: false,
174
+ halt: true,
175
+ error_message: error_msg,
176
+ )
177
+ end
178
+ rescue Timeout::Error
179
+ # Timeout: halt workflow
180
+ TransformerResult.new(
181
+ success: false,
182
+ content: nil,
183
+ skip_execution: false,
184
+ halt: true,
185
+ error_message: "Transformer command timed out after #{timeout}s",
186
+ )
187
+ rescue StandardError => e
188
+ # Execution error: halt workflow
189
+ TransformerResult.new(
190
+ success: false,
191
+ content: nil,
192
+ skip_execution: false,
193
+ halt: true,
194
+ error_message: "Transformer command failed: #{e.message}",
195
+ )
196
+ end
197
+
198
+ private
199
+
200
+ # Build JSON input for transformer command
201
+ #
202
+ # @param context [NodeContext] Node context
203
+ # @param event [String] Event type ("input" or "output")
204
+ # @param node_name [Symbol] Node name
205
+ # @return [Hash] JSON data to pass on stdin
206
+ def build_transformer_input(context, event, node_name)
207
+ base = {
208
+ event: event,
209
+ node: node_name.to_s,
210
+ original_prompt: context.original_prompt,
211
+ content: context.content,
212
+ }
213
+
214
+ # Add all_results (convert Result objects to hashes)
215
+ if context.all_results && !context.all_results.empty?
216
+ base[:all_results] = context.all_results.transform_values do |result|
217
+ {
218
+ content: result.content,
219
+ agent: result.agent,
220
+ duration: result.duration,
221
+ success: result.success?,
222
+ }
223
+ end
224
+ end
225
+
226
+ # Add dependencies for input transformers
227
+ if event == "input" && context.dependencies
228
+ base[:dependencies] = context.dependencies.map(&:to_s)
229
+ end
230
+
231
+ base
232
+ end
233
+
234
+ # Build environment variables for transformer execution
235
+ #
236
+ # @param node_name [Symbol] Current node name
237
+ # @return [Hash] Environment variables
238
+ def build_environment(node_name:)
239
+ {
240
+ "SWARM_SDK_PROJECT_DIR" => Dir.pwd,
241
+ "SWARM_SDK_NODE_NAME" => node_name.to_s,
242
+ "PATH" => ENV.fetch("PATH", ""),
243
+ }
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ # NodeContext provides context information to node transformers
5
+ #
6
+ # This class is passed to input and output transformers, giving them access to:
7
+ # - The original user prompt
8
+ # - Results from all previous nodes
9
+ # - Current node metadata
10
+ # - Convenience accessors for common operations
11
+ #
12
+ # @example Input transformer
13
+ # input do |ctx|
14
+ # ctx.content # Previous node's content (convenience)
15
+ # ctx.original_prompt # Original user prompt
16
+ # ctx.all_results[:plan] # Access any previous node
17
+ # ctx.node_name # Current node name
18
+ # end
19
+ #
20
+ # @example Output transformer
21
+ # output do |ctx|
22
+ # ctx.content # Current result's content (convenience)
23
+ # ctx.original_prompt # Original user prompt
24
+ # ctx.all_results[:plan] # Access previous nodes
25
+ # end
26
+ class NodeContext
27
+ attr_reader :original_prompt, :all_results, :node_name, :dependencies
28
+
29
+ # For input transformers: result from previous node(s)
30
+ attr_reader :previous_result
31
+
32
+ # For output transformers: current node's result
33
+ attr_reader :result
34
+
35
+ class << self
36
+ # Create a NodeContext for input transformers
37
+ #
38
+ # @param previous_result [Result, Hash, String] Previous node's result or hash of results
39
+ # @param all_results [Hash<Symbol, Result>] Results from all completed nodes
40
+ # @param original_prompt [String] The original user prompt
41
+ # @param node_name [Symbol] Current node name
42
+ # @param dependencies [Array<Symbol>] Node dependencies
43
+ # @param transformed_content [String, nil] Already-transformed content from previous output transformer
44
+ # @return [NodeContext]
45
+ def for_input(previous_result:, all_results:, original_prompt:, node_name:, dependencies:, transformed_content: nil)
46
+ new(
47
+ previous_result: previous_result,
48
+ all_results: all_results,
49
+ original_prompt: original_prompt,
50
+ node_name: node_name,
51
+ dependencies: dependencies,
52
+ result: nil,
53
+ transformed_content: transformed_content,
54
+ )
55
+ end
56
+
57
+ # Create a NodeContext for output transformers
58
+ #
59
+ # @param result [Result] Current node's execution result
60
+ # @param all_results [Hash<Symbol, Result>] Results from all completed nodes (including current)
61
+ # @param original_prompt [String] The original user prompt
62
+ # @param node_name [Symbol] Current node name
63
+ # @return [NodeContext]
64
+ def for_output(result:, all_results:, original_prompt:, node_name:)
65
+ new(
66
+ result: result,
67
+ all_results: all_results,
68
+ original_prompt: original_prompt,
69
+ node_name: node_name,
70
+ dependencies: [],
71
+ previous_result: nil,
72
+ transformed_content: nil,
73
+ )
74
+ end
75
+ end
76
+
77
+ def initialize(previous_result:, all_results:, original_prompt:, node_name:, dependencies:, result:, transformed_content:)
78
+ @previous_result = previous_result
79
+ @result = result
80
+ @all_results = all_results
81
+ @original_prompt = original_prompt
82
+ @node_name = node_name
83
+ @dependencies = dependencies
84
+ @transformed_content = transformed_content
85
+ end
86
+
87
+ # Convenience accessor: Get content from previous_result or result
88
+ #
89
+ # For input transformers:
90
+ # - Returns transformed_content if available (from previous output transformer)
91
+ # - Otherwise returns previous_result.content (original content)
92
+ # - Returns nil for multiple dependencies (use all_results instead)
93
+ # For output transformers: returns result.content
94
+ #
95
+ # @return [String, nil]
96
+ def content
97
+ if @result
98
+ # Output transformer context: return current result's content
99
+ @result.content
100
+ elsif @transformed_content
101
+ # Input transformer with transformed content from previous output
102
+ @transformed_content
103
+ elsif @previous_result.respond_to?(:content)
104
+ # Input transformer context with Result object (original content)
105
+ @previous_result.content
106
+ elsif @previous_result.is_a?(Hash)
107
+ # Input transformer with multiple dependencies (hash of results)
108
+ nil # No single "content" - user must pick from all_results hash
109
+ else
110
+ # String or other type (initial prompt, no dependencies)
111
+ @previous_result.to_s
112
+ end
113
+ end
114
+
115
+ # Convenience accessor: Get agent from previous_result or result
116
+ #
117
+ # @return [String, nil]
118
+ def agent
119
+ if @result
120
+ @result.agent
121
+ elsif @previous_result.respond_to?(:agent)
122
+ @previous_result.agent
123
+ end
124
+ end
125
+
126
+ # Convenience accessor: Get logs from previous_result or result
127
+ #
128
+ # @return [Array, nil]
129
+ def logs
130
+ if @result
131
+ @result.logs
132
+ elsif @previous_result.respond_to?(:logs)
133
+ @previous_result.logs
134
+ end
135
+ end
136
+
137
+ # Convenience accessor: Get duration from previous_result or result
138
+ #
139
+ # @return [Float, nil]
140
+ def duration
141
+ if @result
142
+ @result.duration
143
+ elsif @previous_result.respond_to?(:duration)
144
+ @previous_result.duration
145
+ end
146
+ end
147
+
148
+ # Convenience accessor: Get error from previous_result or result
149
+ #
150
+ # @return [Exception, nil]
151
+ def error
152
+ if @result
153
+ @result.error
154
+ elsif @previous_result.respond_to?(:error)
155
+ @previous_result.error
156
+ end
157
+ end
158
+
159
+ # Convenience accessor: Check success status
160
+ #
161
+ # @return [Boolean, nil]
162
+ def success?
163
+ if @result
164
+ @result.success?
165
+ elsif @previous_result.respond_to?(:success?)
166
+ @previous_result.success?
167
+ end
168
+ end
169
+ end
170
+ end