claude_swarm 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1aaf0eadabd67afb0bd79738de8c7fcbfb65887d477a2c0c002017cff9de15ec
4
- data.tar.gz: 777f04c044a2204477d1328dc31b0b5361fa0007d0cf637254d3531260e3149d
3
+ metadata.gz: fdad008c72dacb5ca491dc7c6b912c4bc8d46c7f2b578b3762ed7b5e09b4e3f4
4
+ data.tar.gz: a426057ff0bb859cd56a0bec71e80d02c1232fe6eb69a74e47ed3044f7121a58
5
5
  SHA512:
6
- metadata.gz: cb39225f7a1f4cfd7c0d7e0e3068ad5527473a04744ed0d18525b37452e7b7d4fe67c83f03fbb5c662591e51ed9c10a2360c5943676dc944ea83680652c977b7
7
- data.tar.gz: 807b10f87678f4f49df363fbcc53bf3a71d14848adf2ebbad1d701737aad8f9ce6b894ebe7f6e84c45234a1f414ec1c1d171b79189090b6d6de2a71df00e05a7
6
+ metadata.gz: b7fb86ea647403073665b860dde694ef2ae0c983f8fc011189d999f97d57e8b80f552b9e736b1f46cb888ac3c4877666dac060e1335db39bd2719f120ad35937
7
+ data.tar.gz: f1670e4c35e9681054dbe5ab2bcc87d7ef24a6ad4f33a21d3e445c32cb1e917eedbc2984c483b06750469fa08e8256109693a121f337ff813a8ff1944ecc42f1
data/.rubocop.yml CHANGED
@@ -37,6 +37,9 @@ Naming/AccessorMethodName:
37
37
  Metrics/BlockLength:
38
38
  Enabled: false
39
39
 
40
+ Metrics/BlockNesting:
41
+ Enabled: false
42
+
40
43
  Metrics/ClassLength:
41
44
  Enabled: false
42
45
 
data/CHANGELOG.md CHANGED
@@ -1,3 +1,42 @@
1
+ ## [0.1.20]
2
+
3
+ ### Added
4
+ - **External worktree directory**: Git worktrees are now created in `~/.claude-swarm/worktrees/` for better isolation from the main repository
5
+ - Prevents conflicts with bundler and other tools
6
+ - Each unique Git repository gets its own worktree with the same name
7
+ - Session metadata tracks worktree information for restoration
8
+ - **Zeitwerk autoloading**: Implemented Zeitwerk for better code organization and loading
9
+
10
+ ### Changed
11
+ - **Improved team coordination**: Removed circular dependencies in team configurations
12
+ - **Better code organization**: Refactored code structure to work with Zeitwerk autoloading
13
+
14
+ ### Internal
15
+ - Updated Gemfile.lock to include zeitwerk dependency
16
+ - Added prompt best practices documentation
17
+
18
+ ## [0.1.19]
19
+
20
+ ### Added
21
+ - **Interactive configuration generator**: New `claude-swarm generate` command launches Claude to help create swarm configurations
22
+ - Runs Claude in interactive mode with an initial prompt to guide configuration creation
23
+ - Customizable output file with `-o/--output` option
24
+ - When no output file is specified, Claude names the file based on the swarm's function (e.g., web-dev-swarm.yml, data-pipeline-swarm.yml)
25
+ - Model selection with `-m/--model` option (default: sonnet)
26
+ - Checks for Claude CLI installation and provides helpful error message if not found
27
+ - Includes comprehensive initial prompt with Claude Swarm overview, best practices, and common patterns
28
+ - Full README content is included in the prompt within `<full_readme>` tags for complete context
29
+ - Examples:
30
+ - `claude-swarm generate` - Claude names file based on swarm function
31
+ - `claude-swarm generate -o my-team.yml --model opus` - Custom file and model
32
+
33
+ ### Fixed
34
+ - **ps command path display**: The `claude-swarm ps` command now shows expanded absolute paths instead of raw YAML values
35
+ - Relative paths like `.` are expanded to their full absolute paths (e.g., `/Users/paulo/project`)
36
+ - Multiple directories are properly expanded and displayed as comma-separated values
37
+ - Worktree directories are correctly shown when sessions use worktrees (e.g., `/path/to/repo/.worktrees/feature-branch`)
38
+ - Path resolution uses the start_directory from session metadata for accurate expansion
39
+
1
40
  ## [0.1.18]
