feat(identity): add gt commit wrapper and gt trail command
gt-f6mkz: Agent git identity - Add `gt commit` wrapper that sets git author from agent identity - Identity mapping: gastown/crew/jack → gastown.crew.jack@gastown.local - Add `agent_email_domain` to TownSettings (default: gastown.local) - Add `gt config agent-email-domain` command to manage domain gt-j1m5v: gt trail command - Add `gt trail` with aliases `gt recent` and `gt recap` - Subcommands: commits, beads, hooks - Flags: --since, --limit, --json, --all - Filter commits by agent email domain Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
0a6fa457f6
commit
60da5de104
118
internal/cmd/commit.go
Normal file
118
internal/cmd/commit.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/config"
|
||||
"github.com/steveyegge/gastown/internal/workspace"
|
||||
)
|
||||
|
||||
// DefaultAgentEmailDomain is the default domain for agent git emails.
|
||||
const DefaultAgentEmailDomain = "gastown.local"
|
||||
|
||||
var commitCmd = &cobra.Command{
|
||||
Use: "commit [flags] [-- git-commit-args...]",
|
||||
Short: "Git commit with automatic agent identity",
|
||||
Long: `Git commit wrapper that automatically sets git author identity for agents.
|
||||
|
||||
When run by an agent (GT_ROLE set), this command:
|
||||
1. Detects the agent identity from environment variables
|
||||
2. Converts it to a git-friendly name and email
|
||||
3. Runs 'git commit' with the correct identity
|
||||
|
||||
The email domain is configurable in town settings (agent_email_domain).
|
||||
Default: gastown.local
|
||||
|
||||
Examples:
|
||||
gt commit -m "Fix bug" # Commit as current agent
|
||||
gt commit -am "Quick fix" # Stage all and commit
|
||||
gt commit -- --amend # Amend last commit
|
||||
|
||||
Identity mapping:
|
||||
Agent: gastown/crew/jack → Name: gastown/crew/jack
|
||||
Email: gastown.crew.jack@gastown.local
|
||||
|
||||
When run without GT_ROLE (human), passes through to git commit with no changes.`,
|
||||
RunE: runCommit,
|
||||
DisableFlagParsing: true, // We'll parse flags ourselves to pass them to git
|
||||
}
|
||||
|
||||
func init() {
|
||||
commitCmd.GroupID = GroupWork
|
||||
rootCmd.AddCommand(commitCmd)
|
||||
}
|
||||
|
||||
func runCommit(cmd *cobra.Command, args []string) error {
|
||||
// Detect agent identity
|
||||
identity := detectSender()
|
||||
|
||||
// If overseer (human), just pass through to git commit
|
||||
if identity == "overseer" {
|
||||
return runGitCommit(args, "", "")
|
||||
}
|
||||
|
||||
// Load agent email domain from town settings
|
||||
domain := DefaultAgentEmailDomain
|
||||
townRoot, err := workspace.FindFromCwd()
|
||||
if err == nil && townRoot != "" {
|
||||
settings, err := config.LoadOrCreateTownSettings(config.TownSettingsPath(townRoot))
|
||||
if err == nil && settings.AgentEmailDomain != "" {
|
||||
domain = settings.AgentEmailDomain
|
||||
}
|
||||
}
|
||||
|
||||
// Convert identity to git-friendly email
|
||||
// "gastown/crew/jack" → "gastown.crew.jack@domain"
|
||||
email := identityToEmail(identity, domain)
|
||||
|
||||
// Use identity as the author name (human-readable)
|
||||
name := identity
|
||||
|
||||
return runGitCommit(args, name, email)
|
||||
}
|
||||
|
||||
// identityToEmail converts a Gas Town identity to a git email address.
|
||||
// "gastown/crew/jack" → "gastown.crew.jack@domain"
|
||||
// "mayor/" → "mayor@domain"
|
||||
func identityToEmail(identity, domain string) string {
|
||||
// Remove trailing slash if present
|
||||
identity = strings.TrimSuffix(identity, "/")
|
||||
|
||||
// Replace slashes with dots for email local part
|
||||
localPart := strings.ReplaceAll(identity, "/", ".")
|
||||
|
||||
return localPart + "@" + domain
|
||||
}
|
||||
|
||||
// runGitCommit executes git commit with optional identity override.
|
||||
// If name and email are empty, runs git commit with no overrides.
|
||||
// Preserves git's exit code for proper wrapper behavior.
|
||||
func runGitCommit(args []string, name, email string) error {
|
||||
var gitArgs []string
|
||||
|
||||
// If we have an identity, prepend -c flags
|
||||
if name != "" && email != "" {
|
||||
gitArgs = append(gitArgs, "-c", "user.name="+name)
|
||||
gitArgs = append(gitArgs, "-c", "user.email="+email)
|
||||
}
|
||||
|
||||
gitArgs = append(gitArgs, "commit")
|
||||
gitArgs = append(gitArgs, args...)
|
||||
|
||||
gitCmd := exec.Command("git", gitArgs...)
|
||||
gitCmd.Stdin = os.Stdin
|
||||
gitCmd.Stdout = os.Stdout
|
||||
gitCmd.Stderr = os.Stderr
|
||||
|
||||
if err := gitCmd.Run(); err != nil {
|
||||
// Preserve git's exit code for proper wrapper behavior
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
os.Exit(exitErr.ExitCode())
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user