claude_swarm 0.1.19 → 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: ecbec0c31b9252f826e28978e871cdc0003644d691a541906a8c36ae60c717f6
4
- data.tar.gz: 9d88ab7118d614108adcd2995b270f3c223e08a0a7ccd8768136f362f9f002d6
3
+ metadata.gz: fdad008c72dacb5ca491dc7c6b912c4bc8d46c7f2b578b3762ed7b5e09b4e3f4
4
+ data.tar.gz: a426057ff0bb859cd56a0bec71e80d02c1232fe6eb69a74e47ed3044f7121a58
5
5
  SHA512:
6
- metadata.gz: 66783c2bc619846d7ce78b33e290d34b414946a5e66714f0528ef312a1e641cc9c6950e30d492e1f428374da391f39e11556bffaa68ced4b4cda726987ea4a8c
7
- data.tar.gz: f04e7f694fb8f151c5ccfa4bd145a2184cb7674b78a1c3af414704664b6efadd5887d9f78e3ad8da4d7f41a25bdfb8e23d355eb79c4bb7675aebb79424995a59
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,20 @@
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
+
1
18
  ## [0.1.19]
2
19
 
3
20
  ### 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
@@ -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
 
@@ -652,10 +657,10 @@ claude-swarm watch 20250617_235233 -n 50
652
657
  claude-swarm list-sessions
653
658
  claude-swarm list-sessions --limit 20
654
659
 
655
- # Clean up stale session symlinks
660
+ # Clean up stale session symlinks and orphaned worktrees
656
661
  claude-swarm clean
657
662
 
658
- # Remove sessions older than 30 days
663
+ # Remove sessions and worktrees older than 30 days
659
664
  claude-swarm clean --days 30