2
41
 
3
42
  ### Added
data/CLAUDE.md CHANGED
@@ -71,14 +71,15 @@ instances:
71
71
  ```
72
72
 
73
73
  ### Worktree Behavior
74
- - Worktrees are created inside each repository in a `.worktrees/` directory
75
- - A `.gitignore` file is automatically created inside `.worktrees/` to ignore all contents
74
+ - Worktrees are created in external directory: `~/.claude-swarm/worktrees/[session_id]/[repo_name-hash]/[worktree_name]`
75
+ - This ensures proper isolation from the main repository and avoids conflicts with bundler and other tools
76
76
  - Each unique Git repository gets its own worktree with the same name
77
77
  - All instance directories are mapped to their worktree equivalents
78
78
  - Worktrees are automatically cleaned up when the swarm exits
79
79
  - Session metadata tracks worktree information for restoration
80
80
  - Non-Git directories are used as-is without creating worktrees
81
81
  - Existing worktrees with the same name are reused
82
+ - The `claude-swarm clean` command removes orphaned worktrees
82
83
 
83
84
  ## Architecture
84
85
 
data/README.md CHANGED
@@ -27,7 +27,13 @@ Claude Swarm orchestrates multiple Claude Code instances as a collaborative AI d
27
27
 
28
28
  ## Installation
29
29
 
30
- Install the gem by executing:
30
+ Install [Claude CLI](https://docs.anthropic.com/en/docs/claude-code/overview) if you haven't already:
31
+
32
+ ```bash
33
+ npm install -g @anthropic-ai/claude-code
34
+ ```
35
+
36
+ Install this gem by executing:
31
37
 
32
38
  ```bash
33
39
  gem install claude_swarm
@@ -55,7 +61,7 @@ bundle install
55
61
 
56
62
  ### Quick Start
57
63
 
58
- 1. Run `claude-swarm init` or create a `claude-swarm.yml` file in your project:
64
+ 1. Run `claude-swarm init` to create a basic template, or use `claude-swarm generate` for an interactive configuration experience with Claude's help. You can also manually create a `claude-swarm.yml` file in your project:
59
65
 
60
66
  ```yaml
61
67
  version: 1
@@ -522,17 +528,16 @@ swarm:
522
528
 
523
529
  #### Git Worktrees
524
530
 
525
- Claude Swarm supports running instances in Git worktrees, allowing isolated work without affecting your main repository state. Worktrees are created inside each repository in a `.worktrees/` directory following Git best practices.
531
+ Claude Swarm supports running instances in Git worktrees, allowing isolated work without affecting your main repository state. Worktrees are created in an external directory (`~/.claude-swarm/worktrees/`) to ensure proper isolation from the main repository and avoid conflicts with bundler and other tools.
526
532
 
527
533
  **Example Structure:**
528
534
  ```
529
- my-repo/
530
- ├── .git/
531
- ├── .worktrees/ (created by Claude Swarm)
532
- ├── .gitignore (auto-created, contains "*")
533
- └── feature-x/ (worktree for feature-x branch)
534
- ├── src/
535
- └── tests/
535
+ ~/.claude-swarm/worktrees/
536
+ └── [session_id]/
537
+ ├── my-repo-[hash]/
538
+ └── feature-x/ (worktree for feature-x branch)
539
+ └── other-repo-[hash]/
540
+ └── feature-x/ (worktree for feature-x branch)
536
541
  ```
537
542
 
538
543
  **CLI Option:**
@@ -577,14 +582,14 @@ swarm:
577
582
  - Omitted - Follows CLI behavior (use worktree if `--worktree` is specified)
578
583
 
579
584
  **Notes:**
580
- - Worktrees are created inside each repository in a `.worktrees/` directory
581
585
  - Auto-generated worktree names use the session ID (e.g., `worktree-20241206_143022`)
582
586
  - This makes it easy to correlate worktrees with their Claude Swarm sessions
583
- - A `.gitignore` file is automatically created inside `.worktrees/` to ignore all worktree contents
587
+ - Worktrees are stored externally in `~/.claude-swarm/worktrees/[session_id]/`
584
588
  - All worktrees are automatically cleaned up when the swarm exits
585
589
  - Worktrees with the same name across different repositories share that name
586
590
  - Non-Git directories are unaffected by worktree settings
587
591
  - Existing worktrees with the same name are reused
592
+ - The `claude-swarm clean` command also removes orphaned worktrees
588
593
 
589
594
  ### Command Line Options
590
595
 
@@ -612,6 +617,15 @@ claude-swarm --worktree # Auto-generated name (worktree-SESSION
612
617
  claude-swarm --worktree feature-branch # Custom worktree name
613
618
  claude-swarm -w # Short form
614
619
 
620
+ # Initialize a new configuration file
621
+ claude-swarm init
622
+ claude-swarm init --force # Overwrite existing file
623
+
624
+ # Generate configuration interactively with Claude's help
625
+ claude-swarm generate # Claude names file based on swarm function
626
+ claude-swarm generate -o my-swarm.yml # Custom output file
627
+ claude-swarm generate --model opus # Use a specific model
628
+
615
629
  # Show version
616
630
  claude-swarm version
617
631
 
@@ -643,10 +657,10 @@ claude-swarm watch 20250617_235233 -n 50
643
657
  claude-swarm list-sessions
644
658
  claude-swarm list-sessions --limit 20
645
659
 
646
- # Clean up stale session symlinks
660
+ # Clean up stale session symlinks and orphaned worktrees
647
661
  claude-swarm clean
648
662
 
649
- # Remove sessions older than 30 days
663
+ # Remove sessions and worktrees older than 30 days
650
664
  claude-swarm clean --days 30
651
665
  ```
