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
47 changes: 23 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ It provides tools to:
- summarize tool usage, paths, binaries, and API cost
- list files changed by a session
- install Claude Code hooks
- enforce a session-specific **Definition of Done** gate
- run a workflow's **verification** checks — commands, LLM judges, and fixtures — as a definition of done
- test and use AI providers from the command line
- run iterative AI agents with verifiers, worktrees, and commits
- generate/build/run containerized Claude Code sandboxes
Expand Down Expand Up @@ -86,21 +86,20 @@ It analyzes:
Captain can install hook commands into Claude settings for:

- **PreToolUse bash scanning** via `hook bash-check`
- **Stop hook gating** via `hook dod install`

The bash-check hook scans bash commands and can deny unsafe or disallowed commands.

### 3. Definition of Done (DoD)
### 3. Verification

Captain supports a per-session Definition of Done workflow:
`captain verify` runs the checks an `api.Workflow` declares against a working tree and reports each one's verdict:

- `dod set` — attach one or more validation commands to a Claude session
- `dod check` — intended for Claude Stop hooks
- `dod run` — manually execute DoD checks
- `dod status` — show current DoD config/results
- `dod clear` — remove the DoD gate
- `--command` — a shell command run as a pass/fail check (repeatable)
- `--prompt` — a `.prompt` LLM judge, judged by the run's provider (repeatable)
- `--fixture` — a fixture document handed to the runner configured as `verify.fixtureRunner` in `~/.captain.yaml`

This lets Claude continue iterating until required checks pass.
The same checks are the generate→verify loop's definition of done: a failing verdict's output feeds the next iteration. A declared check with nothing to run it is an error, never a silent pass.

`captain verify` is local only — it is excluded from both the REST API and the MCP tool set, because `--command` runs through `sh -c` against a caller-chosen `--cwd`, and published as REST or MCP that would be unauthenticated remote code execution.

### 4. Session changes

Expand Down Expand Up @@ -138,7 +137,7 @@ Supported backends are inferred from code and dependencies, including:
### 7. Web UI and MCP server

- `serve` — starts an HTTP API and embedded web UI for launching AI agents and opening follow-up chat sessions; supports `--dev` to proxy to the Vite dev server
- `mcp` — exposes captain commands (history, info, cost, changes, dod, etc.) as MCP tools so Claude Code can invoke them directly
- `mcp` — exposes captain commands (history, info, cost, changes, verify, etc.) as MCP tools so Claude Code can invoke them directly

### 8. Utility commands

Expand Down Expand Up @@ -189,7 +188,6 @@ captain/
├── pkg/collections/ # Generic collection utilities
├── pkg/container/ # Sandbox discovery, generation, build/run logic
├── pkg/container/base/ # Embedded agent base image (Dockerfile, deps.yaml, entrypoint.sh)
├── pkg/dod/ # Definition of Done persistence and execution
├── pkg/git/ # Git worktree helpers
├── pkg/sandbox/ # Token/preset/sandbox helpers
├── Makefile # Thin wrapper around Taskfile
Expand All @@ -210,7 +208,7 @@ captain ai
captain whoami
captain configure
captain serve
captain dod
captain verify
captain hook
captain projects
captain container
Expand Down Expand Up @@ -299,22 +297,24 @@ captain hook bash-check install
captain hook bash-check install --user
```

Install the DoD stop hook and related skill files:
### Verification

```bash
captain hook dod install
captain hook dod install --user
captain verify --command "go test ./..." --command "golangci-lint run"
captain verify --fixture acceptance.md --cwd /path/to/repo
captain verify --prompt review-diff.prompt --model claude-sonnet-4-6
```

### Definition of Done
Running fixtures needs a runner, since captain declares fixtures but does not execute them:

```bash
captain dod set --session-id <session-id> "go test ./..." "golangci-lint run"
captain dod status --session-id <session-id>
captain dod run --session-id <session-id>
captain dod clear --session-id <session-id>
```yaml
# ~/.captain.yaml
verify:
fixtureRunner: [gavel, fixture, verify]
```

The runner is handed the fixture document on stdin, plus `--cwd <dir>` and one `--changed <path>` per changed file, and answers on stdout with NDJSON: any number of `{"progress": <report>}` lines, then exactly one `{"report": <report>}`.

### AI utilities

```bash
Expand Down Expand Up @@ -536,7 +536,7 @@ Starts an HTTP API and embedded web UI. The UI launches `captain ai agent` opera
captain mcp
```

Exposes captain commands as MCP tools. Auto-exposes all commands except `sandbox`, `projects`, `container`, `hook`, `ai`, `dod set/clear/run`.
Exposes captain commands as MCP tools. Auto-exposes all commands except `sandbox`, `projects`, `container`, `hook`, and `ai`.

### Utility commands

Expand Down Expand Up @@ -803,7 +803,6 @@ If you want to use hooks:

```bash
.bin/captain hook bash-check install --user
.bin/captain hook dod install --user
.bin/captain hook monitor install
# Opt in to Claude CLI estimate capture:
.bin/captain hook monitor install --capture-cost
Expand Down
6 changes: 4 additions & 2 deletions cmd/captain/help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (

var _ = Describe("root help", func() {
// commonsMarker is a property only the commons help block documents, so its
// presence distinguishes the appended block from cobra's own output.
const commonsMarker = "http.har.maxBodySize"
// presence distinguishes the appended block from cobra's own output. It is
// a knob that exists in every commons release captain builds against, not
// one whose name is still moving (the body-size cap was renamed after v1.57.0).
const commonsMarker = "http.har.level"

newRoot := func() (*cobra.Command, *cobra.Command, *bytes.Buffer) {
out := &bytes.Buffer{}
Expand Down
1 change: 1 addition & 0 deletions cmd/captain/local_only_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ var _ = Describe("REST executor exposure", func() {
Entry("git-agent add", http.MethodPost, "/api/v1/sandbox/git-agent"),
Entry("git-agent list", http.MethodGet, "/api/v1/sandbox/git-agent"),
Entry("captain serve", http.MethodPost, "/api/v1/serve"),
Entry("captain verify", http.MethodPost, "/api/v1/verify"),
)

// The token group is the load-bearing case: these routes are what stands in
Expand Down
39 changes: 14 additions & 25 deletions cmd/captain/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ func newRootCommand() *cobra.Command {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// The configured fixture runner claims the `fixture` verifier before
// any command can build verify hooks: a workflow declaring a fixture
// must dispatch it, never fall through to an empty hook list.
if err := cli.InstallFixtureVerifier(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// Bind the database context every command reads from, so an unknown
// --context fails before the command runs rather than at first query.
name, err := cli.ResolveDatabaseContextName(cmd.Context())
Expand Down Expand Up @@ -287,25 +294,12 @@ func newRootCommand() *cobra.Command {
rootCmd.AddCommand(attachmentsCmd)
clicky.AddNamedCommand("gc", attachmentsCmd, cli.AttachmentsGCOptions{}, cli.RunAttachmentsGC).Short = "Remove old unreferenced attachments"

dodCmd := &cobra.Command{
Use: "dod",
Short: "Definition of Done checks",
Long: "Manage Definition of Done gates that must pass before Claude Code stops. Use 'status' to check current gate state and 'check' to run the gate commands.",
}
rootCmd.AddCommand(dodCmd)
clicky.AddNamedCommand("set", dodCmd, cli.DodSetOptions{}, cli.RunDodSet)

dodCheckCmd := clicky.AddNamedCommand("check", dodCmd, cli.DodCheckOptions{}, cli.RunDodCheck)
dodCheckCmd.Short = "Run Definition of Done gate checks"
dodCheckCmd.Long = "Execute the configured DoD commands and report pass/fail status for each gate."

clicky.AddNamedCommand("clear", dodCmd, cli.DodClearOptions{}, cli.RunDodClear)

dodStatusCmd := clicky.AddNamedCommand("status", dodCmd, cli.DodStatusOptions{}, cli.RunDodStatus)
dodStatusCmd.Short = "Show current Definition of Done gate status"
dodStatusCmd.Long = "Display which DoD gates are configured and their last pass/fail state."

clicky.AddNamedCommand("run", dodCmd, cli.DodRunOptions{}, cli.RunDodRun)
// Local-only: --command is run through `sh -c` against a caller-chosen --cwd,
// so published as REST or MCP it would be unauthenticated remote execution.
verifyCmd := clicky.AddNamedCommandWithContext("verify", rootCmd, cli.VerifyOptions{}, cli.RunVerify)
verifyCmd.Short = "Run a workflow's verification checks and report the verdict"
verifyCmd.Long = "Run the checks an api.Workflow declares — shell commands, LLM-judge prompts, and a fixture document handed to the configured fixture runner — against a working tree, and print each check's report. Exits non-zero when any check fails or cannot reach a verdict."
clicky.MarkLocalOnly(verifyCmd)

hookCmd := &cobra.Command{Use: "hook", Short: "Claude Code hook commands"}
rootCmd.AddCommand(hookCmd)
Expand All @@ -315,9 +309,6 @@ func newRootCommand() *cobra.Command {
}}
hookCmd.AddCommand(bashCheckCmd)
clicky.AddNamedCommand("install", bashCheckCmd, cli.HookInstallOptions{}, cli.RunBashCheckInstall)
dodHookCmd := &cobra.Command{Use: "dod", Short: "Definition of Done hook"}
hookCmd.AddCommand(dodHookCmd)
clicky.AddNamedCommand("install", dodHookCmd, cli.HookInstallOptions{}, cli.RunDodInstall)

monitorHookCmd := &cobra.Command{Use: "monitor", Short: "Session monitoring hooks (hooks-first session tracking)"}
hookCmd.AddCommand(monitorHookCmd)
Expand Down Expand Up @@ -396,9 +387,7 @@ func newRootCommand() *cobra.Command {
"^container",
"^hook",
"^ai",
"^dod set",
"^dod clear",
"^dod run",
"^verify",
},
},
}
Expand Down
Loading
Loading