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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ go fmt ./...

## Project Structure

- `cmd/opencode-worktree/main.go` — Entry point, subcommand routing, flag parsing
- `cmd/opencode-worktree/main.go` — Entry point, routing switch (`run()`), usage output, `errSilent` dispatch
- `cmd/opencode-worktree/task.go` — `runTask` handler
- `cmd/opencode-worktree/attach.go` — `runAttach` handler
- `cmd/opencode-worktree/merge_cmd.go` — `runMerge` handler
- `cmd/opencode-worktree/sync_cmd.go` — `runSync` handler
- `cmd/opencode-worktree/list.go` — `runList` handler
- `cmd/opencode-worktree/cleanup.go` — `runCleanup` handler
- `cmd/opencode-worktree/completions.go` — `runCompletions` handler
- `cmd/opencode-worktree/output.go` — Shared output helpers: `emoji`, `printMergeResult`, `handleMergeError`, `handleSyncError`, `errSilent`
- `internal/git/` — Thin wrappers around `exec.Command("git", ...)`, no abstractions
- `internal/worktree/` — Create, list, cleanup worktrees; launch opencode; copy config
- `internal/merge/` — Merge agent branch into parent with flock serialization
Expand Down
58 changes: 58 additions & 0 deletions cmd/opencode-worktree/attach.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

import (
"flag"
"fmt"
"os"

"github.com/danhenton/opencode-worktree/internal/git"
"github.com/danhenton/opencode-worktree/internal/worktree"
)

func runAttach(args []string) error {
fs := flag.NewFlagSet("attach", flag.ContinueOnError)
noMerge := fs.Bool("no-merge", false, "Skip auto-merge after opencode exits")
fs.Usage = func() {
fmt.Fprint(os.Stderr, `Usage: opencode-worktree attach <name> [--no-merge]

Reattach to an existing agent worktree session.

Options:
`)
fs.PrintDefaults()
fmt.Fprint(os.Stderr, `
Examples:
opencode-worktree attach fix-auth-bug
opencode-worktree attach fix-auth-bug --no-merge
`)
}

if err := fs.Parse(reorderKnownBoolFlags(args, "--no-merge")); err != nil {
return errSilent
}

positional := fs.Args()
if len(positional) == 0 {
return fmt.Errorf("task name is required\n\nUsage: opencode-worktree attach <name> [--no-merge]")
}
if len(positional) > 1 {
return fmt.Errorf("unexpected extra argument: %s", positional[1])
}

taskName := positional[0]

repoRoot, err := git.RepoRoot(".")
if err != nil {
return fmt.Errorf("not inside a git repository")
}

worktreeDir, err := worktree.ResolveWorktreeDir(repoRoot, taskName)
if err != nil {
return err
}

fmt.Printf("%sAttaching to agent session: %s\n", emoji("🔗 ", ""), taskName)
fmt.Printf(" Path: %s\n\n", worktreeDir)

return launchAndMaybeMerge(worktreeDir, "", *noMerge)
}
50 changes: 50 additions & 0 deletions cmd/opencode-worktree/cleanup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package main

import (
"flag"
"fmt"
"os"

"github.com/danhenton/opencode-worktree/internal/git"
"github.com/danhenton/opencode-worktree/internal/worktree"
)

func runCleanup(args []string) error {
fs := flag.NewFlagSet("cleanup", flag.ContinueOnError)
dryRun := fs.Bool("dry-run", false, "Show what would be removed without removing anything")
yes := fs.Bool("yes", false, "Skip confirmation prompt")
fs.Usage = func() {
fmt.Fprint(os.Stderr, `Usage: opencode-worktree cleanup [--dry-run] [--yes]

Remove orphaned agent worktrees and branches.

Options:
`)
fs.PrintDefaults()
fmt.Fprint(os.Stderr, `
Examples:
opencode-worktree cleanup
opencode-worktree cleanup --dry-run
opencode-worktree cleanup --yes
`)
}

if err := fs.Parse(reorderKnownBoolFlags(args, "--dry-run", "--yes")); err != nil {
return errSilent
}

repoRoot, err := git.RepoRoot(".")
if err != nil {
return fmt.Errorf("not inside a git repository")
}

fmt.Printf("%sCleaning up orphaned agent worktrees and branches...\n", emoji("🧹 ", ""))
opts := worktree.CleanupOptions{DryRun: *dryRun, Yes: *yes}
if err := worktree.Cleanup(repoRoot, opts); err != nil {
return err
}
if !*dryRun {
fmt.Printf("%sCleanup complete.\n", emoji("✅ ", ""))
}
return nil
}
35 changes: 35 additions & 0 deletions cmd/opencode-worktree/completions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"fmt"

"github.com/danhenton/opencode-worktree/internal/git"
"github.com/danhenton/opencode-worktree/internal/worktree"
)

func runCompletions(args []string) error {
repoRoot, err := git.RepoRoot(".")
if err != nil {
return errSilent
}

if len(args) == 0 {
for _, cmd := range []string{"task", "attach", "merge", "sync", "list", "cleanup"} {
fmt.Println(cmd)
}
return nil
}

switch args[0] {
case "attach":
names, err := worktree.ActiveTaskNames(repoRoot)
if err != nil {
return errSilent
}
for _, name := range names {
fmt.Println(name)
}
}

return nil
}
24 changes: 24 additions & 0 deletions cmd/opencode-worktree/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

func reorderKnownBoolFlags(args []string, knownFlags ...string) []string {
if len(args) == 0 || len(knownFlags) == 0 {
return args
}

known := make(map[string]struct{}, len(knownFlags))
for _, flagName := range knownFlags {
known[flagName] = struct{}{}
}

reordered := make([]string, 0, len(args))
remaining := make([]string, 0, len(args))
for _, arg := range args {
if _, ok := known[arg]; ok {
reordered = append(reordered, arg)
continue
}
remaining = append(remaining, arg)
}

return append(reordered, remaining...)
}
Loading