652
666
 
data/claude-swarm.yml CHANGED
@@ -1,14 +1,64 @@
1
1
  version: 1
2
2
  swarm:
3
- name: "Swarm Name"
4
- main: claude_swarm_architect
3
+ name: "Codex CLI Integration Team"
4
+ main: claude_swarm_developer
5
5
  instances:
6
- claude_swarm_architect:
7
- description: "Lead architect"
6
+ claude_swarm_developer:
7
+ description: "Claude Swarm developer implementing Codex CLI integration to enable OpenAI model support"
8
8
  directory: .
9
9
  model: opus
10
- prompt: "You are an expert in Claude swarm architecture"
11
10
  vibe: true
12
- connections: [claudeception_architect]
11
+ connections: [codex_cli_expert]
12
+ prompt: |
13
+ You are an expert Claude Swarm developer responsible for integrating Codex CLI functionality into Claude Swarm to enable the use of OpenAI models alongside Claude models.
13
14
 
14
-
15
+ Your primary objectives:
16
+ 1. Work closely with the codex_cli_expert to understand how Codex CLI works
17
+ 2. Design and implement the integration of Codex CLI into Claude Swarm
18
+ 3. Ensure seamless interoperability between Claude and OpenAI models in swarm configurations
19
+ 4. Extend the YAML configuration format to support OpenAI model specifications
20
+ 5. Implement the necessary Ruby code to launch and manage Codex CLI instances
21
+ 6. Ensure proper session management and MCP communication for Codex instances
22
+ 7. Update documentation and examples to showcase the new capabilities
23
+
24
+ When working on this integration:
25
+ - First, consult with the codex_cli_expert to understand Codex CLI's architecture, API, and usage patterns
26
+ - Identify the key integration points and design patterns needed
27
+ - Implement the changes incrementally, testing as you go
28
+ - Consider backward compatibility with existing Claude Swarm configurations
29
+ - Think about how users will specify OpenAI models in their swarm configurations
30
+
31
+ For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.
32
+
33
+ Start by asking the codex_cli_expert for a comprehensive overview of how Codex CLI works, its command-line interface, configuration format, and any relevant APIs or extension points.
34
+
35
+ codex_cli_expert:
36
+ description: "Codex CLI expert providing detailed information about Codex CLI architecture and integration points"
37
+ directory: /Users/paulo/src/github.com/openai/codex
38
+ model: opus
39
+ vibe: true
40
+ prompt: |
41
+ You are an expert on the Codex CLI codebase with deep knowledge of its architecture, implementation, and usage patterns. Your role is to help understand how Codex CLI works so they can integrate it into Claude Swarm.
42
+
43
+ Your responsibilities:
44
+ 1. Analyze and explain the Codex CLI codebase structure and architecture
45
+ 2. Document the command-line interface, flags, and configuration options
46
+ 3. Identify key APIs, interfaces, and extension points that could be used for integration
47
+ 4. Explain how Codex CLI manages sessions, contexts, and model interactions
48
+ 5. Provide code examples and usage patterns
49
+ 6. Suggest integration strategies based on your understanding of both systems
50
+ 7. Answer any technical questions about Codex CLI implementation details
51
+
52
+ When analyzing the codebase:
53
+ - Start with a high-level overview of the project structure
54
+ - Examine the main entry points and command processing logic
55
+ - Look for configuration file formats and parsing logic
56
+ - Understand how Codex CLI communicates with OpenAI models
57
+ - Identify any MCP or similar protocol support
58
+ - Look for session management and state persistence features
59
+ - Look for logging output, log streams, log to a file, JSON logs, verbose mode, etc.
60
+ - Check for any existing integration or extension mechanisms
61
+
62
+ For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.
63
+
64
+ Be proactive in providing comprehensive information. Don't wait to be asked for every detail - anticipate what the claude_swarm_developer will need to know for a successful integration.
@@ -6,7 +6,7 @@ swarm:
6
6
  coordinator:
