Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A monorepo containing the Boatman CLI tool and desktop application for AI-powered autonomous software development.

> 🆕 **Runtime platform update** - Boatman now has a provider-neutral runtime layer, OpenAI Responses adapter, inspectable run store, workflow templates, approval policy rules, verifier contracts, file-backed memory docs, integration health checks, and a desktop Runtime tab for inspecting recorded events and memory.
> 🆕 **Runtime platform update** - Boatman now has a provider-neutral runtime layer, OpenAI Responses adapter, inspectable run store, workflow templates, repeatable routines, approval policy rules, verifier contracts, file-backed memory docs, integration health checks, and a desktop Runtime tab for inspecting recorded events and memory.

## Repository Structure

Expand Down Expand Up @@ -37,6 +37,7 @@ Boatman is an AI-powered autonomous development system that:
- **Supports resume** — pick up a failed execution from the review/refactor stage without re-doing the work
- **Routes model calls through provider adapters** so Claude CLI, OpenAI Responses, and future providers can be adopted per workflow role
- **Models workflows as provider-neutral templates** with explicit stages, gates, previews, skips, and validation loops
- **Runs repeatable project routines** from `.boatman/routines.json` or `.boatman/routines/*.json`, including the built-in Datadog GraphQL slow-query investigation
- **Evaluates side-effecting actions through approval policy rules** before humans, chat clients, or future services render durable approvals
- **Runs independent verifier checks** so code review, runtime recordings, and future central-plane quality gates share one contract
- **Records inspectable runtime runs** with normalized events, original requests, artifacts, usage, raw provider payloads, integration status, and memory-load events
Expand All @@ -57,6 +58,7 @@ The command-line interface and core autonomous agent.
- Provider-neutral runtime requests with Claude CLI and OpenAI Responses adapters
- Runtime provider routing by default, role, and workflow profile
- Built-in workflow template inspection with `boatman workflows`
- Repeatable routine inspection and execution with `boatman routines`
- Deterministic approval policy and independent verifier packages for future service/client reuse
- Inspectable run store and memory document commands
- Integration descriptor checks for Linear, Slack, Datadog, and Bugsnag
Expand Down Expand Up @@ -86,6 +88,9 @@ go build -o boatman ./cmd/boatman
./boatman providers check
./boatman workflows

# Dry-run a repeatable Datadog MCP routine
./boatman routines run datadog-gql-slow-queries --graph-area employer --dry-run

# Inspect recorded runs and memory documents
./boatman work --prompt "Update docs"
./boatman runs list
Expand Down Expand Up @@ -114,6 +119,7 @@ A cross-platform desktop application built with Wails that provides a GUI for th
- **Firefighter mode** for production incident investigation
- **Agent logs panel** for real-time visibility into AI actions
- **Runtime tab** for inspecting `.boatman/runs` events, artifacts, and `.boatman/memory` documents
- **Routines tab** for running built-in and project-local routines from the desktop app
- **Integration health** for MCP-backed services before starting incident or autonomous workflows
- **Onboarding wizard** for first-time setup
- **MCP server management** via UI dialog
Expand Down
33 changes: 33 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ An AI-powered development agent that automates ticket execution with peer review
- Normalized runtime events can be emitted and recorded alongside legacy events
- Local tools flow through a shared broker with workspace and approval policy checks
- Built-in workflow templates describe stages, gates, preview points, skips, and validation loops before a service runtime exists
- Repeatable routines package saved prompts, parameters, integrations, runtime recording, and durable reports

### 🌲 Git Worktree Isolation
- Each ticket works in an isolated worktree
Expand Down Expand Up @@ -135,6 +136,9 @@ boatman providers check
boatman workflows
boatman workflows show feature

boatman routines
boatman routines run datadog-gql-slow-queries --graph-area employer --dry-run

boatman integrations
boatman integrations check --emit-events

Expand All @@ -155,6 +159,35 @@ scope, optional source run, and optional expiration.

---

### 📈 Repeatable Datadog Routine (NEW)

Run a saved GraphQL performance investigation through Datadog MCP:

```bash
export DD_API_KEY=...
export DD_APP_KEY=...

boatman routines run datadog-gql-slow-queries \
--graph-area employer \
--top-n 20 \
--lookback 24h \
--environment prod \
--service employer-graphql
```

The routine attaches the Datadog MCP integration to the runtime request, records
the provider run under `.boatman/runs`, and writes a Markdown report under
`.boatman/routines/datadog-gql-slow-queries/`. Use `--dry-run` to preview the
request and integration health without calling a model. The built-in schedule is
`0 8 * * *`, so cron or CI can invoke the same command daily.

Project routines can live in `.boatman/routines.json` or
`.boatman/routines/*.json`. They can `extends` built-ins and set project
defaults, so a repo can expose commands like `daily-employer-gql` without
duplicating the full prompt.