660
665
  ```
661
666
 
@@ -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
@@ -207,7 +205,9 @@ module ClaudeSwarm
207
205
  desc: "Claude model to use for generation"
208
206
  def generate
209
207
  # Check if claude command exists
210
- unless system("which claude > /dev/null 2>&1")
208
+ begin
209
+ system!("command -v claude > /dev/null 2>&1")
210
+ rescue Error
211
211
  error "Claude CLI is not installed or not in PATH"
212
212
  say "To install Claude CLI, visit: https://docs.anthropic.com/en/docs/claude-code"
213
213
  exit 1
@@ -238,49 +238,30 @@ module ClaudeSwarm
238
238
 
239
239
  desc "ps", "List running Claude Swarm sessions"
240
240
  def ps
241
- require_relative "commands/ps"
242
241
  Commands::Ps.new.execute
243
242
  end
244
243
 
245
244
  desc "show SESSION_ID", "Show detailed session information"
246
245
  def show(session_id)
247
- require_relative "commands/show"
248
246
  Commands::Show.new.execute(session_id)
249
247
  end
250
248
 
251
- desc "clean", "Remove stale session symlinks"
249
+ desc "clean", "Remove stale session symlinks and orphaned worktrees"
252
250
  method_option :days, aliases: "-d", type: :numeric, default: 7,
253
251
  desc: "Remove sessions older than N days"
254
252
  def clean
255
- run_dir = File.expand_path("~/.claude-swarm/run")
256
- unless Dir.exist?(run_dir)
257
- say "No run directory found", :yellow
258
- return
259
- end
253
+ # Clean stale symlinks
254
+ cleaned_symlinks = clean_stale_symlinks(options[:days])
260
255
 
261
- cleaned = 0
262
- Dir.glob("#{run_dir}/*").each do |symlink|
263
- next unless File.symlink?(symlink)
256
+ # Clean orphaned worktrees
257
+ cleaned_worktrees = clean_orphaned_worktrees(options[:days])
264
258
 
265
- begin
266
- # Remove if target doesn't exist (stale)
267
- unless File.exist?(File.readlink(symlink))
268
- File.unlink(symlink)
269
- cleaned += 1
270
- next
271
- end
272
-
273
- # Remove if older than specified days
274
- if File.stat(symlink).mtime < Time.now - (options[:days] * 86_400)
275
- File.unlink(symlink)
276
- cleaned += 1
277
- end
278
- rescue StandardError
279
- # Skip problematic symlinks
280
- 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
281
264
  end
282
-
283
- say "Cleaned #{cleaned} stale session#{"s" unless cleaned == 1}", :green
284
265
  end
285
266
 
286
267
  desc "watch SESSION_ID", "Watch session logs"
@@ -477,81 +458,91 @@ module ClaudeSwarm
477
458
  nil
478
459
  end
479
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
+
480
542
  def build_generation_prompt(readme_content, output_file)
481
- <<~PROMPT
482
- You are a Claude Swarm configuration generator assistant. Your role is to help the user create a well-structured claude-swarm.yml file through an interactive conversation.
483
-
484
- ## Claude Swarm Overview
485
- Claude Swarm is a Ruby gem that orchestrates multiple Claude Code instances as a collaborative AI development team. It enables running AI agents with specialized roles, tools, and directory contexts, communicating via MCP (Model Context Protocol).
486
-
487
- Key capabilities:
488
- - Define multiple AI instances with different roles and specializations
489
- - Set up connections between instances for collaboration
490
- - Restrict tools based on each instance's responsibilities
491
- - Run instances in different directories or Git worktrees
492
- - Support for custom system prompts per instance
493
- - Choose appropriate models (opus for complex tasks, sonnet for simpler ones)
494
-
495
- ## Your Task
496
- 1. Start by asking about the user's project structure and development needs
497
- 2. Understand what kind of team they need (roles, specializations)
498
- 3. Suggest an appropriate swarm topology based on their needs
499
- 4. Help them refine and customize the configuration
500
- 5. Generate the final claude-swarm.yml content
501
- 6. When the configuration is complete, save it to: #{output_file || "a descriptive filename based on the swarm's function"}
502
-
503
- ## File Naming Convention
504
- #{output_file ? "The user has specified the output file: #{output_file}" : "Since no output file was specified, name the file based on the swarm's function. Examples:\n - web-dev-swarm.yml for full-stack web development teams\n - data-pipeline-swarm.yml for data processing teams\n - microservices-swarm.yml for microservice architectures\n - mobile-app-swarm.yml for mobile development teams\n - ml-research-swarm.yml for machine learning teams\n - devops-swarm.yml for infrastructure and deployment teams\n Use descriptive names that clearly indicate the swarm's purpose."}
505
-
506
- ## Configuration Structure
507
- ```yaml
508
- version: 1
509
- swarm:
510
- name: "Descriptive Swarm Name"
511
- main: main_instance_name
512
- instances:
513
- instance_name:
514
- description: "Clear description of role and responsibilities"
515
- directory: ./path/to/directory
516
- model: sonnet # or opus for complex tasks
517
- prompt: "Custom system prompt for specialization"
518
- allowed_tools: [Read, Edit, Write, Bash]
519
- connections: [other_instance_names] # Optional
520
- ```
521
-
522
- ## Best Practices to Follow
523
- - Use descriptive, role-based instance names (e.g., frontend_dev, api_architect)
524
- - Write clear descriptions explaining each instance's responsibilities
525
- - Choose opus model for complex architectural or algorithmic tasks and routine development.
526
- - Choose sonnet model for simpler tasks
527
- - Set up logical connections (e.g., lead → team members, architect → implementers), but avoid circular dependencies.
528
- - Always add this to the end of every prompt: `For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.`
529
- - Select tools based on each instance's actual needs:
530
- - Read: For code review and analysis roles
531
- - Edit: For active development roles
532
- - Write: For creating new files
533
- - Bash: For running commands, tests, builds
534
- - MultiEdit: For editing multiple files at once
535
- - WebFetch: For fetching information from the web
536
- - WebSearch: For searching the web
537
- - Use custom prompts to specialize each instance's expertise
538
- - Organize directories to match project structure
539
-
540
- ## Interactive Questions to Ask
541
- - What type of project are you working on?
542
- - What's your project's directory structure?
543
- - What are the main technologies/frameworks you're using?
544
- - What development tasks do you need help with?
545
- - Do you need specialized roles (testing, DevOps, documentation)?
546
- - Are there specific areas that need focused attention?
547
- - Do you have multiple repositories or services to coordinate?
548
-
549
- <full_readme>
550
- #{readme_content}
551
- </full_readme>
552
-
553
- Start the conversation by greeting the user and asking: "What kind of project would you like to create a Claude Swarm for?"
554
- PROMPT
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)
555
546
  end
556
547
  end
557
548
  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