7
7
  description: "Main coordinator managing the team"
8
8
  directory: .
9
- model: haiku
9
+ model: sonnet
10
10
  connections: [analyzer, reporter]
11
11
  prompt: "You coordinate analysis and reporting tasks"
12
12
  allowed_tools: [Read, Edit]
@@ -14,13 +14,13 @@ swarm:
14
14
  analyzer:
15
15
  description: "Data analyzer processing information"
16
16
  directory: ./data
17
- model: haiku
17
+ model: sonnet
18
18
  prompt: "You analyze data and provide insights"
19
19
  allowed_tools: [Read, Bash]
20
20
 
21
21
  reporter:
22
22
  description: "Report generator creating summaries"
23
23
  directory: ./reports
24
- model: haiku
24
+ model: sonnet
25
25
  prompt: "You generate reports from analysis"
26
- allowed_tools: [Write, Edit]
26
+ allowed_tools: [Write, Edit]
@@ -4,7 +4,6 @@ require "json"
4
4
  require "open3"
5
5
  require "logger"
6
6
  require "fileutils"
7
- require_relative "session_path"
8
7
 
9
8
  module ClaudeSwarm
10
9
  class ClaudeCodeExecutor
@@ -2,11 +2,6 @@
2
2
 
3
3
  require "fast_mcp_annotations"
4
4
  require "json"
5
- require_relative "claude_code_executor"
6
- require_relative "task_tool"
7
- require_relative "session_info_tool"
8
- require_relative "reset_session_tool"
9
- require_relative "process_tracker"
10
5
 
11
6
  module ClaudeSwarm
12
7
  class ClaudeMcpServer
@@ -2,13 +2,11 @@
2
2
 
3
3
  require "thor"
4
4
  require "json"
5
- require_relative "configuration"
6
- require_relative "mcp_generator"
7
- require_relative "orchestrator"
8
- require_relative "claude_mcp_server"
5
+ require "erb"
9
6
 
10
7
  module ClaudeSwarm
11
8
  class CLI < Thor
9
+ include SystemUtils
12
10
  def self.exit_on_failure?
13
11
  true
14
12
  end
@@ -200,6 +198,39 @@ module ClaudeSwarm
200
198
  say "Edit this file to configure your swarm, then run 'claude-swarm' to start"
201
199
  end
202
200
 
201
+ desc "generate", "Launch Claude to help generate a swarm configuration interactively"
202
+ method_option :output, aliases: "-o", type: :string,
203
+ desc: "Output file path for the generated configuration"
204
+ method_option :model, aliases: "-m", type: :string, default: "sonnet",
205
+ desc: "Claude model to use for generation"
206
+ def generate
207
+ # Check if claude command exists
208
+ begin
209
+ system!("command -v claude > /dev/null 2>&1")
210
+ rescue Error
211
+ error "Claude CLI is not installed or not in PATH"
212
+ say "To install Claude CLI, visit: https://docs.anthropic.com/en/docs/claude-code"
213
+ exit 1
214
+ end
215
+
216
+ # Read README for context about claude-swarm capabilities
217
+ readme_path = File.join(__dir__, "../../README.md")
218
+ readme_content = File.exist?(readme_path) ? File.read(readme_path) : ""
219
+
220
+ # Build the pre-prompt
221
+ preprompt = build_generation_prompt(readme_content, options[:output])
222
+
223
+ # Launch Claude in interactive mode with the initial prompt
224
+ cmd = [
225
+ "claude",
226
+ "--model", options[:model],
227
+ preprompt
228
+ ]
229
+
230
+ # Execute and let the user take over
231
+ exec(*cmd)
232
+ end
233
+
203
234
  desc "version", "Show Claude Swarm version"