---

### 📊 Backlog Triage Pipeline (NEW)

Analyze and classify entire backlogs for AI-readiness:
Expand Down
76 changes: 45 additions & 31 deletions cli/internal/claude/claude.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ type Client struct {
// If false, tools are explicitly disabled with --tools "".
EnableTools bool

// MCPConfigs are Claude Code --mcp-config JSON strings or file paths.
MCPConfigs []string

// SkipPermissions automatically approves all tool uses without user confirmation.
// WARNING: This is a security risk - only enable for trusted, non-interactive environments.
SkipPermissions bool
Expand Down Expand Up @@ -198,6 +201,7 @@ func (c *Client) messageTmux(ctx context.Context, systemPrompt, userPrompt strin
opts := tmux.ClaudeOptions{
Model: c.Model,
EnablePromptCaching: c.EnablePromptCaching,
MCPConfigs: append([]string(nil), c.MCPConfigs...),
}
return c.TmuxManager.RunClaudeStreamingWithOptions(ctx, sess, systemPrompt, userPrompt, opts)
}
Expand Down Expand Up @@ -237,37 +241,7 @@ type streamResult struct {

// doStreamingRequest performs a single streaming request to Claude.
func (c *Client) doStreamingRequest(ctx context.Context, systemPrompt, userPrompt string) (string, *cost.Usage, error) {
args := []string{
"-p",
"--output-format", "stream-json",
"--verbose",
}

// Auto-approve tool uses if configured (WARNING: security risk)
if c.SkipPermissions {
args = append(args, "--dangerously-skip-permissions")
}

// Handle tool permissions
if !c.EnableTools {
// Explicitly disable tools for backward compatibility
args = append(args, "--tools", "")
} else if len(c.AllowedTools) > 0 {
// Restrict to specific tools
args = append(args, "--tools", strings.Join(c.AllowedTools, ","))
}
// If EnableTools is true and AllowedTools is nil, omit --tools flag entirely (allows all tools)

// Add model selection if specified
if c.Model != "" {
args = append(args, "--model", c.Model)
}
if c.Agent != "" {
args = append(args, "--agent", c.Agent)
}
if c.Effort != "" {
args = append(args, "--effort", c.Effort)
}
args := c.streamingArgs()

// Note: Prompt caching is automatically handled by Claude CLI when using system prompts
// No explicit flag needed in current version (2.1.39+)
Expand Down Expand Up @@ -458,6 +432,46 @@ func (c *Client) doStreamingRequest(ctx context.Context, systemPrompt, userPromp
return fullResponse.String(), resultUsage, nil
}

func (c *Client) streamingArgs() []string {
args := []string{
"-p",
"--output-format", "stream-json",
"--verbose",
}

// Auto-approve tool uses if configured (WARNING: security risk)
if c.SkipPermissions {
args = append(args, "--dangerously-skip-permissions")
}

// Handle tool permissions
if !c.EnableTools {
// Explicitly disable tools for backward compatibility
args = append(args, "--tools", "")
} else if len(c.AllowedTools) > 0 {
// Restrict to specific tools
args = append(args, "--tools", strings.Join(c.AllowedTools, ","))
}
// If EnableTools is true and AllowedTools is nil, omit --tools flag entirely (allows all tools)

// Add model selection if specified
if c.Model != "" {
args = append(args, "--model", c.Model)
}
if c.Agent != "" {
args = append(args, "--agent", c.Agent)
}
if c.Effort != "" {
args = append(args, "--effort", c.Effort)
}
for _, config := range c.MCPConfigs {
if strings.TrimSpace(config) != "" {
args = append(args, "--mcp-config", config)
}
}
return args
}

// messageNonStreaming sends a message without streaming.
// Note: Non-streaming text output doesn't include usage data.
func (c *Client) messageNonStreaming(ctx context.Context, systemPrompt, userPrompt string) (string, *cost.Usage, error) {
Expand Down
23 changes: 23 additions & 0 deletions cli/internal/claude/claude_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,26 @@ func TestNewWithTmux_BackwardCompat(t *testing.T) {
t.Error("NewWithTmux should have nil AllowedTools")
}
}

func TestStreamingArgsIncludesMCPConfig(t *testing.T) {
client := NewWithWorkDir("/tmp")
client.EnableTools = true
client.MCPConfigs = []string{`{"mcpServers":{"datadog":{"command":"npx"}}}`}

args := client.streamingArgs()
if !containsArg(args, "--mcp-config") || !containsArg(args, client.MCPConfigs[0]) {
t.Fatalf("args = %#v, want MCP config", args)
}
if containsArg(args, "--tools") {
t.Fatalf("args = %#v, should not disable tools when MCP config is present", args)
}
}

func containsArg(args []string, want string) bool {
for _, arg := range args {
if arg == want {
return true
}
}
return false
}
Loading
Loading