diff --git a/.golangci.yml b/.golangci.yml
index 576644a0d..f8b6ac54d 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -170,6 +170,11 @@ linters:
linters:
- staticcheck
text: "SA1019:.*FunctionCall.*deprecated"
+ # Vendored verbatim from moby/moby; keep its //nolint:gosec directives
+ # even though our config excludes gosec G404 globally.
+ - path: pkg/worktree/namesgenerator/
+ linters:
+ - nolintlint
issues:
max-same-issues: 3
formatters:
diff --git a/agent-schema.json b/agent-schema.json
index 583be95ee..e601dd412 100644
--- a/agent-schema.json
+++ b/agent-schema.json
@@ -1038,6 +1038,13 @@
"items": {
"$ref": "#/definitions/HookMatcherConfig"
}
+ },
+ "worktree_create": {
+ "type": "array",
+ "description": "Hooks that run once, just after `docker agent run --worktree` creates a git worktree and before the session starts. They execute inside the new worktree (their working directory is the fresh checkout), and the worktree path, branch, and source repository root are passed in worktree_path / worktree_branch / worktree_source_dir. Use them to prepare the checkout: copy untracked files like .env, install dependencies, warm caches, before the agent begins. A hook may abort the run by blocking (decision=block / continue=false / exit code 2); stdout is surfaced as additional context. Dispatched from the CLI rather than the run loop because the worktree (and the working directory every downstream component captures) must be settled before the runtime and session exist.",
+ "items": {
+ "$ref": "#/definitions/HookDefinition"
+ }
}
},
"additionalProperties": false
diff --git a/cmd/root/run.go b/cmd/root/run.go
index 6a97f9aef..cc30bcc51 100644
--- a/cmd/root/run.go
+++ b/cmd/root/run.go
@@ -22,18 +22,27 @@ import (
latestcfg "github.com/docker/docker-agent/pkg/config/latest"
"github.com/docker/docker-agent/pkg/hooks"
"github.com/docker/docker-agent/pkg/hooks/builtins"
+ "github.com/docker/docker-agent/pkg/input"
"github.com/docker/docker-agent/pkg/paths"
"github.com/docker/docker-agent/pkg/permissions"
"github.com/docker/docker-agent/pkg/profiling"
"github.com/docker/docker-agent/pkg/runtime"
"github.com/docker/docker-agent/pkg/session"
+ "github.com/docker/docker-agent/pkg/team"
"github.com/docker/docker-agent/pkg/teamloader"
"github.com/docker/docker-agent/pkg/telemetry"
"github.com/docker/docker-agent/pkg/tui"
"github.com/docker/docker-agent/pkg/tui/styles"
"github.com/docker/docker-agent/pkg/userconfig"
+ "github.com/docker/docker-agent/pkg/worktree"
)
+// worktreeAutoName is the value stored when --worktree is given without an
+// explicit name (cobra's NoOptDefVal). It also doubles as the reserved name a
+// user can pass explicitly (--worktree=auto) to request a generated name; with
+// cobra's optional-value flags the two are indistinguishable by design.
+const worktreeAutoName = "auto"
+
type runExecFlags struct {
agentName string
autoApprove bool
@@ -56,6 +65,9 @@ type runExecFlags struct {
sandboxTemplate string
sbx bool
noKit bool
+ worktree bool
+ worktreeName string
+ worktreePR string
// Exec only
exec bool
@@ -152,12 +164,22 @@ func addRunOrExecFlags(cmd *cobra.Command, flags *runExecFlags) {
cmd.PersistentFlags().StringVar(&flags.sandboxTemplate, "template", "docker/sandbox-templates:docker-agent", "Template image for the sandbox (passed to docker sandbox create -t)")
cmd.PersistentFlags().BoolVar(&flags.sbx, "sbx", true, "Prefer the sbx CLI backend when available (set --sbx=false to force docker sandbox)")
cmd.PersistentFlags().BoolVar(&flags.noKit, "no-kit", false, "Do not stage a docker-agent kit (skills, prompt files) when running in a sandbox")
+ cmd.PersistentFlags().StringVarP(&flags.worktreeName, "worktree", "w", "", "Run the agent in a fresh git worktree of the working directory (isolates changes from your checkout). Optionally name it: --worktree=my-name")
+ cmd.PersistentFlags().Lookup("worktree").NoOptDefVal = worktreeAutoName
+ cmd.PersistentFlags().StringVar(&flags.worktreePR, "worktree-pr", "", "Run the agent in a git worktree checked out on an existing GitHub pull request (number or URL). Continues the PR's branch; requires the GitHub CLI (gh).")
cmd.MarkFlagsMutuallyExclusive("fake", "record")
cmd.MarkFlagsMutuallyExclusive("remote", "sandbox")
cmd.MarkFlagsMutuallyExclusive("remote", "session-db")
cmd.MarkFlagsMutuallyExclusive("remote", "session")
cmd.MarkFlagsMutuallyExclusive("remote", "record")
cmd.MarkFlagsMutuallyExclusive("remote", "fake")
+ // A worktree is a local directory: it has no meaning for a remote runtime
+ // and is not wired through the sandbox boundary.
+ cmd.MarkFlagsMutuallyExclusive("remote", "worktree")
+ cmd.MarkFlagsMutuallyExclusive("sandbox", "worktree")
+ cmd.MarkFlagsMutuallyExclusive("remote", "worktree-pr")
+ cmd.MarkFlagsMutuallyExclusive("sandbox", "worktree-pr")
+ cmd.MarkFlagsMutuallyExclusive("worktree", "worktree-pr")
// --exec only
cmd.PersistentFlags().BoolVar(&flags.exec, "exec", false, "Execute without a TUI")
@@ -202,9 +224,17 @@ func (f *runExecFlags) runRunCommand(cmd *cobra.Command, args []string) (command
}
if f.sandbox {
+ if cmd.Flags().Changed("worktree") || cmd.Flags().Changed("worktree-pr") {
+ return errors.New("--worktree/--worktree-pr cannot be combined with a sandboxed run")
+ }
return runInSandbox(ctx, cmd, args, &f.runConfig, f.sandboxTemplate, f.sbx, f.noKit, agentCfg)
}
+ // --worktree was provided (with or without a value). The string flag lets
+ // users name the worktree (--worktree=my-name); without a value cobra
+ // stores the sentinel that triggers a random name.
+ f.worktree = cmd.Flags().Changed("worktree")
+
out := cli.NewPrinter(cmd.OutOrStdout())
useTUI := !f.exec && (f.forceTUI || isatty.IsTerminal(os.Stdout.Fd()))
@@ -319,6 +349,27 @@ func (f *runExecFlags) runOrExec(ctx context.Context, out *cli.Printer, args []s
}
wd, _ := os.Getwd()
+ createdWorktree, err := f.setupWorktree(ctx, wd)
+ if err != nil {
+ if loadResult != nil {
+ stopToolSets(loadResult.Team)
+ }
+ return err
+ }
+ if createdWorktree != nil {
+ wd = createdWorktree.Dir
+ f.runConfig.WorkingDir = createdWorktree.Dir
+ out.Println("Using git worktree: " + createdWorktree.Dir + " (branch " + createdWorktree.Branch + ")")
+ // loadResult is nil for the remote backend; worktrees are mutually
+ // exclusive with --remote so this is belt-and-suspenders, matching
+ // the nil-guard used for cleanup throughout this function.
+ if loadResult != nil {
+ if err := f.dispatchWorktreeCreate(ctx, out, loadResult.Team, createdWorktree); err != nil {
+ stopToolSets(loadResult.Team)
+ return err
+ }
+ }
+ }
rt, sess, cleanup, err := b.CreateSession(ctx, loadResult, b.CreateSessionRequest(wd))
if err != nil {
return err
@@ -326,6 +377,9 @@ func (f *runExecFlags) runOrExec(ctx context.Context, out *cli.Printer, args []s
defer cleanup()
if !useTUI {
+ // Non-interactive (--exec) runs never clean up the worktree: there
+ // is no safe moment to prompt, and silently discarding work would
+ // be surprising. The worktree is left in place for later inspection.
return f.handleExecMode(ctx, out, rt, sess, args)
}
@@ -351,7 +405,173 @@ func (f *runExecFlags) runOrExec(ctx context.Context, out *cli.Printer, args []s
opts = append(opts, hookOpt)
}
- return runTUI(ctx, rt, sess, b.Spawner(rt), cleanup, f.tuiOpts(), opts...)
+ if err := runTUI(ctx, rt, sess, b.Spawner(rt), cleanup, f.tuiOpts(), opts...); err != nil {
+ // On a TUI error we deliberately leave the worktree in place rather
+ // than risk discarding work after an abnormal exit.
+ return err
+ }
+
+ // The interactive session is over. Offer to clean up the worktree we
+ // created for it (never a pre-existing one, since Create only makes new
+ // worktrees). Shut the session down first so tools release any file
+ // handles inside the worktree before we try to remove it; cleanup is
+ // idempotent, so the deferred call above becomes a no-op.
+ //
+ // A fresh context is used because the TUI may have exited via a
+ // canceled ctx (Ctrl-C), which would otherwise abort the prompt and
+ // the git commands.
+ if createdWorktree != nil {
+ cleanup()
+ f.cleanupWorktree(context.WithoutCancel(ctx), out, createdWorktree)
+ }
+ return nil
+}
+
+// setupWorktree creates the git worktree requested by --worktree or
+// --worktree-pr, returning nil when neither was given. The returned worktree
+// (when non-nil) becomes the session's working directory and is cleaned up
+// when an interactive run ends.
+func (f *runExecFlags) setupWorktree(ctx context.Context, wd string) (*worktree.Worktree, error) {
+ switch {
+ case f.worktreePR != "":
+ wt, err := worktree.CreatePR(ctx, wd, f.worktreePR)
+ if err != nil {
+ switch {
+ case errors.Is(err, worktree.ErrNotGitRepository):
+ return nil, fmt.Errorf("--worktree-pr requires %s to be inside a git repository", wd)
+ case errors.Is(err, worktree.ErrInvalidPRRef):
+ return nil, fmt.Errorf("invalid --worktree-pr value: %w", err)
+ case errors.Is(err, worktree.ErrGHNotFound):
+ return nil, fmt.Errorf("--worktree-pr requires the GitHub CLI: %w", err)
+ default:
+ return nil, err
+ }
+ }
+ return wt, nil
+
+ case f.worktree:
+ name := f.worktreeName
+ if name == worktreeAutoName {
+ name = ""
+ }
+ wt, err := worktree.Create(ctx, wd, name)
+ if err != nil {
+ switch {
+ case errors.Is(err, worktree.ErrNotGitRepository):
+ return nil, fmt.Errorf("--worktree requires %s to be inside a git repository", wd)
+ case errors.Is(err, worktree.ErrInvalidName):
+ return nil, fmt.Errorf("invalid --worktree name: %w", err)
+ default:
+ return nil, err
+ }
+ }
+ return wt, nil
+
+ default:
+ return nil, nil
+ }
+}
+
+// cleanupWorktree removes a worktree created for an interactive run once it
+// ends. A clean worktree (no uncommitted changes, untracked files, or new
+// commits) is removed automatically. A dirty one is kept unless the user
+// explicitly asks to remove it, so work is never discarded silently.
+// Failures are reported but never abort the command — the run already
+// succeeded.
+func (f *runExecFlags) cleanupWorktree(ctx context.Context, out *cli.Printer, wt *worktree.Worktree) {
+ st, err := wt.Status(ctx)
+ if err != nil {
+ out.Println("Could not inspect git worktree " + wt.Dir + ": " + err.Error())
+ out.Println("Leaving it in place. Remove it manually with: git -C " + wt.SourceDir + " worktree remove " + wt.Dir)
+ return
+ }
+
+ if st.IsDirty() {
+ if !promptRemoveDirtyWorktree(ctx, out, wt, st) {
+ out.Println("Keeping git worktree " + wt.Dir + " (branch " + wt.Branch + ").")
+ return
+ }
+ }
+
+ if err := wt.Remove(ctx); err != nil {
+ out.Println("Failed to remove git worktree " + wt.Dir + ": " + err.Error())
+ return
+ }
+ out.Println("Removed git worktree " + wt.Dir + " (branch " + wt.Branch + ").")
+}
+
+// promptRemoveDirtyWorktree asks the user whether to discard a worktree that
+// still holds work. It defaults to keeping (returns false) on any non-yes
+// answer or read error, so uncommitted work is never lost by accident.
+func promptRemoveDirtyWorktree(ctx context.Context, out *cli.Printer, wt *worktree.Worktree, st worktree.Status) bool {
+ var held []string
+ if st.Modified {
+ held = append(held, "uncommitted changes")
+ }
+ if st.Untracked {
+ held = append(held, "untracked files")
+ }
+ if st.NewCommits {
+ held = append(held, "new commits")
+ }
+
+ out.Println("\nThe git worktree " + wt.Dir + " (branch " + wt.Branch + ") still has " + strings.Join(held, ", ") + ".")
+ out.Println("Remove it and discard this work? Keeping preserves the directory and branch so you can return later. (y/N):")
+
+ response, err := input.ReadLine(ctx, os.Stdin)
+ if err != nil {
+ return false
+ }
+ response = strings.TrimSpace(strings.ToLower(response))
+ return response == "y" || response == "yes"
+}
+
+// dispatchWorktreeCreate fires the worktree_create hooks of the agent the
+// run targets, just after the worktree is created and before the session
+// exists. Unlike every other event, this is dispatched from the CLI rather
+// than the run loop: the worktree (and the working directory the runtime,
+// session, tools and snapshot machinery all capture) must be settled first.
+// Hooks run inside the new worktree so setup commands (copy .env, install
+// deps) operate on the fresh checkout. A blocking verdict aborts the run.
+func (f *runExecFlags) dispatchWorktreeCreate(ctx context.Context, out *cli.Printer, t *team.Team, wt *worktree.Worktree) error {
+ agt, err := t.AgentOrDefault(f.agentName)
+ if err != nil {
+ return err
+ }
+ hooksCfg := agt.Hooks()
+ if hooksCfg == nil {
+ return nil
+ }
+
+ executor := hooks.NewExecutor(hooksCfg, wt.Dir, os.Environ())
+ if !executor.Has(hooks.EventWorktreeCreate) {
+ return nil
+ }
+
+ result, err := executor.Dispatch(ctx, hooks.EventWorktreeCreate, &hooks.Input{
+ AgentName: agt.Name(),
+ Cwd: wt.Dir,
+ WorktreePath: wt.Dir,
+ WorktreeBranch: wt.Branch,
+ WorktreeSourceDir: wt.SourceDir,
+ })
+ if err != nil {
+ return fmt.Errorf("running worktree_create hooks: %w", err)
+ }
+ if result.SystemMessage != "" {
+ out.Println(result.SystemMessage)
+ }
+ if result.AdditionalContext != "" {
+ out.Println(result.AdditionalContext)
+ }
+ if !result.Allowed {
+ msg := result.Message
+ if msg == "" {
+ msg = "a worktree_create hook blocked the run"
+ }
+ return fmt.Errorf("worktree_create hook aborted the run: %s", msg)
+ }
+ return nil
}
func (f *runExecFlags) loadAgentFrom(ctx context.Context, req runtime.LoadTeamRequest) (*teamloader.LoadResult, error) {
diff --git a/cmd/root/run_worktree_test.go b/cmd/root/run_worktree_test.go
new file mode 100644
index 000000000..03fc28ee2
--- /dev/null
+++ b/cmd/root/run_worktree_test.go
@@ -0,0 +1,111 @@
+package root
+
+import (
+ "io"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/docker/docker-agent/pkg/cli"
+ "github.com/docker/docker-agent/pkg/paths"
+ "github.com/docker/docker-agent/pkg/worktree"
+)
+
+// TestCleanupWorktreeAutoRemovesWhenClean verifies a pristine worktree is
+// removed without prompting.
+func TestCleanupWorktreeAutoRemovesWhenClean(t *testing.T) {
+ wt := createTestWorktree(t)
+
+ var f runExecFlags
+ f.cleanupWorktree(t.Context(), discardPrinter(), wt)
+
+ assert.NoDirExists(t, wt.Dir)
+}
+
+// TestCleanupWorktreeKeepsDirtyWithoutConfirmation verifies that a worktree
+// holding uncommitted work is preserved when the user does not confirm
+// removal (here: stdin is at EOF, so the prompt reads no "yes").
+func TestCleanupWorktreeKeepsDirtyWithoutConfirmation(t *testing.T) {
+ wt := createTestWorktree(t)
+ require.NoError(t, os.WriteFile(filepath.Join(wt.Dir, "untracked.txt"), []byte("x"), 0o644))
+
+ setStdin(t, "") // EOF => prompt reads no answer => keep.
+
+ var f runExecFlags
+ f.cleanupWorktree(t.Context(), discardPrinter(), wt)
+
+ assert.DirExists(t, wt.Dir)
+}
+
+// TestCleanupWorktreeRemovesDirtyOnConfirmation verifies that explicit
+// confirmation discards a worktree that still holds work.
+func TestCleanupWorktreeRemovesDirtyOnConfirmation(t *testing.T) {
+ wt := createTestWorktree(t)
+ require.NoError(t, os.WriteFile(filepath.Join(wt.Dir, "untracked.txt"), []byte("x"), 0o644))
+
+ setStdin(t, "y\n")
+
+ var f runExecFlags
+ f.cleanupWorktree(t.Context(), discardPrinter(), wt)
+
+ assert.NoDirExists(t, wt.Dir)
+}
+
+func discardPrinter() *cli.Printer {
+ return cli.NewPrinter(io.Discard)
+}
+
+// setStdin replaces os.Stdin with a pipe pre-loaded with the given input for
+// the duration of the test.
+func setStdin(t *testing.T, input string) {
+ t.Helper()
+ r, w, err := os.Pipe()
+ require.NoError(t, err)
+ if input != "" {
+ _, err = w.WriteString(input)
+ require.NoError(t, err)
+ }
+ require.NoError(t, w.Close())
+
+ old := os.Stdin
+ os.Stdin = r
+ t.Cleanup(func() { os.Stdin = old; _ = r.Close() })
+}
+
+func createTestWorktree(t *testing.T) *worktree.Worktree {
+ t.Helper()
+ if _, err := exec.LookPath("git"); err != nil {
+ t.Skip("git not available")
+ }
+ dir, err := filepath.EvalSymlinks(t.TempDir())
+ require.NoError(t, err)
+ for _, args := range [][]string{
+ {"init"},
+ {"config", "user.email", "test@example.com"},
+ {"config", "user.name", "Test User"},
+ } {
+ cmd := exec.CommandContext(t.Context(), "git", append([]string{"-C", dir}, args...)...)
+ out, err := cmd.CombinedOutput()
+ require.NoError(t, err, string(out))
+ }
+ require.NoError(t, os.WriteFile(filepath.Join(dir, "a.txt"), []byte("A"), 0o644))
+ for _, args := range [][]string{
+ {"add", "."},
+ {"commit", "-m", "init"},
+ } {
+ cmd := exec.CommandContext(t.Context(), "git", append([]string{"-C", dir}, args...)...)
+ out, err := cmd.CombinedOutput()
+ require.NoError(t, err, string(out))
+ }
+
+ paths.SetDataDir(t.TempDir())
+ t.Cleanup(func() { paths.SetDataDir("") })
+
+ wt, err := worktree.Create(t.Context(), dir, "")
+ require.NoError(t, err)
+ return wt
+}
diff --git a/docs/configuration/hooks/index.md b/docs/configuration/hooks/index.md
index 52ca42be1..23ee940f6 100644
--- a/docs/configuration/hooks/index.md
+++ b/docs/configuration/hooks/index.md
@@ -59,6 +59,7 @@ docker-agent dispatches the following hook events:
| `on_agent_switch` | When the runtime moves the active agent (transfer_task, handoff, return) | No |
| `on_session_resume` | When the user explicitly approves continuation past `max_iterations` | No |
| `on_tool_approval_decision` | After the runtime's approval chain (yolo / permissions / readonly / ask) resolves | No |
+| `worktree_create` | After `docker agent run --worktree` creates a git worktree, before the session | Yes |
Two compaction events
@@ -270,6 +271,7 @@ In addition to the common fields, each event ships its own payload:
| `on_agent_switch` | `from_agent`, `to_agent`, `agent_switch_kind` (`transfer_task`, `transfer_task_return`, or `handoff`) |
| `on_session_resume` | `previous_max_iterations`, `new_max_iterations` |
| `on_tool_approval_decision` | `tool_name`, `tool_use_id`, `tool_input`, `approval_decision`, `approval_source` |
+| `worktree_create` | `worktree_path`, `worktree_branch`, `worktree_source_dir` (`cwd` is also set to the new worktree) |
Notes:
@@ -336,7 +338,7 @@ This is the symmetric counterpart of `pre_tool_use`'s `updated_input`, applied t
### Context-Contributing Events
-For `session_start`, `user_prompt_submit`, `turn_start`, `post_tool_use`, `pre_compact`, and `stop`, hooks may set `hook_specific_output.additional_context` to inject text into the conversation. `turn_start` context is **transient** (recomputed every turn, never persisted); `session_start` context **persists** for the life of the session.
+For `session_start`, `user_prompt_submit`, `turn_start`, `post_tool_use`, `pre_compact`, and `stop`, hooks may set `hook_specific_output.additional_context` to inject text into the conversation. `turn_start` context is **transient** (recomputed every turn, never persisted); `session_start` context **persists** for the life of the session. (`worktree_create` also surfaces stdout, but to the CLI user rather than the conversation — the session doesn't exist yet.)
### Before-Compaction Specific Output
@@ -599,6 +601,35 @@ At every transfer the runtime ships a snapshot of the previous agent's model end
`on_tool_approval_decision` fires after the runtime's tool-approval chain (yolo / permissions / readonly / pre_tool_use hooks / interactive prompt) has resolved a verdict for a tool call. `approval_decision` is `allow`, `deny`, or `canceled`; `approval_source` is a stable classifier of which step produced the verdict. Observational only — it gives audit pipelines a single, structured "who approved what" record without re-implementing the chain.
+### Worktree-Create: prepare an isolated checkout
+
+`worktree_create` fires once, just after `docker agent run --worktree[=name]` creates a fresh [git worktree]({{ '/features/cli/' | relative_url }}) and **before** the session starts. Each hook runs **inside** the new worktree — its working directory (and `cwd` in the input) is the fresh checkout — so setup commands operate on the new tree rather than your original one. The worktree path and branch are in `worktree_path` and `worktree_branch`, and `worktree_source_dir` carries the repository root it was branched from.
+
+Use it to prepare the checkout before the agent begins: copy untracked files git won't carry over (`.env`, local config), install dependencies, or warm caches. Because the worktree lives under the docker-agent data directory — not next to your checkout — resolve the original files through `worktree_source_dir` rather than a relative path. A hook may **abort the run** by returning `decision: block` / `{"continue": false}` / exit code 2 (for example, when a setup step fails); plain stdout is surfaced as additional context.
+
+```yaml
+hooks:
+ worktree_create:
+ # Copy untracked dotfiles git won't bring into the new worktree.
+ - name: seed local env
+ type: command
+ command: |
+ INPUT=$(cat)
+ SRC=$(echo "$INPUT" | jq -r '.worktree_source_dir // ""')
+ [ -n "$SRC" ] && [ -f "$SRC/.env" ] && [ ! -f .env ] && cp "$SRC/.env" .env
+ echo "Prepared worktree"
+ # Install dependencies, aborting the run on failure.
+ - name: install dependencies
+ type: command
+ timeout: 600
+ command: |
+ if [ -f package.json ]; then
+ npm install || { echo '{"continue": false, "system_message": "npm install failed"}'; exit 2; }
+ fi
+```
+
+Unlike most events, `worktree_create` is dispatched from the CLI rather than the run loop, because the worktree (and the working directory the runtime, session, tools, and snapshot machinery all capture) must be settled before the runtime and session exist. See [`examples/worktree_create_hook.yaml`](https://github.com/docker/docker-agent/blob/main/examples/worktree_create_hook.yaml) for the full file.
+
### Pre-Compact: steer the summary
`pre_compact` fires just before the runtime compacts the session transcript. Its `source` field tells you why compaction was triggered:
diff --git a/docs/features/cli/index.md b/docs/features/cli/index.md
index 2b5177336..ee94b4308 100644
--- a/docs/features/cli/index.md
+++ b/docs/features/cli/index.md
@@ -37,7 +37,6 @@ $ docker agent run [config] [message...] [flags]
| `--dry-run` | Initialize the agent without executing anything (useful for validating a config) |
| `--remote
` | Use a remote runtime at the given address instead of running the agent locally |
| `--lean` | Use a simplified TUI with minimal chrome |
-| `--theme ` | Preselect a TUI theme by name at launch (overrides `settings.theme` in user config; ignored in `--exec` mode). Run `/theme` to browse available names. |
| `--app-name ` | Override the application name label shown in the TUI (status bar, window title, "/exit" notifications). |
| `--sidebar` | Control sidebar visibility. Set to `--sidebar=false` to hide the sidebar and disable the Ctrl+B toggle (default: `true`). |
| `--disable-commands ` | Hide and disable specific slash commands in the TUI. Accepts a comma-separated list of command names (leading slash optional, case-insensitive). E.g. `--disable-commands="/cost,/eval,/model"`. |
@@ -48,6 +47,8 @@ $ docker agent run [config] [message...] [flags]
| `--template ` | Template image for the sandbox (default: `docker/sandbox-templates:docker-agent`) |
| `--sbx` | Prefer the `sbx` CLI backend when available (default `true`; set `--sbx=false` to force `docker sandbox`) |
| `--no-kit` | Disable the [auto-kit]({{ '/configuration/sandbox/' | relative_url }}#auto-kit): do not stage skills or prompt files into the sandbox |
+| `-w, --worktree [name]` | Run the agent in a fresh git worktree of the working directory, isolating its changes from your checkout. Optionally name it (`--worktree=my-feature`); otherwise a name is generated. Requires the working directory to be inside a git repository. Cannot be combined with `--remote` or `--sandbox`. When the session ends, a clean worktree is removed automatically; one with work prompts to keep or remove (never in `--exec`). |
+| `--worktree-pr ` | Run the agent in a git worktree checked out on an existing GitHub pull request (PR number, `#123`, or PR URL). Continues the PR's branch so commits push back to it. Requires the [GitHub CLI](https://cli.github.com/) (`gh`). Cannot be combined with `--worktree`, `--remote`, or `--sandbox`. |
| `--working-dir ` | Set the working directory for the session (applies to tools and relative paths) |
| `--env-from-file ` | Load environment variables from file (repeatable) |
| `--code-mode-tools` | Provide a single tool to call other tools via JavaScript (forces code-mode tools globally) |
@@ -83,12 +84,40 @@ $ docker agent run agent.yaml --hook-pre-tool-use "./scripts/validate.sh" --hook
$ docker agent run agent.yaml "question 1" "question 2" "question 3"
# Customize TUI display
-$ docker agent run agent.yaml --theme dracula
$ docker agent run agent.yaml --app-name "My Project"
$ docker agent run agent.yaml --sidebar=false
$ docker agent run agent.yaml --disable-commands="/cost,/eval,/model"
```
+
+
Isolate a run in a git worktree
+
+
When the working directory is inside a git repository, --worktree creates a fresh git worktree and points the session at it, so the agent's edits land on a separate branch and never touch your checkout. The worktree is stored under <data-dir>/worktrees/<name> on a branch named worktree-<name>.
+
+
+```bash
+# Run in an isolated worktree with a generated name (e.g. "focused_turing")
+$ docker agent run agent.yaml --worktree
+$ docker agent run agent.yaml -w "Refactor the auth package"
+
+# Give the worktree (and its branch) an explicit name
+$ docker agent run agent.yaml --worktree=auth-refactor
+
+# Check out an existing GitHub pull request to continue it (requires gh)
+$ docker agent run agent.yaml --worktree-pr 123
+$ docker agent run agent.yaml --worktree-pr https://github.com/owner/repo/pull/123
+```
+
+With `--worktree-pr`, the PR's head branch is checked out tracking its remote (via the [GitHub CLI](https://cli.github.com/)), so commits made during the run push straight back to the pull request. The worktree is stored under `/worktrees/pr-`.
+
+When the interactive session ends, the worktree is cleaned up based on its state:
+
+- **Clean** (no uncommitted changes, untracked files, or new commits): the worktree and its branch are removed automatically.
+- **Has work** (uncommitted changes, untracked files, or new commits): you're prompted to keep or remove it. Keeping preserves the directory and branch so you can return later; removing discards the worktree, its branch, and all that work.
+- **Non-interactive runs** (`--exec`): the worktree is never cleaned up — it's left in place for inspection.
+
+A worktree is only ever removed if `--worktree` created it for this run; a pre-existing worktree is never touched.
+
### `docker agent run --exec`
Run an agent in non-interactive (headless) mode. No TUI — output goes to stdout.
diff --git a/examples/worktree_create_hook.yaml b/examples/worktree_create_hook.yaml
new file mode 100644
index 000000000..03276f696
--- /dev/null
+++ b/examples/worktree_create_hook.yaml
@@ -0,0 +1,74 @@
+#
+# worktree_create hook
+# ==========================================================================
+#
+# The worktree_create event fires once, just after
+#
+# docker agent run --worktree[=name] agent.yaml
+#
+# creates a fresh git worktree and BEFORE the session starts. Each hook runs
+# INSIDE the new worktree (its working directory is the fresh checkout), so
+# setup commands operate on the new tree rather than your original checkout.
+#
+# The hook Input carries:
+#
+# .cwd - the new worktree directory (also the hook's workdir)
+# .worktree_path - same as .cwd, passed explicitly for convenience
+# .worktree_branch - the branch checked out in the worktree (worktree-)
+# .worktree_source_dir - the repo root the worktree was branched from
+#
+# Use it to prepare the checkout before the agent begins: copy untracked
+# files git won't carry over (.env, local config), install dependencies,
+# warm caches, etc.
+#
+# A hook may ABORT the run by blocking — return decision=block /
+# {"continue": false} or exit with code 2 — for example when a setup step
+# fails. Plain stdout is surfaced to the user as additional context.
+#
+# Unlike most events, worktree_create is dispatched from the CLI rather than
+# the run loop, because the worktree (and the working directory the runtime,
+# session, tools and snapshot machinery all capture) must be settled before
+# the runtime and session exist.
+#
+# Try it:
+# cd into a git repo, then:
+# docker agent run --worktree examples/worktree_create_hook.yaml
+#
+
+agents:
+ root:
+ model: openai/gpt-4o
+ description: Demonstrates the worktree_create hook for preparing a fresh git worktree.
+ instruction: |
+ You are a coding assistant working inside an isolated git worktree.
+ Use the shell and filesystem tools to help the user.
+ toolsets:
+ - type: shell
+ - type: filesystem
+
+ hooks:
+ worktree_create:
+ # Copy untracked dotfiles git won't bring into the new worktree.
+ # The worktree lives under the docker-agent data directory, far
+ # from the original checkout, so resolve the source via
+ # .worktree_source_dir rather than a relative path.
+ - name: seed local env
+ type: command
+ timeout: 30
+ command: |
+ INPUT=$(cat)
+ BRANCH=$(echo "$INPUT" | jq -r '.worktree_branch // "unknown"')
+ SRC=$(echo "$INPUT" | jq -r '.worktree_source_dir // ""')
+ if [ -n "$SRC" ] && [ -f "$SRC/.env" ] && [ ! -f .env ]; then
+ cp "$SRC/.env" .env
+ fi
+ echo "Prepared worktree on branch $BRANCH"
+
+ # Install dependencies in the fresh tree, aborting the run on failure.
+ - name: install dependencies
+ type: command
+ timeout: 600
+ command: |
+ if [ -f package.json ]; then
+ npm install || { echo '{"continue": false, "system_message": "npm install failed"}'; exit 2; }
+ fi
diff --git a/pkg/config/latest/types.go b/pkg/config/latest/types.go
index 406c3fb2b..89cf60c48 100644
--- a/pkg/config/latest/types.go
+++ b/pkg/config/latest/types.go
@@ -1949,6 +1949,18 @@ type HooksConfig struct {
// tool_response_transform scrubs tool output. Tool-matched, like
// pre_tool_use / post_tool_use.
ToolResponseTransform []HookMatcherConfig `json:"tool_response_transform,omitempty" yaml:"tool_response_transform,omitempty"`
+
+ // WorktreeCreate hooks run once, just after `docker agent run
+ // --worktree` creates a git worktree and before the session starts.
+ // They execute inside the new worktree (their working directory is
+ // the fresh checkout) with the worktree path, branch, and source
+ // repository root passed in worktree_path / worktree_branch /
+ // worktree_source_dir. Use them to prepare the checkout — copy
+ // untracked files like .env from the source dir, install
+ // dependencies, warm caches. A hook may abort the run by blocking
+ // (decision="block" / continue=false / exit code 2); stdout is added
+ // as context.
+ WorktreeCreate []HookDefinition `json:"worktree_create,omitempty" yaml:"worktree_create,omitempty"`
}
// IsEmpty returns true if no hooks are configured
@@ -1978,7 +1990,8 @@ func (h *HooksConfig) IsEmpty() bool {
len(h.OnToolApprovalDecision) == 0 &&
len(h.BeforeCompaction) == 0 &&
len(h.AfterCompaction) == 0 &&
- len(h.ToolResponseTransform) == 0
+ len(h.ToolResponseTransform) == 0 &&
+ len(h.WorktreeCreate) == 0
}
// HookMatcherConfig represents a hook matcher with its hooks.
@@ -2238,6 +2251,13 @@ func (h *HooksConfig) Validate() error {
}
}
+ // Validate WorktreeCreate hooks
+ for i, hook := range h.WorktreeCreate {
+ if err := hook.validate("worktree_create", i); err != nil {
+ return err
+ }
+ }
+
return nil
}
diff --git a/pkg/hooks/executor.go b/pkg/hooks/executor.go
index b771ad1e9..d5cee1336 100644
--- a/pkg/hooks/executor.go
+++ b/pkg/hooks/executor.go
@@ -96,6 +96,7 @@ func compileEvents(c *Config) map[EventType][]matcher {
EventBeforeCompaction: flat(c.BeforeCompaction),
EventAfterCompaction: flat(c.AfterCompaction),
EventToolResponseTransform: compileMatchers(c.ToolResponseTransform),
+ EventWorktreeCreate: flat(c.WorktreeCreate),
}
}
@@ -294,7 +295,8 @@ func stdoutAsContext(event EventType) bool {
EventUserPromptSubmit,
EventTurnStart,
EventPreCompact,
- EventStop:
+ EventStop,
+ EventWorktreeCreate:
return true
}
return false
diff --git a/pkg/hooks/handler.go b/pkg/hooks/handler.go
index eaf21fdc1..c1a3849d7 100644
--- a/pkg/hooks/handler.go
+++ b/pkg/hooks/handler.go
@@ -124,10 +124,21 @@ func newCommandFactory() HandlerFactory {
}
}
+// hookWorkingDir resolves the directory a command hook runs in. An
+// absolute override wins; a relative override is joined onto the
+// executor's working directory; with no override the executor's working
+// directory is used. Falling back to the executor's working directory
+// (rather than "", which would inherit the process cwd) matters when the
+// executor runs before the process has chdir'd into it — e.g. the
+// CLI-dispatched worktree_create event, whose working dir is the freshly
+// created worktree.
func hookWorkingDir(base, override string) string {
- if override == "" || filepath.IsAbs(override) {
+ if filepath.IsAbs(override) {
return override
}
+ if override == "" {
+ return base
+ }
if base == "" {
return override
}
diff --git a/pkg/hooks/handler_test.go b/pkg/hooks/handler_test.go
index ea4eedfb8..c3d32c243 100644
--- a/pkg/hooks/handler_test.go
+++ b/pkg/hooks/handler_test.go
@@ -230,6 +230,32 @@ func TestCommandHookUsesPerHookEnvAndWorkingDir(t *testing.T) {
assert.True(t, strings.HasSuffix(result.AdditionalContext, "/scripts"), result.AdditionalContext)
}
+// TestCommandHookDefaultsToExecutorWorkingDir pins that a hook WITHOUT a
+// working_dir override runs in the executor's working directory rather
+// than inheriting the process cwd. This matters for executors that run
+// before the process has chdir'd into their working dir — e.g. the
+// CLI-dispatched worktree_create event, whose working dir is the freshly
+// created worktree.
+func TestCommandHookDefaultsToExecutorWorkingDir(t *testing.T) {
+ t.Parallel()
+
+ // Resolve symlinks so the comparison is stable on macOS, where
+ // TempDir lives under /var -> /private/var.
+ workDir, err := filepath.EvalSymlinks(t.TempDir())
+ require.NoError(t, err)
+
+ exec := NewExecutor(&Config{SessionStart: []Hook{{
+ Type: HookTypeCommand,
+ Command: `printf '{"hook_specific_output":{"additional_context":"%s"}}' "$(pwd)"`,
+ }}}, workDir, os.Environ())
+
+ result, err := exec.Dispatch(t.Context(), EventSessionStart, &Input{SessionID: "s"})
+ require.NoError(t, err)
+ got, err := filepath.EvalSymlinks(strings.TrimSpace(result.AdditionalContext))
+ require.NoError(t, err)
+ assert.Equal(t, workDir, got)
+}
+
func TestHookOnErrorBlockCanDenyNonFailClosedEvent(t *testing.T) {
t.Parallel()
diff --git a/pkg/hooks/types.go b/pkg/hooks/types.go
index 2e0dddadc..06e16be3f 100644
--- a/pkg/hooks/types.go
+++ b/pkg/hooks/types.go
@@ -161,6 +161,22 @@ const (
// Tool-scoped: matchers select which tools the hook runs against,
// like pre_tool_use / post_tool_use.
EventToolResponseTransform EventType = "tool_response_transform"
+ // EventWorktreeCreate fires once, just after the CLI creates a git
+ // worktree for a `--worktree` run and before the session starts. The
+ // new working directory is reported in [Input.Cwd] (hooks run there)
+ // and the worktree path, branch, and source repository root are also
+ // carried explicitly in [Input.WorktreePath], [Input.WorktreeBranch],
+ // and [Input.WorktreeSourceDir]. Use it to prepare the fresh checkout
+ // — copy untracked files like .env from the source dir, install
+ // dependencies, warm caches — before the agent begins.
+ //
+ // Unlike most events it is dispatched from the CLI rather than the
+ // run loop, because the worktree (and the working directory every
+ // downstream component captures) must be settled before the runtime
+ // and session exist. Plain stdout is surfaced as additional context;
+ // a hook may abort the run by returning decision="block" (or
+ // continue=false / exit code 2), e.g. when setup fails.
+ EventWorktreeCreate EventType = "worktree_create"
)
// Input is the JSON-serializable payload passed to hooks via stdin.
@@ -293,6 +309,16 @@ type Input struct {
// applied to the session so observability handlers can audit /
// archive what was summarized.
Summary string `json:"summary,omitempty"`
+
+ // WorktreeCreate specific: the absolute path of the freshly created
+ // git worktree and the branch checked out in it. [Input.Cwd] is set
+ // to the same path so command hooks run inside the new worktree.
+ // WorktreeSourceDir is the repository root the worktree was branched
+ // from, so setup hooks can copy untracked files (.env, local config)
+ // from the original checkout into the fresh one.
+ WorktreePath string `json:"worktree_path,omitempty"`
+ WorktreeBranch string `json:"worktree_branch,omitempty"`
+ WorktreeSourceDir string `json:"worktree_source_dir,omitempty"`
}
// ModelEndpoint identifies one of an agent's configured models plus
diff --git a/pkg/worktree/namesgenerator/names-generator.go b/pkg/worktree/namesgenerator/names-generator.go
new file mode 100644
index 000000000..76e8a4db0
--- /dev/null
+++ b/pkg/worktree/namesgenerator/names-generator.go
@@ -0,0 +1,867 @@
+// This file is copied verbatim (word lists and logic) from Moby's
+// github.com/moby/moby/pkg/namesgenerator (v27.3.1), which is licensed under
+// the Apache License, Version 2.0. It is vendored here to avoid taking a
+// dependency on the full moby engine module just for the name lists.
+//
+// Source: https://github.com/moby/moby/blob/v27.3.1/pkg/namesgenerator/names-generator.go
+
+// Package namesgenerator generates random names.
+//
+// This package is officially "frozen" - no new additions will be accepted.
+//
+// For a long time, this package provided a lot of joy within the project, but
+// at some point the conflicts of opinion became greater than the added joy.
+//
+// At some future time, this may be replaced with something that sparks less
+// controversy, but for now it will remain as-is.
+//
+// See also https://github.com/moby/moby/pull/43210#issuecomment-1029934277
+package namesgenerator
+
+import (
+ "math/rand"
+ "strconv"
+)
+
+var (
+ left = [...]string{
+ "admiring",
+ "adoring",
+ "affectionate",
+ "agitated",
+ "amazing",
+ "angry",
+ "awesome",
+ "beautiful",
+ "blissful",
+ "bold",
+ "boring",
+ "brave",
+ "busy",
+ "charming",
+ "clever",
+ "compassionate",
+ "competent",
+ "condescending",
+ "confident",
+ "cool",
+ "cranky",
+ "crazy",
+ "dazzling",
+ "determined",
+ "distracted",
+ "dreamy",
+ "eager",
+ "ecstatic",
+ "elastic",
+ "elated",
+ "elegant",
+ "eloquent",
+ "epic",
+ "exciting",
+ "fervent",
+ "festive",
+ "flamboyant",
+ "focused",
+ "friendly",
+ "frosty",
+ "funny",
+ "gallant",
+ "gifted",
+ "goofy",
+ "gracious",
+ "great",
+ "happy",
+ "hardcore",
+ "heuristic",
+ "hopeful",
+ "hungry",
+ "infallible",
+ "inspiring",
+ "intelligent",
+ "interesting",
+ "jolly",
+ "jovial",
+ "keen",
+ "kind",
+ "laughing",
+ "loving",
+ "lucid",
+ "magical",
+ "modest",
+ "musing",
+ "mystifying",
+ "naughty",
+ "nervous",
+ "nice",
+ "nifty",
+ "nostalgic",
+ "objective",
+ "optimistic",
+ "peaceful",
+ "pedantic",
+ "pensive",
+ "practical",
+ "priceless",
+ "quirky",
+ "quizzical",
+ "recursing",
+ "relaxed",
+ "reverent",
+ "romantic",
+ "sad",
+ "serene",
+ "sharp",
+ "silly",
+ "sleepy",
+ "stoic",
+ "strange",
+ "stupefied",
+ "suspicious",
+ "sweet",
+ "tender",
+ "thirsty",
+ "trusting",
+ "unruffled",
+ "upbeat",
+ "vibrant",
+ "vigilant",
+ "vigorous",
+ "wizardly",
+ "wonderful",
+ "xenodochial",
+ "youthful",
+ "zealous",
+ "zen",
+ }
+
+ // Docker, starting from 0.7.x, generates names from notable scientists and hackers.
+ // Please, for any amazing man that you add to the list, consider adding an equally amazing woman to it, and vice versa.
+ right = [...]string{
+ // Maria Gaetana Agnesi - Italian mathematician, philosopher, theologian and humanitarian. She was the first woman to write a mathematics handbook and the first woman appointed as a Mathematics Professor at a University. https://en.wikipedia.org/wiki/Maria_Gaetana_Agnesi
+ "agnesi",
+
+ // Muhammad ibn Jābir al-Ḥarrānī al-Battānī was a founding father of astronomy. https://en.wikipedia.org/wiki/Mu%E1%B8%A5ammad_ibn_J%C4%81bir_al-%E1%B8%A4arr%C4%81n%C4%AB_al-Batt%C4%81n%C4%AB
+ "albattani",
+
+ // Frances E. Allen, became the first female IBM Fellow in 1989. In 2006, she became the first female recipient of the ACM's Turing Award. https://en.wikipedia.org/wiki/Frances_E._Allen
+ "allen",
+
+ // June Almeida - Scottish virologist who took the first pictures of the rubella virus - https://en.wikipedia.org/wiki/June_Almeida
+ "almeida",
+
+ // Kathleen Antonelli, American computer programmer and one of the six original programmers of the ENIAC - https://en.wikipedia.org/wiki/Kathleen_Antonelli
+ "antonelli",
+
+ // Archimedes was a physicist, engineer and mathematician who invented too many things to list them here. https://en.wikipedia.org/wiki/Archimedes
+ "archimedes",
+
+ // Maria Ardinghelli - Italian translator, mathematician and physicist - https://en.wikipedia.org/wiki/Maria_Ardinghelli
+ "ardinghelli",
+
+ // Aryabhata - Ancient Indian mathematician-astronomer during 476-550 CE https://en.wikipedia.org/wiki/Aryabhata
+ "aryabhata",
+
+ // Wanda Austin - Wanda Austin is the President and CEO of The Aerospace Corporation, a leading architect for the US security space programs. https://en.wikipedia.org/wiki/Wanda_Austin
+ "austin",
+
+ // Charles Babbage invented the concept of a programmable computer. https://en.wikipedia.org/wiki/Charles_Babbage.
+ "babbage",
+
+ // Stefan Banach - Polish mathematician, was one of the founders of modern functional analysis. https://en.wikipedia.org/wiki/Stefan_Banach
+ "banach",
+
+ // Buckaroo Banzai and his mentor Dr. Hikita perfected the "oscillation overthruster", a device that allows one to pass through solid matter. - https://en.wikipedia.org/wiki/The_Adventures_of_Buckaroo_Banzai_Across_the_8th_Dimension
+ "banzai",
+
+ // John Bardeen co-invented the transistor - https://en.wikipedia.org/wiki/John_Bardeen
+ "bardeen",
+
+ // Jean Bartik, born Betty Jean Jennings, was one of the original programmers for the ENIAC computer. https://en.wikipedia.org/wiki/Jean_Bartik
+ "bartik",
+
+ // Laura Bassi, the world's first female professor https://en.wikipedia.org/wiki/Laura_Bassi
+ "bassi",
+
+ // Hugh Beaver, British engineer, founder of the Guinness Book of World Records https://en.wikipedia.org/wiki/Hugh_Beaver
+ "beaver",
+
+ // Alexander Graham Bell - an eminent Scottish-born scientist, inventor, engineer and innovator who is credited with inventing the first practical telephone - https://en.wikipedia.org/wiki/Alexander_Graham_Bell
+ "bell",
+
+ // Karl Friedrich Benz - a German automobile engineer. Inventor of the first practical motorcar. https://en.wikipedia.org/wiki/Karl_Benz
+ "benz",
+
+ // Homi J Bhabha - was an Indian nuclear physicist, founding director, and professor of physics at the Tata Institute of Fundamental Research. Colloquially known as "father of Indian nuclear programme"- https://en.wikipedia.org/wiki/Homi_J._Bhabha
+ "bhabha",
+
+ // Bhaskara II - Ancient Indian mathematician-astronomer whose work on calculus predates Newton and Leibniz by over half a millennium - https://en.wikipedia.org/wiki/Bh%C4%81skara_II#Calculus
+ "bhaskara",
+
+ // Sue Black - British computer scientist and campaigner. She has been instrumental in saving Bletchley Park, the site of World War II codebreaking - https://en.wikipedia.org/wiki/Sue_Black_(computer_scientist)
+ "black",
+
+ // Elizabeth Helen Blackburn - Australian-American Nobel laureate; best known for co-discovering telomerase. https://en.wikipedia.org/wiki/Elizabeth_Blackburn
+ "blackburn",
+
+ // Elizabeth Blackwell - American doctor and first American woman to receive a medical degree - https://en.wikipedia.org/wiki/Elizabeth_Blackwell
+ "blackwell",
+
+ // Niels Bohr is the father of quantum theory. https://en.wikipedia.org/wiki/Niels_Bohr.
+ "bohr",
+
+ // Kathleen Booth, she's credited with writing the first assembly language. https://en.wikipedia.org/wiki/Kathleen_Booth
+ "booth",
+
+ // Anita Borg - Anita Borg was the founding director of the Institute for Women and Technology (IWT). https://en.wikipedia.org/wiki/Anita_Borg
+ "borg",
+
+ // Satyendra Nath Bose - He provided the foundation for Bose–Einstein statistics and the theory of the Bose–Einstein condensate. - https://en.wikipedia.org/wiki/Satyendra_Nath_Bose
+ "bose",
+
+ // Katherine Louise Bouman is an imaging scientist and Assistant Professor of Computer Science at the California Institute of Technology. She researches computational methods for imaging, and developed an algorithm that made possible the picture first visualization of a black hole using the Event Horizon Telescope. - https://en.wikipedia.org/wiki/Katie_Bouman
+ "bouman",
+
+ // Evelyn Boyd Granville - She was one of the first African-American woman to receive a Ph.D. in mathematics; she earned it in 1949 from Yale University. https://en.wikipedia.org/wiki/Evelyn_Boyd_Granville
+ "boyd",
+
+ // Brahmagupta - Ancient Indian mathematician during 598-670 CE who gave rules to compute with zero - https://en.wikipedia.org/wiki/Brahmagupta#Zero
+ "brahmagupta",
+
+ // Walter Houser Brattain co-invented the transistor - https://en.wikipedia.org/wiki/Walter_Houser_Brattain
+ "brattain",
+
+ // Emmett Brown invented time travel. https://en.wikipedia.org/wiki/Emmett_Brown (thanks Brian Goff)
+ "brown",
+
+ // Linda Brown Buck - American biologist and Nobel laureate best known for her genetic and molecular analyses of the mechanisms of smell. https://en.wikipedia.org/wiki/Linda_B._Buck
+ "buck",
+
+ // Dame Susan Jocelyn Bell Burnell - Northern Irish astrophysicist who discovered radio pulsars and was the first to analyse them. https://en.wikipedia.org/wiki/Jocelyn_Bell_Burnell
+ "burnell",
+
+ // Annie Jump Cannon - pioneering female astronomer who classified hundreds of thousands of stars and created the system we use to understand stars today. https://en.wikipedia.org/wiki/Annie_Jump_Cannon
+ "cannon",
+
+ // Rachel Carson - American marine biologist and conservationist, her book Silent Spring and other writings are credited with advancing the global environmental movement. https://en.wikipedia.org/wiki/Rachel_Carson
+ "carson",
+
+ // Dame Mary Lucy Cartwright - British mathematician who was one of the first to study what is now known as chaos theory. Also known for Cartwright's theorem which finds applications in signal processing. https://en.wikipedia.org/wiki/Mary_Cartwright
+ "cartwright",
+
+ // George Washington Carver - American agricultural scientist and inventor. He was the most prominent black scientist of the early 20th century. https://en.wikipedia.org/wiki/George_Washington_Carver
+ "carver",
+
+ // Vinton Gray Cerf - American Internet pioneer, recognised as one of "the fathers of the Internet". With Robert Elliot Kahn, he designed TCP and IP, the primary data communication protocols of the Internet and other computer networks. https://en.wikipedia.org/wiki/Vint_Cerf
+ "cerf",
+
+ // Subrahmanyan Chandrasekhar - Astrophysicist known for his mathematical theory on different stages and evolution in structures of the stars. He has won nobel prize for physics - https://en.wikipedia.org/wiki/Subrahmanyan_Chandrasekhar
+ "chandrasekhar",
+
+ // Sergey Alexeyevich Chaplygin (Russian: Серге́й Алексе́евич Чаплы́гин; April 5, 1869 – October 8, 1942) was a Russian and Soviet physicist, mathematician, and mechanical engineer. He is known for mathematical formulas such as Chaplygin's equation and for a hypothetical substance in cosmology called Chaplygin gas, named after him. https://en.wikipedia.org/wiki/Sergey_Chaplygin
+ "chaplygin",
+
+ // Émilie du Châtelet - French natural philosopher, mathematician, physicist, and author during the early 1730s, known for her translation of and commentary on Isaac Newton's book Principia containing basic laws of physics. https://en.wikipedia.org/wiki/%C3%89milie_du_Ch%C3%A2telet
+ "chatelet",
+
+ // Asima Chatterjee was an Indian organic chemist noted for her research on vinca alkaloids, development of drugs for treatment of epilepsy and malaria - https://en.wikipedia.org/wiki/Asima_Chatterjee
+ "chatterjee",
+
+ // David Lee Chaum - American computer scientist and cryptographer. Known for his seminal contributions in the field of anonymous communication. https://en.wikipedia.org/wiki/David_Chaum
+ "chaum",
+
+ // Pafnuty Chebyshev - Russian mathematician. He is known fo his works on probability, statistics, mechanics, analytical geometry and number theory https://en.wikipedia.org/wiki/Pafnuty_Chebyshev
+ "chebyshev",
+
+ // Joan Clarke - Bletchley Park code breaker during the Second World War who pioneered techniques that remained top secret for decades. Also an accomplished numismatist https://en.wikipedia.org/wiki/Joan_Clarke
+ "clarke",
+
+ // Bram Cohen - American computer programmer and author of the BitTorrent peer-to-peer protocol. https://en.wikipedia.org/wiki/Bram_Cohen
+ "cohen",
+
+ // Jane Colden - American botanist widely considered the first female American botanist - https://en.wikipedia.org/wiki/Jane_Colden
+ "colden",
+
+ // Gerty Theresa Cori - American biochemist who became the third woman—and first American woman—to win a Nobel Prize in science, and the first woman to be awarded the Nobel Prize in Physiology or Medicine. Cori was born in Prague. https://en.wikipedia.org/wiki/Gerty_Cori
+ "cori",
+
+ // Seymour Roger Cray was an American electrical engineer and supercomputer architect who designed a series of computers that were the fastest in the world for decades. https://en.wikipedia.org/wiki/Seymour_Cray
+ "cray",
+
+ // Marie Curie discovered radioactivity. https://en.wikipedia.org/wiki/Marie_Curie.
+ "curie",
+
+ // This entry reflects a husband and wife team who worked together:
+ // Joan Curran was a Welsh scientist who developed radar and invented chaff, a radar countermeasure. https://en.wikipedia.org/wiki/Joan_Curran
+ // Samuel Curran was an Irish physicist who worked alongside his wife during WWII and invented the proximity fuse. https://en.wikipedia.org/wiki/Samuel_Curran
+ "curran",
+
+ // Charles Darwin established the principles of natural evolution. https://en.wikipedia.org/wiki/Charles_Darwin.
+ "darwin",
+
+ // Leonardo Da Vinci invented too many things to list here. https://en.wikipedia.org/wiki/Leonardo_da_Vinci.
+ "davinci",
+
+ // A. K. (Alexander Keewatin) Dewdney, Canadian mathematician, computer scientist, author and filmmaker. Contributor to Scientific American's "Computer Recreations" from 1984 to 1991. Author of Core War (program), The Planiverse, The Armchair Universe, The Magic Machine, The New Turing Omnibus, and more. https://en.wikipedia.org/wiki/Alexander_Dewdney
+ "dewdney",
+
+ // Satish Dhawan - Indian mathematician and aerospace engineer, known for leading the successful and indigenous development of the Indian space programme. https://en.wikipedia.org/wiki/Satish_Dhawan
+ "dhawan",
+
+ // Bailey Whitfield Diffie - American cryptographer and one of the pioneers of public-key cryptography. https://en.wikipedia.org/wiki/Whitfield_Diffie
+ "diffie",
+
+ // Edsger Wybe Dijkstra was a Dutch computer scientist and mathematical scientist. https://en.wikipedia.org/wiki/Edsger_W._Dijkstra.
+ "dijkstra",
+
+ // Paul Adrien Maurice Dirac - English theoretical physicist who made fundamental contributions to the early development of both quantum mechanics and quantum electrodynamics. https://en.wikipedia.org/wiki/Paul_Dirac
+ "dirac",
+
+ // Agnes Meyer Driscoll - American cryptanalyst during World Wars I and II who successfully cryptanalysed a number of Japanese ciphers. She was also the co-developer of one of the cipher machines of the US Navy, the CM. https://en.wikipedia.org/wiki/Agnes_Meyer_Driscoll
+ "driscoll",
+
+ // Donna Dubinsky - played an integral role in the development of personal digital assistants (PDAs) serving as CEO of Palm, Inc. and co-founding Handspring. https://en.wikipedia.org/wiki/Donna_Dubinsky
+ "dubinsky",
+
+ // Annie Easley - She was a leading member of the team which developed software for the Centaur rocket stage and one of the first African-Americans in her field. https://en.wikipedia.org/wiki/Annie_Easley
+ "easley",
+
+ // Thomas Alva Edison, prolific inventor https://en.wikipedia.org/wiki/Thomas_Edison
+ "edison",
+
+ // Albert Einstein invented the general theory of relativity. https://en.wikipedia.org/wiki/Albert_Einstein
+ "einstein",
+
+ // Alexandra Asanovna Elbakyan (Russian: Алекса́ндра Аса́новна Элбакя́н) is a Kazakhstani graduate student, computer programmer, internet pirate in hiding, and the creator of the site Sci-Hub. Nature has listed her in 2016 in the top ten people that mattered in science, and Ars Technica has compared her to Aaron Swartz. - https://en.wikipedia.org/wiki/Alexandra_Elbakyan
+ "elbakyan",
+
+ // Taher A. ElGamal - Egyptian cryptographer best known for the ElGamal discrete log cryptosystem and the ElGamal digital signature scheme. https://en.wikipedia.org/wiki/Taher_Elgamal
+ "elgamal",
+
+ // Gertrude Elion - American biochemist, pharmacologist and the 1988 recipient of the Nobel Prize in Medicine - https://en.wikipedia.org/wiki/Gertrude_Elion
+ "elion",
+
+ // James Henry Ellis - British engineer and cryptographer employed by the GCHQ. Best known for conceiving for the first time, the idea of public-key cryptography. https://en.wikipedia.org/wiki/James_H._Ellis
+ "ellis",
+
+ // Douglas Engelbart gave the mother of all demos: https://en.wikipedia.org/wiki/Douglas_Engelbart
+ "engelbart",
+
+ // Euclid invented geometry. https://en.wikipedia.org/wiki/Euclid
+ "euclid",
+
+ // Leonhard Euler invented large parts of modern mathematics. https://de.wikipedia.org/wiki/Leonhard_Euler
+ "euler",
+
+ // Michael Faraday - British scientist who contributed to the study of electromagnetism and electrochemistry. https://en.wikipedia.org/wiki/Michael_Faraday
+ "faraday",
+
+ // Horst Feistel - German-born American cryptographer who was one of the earliest non-government researchers to study the design and theory of block ciphers. Co-developer of DES and Lucifer. Feistel networks, a symmetric structure used in the construction of block ciphers are named after him. https://en.wikipedia.org/wiki/Horst_Feistel
+ "feistel",
+
+ // Pierre de Fermat pioneered several aspects of modern mathematics. https://en.wikipedia.org/wiki/Pierre_de_Fermat
+ "fermat",
+
+ // Enrico Fermi invented the first nuclear reactor. https://en.wikipedia.org/wiki/Enrico_Fermi.
+ "fermi",
+
+ // Richard Feynman was a key contributor to quantum mechanics and particle physics. https://en.wikipedia.org/wiki/Richard_Feynman
+ "feynman",
+
+ // Benjamin Franklin is famous for his experiments in electricity and the invention of the lightning rod.
+ "franklin",
+
+ // Yuri Alekseyevich Gagarin - Soviet pilot and cosmonaut, best known as the first human to journey into outer space. https://en.wikipedia.org/wiki/Yuri_Gagarin
+ "gagarin",
+
+ // Galileo was a founding father of modern astronomy, and faced politics and obscurantism to establish scientific truth. https://en.wikipedia.org/wiki/Galileo_Galilei
+ "galileo",
+
+ // Évariste Galois - French mathematician whose work laid the foundations of Galois theory and group theory, two major branches of abstract algebra, and the subfield of Galois connections, all while still in his late teens. https://en.wikipedia.org/wiki/%C3%89variste_Galois
+ "galois",
+
+ // Kadambini Ganguly - Indian physician, known for being the first South Asian female physician, trained in western medicine, to graduate in South Asia. https://en.wikipedia.org/wiki/Kadambini_Ganguly
+ "ganguly",
+
+ // William Henry "Bill" Gates III is an American business magnate, philanthropist, investor, computer programmer, and inventor. https://en.wikipedia.org/wiki/Bill_Gates
+ "gates",
+
+ // Johann Carl Friedrich Gauss - German mathematician who made significant contributions to many fields, including number theory, algebra, statistics, analysis, differential geometry, geodesy, geophysics, mechanics, electrostatics, magnetic fields, astronomy, matrix theory, and optics. https://en.wikipedia.org/wiki/Carl_Friedrich_Gauss
+ "gauss",
+
+ // Marie-Sophie Germain - French mathematician, physicist and philosopher. Known for her work on elasticity theory, number theory and philosophy. https://en.wikipedia.org/wiki/Sophie_Germain
+ "germain",
+
+ // Adele Goldberg, was one of the designers and developers of the Smalltalk language. https://en.wikipedia.org/wiki/Adele_Goldberg_(computer_scientist)
+ "goldberg",
+
+ // Adele Goldstine, born Adele Katz, wrote the complete technical description for the first electronic digital computer, ENIAC. https://en.wikipedia.org/wiki/Adele_Goldstine
+ "goldstine",
+
+ // Shafi Goldwasser is a computer scientist known for creating theoretical foundations of modern cryptography. Winner of 2012 ACM Turing Award. https://en.wikipedia.org/wiki/Shafi_Goldwasser
+ "goldwasser",
+
+ // James Golick, all around gangster.
+ "golick",
+
+ // Jane Goodall - British primatologist, ethologist, and anthropologist who is considered to be the world's foremost expert on chimpanzees - https://en.wikipedia.org/wiki/Jane_Goodall
+ "goodall",
+
+ // Stephen Jay Gould was an American paleontologist, evolutionary biologist, and historian of science. He is most famous for the theory of punctuated equilibrium - https://en.wikipedia.org/wiki/Stephen_Jay_Gould
+ "gould",
+
+ // Carolyn Widney Greider - American molecular biologist and joint winner of the 2009 Nobel Prize for Physiology or Medicine for the discovery of telomerase. https://en.wikipedia.org/wiki/Carol_W._Greider
+ "greider",
+
+ // Alexander Grothendieck - German-born French mathematician who became a leading figure in the creation of modern algebraic geometry. https://en.wikipedia.org/wiki/Alexander_Grothendieck
+ "grothendieck",
+
+ // Lois Haibt - American computer scientist, part of the team at IBM that developed FORTRAN - https://en.wikipedia.org/wiki/Lois_Haibt
+ "haibt",
+
+ // Margaret Hamilton - Director of the Software Engineering Division of the MIT Instrumentation Laboratory, which developed on-board flight software for the Apollo space program. https://en.wikipedia.org/wiki/Margaret_Hamilton_(scientist)
+ "hamilton",
+
+ // Caroline Harriet Haslett - English electrical engineer, electricity industry administrator and champion of women's rights. Co-author of British Standard 1363 that specifies AC power plugs and sockets used across the United Kingdom (which is widely considered as one of the safest designs). https://en.wikipedia.org/wiki/Caroline_Haslett
+ "haslett",
+
+ // Stephen Hawking pioneered the field of cosmology by combining general relativity and quantum mechanics. https://en.wikipedia.org/wiki/Stephen_Hawking
+ "hawking",
+
+ // Werner Heisenberg was a founding father of quantum mechanics. https://en.wikipedia.org/wiki/Werner_Heisenberg
+ "heisenberg",
+
+ // Martin Edward Hellman - American cryptologist, best known for his invention of public-key cryptography in co-operation with Whitfield Diffie and Ralph Merkle. https://en.wikipedia.org/wiki/Martin_Hellman
+ "hellman",
+
+ // Grete Hermann was a German philosopher noted for her philosophical work on the foundations of quantum mechanics. https://en.wikipedia.org/wiki/Grete_Hermann
+ "hermann",
+
+ // Caroline Lucretia Herschel - German astronomer and discoverer of several comets. https://en.wikipedia.org/wiki/Caroline_Herschel
+ "herschel",
+
+ // Heinrich Rudolf Hertz - German physicist who first conclusively proved the existence of the electromagnetic waves. https://en.wikipedia.org/wiki/Heinrich_Hertz
+ "hertz",
+
+ // Jaroslav Heyrovský was the inventor of the polarographic method, father of the electroanalytical method, and recipient of the Nobel Prize in 1959. His main field of work was polarography. https://en.wikipedia.org/wiki/Jaroslav_Heyrovsk%C3%BD
+ "heyrovsky",
+
+ // Dorothy Hodgkin was a British biochemist, credited with the development of protein crystallography. She was awarded the Nobel Prize in Chemistry in 1964. https://en.wikipedia.org/wiki/Dorothy_Hodgkin
+ "hodgkin",
+
+ // Douglas R. Hofstadter is an American professor of cognitive science and author of the Pulitzer Prize and American Book Award-winning work Goedel, Escher, Bach: An Eternal Golden Braid in 1979. A mind-bending work which coined Hofstadter's Law: "It always takes longer than you expect, even when you take into account Hofstadter's Law." https://en.wikipedia.org/wiki/Douglas_Hofstadter
+ "hofstadter",
+
+ // Erna Schneider Hoover revolutionized modern communication by inventing a computerized telephone switching method. https://en.wikipedia.org/wiki/Erna_Schneider_Hoover
+ "hoover",
+
+ // Grace Hopper developed the first compiler for a computer programming language and is credited with popularizing the term "debugging" for fixing computer glitches. https://en.wikipedia.org/wiki/Grace_Hopper
+ "hopper",
+
+ // Frances Hugle, she was an American scientist, engineer, and inventor who contributed to the understanding of semiconductors, integrated circuitry, and the unique electrical principles of microscopic materials. https://en.wikipedia.org/wiki/Frances_Hugle
+ "hugle",
+
+ // Hypatia - Greek Alexandrine Neoplatonist philosopher in Egypt who was one of the earliest mothers of mathematics - https://en.wikipedia.org/wiki/Hypatia
+ "hypatia",
+
+ // Teruko Ishizaka - Japanese scientist and immunologist who co-discovered the antibody class Immunoglobulin E. https://en.wikipedia.org/wiki/Teruko_Ishizaka
+ "ishizaka",
+
+ // Mary Jackson, American mathematician and aerospace engineer who earned the highest title within NASA's engineering department - https://en.wikipedia.org/wiki/Mary_Jackson_(engineer)
+ "jackson",
+
+ // Yeong-Sil Jang was a Korean scientist and astronomer during the Joseon Dynasty; he invented the first metal printing press and water gauge. https://en.wikipedia.org/wiki/Jang_Yeong-sil
+ "jang",
+
+ // Mae Carol Jemison - is an American engineer, physician, and former NASA astronaut. She became the first black woman to travel in space when she served as a mission specialist aboard the Space Shuttle Endeavour - https://en.wikipedia.org/wiki/Mae_Jemison
+ "jemison",
+
+ // Betty Jennings - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Jean_Bartik
+ "jennings",
+
+ // Mary Lou Jepsen, was the founder and chief technology officer of One Laptop Per Child (OLPC), and the founder of Pixel Qi. https://en.wikipedia.org/wiki/Mary_Lou_Jepsen
+ "jepsen",
+
+ // Katherine Coleman Goble Johnson - American physicist and mathematician contributed to the NASA. https://en.wikipedia.org/wiki/Katherine_Johnson
+ "johnson",
+
+ // Irène Joliot-Curie - French scientist who was awarded the Nobel Prize for Chemistry in 1935. Daughter of Marie and Pierre Curie. https://en.wikipedia.org/wiki/Ir%C3%A8ne_Joliot-Curie
+ "joliot",
+
+ // Karen Spärck Jones came up with the concept of inverse document frequency, which is used in most search engines today. https://en.wikipedia.org/wiki/Karen_Sp%C3%A4rck_Jones
+ "jones",
+
+ // A. P. J. Abdul Kalam - is an Indian scientist aka Missile Man of India for his work on the development of ballistic missile and launch vehicle technology - https://en.wikipedia.org/wiki/A._P._J._Abdul_Kalam
+ "kalam",
+
+ // Sergey Petrovich Kapitsa (Russian: Серге́й Петро́вич Капи́ца; 14 February 1928 – 14 August 2012) was a Russian physicist and demographer. He was best known as host of the popular and long-running Russian scientific TV show, Evident, but Incredible. His father was the Nobel laureate Soviet-era physicist Pyotr Kapitsa, and his brother was the geographer and Antarctic explorer Andrey Kapitsa. - https://en.wikipedia.org/wiki/Sergey_Kapitsa
+ "kapitsa",
+
+ // Susan Kare, created the icons and many of the interface elements for the original Apple Macintosh in the 1980s, and was an original employee of NeXT, working as the Creative Director. https://en.wikipedia.org/wiki/Susan_Kare
+ "kare",
+
+ // Mstislav Keldysh - a Soviet scientist in the field of mathematics and mechanics, academician of the USSR Academy of Sciences (1946), President of the USSR Academy of Sciences (1961–1975), three times Hero of Socialist Labor (1956, 1961, 1971), fellow of the Royal Society of Edinburgh (1968). https://en.wikipedia.org/wiki/Mstislav_Keldysh
+ "keldysh",
+
+ // Mary Kenneth Keller, Sister Mary Kenneth Keller became the first American woman to earn a PhD in Computer Science in 1965. https://en.wikipedia.org/wiki/Mary_Kenneth_Keller
+ "keller",
+
+ // Johannes Kepler, German astronomer known for his three laws of planetary motion - https://en.wikipedia.org/wiki/Johannes_Kepler
+ "kepler",
+
+ // Omar Khayyam - Persian mathematician, astronomer and poet. Known for his work on the classification and solution of cubic equations, for his contribution to the understanding of Euclid's fifth postulate and for computing the length of a year very accurately. https://en.wikipedia.org/wiki/Omar_Khayyam
+ "khayyam",
+
+ // Har Gobind Khorana - Indian-American biochemist who shared the 1968 Nobel Prize for Physiology - https://en.wikipedia.org/wiki/Har_Gobind_Khorana
+ "khorana",
+
+ // Jack Kilby invented silicon integrated circuits and gave Silicon Valley its name. - https://en.wikipedia.org/wiki/Jack_Kilby
+ "kilby",
+
+ // Maria Kirch - German astronomer and first woman to discover a comet - https://en.wikipedia.org/wiki/Maria_Margarethe_Kirch
+ "kirch",
+
+ // Donald Knuth - American computer scientist, author of "The Art of Computer Programming" and creator of the TeX typesetting system. https://en.wikipedia.org/wiki/Donald_Knuth
+ "knuth",
+
+ // Sophie Kowalevski - Russian mathematician responsible for important original contributions to analysis, differential equations and mechanics - https://en.wikipedia.org/wiki/Sofia_Kovalevskaya
+ "kowalevski",
+
+ // Marie-Jeanne de Lalande - French astronomer, mathematician and cataloguer of stars - https://en.wikipedia.org/wiki/Marie-Jeanne_de_Lalande
+ "lalande",
+
+ // Hedy Lamarr - Actress and inventor. The principles of her work are now incorporated into modern Wi-Fi, CDMA and Bluetooth technology. https://en.wikipedia.org/wiki/Hedy_Lamarr
+ "lamarr",
+
+ // Leslie B. Lamport - American computer scientist. Lamport is best known for his seminal work in distributed systems and was the winner of the 2013 Turing Award. https://en.wikipedia.org/wiki/Leslie_Lamport
+ "lamport",
+
+ // Mary Leakey - British paleoanthropologist who discovered the first fossilized Proconsul skull - https://en.wikipedia.org/wiki/Mary_Leakey
+ "leakey",
+
+ // Henrietta Swan Leavitt - she was an American astronomer who discovered the relation between the luminosity and the period of Cepheid variable stars. https://en.wikipedia.org/wiki/Henrietta_Swan_Leavitt
+ "leavitt",
+
+ // Esther Miriam Zimmer Lederberg - American microbiologist and a pioneer of bacterial genetics. https://en.wikipedia.org/wiki/Esther_Lederberg
+ "lederberg",
+
+ // Inge Lehmann - Danish seismologist and geophysicist. Known for discovering in 1936 that the Earth has a solid inner core inside a molten outer core. https://en.wikipedia.org/wiki/Inge_Lehmann
+ "lehmann",
+
+ // Daniel Lewin - Mathematician, Akamai co-founder, soldier, 9/11 victim-- Developed optimization techniques for routing traffic on the internet. Died attempting to stop the 9-11 hijackers. https://en.wikipedia.org/wiki/Daniel_Lewin
+ "lewin",
+
+ // Ruth Lichterman - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Ruth_Teitelbaum
+ "lichterman",
+
+ // Barbara Liskov - co-developed the Liskov substitution principle. Liskov was also the winner of the Turing Prize in 2008. - https://en.wikipedia.org/wiki/Barbara_Liskov
+ "liskov",
+
+ // Ada Lovelace invented the first algorithm. https://en.wikipedia.org/wiki/Ada_Lovelace (thanks James Turnbull)
+ "lovelace",
+
+ // Auguste and Louis Lumière - the first filmmakers in history - https://en.wikipedia.org/wiki/Auguste_and_Louis_Lumi%C3%A8re
+ "lumiere",
+
+ // Mahavira - Ancient Indian mathematician during 9th century AD who discovered basic algebraic identities - https://en.wikipedia.org/wiki/Mah%C4%81v%C4%ABra_(mathematician)
+ "mahavira",
+
+ // Lynn Margulis (b. Lynn Petra Alexander) - an American evolutionary theorist and biologist, science author, educator, and popularizer, and was the primary modern proponent for the significance of symbiosis in evolution. - https://en.wikipedia.org/wiki/Lynn_Margulis
+ "margulis",
+
+ // Yukihiro Matsumoto - Japanese computer scientist and software programmer best known as the chief designer of the Ruby programming language. https://en.wikipedia.org/wiki/Yukihiro_Matsumoto
+ "matsumoto",
+
+ // James Clerk Maxwell - Scottish physicist, best known for his formulation of electromagnetic theory. https://en.wikipedia.org/wiki/James_Clerk_Maxwell
+ "maxwell",
+
+ // Maria Mayer - American theoretical physicist and Nobel laureate in Physics for proposing the nuclear shell model of the atomic nucleus - https://en.wikipedia.org/wiki/Maria_Mayer
+ "mayer",
+
+ // John McCarthy invented LISP: https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)
+ "mccarthy",
+
+ // Barbara McClintock - a distinguished American cytogeneticist, 1983 Nobel Laureate in Physiology or Medicine for discovering transposons. https://en.wikipedia.org/wiki/Barbara_McClintock
+ "mcclintock",
+
+ // Anne Laura Dorinthea McLaren - British developmental biologist whose work helped lead to human in-vitro fertilisation. https://en.wikipedia.org/wiki/Anne_McLaren
+ "mclaren",
+
+ // Malcolm McLean invented the modern shipping container: https://en.wikipedia.org/wiki/Malcom_McLean
+ "mclean",
+
+ // Kay McNulty - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Kathleen_Antonelli
+ "mcnulty",
+
+ // Lise Meitner - Austrian/Swedish physicist who was involved in the discovery of nuclear fission. The element meitnerium is named after her - https://en.wikipedia.org/wiki/Lise_Meitner
+ "meitner",
+
+ // Gregor Johann Mendel - Czech scientist and founder of genetics. https://en.wikipedia.org/wiki/Gregor_Mendel
+ "mendel",
+
+ // Dmitri Mendeleev - a chemist and inventor. He formulated the Periodic Law, created a farsighted version of the periodic table of elements, and used it to correct the properties of some already discovered elements and also to predict the properties of eight elements yet to be discovered. https://en.wikipedia.org/wiki/Dmitri_Mendeleev
+ "mendeleev",
+
+ // Carla Meninsky, was the game designer and programmer for Atari 2600 games Dodge 'Em and Warlords. https://en.wikipedia.org/wiki/Carla_Meninsky
+ "meninsky",
+
+ // Ralph C. Merkle - American computer scientist, known for devising Merkle's puzzles - one of the very first schemes for public-key cryptography. Also, inventor of Merkle trees and co-inventor of the Merkle-Damgård construction for building collision-resistant cryptographic hash functions and the Merkle-Hellman knapsack cryptosystem. https://en.wikipedia.org/wiki/Ralph_Merkle
+ "merkle",
+
+ // Johanna Mestorf - German prehistoric archaeologist and first female museum director in Germany - https://en.wikipedia.org/wiki/Johanna_Mestorf
+ "mestorf",
+
+ // Maryam Mirzakhani - an Iranian mathematician and the first woman to win the Fields Medal. https://en.wikipedia.org/wiki/Maryam_Mirzakhani
+ "mirzakhani",
+
+ // Rita Levi-Montalcini - Won Nobel Prize in Physiology or Medicine jointly with colleague Stanley Cohen for the discovery of nerve growth factor (https://en.wikipedia.org/wiki/Rita_Levi-Montalcini)
+ "montalcini",
+
+ // Gordon Earle Moore - American engineer, Silicon Valley founding father, author of Moore's law. https://en.wikipedia.org/wiki/Gordon_Moore
+ "moore",
+
+ // Samuel Morse - contributed to the invention of a single-wire telegraph system based on European telegraphs and was a co-developer of the Morse code - https://en.wikipedia.org/wiki/Samuel_Morse
+ "morse",
+
+ // May-Britt Moser - Nobel prize winner neuroscientist who contributed to the discovery of grid cells in the brain. https://en.wikipedia.org/wiki/May-Britt_Moser
+ "moser",
+
+ // Ian Murdock - founder of the Debian project - https://en.wikipedia.org/wiki/Ian_Murdock
+ "murdock",
+
+ // John Napier of Merchiston - Scottish landowner known as an astronomer, mathematician and physicist. Best known for his discovery of logarithms. https://en.wikipedia.org/wiki/John_Napier
+ "napier",
+
+ // John Forbes Nash, Jr. - American mathematician who made fundamental contributions to game theory, differential geometry, and the study of partial differential equations. https://en.wikipedia.org/wiki/John_Forbes_Nash_Jr.
+ "nash",
+
+ // John von Neumann - todays computer architectures are based on the von Neumann architecture. https://en.wikipedia.org/wiki/Von_Neumann_architecture
+ "neumann",
+
+ // Isaac Newton invented classic mechanics and modern optics. https://en.wikipedia.org/wiki/Isaac_Newton
+ "newton",
+
+ // Florence Nightingale, more prominently known as a nurse, was also the first female member of the Royal Statistical Society and a pioneer in statistical graphics https://en.wikipedia.org/wiki/Florence_Nightingale#Statistics_and_sanitary_reform
+ "nightingale",
+
+ // Alfred Nobel - a Swedish chemist, engineer, innovator, and armaments manufacturer (inventor of dynamite) - https://en.wikipedia.org/wiki/Alfred_Nobel
+ "nobel",
+
+ // Emmy Noether, German mathematician. Noether's Theorem is named after her. https://en.wikipedia.org/wiki/Emmy_Noether
+ "noether",
+
+ // Poppy Northcutt. Poppy Northcutt was the first woman to work as part of NASA’s Mission Control. http://www.businessinsider.com/poppy-northcutt-helped-apollo-astronauts-2014-12?op=1
+ "northcutt",
+
+ // Robert Noyce invented silicon integrated circuits and gave Silicon Valley its name. - https://en.wikipedia.org/wiki/Robert_Noyce
+ "noyce",
+
+ // Panini - Ancient Indian linguist and grammarian from 4th century CE who worked on the world's first formal system - https://en.wikipedia.org/wiki/P%C4%81%E1%B9%87ini#Comparison_with_modern_formal_systems
+ "panini",
+
+ // Ambroise Pare invented modern surgery. https://en.wikipedia.org/wiki/Ambroise_Par%C3%A9
+ "pare",
+
+ // Blaise Pascal, French mathematician, physicist, and inventor - https://en.wikipedia.org/wiki/Blaise_Pascal
+ "pascal",
+
+ // Louis Pasteur discovered vaccination, fermentation and pasteurization. https://en.wikipedia.org/wiki/Louis_Pasteur.
+ "pasteur",
+
+ // Cecilia Payne-Gaposchkin was an astronomer and astrophysicist who, in 1925, proposed in her Ph.D. thesis an explanation for the composition of stars in terms of the relative abundances of hydrogen and helium. https://en.wikipedia.org/wiki/Cecilia_Payne-Gaposchkin
+ "payne",
+
+ // Radia Perlman is a software designer and network engineer and most famous for her invention of the spanning-tree protocol (STP). https://en.wikipedia.org/wiki/Radia_Perlman
+ "perlman",
+
+ // Rob Pike was a key contributor to Unix, Plan 9, the X graphic system, utf-8, and the Go programming language. https://en.wikipedia.org/wiki/Rob_Pike
+ "pike",
+
+ // Henri Poincaré made fundamental contributions in several fields of mathematics. https://en.wikipedia.org/wiki/Henri_Poincar%C3%A9
+ "poincare",
+
+ // Laura Poitras is a director and producer whose work, made possible by open source crypto tools, advances the causes of truth and freedom of information by reporting disclosures by whistleblowers such as Edward Snowden. https://en.wikipedia.org/wiki/Laura_Poitras
+ "poitras",
+
+ // Tat’yana Avenirovna Proskuriakova (Russian: Татья́на Авени́ровна Проскуряко́ва) (January 23 [O.S. January 10] 1909 – August 30, 1985) was a Russian-American Mayanist scholar and archaeologist who contributed significantly to the deciphering of Maya hieroglyphs, the writing system of the pre-Columbian Maya civilization of Mesoamerica. https://en.wikipedia.org/wiki/Tatiana_Proskouriakoff
+ "proskuriakova",
+
+ // Claudius Ptolemy - a Greco-Egyptian writer of Alexandria, known as a mathematician, astronomer, geographer, astrologer, and poet of a single epigram in the Greek Anthology - https://en.wikipedia.org/wiki/Ptolemy
+ "ptolemy",
+
+ // C. V. Raman - Indian physicist who won the Nobel Prize in 1930 for proposing the Raman effect. - https://en.wikipedia.org/wiki/C._V._Raman
+ "raman",
+
+ // Srinivasa Ramanujan - Indian mathematician and autodidact who made extraordinary contributions to mathematical analysis, number theory, infinite series, and continued fractions. - https://en.wikipedia.org/wiki/Srinivasa_Ramanujan
+ "ramanujan",
+
+ // Ida Rhodes - American pioneer in computer programming, designed the first computer used for Social Security. https://en.wikipedia.org/wiki/Ida_Rhodes
+ "rhodes",
+
+ // Sally Kristen Ride was an American physicist and astronaut. She was the first American woman in space, and the youngest American astronaut. https://en.wikipedia.org/wiki/Sally_Ride
+ "ride",
+
+ // Dennis Ritchie - co-creator of UNIX and the C programming language. - https://en.wikipedia.org/wiki/Dennis_Ritchie
+ "ritchie",
+
+ // Julia Hall Bowman Robinson - American mathematician renowned for her contributions to the fields of computability theory and computational complexity theory. https://en.wikipedia.org/wiki/Julia_Robinson
+ "robinson",
+
+ // Wilhelm Conrad Röntgen - German physicist who was awarded the first Nobel Prize in Physics in 1901 for the discovery of X-rays (Röntgen rays). https://en.wikipedia.org/wiki/Wilhelm_R%C3%B6ntgen
+ "roentgen",
+
+ // Rosalind Franklin - British biophysicist and X-ray crystallographer whose research was critical to the understanding of DNA - https://en.wikipedia.org/wiki/Rosalind_Franklin
+ "rosalind",
+
+ // Vera Rubin - American astronomer who pioneered work on galaxy rotation rates. https://en.wikipedia.org/wiki/Vera_Rubin
+ "rubin",
+
+ // Meghnad Saha - Indian astrophysicist best known for his development of the Saha equation, used to describe chemical and physical conditions in stars - https://en.wikipedia.org/wiki/Meghnad_Saha
+ "saha",
+
+ // Jean E. Sammet developed FORMAC, the first widely used computer language for symbolic manipulation of mathematical formulas. https://en.wikipedia.org/wiki/Jean_E._Sammet
+ "sammet",
+
+ // Mildred Sanderson - American mathematician best known for Sanderson's theorem concerning modular invariants. https://en.wikipedia.org/wiki/Mildred_Sanderson
+ "sanderson",
+
+ // Satoshi Nakamoto is the name used by the unknown person or group of people who developed bitcoin, authored the bitcoin white paper, and created and deployed bitcoin's original reference implementation. https://en.wikipedia.org/wiki/Satoshi_Nakamoto
+ "satoshi",
+
+ // Adi Shamir - Israeli cryptographer whose numerous inventions and contributions to cryptography include the Ferge Fiat Shamir identification scheme, the Rivest Shamir Adleman (RSA) public-key cryptosystem, the Shamir's secret sharing scheme, the breaking of the Merkle-Hellman cryptosystem, the TWINKLE and TWIRL factoring devices and the discovery of differential cryptanalysis (with Eli Biham). https://en.wikipedia.org/wiki/Adi_Shamir
+ "shamir",
+
+ // Claude Shannon - The father of information theory and founder of digital circuit design theory. (https://en.wikipedia.org/wiki/Claude_Shannon)
+ "shannon",
+
+ // Carol Shaw - Originally an Atari employee, Carol Shaw is said to be the first female video game designer. https://en.wikipedia.org/wiki/Carol_Shaw_(video_game_designer)
+ "shaw",
+
+ // Dame Stephanie "Steve" Shirley - Founded a software company in 1962 employing women working from home. https://en.wikipedia.org/wiki/Steve_Shirley
+ "shirley",
+
+ // William Shockley co-invented the transistor - https://en.wikipedia.org/wiki/William_Shockley
+ "shockley",
+
+ // Lina Solomonovna Stern (or Shtern; Russian: Лина Соломоновна Штерн; 26 August 1878 – 7 March 1968) was a Soviet biochemist, physiologist and humanist whose medical discoveries saved thousands of lives at the fronts of World War II. She is best known for her pioneering work on blood–brain barrier, which she described as hemato-encephalic barrier in 1921. https://en.wikipedia.org/wiki/Lina_Stern
+ "shtern",
+
+ // Françoise Barré-Sinoussi - French virologist and Nobel Prize Laureate in Physiology or Medicine; her work was fundamental in identifying HIV as the cause of AIDS. https://en.wikipedia.org/wiki/Fran%C3%A7oise_Barr%C3%A9-Sinoussi
+ "sinoussi",
+
+ // Betty Snyder - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Betty_Holberton
+ "snyder",
+
+ // Cynthia Solomon - Pioneer in the fields of artificial intelligence, computer science and educational computing. Known for creation of Logo, an educational programming language. https://en.wikipedia.org/wiki/Cynthia_Solomon
+ "solomon",
+
+ // Frances Spence - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Frances_Spence
+ "spence",
+
+ // Michael Stonebraker is a database research pioneer and architect of Ingres, Postgres, VoltDB and SciDB. Winner of 2014 ACM Turing Award. https://en.wikipedia.org/wiki/Michael_Stonebraker
+ "stonebraker",
+
+ // Ivan Edward Sutherland - American computer scientist and Internet pioneer, widely regarded as the father of computer graphics. https://en.wikipedia.org/wiki/Ivan_Sutherland
+ "sutherland",
+
+ // Janese Swanson (with others) developed the first of the Carmen Sandiego games. She went on to found Girl Tech. https://en.wikipedia.org/wiki/Janese_Swanson
+ "swanson",
+
+ // Aaron Swartz was influential in creating RSS, Markdown, Creative Commons, Reddit, and much of the internet as we know it today. He was devoted to freedom of information on the web. https://en.wikiquote.org/wiki/Aaron_Swartz
+ "swartz",
+
+ // Bertha Swirles was a theoretical physicist who made a number of contributions to early quantum theory. https://en.wikipedia.org/wiki/Bertha_Swirles
+ "swirles",
+
+ // Helen Brooke Taussig - American cardiologist and founder of the field of paediatric cardiology. https://en.wikipedia.org/wiki/Helen_B._Taussig
+ "taussig",
+
+ // Nikola Tesla invented the AC electric system and every gadget ever used by a James Bond villain. https://en.wikipedia.org/wiki/Nikola_Tesla
+ "tesla",
+
+ // Marie Tharp - American geologist and oceanic cartographer who co-created the first scientific map of the Atlantic Ocean floor. Her work led to the acceptance of the theories of plate tectonics and continental drift. https://en.wikipedia.org/wiki/Marie_Tharp
+ "tharp",
+
+ // Ken Thompson - co-creator of UNIX and the C programming language - https://en.wikipedia.org/wiki/Ken_Thompson
+ "thompson",
+
+ // Linus Torvalds invented Linux and Git. https://en.wikipedia.org/wiki/Linus_Torvalds
+ "torvalds",
+
+ // Youyou Tu - Chinese pharmaceutical chemist and educator known for discovering artemisinin and dihydroartemisinin, used to treat malaria, which has saved millions of lives. Joint winner of the 2015 Nobel Prize in Physiology or Medicine. https://en.wikipedia.org/wiki/Tu_Youyou
+ "tu",
+
+ // Alan Turing was a founding father of computer science. https://en.wikipedia.org/wiki/Alan_Turing.
+ "turing",
+
+ // Varahamihira - Ancient Indian mathematician who discovered trigonometric formulae during 505-587 CE - https://en.wikipedia.org/wiki/Var%C4%81hamihira#Contributions
+ "varahamihira",
+
+ // Dorothy Vaughan was a NASA mathematician and computer programmer on the SCOUT launch vehicle program that put America's first satellites into space - https://en.wikipedia.org/wiki/Dorothy_Vaughan
+ "vaughan",
+
+ // Cédric Villani - French mathematician, won Fields Medal, Fermat Prize and Poincaré Price for his work in differential geometry and statistical mechanics. https://en.wikipedia.org/wiki/C%C3%A9dric_Villani
+ "villani",
+
+ // Sir Mokshagundam Visvesvaraya - is a notable Indian engineer. He is a recipient of the Indian Republic's highest honour, the Bharat Ratna, in 1955. On his birthday, 15 September is celebrated as Engineer's Day in India in his memory - https://en.wikipedia.org/wiki/Visvesvaraya
+ "visvesvaraya",
+
+ // Christiane Nüsslein-Volhard - German biologist, won Nobel Prize in Physiology or Medicine in 1995 for research on the genetic control of embryonic development. https://en.wikipedia.org/wiki/Christiane_N%C3%BCsslein-Volhard
+ "volhard",
+
+ // Marlyn Wescoff - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Marlyn_Meltzer
+ "wescoff",
+
+ // Sylvia B. Wilbur - British computer scientist who helped develop the ARPANET, was one of the first to exchange email in the UK and a leading researcher in computer-supported collaborative work. https://en.wikipedia.org/wiki/Sylvia_Wilbur
+ "wilbur",
+
+ // Andrew Wiles - Notable British mathematician who proved the enigmatic Fermat's Last Theorem - https://en.wikipedia.org/wiki/Andrew_Wiles
+ "wiles",
+
+ // Roberta Williams, did pioneering work in graphical adventure games for personal computers, particularly the King's Quest series. https://en.wikipedia.org/wiki/Roberta_Williams
+ "williams",
+
+ // Malcolm John Williamson - British mathematician and cryptographer employed by the GCHQ. Developed in 1974 what is now known as Diffie-Hellman key exchange (Diffie and Hellman first published the scheme in 1976). https://en.wikipedia.org/wiki/Malcolm_J._Williamson
+ "williamson",
+
+ // Sophie Wilson designed the first Acorn Micro-Computer and the instruction set for ARM processors. https://en.wikipedia.org/wiki/Sophie_Wilson
+ "wilson",
+
+ // Jeannette Wing - co-developed the Liskov substitution principle. - https://en.wikipedia.org/wiki/Jeannette_Wing
+ "wing",
+
+ // Steve Wozniak invented the Apple I and Apple II. https://en.wikipedia.org/wiki/Steve_Wozniak
+ "wozniak",
+
+ // The Wright brothers, Orville and Wilbur - credited with inventing and building the world's first successful airplane and making the first controlled, powered and sustained heavier-than-air human flight - https://en.wikipedia.org/wiki/Wright_brothers
+ "wright",
+
+ // Chien-Shiung Wu - Chinese-American experimental physicist who made significant contributions to nuclear physics. https://en.wikipedia.org/wiki/Chien-Shiung_Wu
+ "wu",
+
+ // Rosalyn Sussman Yalow - Rosalyn Sussman Yalow was an American medical physicist, and a co-winner of the 1977 Nobel Prize in Physiology or Medicine for development of the radioimmunoassay technique. https://en.wikipedia.org/wiki/Rosalyn_Sussman_Yalow
+ "yalow",
+
+ // Ada Yonath - an Israeli crystallographer, the first woman from the Middle East to win a Nobel prize in the sciences. https://en.wikipedia.org/wiki/Ada_Yonath
+ "yonath",
+
+ // Nikolay Yegorovich Zhukovsky (Russian: Никола́й Его́рович Жуко́вский, January 17 1847 – March 17, 1921) was a Russian scientist, mathematician and engineer, and a founding father of modern aero- and hydrodynamics. Whereas contemporary scientists scoffed at the idea of human flight, Zhukovsky was the first to undertake the study of airflow. He is often called the Father of Russian Aviation. https://en.wikipedia.org/wiki/Nikolay_Yegorovich_Zhukovsky
+ "zhukovsky",
+ }
+)
+
+// GetRandomName generates a random name from the list of adjectives and surnames in this package
+// formatted as "adjective_surname". For example 'focused_turing'. If retry is non-zero, a random
+// integer between 0 and 10 will be added to the end of the name, e.g `focused_turing3`
+func GetRandomName(retry int) string {
+begin:
+ name := left[rand.Intn(len(left))] + "_" + right[rand.Intn(len(right))] //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand)
+ if name == "boring_wozniak" /* Steve Wozniak is not boring */ {
+ goto begin
+ }
+
+ if retry > 0 {
+ name += strconv.Itoa(rand.Intn(10)) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand)
+ }
+ return name
+}
diff --git a/pkg/worktree/worktree.go b/pkg/worktree/worktree.go
new file mode 100644
index 000000000..758174cd0
--- /dev/null
+++ b/pkg/worktree/worktree.go
@@ -0,0 +1,322 @@
+// Package worktree creates throwaway git worktrees so an agent can work in
+// isolation from the user's checkout. The worktree shares the repository's
+// object store but has its own working directory and branch, letting the
+// user keep using the original checkout while the agent makes changes.
+package worktree
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/docker/docker-agent/pkg/paths"
+ "github.com/docker/docker-agent/pkg/worktree/namesgenerator"
+)
+
+// ErrNotGitRepository means the requested directory is not inside a git worktree.
+var ErrNotGitRepository = errors.New("not a git repository")
+
+// ErrInvalidName means the requested worktree name cannot be safely used as a
+// directory and branch component.
+var ErrInvalidName = errors.New("invalid worktree name")
+
+// ErrInvalidPRRef means a --worktree-pr value is not a PR number or URL.
+var ErrInvalidPRRef = errors.New("invalid pull request reference")
+
+// ErrGHNotFound means the GitHub CLI (gh) is required but not installed.
+var ErrGHNotFound = errors.New("the GitHub CLI (gh) is required to check out a pull request")
+
+// Worktree describes a git worktree created for an agent session.
+type Worktree struct {
+ // Dir is the absolute path of the worktree's working directory.
+ Dir string
+ // Branch is the branch checked out in the worktree.
+ Branch string
+ // Name is the worktree's name (the part after the "worktree-" branch prefix).
+ Name string
+ // SourceDir is the root of the repository the worktree was branched
+ // from. The worktree lives under the data directory, far from the
+ // original checkout, so setup hooks need this to copy untracked files
+ // (.env, local config) git won't carry over.
+ SourceDir string
+ // BaseCommit is the commit the worktree's branch was created at. It is
+ // used to detect commits added during the session (see [Status]).
+ BaseCommit string
+}
+
+// Status describes whether a worktree holds work that would be lost if it
+// were removed.
+type Status struct {
+ // Modified is true when tracked files have uncommitted changes.
+ Modified bool
+ // Untracked is true when the worktree contains untracked files.
+ Untracked bool
+ // NewCommits is true when commits were added since the worktree was
+ // created (HEAD moved away from [Worktree.BaseCommit]).
+ NewCommits bool
+}
+
+// IsDirty reports whether the worktree holds any work (uncommitted changes,
+// untracked files, or new commits) that removing it would discard.
+func (s Status) IsDirty() bool {
+ return s.Modified || s.Untracked || s.NewCommits
+}
+
+// Create creates a new git worktree for the repository containing dir and
+// returns it. The worktree lives under the data directory and checks out a
+// freshly created branch so the agent's changes stay isolated from the user's
+// checkout.
+//
+// When name is empty, a friendly random name (e.g. "focused_turing") is
+// generated. The branch is named "worktree-" and the worktree is stored
+// under /worktrees/.
+//
+// Returns [ErrNotGitRepository] when dir is not inside a git worktree, and
+// [ErrInvalidName] when an explicit name is not a safe path/branch component.
+func Create(ctx context.Context, dir, name string) (*Worktree, error) {
+ root, err := repoRoot(ctx, dir)
+ if err != nil {
+ return nil, err
+ }
+
+ if name == "" {
+ name = namesgenerator.GetRandomName(0)
+ } else if err := validateName(name); err != nil {
+ return nil, err
+ }
+
+ branch := "worktree-" + name
+ dest := filepath.Join(paths.GetDataDir(), "worktrees", name)
+
+ if _, err := os.Stat(dest); err == nil {
+ return nil, fmt.Errorf("%w: worktree %q already exists at %s", ErrInvalidName, name, dest)
+ }
+
+ if err := git(ctx, root, "worktree", "add", "-b", branch, dest); err != nil {
+ return nil, fmt.Errorf("creating git worktree: %w", err)
+ }
+
+ wt := &Worktree{Dir: dest, Branch: branch, Name: name, SourceDir: root}
+
+ // Record the branch point so [Status] can later tell whether the
+ // session added commits. A brand-new repository with no commits has no
+ // HEAD yet; leave BaseCommit empty in that case.
+ if head, err := gitOutput(ctx, dest, "rev-parse", "HEAD"); err == nil {
+ wt.BaseCommit = head
+ }
+
+ return wt, nil
+}
+
+// CreatePR creates a git worktree that checks out an existing GitHub pull
+// request so the agent can continue it. ref is a PR number ("123") or a
+// GitHub pull request URL. The PR's head branch is checked out tracking its
+// remote, so commits made in the worktree push back to the pull request.
+//
+// It delegates PR resolution to the GitHub CLI (gh), which handles head-branch
+// lookup, fork remotes, and upstream tracking. Returns [ErrGHNotFound] when gh
+// is not installed, [ErrInvalidPRRef] when ref is malformed, and
+// [ErrNotGitRepository] when dir is not inside a git repository.
+func CreatePR(ctx context.Context, dir, ref string) (*Worktree, error) {
+ number, err := parsePRRef(ref)
+ if err != nil {
+ return nil, err
+ }
+
+ root, err := repoRoot(ctx, dir)
+ if err != nil {
+ return nil, err
+ }
+
+ if _, err := exec.LookPath("gh"); err != nil {
+ return nil, ErrGHNotFound
+ }
+
+ name := fmt.Sprintf("pr-%d", number)
+ dest := filepath.Join(paths.GetDataDir(), "worktrees", name)
+ if _, err := os.Stat(dest); err == nil {
+ return nil, fmt.Errorf("%w: worktree %q already exists at %s", ErrInvalidName, name, dest)
+ }
+
+ // Create the worktree first (detached at HEAD), then let gh check out the
+ // PR head into it. gh resolves the PR's head branch, adds a fork remote
+ // when needed, and sets upstream tracking so pushes return to the PR.
+ if err := git(ctx, root, "worktree", "add", "--detach", dest); err != nil {
+ return nil, fmt.Errorf("creating git worktree: %w", err)
+ }
+
+ if err := gh(ctx, dest, "pr", "checkout", strconv.Itoa(number)); err != nil {
+ // Roll back the empty worktree so a failed checkout leaves no trace.
+ _ = git(ctx, root, "worktree", "remove", "--force", dest)
+ return nil, fmt.Errorf("checking out pull request #%d: %w", number, err)
+ }
+
+ branch, err := gitOutput(ctx, dest, "rev-parse", "--abbrev-ref", "HEAD")
+ if err != nil {
+ return nil, fmt.Errorf("resolving pull request branch: %w", err)
+ }
+
+ wt := &Worktree{Dir: dest, Branch: branch, Name: name, SourceDir: root}
+ if head, err := gitOutput(ctx, dest, "rev-parse", "HEAD"); err == nil {
+ wt.BaseCommit = head
+ }
+ return wt, nil
+}
+
+// parsePRRef extracts a PR number from a bare number ("123", "#123") or a
+// GitHub pull request URL (".../pull/123").
+func parsePRRef(ref string) (int, error) {
+ ref = strings.TrimSpace(ref)
+ if ref == "" {
+ return 0, fmt.Errorf("%w: empty reference", ErrInvalidPRRef)
+ }
+
+ candidate := strings.TrimPrefix(ref, "#")
+ if strings.Contains(ref, "://") || strings.Contains(ref, "/pull/") {
+ // Pull the segment after "/pull/" from a URL like
+ // https://github.com/owner/repo/pull/123(/files|#discussion...).
+ _, rest, ok := strings.Cut(ref, "/pull/")
+ if !ok {
+ return 0, fmt.Errorf("%w: %q", ErrInvalidPRRef, ref)
+ }
+ candidate, _, _ = strings.Cut(rest, "/")
+ candidate, _, _ = strings.Cut(candidate, "#")
+ candidate, _, _ = strings.Cut(candidate, "?")
+ }
+
+ number, err := strconv.Atoi(candidate)
+ if err != nil || number <= 0 {
+ return 0, fmt.Errorf("%w: %q", ErrInvalidPRRef, ref)
+ }
+ return number, nil
+}
+
+// Status inspects the worktree and reports whether it holds uncommitted
+// changes, untracked files, or commits added since creation.
+func (wt *Worktree) Status(ctx context.Context) (Status, error) {
+ out, err := gitOutput(ctx, wt.Dir, "status", "--porcelain")
+ if err != nil {
+ return Status{}, fmt.Errorf("inspecting worktree: %w", err)
+ }
+
+ var st Status
+ for line := range strings.SplitSeq(out, "\n") {
+ if line == "" {
+ continue
+ }
+ if strings.HasPrefix(line, "??") {
+ st.Untracked = true
+ } else {
+ st.Modified = true
+ }
+ }
+
+ // HEAD moving away from the recorded branch point means the session
+ // committed something. When BaseCommit is empty (worktree created in a
+ // repo without commits) any resolvable HEAD counts as new work.
+ if head, err := gitOutput(ctx, wt.Dir, "rev-parse", "HEAD"); err == nil {
+ st.NewCommits = head != wt.BaseCommit
+ }
+
+ return st, nil
+}
+
+// Remove deletes the worktree's directory and its branch, discarding any
+// uncommitted changes, untracked files, and commits. Callers decide when
+// removal is appropriate (e.g. only for worktrees they created, never
+// pre-existing ones).
+func (wt *Worktree) Remove(ctx context.Context) error {
+ if err := git(ctx, wt.SourceDir, "worktree", "remove", "--force", wt.Dir); err != nil {
+ return fmt.Errorf("removing git worktree: %w", err)
+ }
+ // The branch can only be deleted once the worktree no longer occupies it;
+ // -D discards unmerged commits, which is the intended "remove and forget".
+ if err := git(ctx, wt.SourceDir, "branch", "-D", wt.Branch); err != nil {
+ return fmt.Errorf("deleting worktree branch: %w", err)
+ }
+ return nil
+}
+
+// validateName rejects names that would escape the worktrees directory or
+// produce an invalid git branch. Names must be a single path segment made of
+// safe characters, which also keeps the derived "worktree-" branch valid.
+func validateName(name string) error {
+ if name != strings.TrimSpace(name) {
+ return fmt.Errorf("%w: %q has surrounding whitespace", ErrInvalidName, name)
+ }
+ if strings.ContainsAny(name, `/\`) {
+ return fmt.Errorf("%w: %q must not contain path separators", ErrInvalidName, name)
+ }
+ if name == "." || name == ".." {
+ return fmt.Errorf("%w: %q is not allowed", ErrInvalidName, name)
+ }
+ // filepath.Base collapses separators and ".."; if the cleaned segment
+ // differs, the input was not a plain single path component.
+ if filepath.Base(name) != name {
+ return fmt.Errorf("%w: %q must be a single path segment", ErrInvalidName, name)
+ }
+ return nil
+}
+
+// repoRoot returns the worktree root of the git repository containing dir,
+// or [ErrNotGitRepository] when dir is not inside one.
+func repoRoot(ctx context.Context, dir string) (string, error) {
+ out, err := gitOutput(ctx, dir, "rev-parse", "--show-toplevel")
+ if err != nil {
+ var exitErr *exec.ExitError
+ if errors.As(err, &exitErr) {
+ return "", ErrNotGitRepository
+ }
+ return "", err
+ }
+ return filepath.Clean(out), nil
+}
+
+func git(ctx context.Context, dir string, args ...string) error {
+ cmd := exec.CommandContext(ctx, "git", append([]string{"-C", dir}, args...)...)
+ var stderr bytes.Buffer
+ cmd.Stderr = &stderr
+ if err := cmd.Run(); err != nil {
+ if msg := strings.TrimSpace(stderr.String()); msg != "" {
+ return fmt.Errorf("%w: %s", err, msg)
+ }
+ return err
+ }
+ return nil
+}
+
+// gitOutput runs a git command in dir and returns its trimmed stdout.
+func gitOutput(ctx context.Context, dir string, args ...string) (string, error) {
+ cmd := exec.CommandContext(ctx, "git", append([]string{"-C", dir}, args...)...)
+ var stdout, stderr bytes.Buffer
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ if err := cmd.Run(); err != nil {
+ if msg := strings.TrimSpace(stderr.String()); msg != "" {
+ return "", fmt.Errorf("%w: %s", err, msg)
+ }
+ return "", err
+ }
+ return strings.TrimSpace(stdout.String()), nil
+}
+
+// gh runs a GitHub CLI command in dir, surfacing its stderr on failure.
+func gh(ctx context.Context, dir string, args ...string) error {
+ cmd := exec.CommandContext(ctx, "gh", args...)
+ cmd.Dir = dir
+ var stderr bytes.Buffer
+ cmd.Stderr = &stderr
+ if err := cmd.Run(); err != nil {
+ if msg := strings.TrimSpace(stderr.String()); msg != "" {
+ return fmt.Errorf("%w: %s", err, msg)
+ }
+ return err
+ }
+ return nil
+}
diff --git a/pkg/worktree/worktree_test.go b/pkg/worktree/worktree_test.go
new file mode 100644
index 000000000..40e9a8a3f
--- /dev/null
+++ b/pkg/worktree/worktree_test.go
@@ -0,0 +1,288 @@
+package worktree
+
+import (
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/docker/docker-agent/pkg/paths"
+)
+
+func TestCreateWorktree(t *testing.T) {
+ dir := bootstrapRepo(t)
+ dataDir := t.TempDir()
+ paths.SetDataDir(dataDir)
+ t.Cleanup(func() { paths.SetDataDir("") })
+
+ wt, err := Create(t.Context(), dir, "")
+ require.NoError(t, err)
+ require.NotNil(t, wt)
+
+ assert.DirExists(t, wt.Dir)
+ // A random name looks like "focused_turing" (adjective_surname).
+ assert.Regexp(t, `^[a-z]+_[a-z]+$`, wt.Name)
+ assert.Equal(t, "worktree-"+wt.Name, wt.Branch)
+ assert.Equal(t, filepath.Join(dataDir, "worktrees", wt.Name), wt.Dir)
+
+ // The worktree shares the repository's history: the initial commit's
+ // files must be present in the new working directory.
+ assert.FileExists(t, filepath.Join(wt.Dir, "a.txt"))
+
+ // The checked-out branch must match the one reported by the worktree.
+ out := gitOut(t, wt.Dir, "rev-parse", "--abbrev-ref", "HEAD")
+ assert.Equal(t, wt.Branch, out)
+}
+
+func TestCreateWithName(t *testing.T) {
+ dir := bootstrapRepo(t)
+ dataDir := t.TempDir()
+ paths.SetDataDir(dataDir)
+ t.Cleanup(func() { paths.SetDataDir("") })
+
+ wt, err := Create(t.Context(), dir, "my-feature")
+ require.NoError(t, err)
+
+ assert.Equal(t, "my-feature", wt.Name)
+ assert.Equal(t, "worktree-my-feature", wt.Branch)
+ assert.Equal(t, filepath.Join(dataDir, "worktrees", "my-feature"), wt.Dir)
+ assert.FileExists(t, filepath.Join(wt.Dir, "a.txt"))
+}
+
+func TestCreateFromSubfolder(t *testing.T) {
+ root := bootstrapRepo(t)
+ sub := filepath.Join(root, "nested")
+ require.NoError(t, os.MkdirAll(sub, 0o755))
+ paths.SetDataDir(t.TempDir())
+ t.Cleanup(func() { paths.SetDataDir("") })
+
+ wt, err := Create(t.Context(), sub, "")
+ require.NoError(t, err)
+ assert.DirExists(t, wt.Dir)
+ assert.FileExists(t, filepath.Join(wt.Dir, "a.txt"))
+}
+
+func TestCreateOutsideGitRepo(t *testing.T) {
+ _, err := Create(t.Context(), t.TempDir(), "")
+ assert.ErrorIs(t, err, ErrNotGitRepository)
+}
+
+func TestCreateRejectsUnsafeNames(t *testing.T) {
+ dir := bootstrapRepo(t)
+ paths.SetDataDir(t.TempDir())
+ t.Cleanup(func() { paths.SetDataDir("") })
+
+ for _, name := range []string{
+ "../escape",
+ "../../etc/evil",
+ "foo/bar",
+ `foo\bar`,
+ ".",
+ "..",
+ " leading",
+ "trailing ",
+ } {
+ t.Run(name, func(t *testing.T) {
+ _, err := Create(t.Context(), dir, name)
+ assert.ErrorIs(t, err, ErrInvalidName)
+ })
+ }
+}
+
+func TestCreateRejectsDuplicateName(t *testing.T) {
+ dir := bootstrapRepo(t)
+ paths.SetDataDir(t.TempDir())
+ t.Cleanup(func() { paths.SetDataDir("") })
+
+ _, err := Create(t.Context(), dir, "dup")
+ require.NoError(t, err)
+
+ _, err = Create(t.Context(), dir, "dup")
+ assert.ErrorIs(t, err, ErrInvalidName)
+}
+
+func TestStatusClean(t *testing.T) {
+ dir := bootstrapRepo(t)
+ paths.SetDataDir(t.TempDir())
+ t.Cleanup(func() { paths.SetDataDir("") })
+
+ wt, err := Create(t.Context(), dir, "clean")
+ require.NoError(t, err)
+
+ st, err := wt.Status(t.Context())
+ require.NoError(t, err)
+ assert.False(t, st.IsDirty())
+ assert.False(t, st.Modified)
+ assert.False(t, st.Untracked)
+ assert.False(t, st.NewCommits)
+}
+
+func TestStatusDetectsUntracked(t *testing.T) {
+ dir := bootstrapRepo(t)
+ paths.SetDataDir(t.TempDir())
+ t.Cleanup(func() { paths.SetDataDir("") })
+
+ wt, err := Create(t.Context(), dir, "untracked")
+ require.NoError(t, err)
+ require.NoError(t, os.WriteFile(filepath.Join(wt.Dir, "new.txt"), []byte("x"), 0o644))
+
+ st, err := wt.Status(t.Context())
+ require.NoError(t, err)
+ assert.True(t, st.IsDirty())
+ assert.True(t, st.Untracked)
+ assert.False(t, st.Modified)
+ assert.False(t, st.NewCommits)
+}
+
+func TestStatusDetectsModified(t *testing.T) {
+ dir := bootstrapRepo(t)
+ paths.SetDataDir(t.TempDir())
+ t.Cleanup(func() { paths.SetDataDir("") })
+
+ wt, err := Create(t.Context(), dir, "modified")
+ require.NoError(t, err)
+ require.NoError(t, os.WriteFile(filepath.Join(wt.Dir, "a.txt"), []byte("changed"), 0o644))
+
+ st, err := wt.Status(t.Context())
+ require.NoError(t, err)
+ assert.True(t, st.IsDirty())
+ assert.True(t, st.Modified)
+ assert.False(t, st.Untracked)
+ assert.False(t, st.NewCommits)
+}
+
+func TestStatusDetectsNewCommits(t *testing.T) {
+ dir := bootstrapRepo(t)
+ paths.SetDataDir(t.TempDir())
+ t.Cleanup(func() { paths.SetDataDir("") })
+
+ wt, err := Create(t.Context(), dir, "committed")
+ require.NoError(t, err)
+
+ require.NoError(t, os.WriteFile(filepath.Join(wt.Dir, "a.txt"), []byte("changed"), 0o644))
+ runGit(t, wt.Dir, "commit", "-am", "work")
+
+ st, err := wt.Status(t.Context())
+ require.NoError(t, err)
+ assert.True(t, st.IsDirty())
+ assert.True(t, st.NewCommits)
+ // A committed change leaves a clean tree.
+ assert.False(t, st.Modified)
+ assert.False(t, st.Untracked)
+}
+
+func TestRemove(t *testing.T) {
+ dir := bootstrapRepo(t)
+ paths.SetDataDir(t.TempDir())
+ t.Cleanup(func() { paths.SetDataDir("") })
+
+ wt, err := Create(t.Context(), dir, "gone")
+ require.NoError(t, err)
+ require.DirExists(t, wt.Dir)
+
+ require.NoError(t, wt.Remove(t.Context()))
+ assert.NoDirExists(t, wt.Dir)
+
+ // The branch must be gone too.
+ branches := gitOut(t, dir, "branch", "--list", wt.Branch)
+ assert.Empty(t, branches)
+}
+
+func TestRemoveDiscardsDirtyWork(t *testing.T) {
+ dir := bootstrapRepo(t)
+ paths.SetDataDir(t.TempDir())
+ t.Cleanup(func() { paths.SetDataDir("") })
+
+ wt, err := Create(t.Context(), dir, "dirty")
+ require.NoError(t, err)
+ // Uncommitted change + untracked file + a new commit: removal must
+ // still succeed and wipe everything.
+ require.NoError(t, os.WriteFile(filepath.Join(wt.Dir, "a.txt"), []byte("changed"), 0o644))
+ runGit(t, wt.Dir, "commit", "-am", "work")
+ require.NoError(t, os.WriteFile(filepath.Join(wt.Dir, "untracked.txt"), []byte("x"), 0o644))
+
+ require.NoError(t, wt.Remove(t.Context()))
+ assert.NoDirExists(t, wt.Dir)
+}
+
+func bootstrapRepo(t *testing.T) string {
+ t.Helper()
+ if _, err := exec.LookPath("git"); err != nil {
+ t.Skip("git not available")
+ }
+ dir, err := filepath.EvalSymlinks(t.TempDir())
+ require.NoError(t, err)
+ runGit(t, dir, "init")
+ runGit(t, dir, "config", "user.email", "test@example.com")
+ runGit(t, dir, "config", "user.name", "Test User")
+ require.NoError(t, os.WriteFile(filepath.Join(dir, "a.txt"), []byte("A"), 0o644))
+ runGit(t, dir, "add", ".")
+ runGit(t, dir, "commit", "-m", "init")
+ return dir
+}
+
+func runGit(t *testing.T, dir string, args ...string) {
+ t.Helper()
+ cmd := exec.CommandContext(t.Context(), "git", append([]string{"-C", dir}, args...)...)
+ out, err := cmd.CombinedOutput()
+ require.NoError(t, err, string(out))
+}
+
+func gitOut(t *testing.T, dir string, args ...string) string {
+ t.Helper()
+ cmd := exec.CommandContext(t.Context(), "git", append([]string{"-C", dir}, args...)...)
+ out, err := cmd.Output()
+ require.NoError(t, err)
+ return string(trimNL(out))
+}
+
+func trimNL(b []byte) []byte {
+ for len(b) > 0 && (b[len(b)-1] == '\n' || b[len(b)-1] == '\r') {
+ b = b[:len(b)-1]
+ }
+ return b
+}
+
+func TestParsePRRef(t *testing.T) {
+ t.Parallel()
+ tests := []struct {
+ in string
+ want int
+ wantErr bool
+ }{
+ {in: "123", want: 123},
+ {in: "#123", want: 123},
+ {in: " 42 ", want: 42},
+ {in: "https://github.com/owner/repo/pull/123", want: 123},
+ {in: "https://github.com/owner/repo/pull/123/files", want: 123},
+ {in: "https://github.com/owner/repo/pull/123#issuecomment-1", want: 123},
+ {in: "https://github.com/owner/repo/pull/7?w=1", want: 7},
+ {in: "", wantErr: true},
+ {in: "abc", wantErr: true},
+ {in: "0", wantErr: true},
+ {in: "-3", wantErr: true},
+ {in: "https://github.com/owner/repo/issues/123", wantErr: true},
+ }
+ for _, tt := range tests {
+ t.Run(tt.in, func(t *testing.T) {
+ t.Parallel()
+ got, err := parsePRRef(tt.in)
+ if tt.wantErr {
+ assert.ErrorIs(t, err, ErrInvalidPRRef)
+ return
+ }
+ require.NoError(t, err)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+// TestCreatePRValidatesRefBeforeGit checks a malformed ref fails fast with
+// ErrInvalidPRRef, before touching git or gh, even outside a repository.
+func TestCreatePRValidatesRefBeforeGit(t *testing.T) {
+ _, err := CreatePR(t.Context(), t.TempDir(), "not-a-pr")
+ assert.ErrorIs(t, err, ErrInvalidPRRef)
+}