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 +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +39 -0
- data/CLAUDE.md +3 -2
- data/README.md +28 -14
- data/claude-swarm.yml +57 -7
- data/examples/monitoring-demo.yml +4 -4
- data/lib/claude_swarm/claude_code_executor.rb +0 -1
- data/lib/claude_swarm/claude_mcp_server.rb +0 -5
- data/lib/claude_swarm/cli.rb +132 -33
- data/lib/claude_swarm/commands/ps.rb +53 -1
- data/lib/claude_swarm/mcp_generator.rb +0 -1
- data/lib/claude_swarm/orchestrator.rb +16 -6
- data/lib/claude_swarm/system_utils.rb +18 -0
- data/lib/claude_swarm/templates/generation_prompt.md.erb +230 -0
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/claude_swarm/worktree_manager.rb +136 -32
- data/lib/claude_swarm.rb +21 -12
- data/llms.txt +2 -2
- data/single.yml +92 -0
- data/team.yml +344 -0
- metadata +19 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fdad008c72dacb5ca491dc7c6b912c4bc8d46c7f2b578b3762ed7b5e09b4e3f4
|
4
|
+
data.tar.gz: a426057ff0bb859cd56a0bec71e80d02c1232fe6eb69a74e47ed3044f7121a58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7fb86ea647403073665b860dde694ef2ae0c983f8fc011189d999f97d57e8b80f552b9e736b1f46cb888ac3c4877666dac060e1335db39bd2719f120ad35937
|
7
|
+
data.tar.gz: f1670e4c35e9681054dbe5ab2bcc87d7ef24a6ad4f33a21d3e445c32cb1e917eedbc2984c483b06750469fa08e8256109693a121f337ff813a8ff1944ecc42f1
|
data/.rubocop.yml
CHANGED
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
|
75
|
-
-
|
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
|
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
|
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
|
-
|
530
|
-
|
531
|
-
├──
|
532
|
-
│
|
533
|
-
|
534
|
-
|
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
|
-
-
|
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: "
|
4
|
-
main:
|
3
|
+
name: "Codex CLI Integration Team"
|
4
|
+
main: claude_swarm_developer
|
5
5
|
instances:
|
6
|
-
|
7
|
-
description: "
|
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: [
|
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:
|
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:
|
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:
|
24
|
+
model: sonnet
|
25
25
|
prompt: "You generate reports from analysis"
|
26
|
-
allowed_tools: [Write, Edit]
|
26
|
+
allowed_tools: [Write, Edit]
|
@@ -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
|
data/lib/claude_swarm/cli.rb
CHANGED
@@ -2,13 +2,11 @@
|
|
2
2
|
|
3
3
|
require "thor"
|
4
4
|
require "json"
|
5
|
-
|
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
|
-
|
225
|
-
|
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
|
-
|
231
|
-
|
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
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
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
|
-
|
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,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
|
-
|
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
|