204
235
  def version
205
236
  say "Claude Swarm #{VERSION}"
@@ -207,49 +238,30 @@ module ClaudeSwarm
207
238
 
208
239
  desc "ps", "List running Claude Swarm sessions"
209
240
  def ps
210
- require_relative "commands/ps"
211
241
  Commands::Ps.new.execute
212
242
  end
213
243
 
214
244
  desc "show SESSION_ID", "Show detailed session information"
215
245
  def show(session_id)
216
- require_relative "commands/show"
217
246
  Commands::Show.new.execute(session_id)
218
247
  end
219
248
 
220
- desc "clean", "Remove stale session symlinks"
249
+ desc "clean", "Remove stale session symlinks and orphaned worktrees"
221
250
  method_option :days, aliases: "-d", type: :numeric, default: 7,
222
251
  desc: "Remove sessions older than N days"
223
252
  def clean
224
- run_dir = File.expand_path("~/.claude-swarm/run")
225
- unless Dir.exist?(run_dir)
226
- say "No run directory found", :yellow
227
- return
228
- end
253
+ # Clean stale symlinks
254
+ cleaned_symlinks = clean_stale_symlinks(options[:days])
229
255
 
230
- cleaned = 0
231
- Dir.glob("#{run_dir}/*").each do |symlink|
232
- next unless File.symlink?(symlink)
233
-
234
- begin
235
- # Remove if target doesn't exist (stale)
236
- unless File.exist?(File.readlink(symlink))
237
- File.unlink(symlink)
238
- cleaned += 1
239
- next
240
- end
256
+ # Clean orphaned worktrees
257
+ cleaned_worktrees = clean_orphaned_worktrees(options[:days])
241
258
 
242
- # Remove if older than specified days
243
- if File.stat(symlink).mtime < Time.now - (options[:days] * 86_400)
244
- File.unlink(symlink)
245
- cleaned += 1
246
- end
247
- rescue StandardError
248
- # Skip problematic symlinks
249
- end
259
+ if cleaned_symlinks.positive? || cleaned_worktrees.positive?
260
+ say "Cleaned #{cleaned_symlinks} stale symlink#{"s" unless cleaned_symlinks == 1}", :green
261
+ say "Cleaned #{cleaned_worktrees} orphaned worktree#{"s" unless cleaned_worktrees == 1}", :green
262
+ else
263
+ say "No cleanup needed", :green
250
264
  end
251
-
252
- say "Cleaned #{cleaned} stale session#{"s" unless cleaned == 1}", :green
253
265
  end
254
266
 
255
267
  desc "watch SESSION_ID", "Watch session logs"
@@ -445,5 +457,92 @@ module ClaudeSwarm
445
457
 
446
458
  nil
447
459
  end
