BoatmanMode can be used as a Go module/library in your own applications, allowing you to programmatically execute development workflows.
Add BoatmanMode to your project:
go get github.com/philjestin/boatmanmode@latestOr install a specific version:
go get github.com/philjestin/boatmanmode@v1.0.0package main
import (
"context"
"fmt"
"log"
"github.com/philjestin/boatmanmode"
)
func main() {
// Load configuration
cfg := &boatmanmode.Config{
LinearKey: "your-linear-api-key",
BaseBranch: "main",
MaxIterations: 3,
ReviewSkill: "peer-review",
EnableTools: true,
}
// Create agent
a, err := boatmanmode.NewAgent(cfg)
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
// Create a task from a prompt
t, err := boatmanmode.NewPromptTask(
"Add a health check endpoint at /health",
"", // auto-generate title
"", // auto-generate branch name
)
if err != nil {
log.Fatalf("Failed to create task: %v", err)
}
// Execute the workflow
ctx := context.Background()
result, err := a.Work(ctx, t)
if err != nil {
log.Fatalf("Work failed: %v", err)
}
// Check results
if result.PRCreated {
fmt.Printf("✅ PR created: %s\n", result.PRURL)
} else {
fmt.Printf("⚠️ %s\n", result.Message)
}
}import "github.com/philjestin/boatmanmode"
// Fetch from Linear
t, err := boatmanmode.NewLinearTask(ctx, "your-api-key", "ENG-123")t, err := boatmanmode.NewPromptTask(
"Implement user authentication with JWT tokens",
"Authentication Feature", // custom title
"feature/auth", // custom branch
)t, err := boatmanmode.NewFileTask(
"./tasks/implement-caching.md",
"", // auto-generate title from file
"", // auto-generate branch
)All task types implement the Task interface:
type Task interface {
GetID() string
GetTitle() string
GetDescription() string
GetBranchName() string
GetLabels() []string
GetMetadata() TaskMetadata
}This allows you to write code that works with any task source:
import "github.com/philjestin/boatmanmode"
func processTask(t boatmanmode.Task) {
fmt.Printf("Task ID: %s\n", t.GetID())
fmt.Printf("Title: %s\n", t.GetTitle())
fmt.Printf("Branch: %s\n", t.GetBranchName())
metadata := t.GetMetadata()
fmt.Printf("Source: %s\n", metadata.Source)
}type Config struct {
// Linear API configuration
LinearKey string
// Git configuration
BaseBranch string // Base branch for worktrees (default: "main")
// Workflow configuration
MaxIterations int // Max review/refactor iterations (default: 3)
ReviewSkill string // Claude skill for review (default: "peer-review")
EnableTools bool // Enable Claude tools (default: true)
// Claude configuration
Claude ClaudeConfig
}
type ClaudeConfig struct {
Models struct {
Planner string // Model for planning (default: uses Claude default)
Executor string // Model for execution (default: uses Claude default)
Reviewer string // Model for review and skill execution (default: uses Claude default)
Refactor string // Model for refactoring (default: uses Claude default)
}
EnablePromptCaching bool // Enable prompt caching (default: true)
}package main
import (
"context"
"fmt"
"log"
"github.com/philjestin/boatmanmode/internal/agent"
"github.com/philjestin/boatmanmode/internal/config"
"github.com/philjestin/boatmanmode/internal/task"
)
func main() {
cfg := &config.Config{
LinearKey: "your-api-key",
BaseBranch: "main",
MaxIterations: 3,
EnableTools: true,
}
a, err := agent.New(cfg)
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
// List of tasks to process
prompts := []string{
"Add health check endpoint",
"Implement rate limiting",
"Add request logging middleware",
}
ctx := context.Background()
for i, prompt := range prompts {
fmt.Printf("\n=== Processing task %d/%d ===\n", i+1, len(prompts))
t, err := task.CreateFromPrompt(prompt, "", "")
if err != nil {
log.Printf("Failed to create task: %v", err)
continue
}
result, err := a.Work(ctx, t)
if err != nil {
log.Printf("Task failed: %v", err)
continue
}
if result.PRCreated {
fmt.Printf("✅ PR created: %s\n", result.PRURL)
}
}
}Implement your own task source by implementing the Task interface:
package main
import (
"time"
"github.com/philjestin/boatmanmode/internal/task"
)
// JiraTask implements task.Task for Jira issues
type JiraTask struct {
issueKey string
summary string
description string
labels []string
}
func NewJiraTask(issueKey, summary, description string, labels []string) task.Task {
return &JiraTask{
issueKey: issueKey,
summary: summary,
description: description,
labels: labels,
}
}
func (t *JiraTask) GetID() string {
return t.issueKey
}
func (t *JiraTask) GetTitle() string {
return t.summary
}
func (t *JiraTask) GetDescription() string {
return t.description
}
func (t *JiraTask) GetBranchName() string {
// Sanitize for git branch name
return fmt.Sprintf("%s-%s",
strings.ToLower(t.issueKey),
sanitizeBranchName(t.summary))
}
func (t *JiraTask) GetLabels() []string {
return t.labels
}
func (t *JiraTask) GetMetadata() task.TaskMetadata {
return task.TaskMetadata{
Source: task.TaskSource("jira"),
CreatedAt: time.Now(),
}
}
// Now use it with the agent
func main() {
jiraTask := NewJiraTask(
"PROJ-123",
"Add authentication",
"Implement JWT-based authentication for the API",
[]string{"feature", "security"},
)
// ... create agent and execute
result, err := agent.Work(ctx, jiraTask)
}package main
import (
"encoding/json"
"net/http"
"github.com/philjestin/boatmanmode/internal/agent"
"github.com/philjestin/boatmanmode/internal/config"
"github.com/philjestin/boatmanmode/internal/task"
)
type TaskRequest struct {
Prompt string `json:"prompt"`
Title string `json:"title"`
Branch string `json:"branch"`
}
type TaskResponse struct {
TaskID string `json:"task_id"`
PRCreated bool `json:"pr_created"`
PRURL string `json:"pr_url,omitempty"`
Message string `json:"message,omitempty"`
}
func main() {
cfg := &config.Config{
LinearKey: "your-api-key",
BaseBranch: "main",
MaxIterations: 3,
EnableTools: true,
}
a, err := agent.New(cfg)
if err != nil {
panic(err)
}
http.HandleFunc("/api/tasks", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req TaskRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Create task from request
t, err := task.CreateFromPrompt(req.Prompt, req.Title, req.Branch)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Execute async (you'd probably want to use a queue)
go func() {
result, err := a.Work(r.Context(), t)
if err != nil {
// Handle error (log, notify, etc.)
return
}
// Store result, notify user, etc.
}()
// Return task ID immediately
resp := TaskResponse{
TaskID: t.GetID(),
Message: "Task queued for processing",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
})
http.ListenAndServe(":8080", nil)
}github.com/philjestin/boatmanmode/internal/agent- Main workflow orchestrationgithub.qkg1.top/philjestin/boatmanmode/internal/task- Task abstraction and implementationsgithub.qkg1.top/philjestin/boatmanmode/internal/config- Configuration management
github.com/philjestin/boatmanmode/internal/linear- Linear API clientgithub.qkg1.top/philjestin/boatmanmode/internal/executor- Claude-powered code executiongithub.qkg1.top/philjestin/boatmanmode/internal/planner- Task planning and analysisgithub.qkg1.top/philjestin/boatmanmode/internal/scottbott- Code review agentgithub.qkg1.top/philjestin/boatmanmode/internal/worktree- Git worktree management
github.com/philjestin/boatmanmode/internal/cost- Cost tracking for Claude APIgithub.qkg1.top/philjestin/boatmanmode/internal/handoff- Context passing between agentsgithub.qkg1.top/philjestin/boatmanmode/internal/retry- Retry logic with backoff
BoatmanMode follows Semantic Versioning:
- Major version (v2.x.x): Breaking API changes
- Minor version (v1.x.x): New features, backward compatible
- Patch version (v1.0.x): Bug fixes, backward compatible
The library is currently in active development. While we maintain backward compatibility within major versions, APIs may evolve as we gather feedback.
Always use the versioned import path:
import "github.com/philjestin/boatmanmode"For specific versions:
go get github.com/philjestin/boatmanmode@v1.0.0- Error Handling: Always check errors returned by the agent
- Context: Use context with timeouts for long-running operations
- Configuration: Load configuration from environment or config files
- Concurrency: The agent is not thread-safe; use one agent per goroutine
- Resource Cleanup: Worktrees are automatically cleaned up on completion
Use boatmanmode in GitHub Actions workflows:
name: Auto-implement feature
on:
issues:
types: [labeled]
jobs:
implement:
if: contains(github.event.issue.labels.*.name, 'auto-implement')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Install boatman
run: |
go install github.com/philjestin/boatmanmode/cmd/boatman@latest
- name: Execute task
env:
LINEAR_KEY: ${{ secrets.LINEAR_KEY }}
run: |
boatman work --prompt "${{ github.event.issue.body }}"Integrate into your deployment pipeline to auto-fix issues:
// In your CI/CD tool
if testsFailed {
prompt := fmt.Sprintf("Fix failing test: %s\nError: %s",
failedTest.Name,
failedTest.Error)
task, _ := task.CreateFromPrompt(prompt, "", "")
result, _ := agent.Work(ctx, task)
if result.PRCreated {
notifyTeam(result.PRURL)
}
}- Documentation: GitHub Wiki
- Issues: GitHub Issues
- Examples: See the
examples/directory in the repository
BoatmanMode is released under the MIT License. See LICENSE file for details.