460
+
461
+ def clean_stale_symlinks(days)
462
+ run_dir = File.expand_path("~/.claude-swarm/run")
463
+ return 0 unless Dir.exist?(run_dir)
464
+
465
+ cleaned = 0
466
+ Dir.glob("#{run_dir}/*").each do |symlink|
467
+ next unless File.symlink?(symlink)
468
+
469
+ begin
470
+ # Remove if target doesn't exist (stale)
471
+ unless File.exist?(File.readlink(symlink))
472
+ File.unlink(symlink)
473
+ cleaned += 1
474
+ next
475
+ end
476
+
477
+ # Remove if older than specified days
478
+ if File.stat(symlink).mtime < Time.now - (days * 86_400)
479
+ File.unlink(symlink)
480
+ cleaned += 1
481
+ end
482
+ rescue StandardError
483
+ # Skip problematic symlinks
484
+ end
485
+ end
486
+
487
+ cleaned
488
+ end
489
+
490
+ def clean_orphaned_worktrees(days)
491
+ worktrees_dir = File.expand_path("~/.claude-swarm/worktrees")
492
+ return 0 unless Dir.exist?(worktrees_dir)
493
+
494
+ sessions_dir = File.expand_path("~/.claude-swarm/sessions")
495
+ cleaned = 0
496
+
497
+ Dir.glob("#{worktrees_dir}/*").each do |session_worktree_dir|
498
+ session_id = File.basename(session_worktree_dir)
499
+
500
+ # Skip if session still exists
501
+ next if Dir.glob("#{sessions_dir}/*/#{session_id}").any? { |path| File.exist?(File.join(path, "config.yml")) }
502
+
503
+ # Check age of worktree directory
504
+ begin
505
+ if File.stat(session_worktree_dir).mtime < Time.now - (days * 86_400)
506
+ # Remove all git worktrees in this session directory
507
+ Dir.glob("#{session_worktree_dir}/*/*").each do |worktree_path|
508
+ next unless File.directory?(worktree_path)
509
+
510
+ # Try to find the git repo and remove the worktree properly
511
+ git_dir = File.join(worktree_path, ".git")
512
+ if File.exist?(git_dir)
513
+ # Read the gitdir file to find the repo
514
+ gitdir_content = File.read(git_dir).strip
515
+ if gitdir_content.start_with?("gitdir:")
516
+ repo_git_path = gitdir_content.sub("gitdir: ", "")
517
+ # Extract repo path from .git/worktrees path
518
+ repo_path = repo_git_path.split("/.git/worktrees/").first
519
+
520
+ # Try to remove worktree via git
521
+ system!("git", "-C", repo_path, "worktree", "remove", worktree_path, "--force",
522
+ out: File::NULL, err: File::NULL)
523
+ end
524
+ end
525
+
526
+ # Force remove directory if it still exists
527
+ FileUtils.rm_rf(worktree_path)
528
+ end
529
+
530
+ # Remove the session worktree directory
531
+ FileUtils.rm_rf(session_worktree_dir)
532
+ cleaned += 1
533
+ end
534
+ rescue StandardError => e
535
+ say "Warning: Failed to clean worktree directory #{session_worktree_dir}: #{e.message}", :yellow if options[:debug]
536
+ end
537
+ end
538
+
539
+ cleaned
540
+ end
541
+
542
+ def build_generation_prompt(readme_content, output_file)
543
+ template_path = File.expand_path("templates/generation_prompt.md.erb", __dir__)
544
+ template = File.read(template_path)
545
+ ERB.new(template, trim_mode: "-").result(binding)
546
+ end
448
547
  end
449
548
  end
@@ -86,6 +86,11 @@ module ClaudeSwarm
86
86
  swarm_name = config.dig("swarm", "name") || "Unknown"
87
87
  main_instance = config.dig("swarm", "main")
88
88
 
89
+ # Get base directory from session metadata or start_directory file
90
+ base_dir = Dir.pwd
91
+ start_dir_file = File.join(session_dir, "start_directory")
92
+ base_dir = File.read(start_dir_file).strip if File.exist?(start_dir_file)
93
+
89
94
  # Get all directories - handle both string and array formats
90
95
  dir_config = config.dig("swarm", "instances", main_instance, "directory")
91
96
  directories = if dir_config.is_a?(Array)
@@ -93,7 +98,16 @@ module ClaudeSwarm
93
98
  else
94
99
  [dir_config || "."]
95
100
  end
96
- directories_str = directories.join(", ")
101
+
102
+ # Expand paths relative to the base directory
103
+ expanded_directories = directories.map do |dir|
104
+ File.expand_path(dir, base_dir)
105
+ end
106
+
107
+ # Check for worktree information in session metadata
108
+ expanded_directories = apply_worktree_paths(expanded_directories, session_dir)
109
+
110
+ directories_str = expanded_directories.join(", ")
97
111
 
98
112
  # Calculate total cost from JSON log
99
113
  total_cost = calculate_total_cost(session_dir)
@@ -143,6 +157,44 @@ module ClaudeSwarm
143
157
  def truncate(str, length)
144
158
  str.length > length ? "#{str[0...length - 2]}.." : str
145
159
  end
160
+
161
+ def apply_worktree_paths(directories, session_dir)
162
+ session_metadata_file = File.join(session_dir, "session_metadata.json")
163
+ return directories unless File.exist?(session_metadata_file)
164
+
165
+ metadata = JSON.parse(File.read(session_metadata_file))
166
+ worktree_info = metadata["worktree"]
167
+ return directories unless worktree_info && worktree_info["enabled"]
168
+
169
+ # Get the created worktree paths
170
+ created_paths = worktree_info["created_paths"] || {}
171
+
172
+ # For each directory, find the appropriate worktree path
173
+ directories.map do |dir|
174
+ # Find if this directory has a worktree created
175
+ repo_root = find_git_root(dir)
176
+ next dir unless repo_root
177
+
178
+ # Look for a worktree with this repo root
179
+ worktree_key = created_paths.keys.find { |key| key.start_with?("#{repo_root}:") }
180
+ worktree_key ? created_paths[worktree_key] : dir
181
+ end
182
+ end
183
+
184
+ def worktree_path_for(dir, worktree_name)
185
+ git_root = find_git_root(dir)
186
+ git_root ? File.join(git_root, ".worktrees", worktree_name) : dir
187
+ end
188
+
189
+ def find_git_root(dir)
190
+ current = File.expand_path(dir)
191
+ while current != "/"
192
+ return current if File.exist?(File.join(current, ".git"))
193
+
194
+ current = File.dirname(current)
195
+ end
196
+ nil
197
+ end
146
198
  end
147
199
  end
148
200
  end
@@ -4,7 +4,6 @@ require "json"
4
4
  require "fileutils"
5
5
  require "shellwords"
6
6
  require "securerandom"
7
- require_relative "session_path"
8
7
 
9
8
  module ClaudeSwarm
10
9
  class McpGenerator
@@ -4,12 +4,10 @@ require "English"
4
4
  require "shellwords"
5
5
  require "json"
6
6
  require "fileutils"
7
- require_relative "session_path"
8
- require_relative "process_tracker"
9
- require_relative "worktree_manager"
10
7
 
11
8
  module ClaudeSwarm
12
9
  class Orchestrator
10
+ include SystemUtils
13
11
  RUN_DIR = File.expand_path("~/.claude-swarm/run")
14
12
 
15
13
  def initialize(configuration, mcp_generator, vibe: false, prompt: nil, stream_logs: false, debug: false,
@@ -174,7 +172,7 @@ module ClaudeSwarm
174
172
 
175
173
  command = build_main_command(main_instance)
176
174
  if @debug && !@prompt
177
- puts "Running: #{command}"
175
+ puts "🏃 Running: #{format_command_for_display(command)}"
178
176
  puts
179
177
  end
180
178
 
@@ -184,7 +182,7 @@ module ClaudeSwarm
184
182
 
185
183
  # Execute the main instance - this will cascade to other instances via MCP
186
184
  Dir.chdir(main_instance[:directory]) do
187
- system(*command)
185
+ system!(*command)
188
186
  end
189
187
 
190
188
  # Clean up log streaming thread
@@ -362,6 +360,16 @@ module ClaudeSwarm
362
360
  end
363
361
  end
364
362
 
363
+ def format_command_for_display(command)
364
+ command.map do |part|
365
+ if part.match?(/\s|'|"/)
366
+ "'#{part.gsub("'", "'\\\\''")}'"
367
+ else
368
+ part
369
+ end
370
+ end.join(" ")
371
+ end
372
+
365
373
  def build_main_command(instance)
366
374
  parts = [
367
375
  "claude",
@@ -456,7 +464,9 @@ module ClaudeSwarm
456
464
  end
457
465
 
458
466
  # Restore worktrees using the saved configuration
459
- @worktree_manager = WorktreeManager.new(worktree_data["shared_name"])
467
+ # Extract session ID from the session path
468
+ session_id = File.basename(session_path)
469
+ @worktree_manager = WorktreeManager.new(worktree_data["shared_name"], session_id: session_id)
460
470
 
461
471
  # Get all instances and restore their worktree paths
462
472
  all_instances = @config.instances.values
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
4
+
5
+ module ClaudeSwarm
6
+ module SystemUtils
7
+ def system!(*args)
8
+ success = system(*args)
9
+ unless success
10
+ exit_status = $CHILD_STATUS&.exitstatus || 1
11
+ command_str = args.size == 1 ? args.first : args.join(" ")
12
+ warn "❌ Command failed with exit status: #{exit_status}"
13
+ raise Error, "Command failed with exit status #{exit_status}: #{command_str}"
14
+ end
15
+ success
16
+ end
17
+ end
18
+ end