Documentation
¶
Overview ¶
* Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
- Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
* Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
* Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
* Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
* Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
* Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
- Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
* Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
* Copyright © 2025 Carlos Mendez <carlos@hadaelectronics.com> | https://cjairm.me/
Index ¶
Constants ¶
This section is empty.
Variables ¶
var Settings = []Setting{ { Key: "worktree.default_ai", Description: "Default AI coder for `dg ws` create when no --layout/--ai/default_layout applies", Kind: "string", Default: resolvedAIFallbackName, Get: func(gc *config.GlobalConfig) (string, bool) { return gc.Worktree.DefaultAI, gc.Worktree.DefaultAI != "" }, Set: func(gc *config.GlobalConfig, raw []string) error { value, err := requireExactlyOne("worktree.default_ai", raw) if err != nil { return err } if _, err := worktree.ResolveAICoder(value); err != nil { return err } gc.Worktree.DefaultAI = value return nil }, Unset: func(gc *config.GlobalConfig) { gc.Worktree.DefaultAI = "" }, }, { Key: "worktree.search_paths", Description: "Directories to scan for git repos in the `dg ws` repo picker; empty disables scanning", Kind: "stringlist", Default: func() string { return "" }, Get: func(gc *config.GlobalConfig) (string, bool) { return strings.Join(gc.Worktree.SearchPaths, ", "), len(gc.Worktree.SearchPaths) > 0 }, Set: func(gc *config.GlobalConfig, raw []string) error { if err := requireAtLeastOne("worktree.search_paths", "path", raw); err != nil { return err } paths := make([]string, len(raw)) copy(paths, raw) gc.Worktree.SearchPaths = paths return nil }, Unset: func(gc *config.GlobalConfig) { gc.Worktree.SearchPaths = nil }, }, { Key: "worktree.scan_depth", Description: "Max directory depth for the `dg ws` repo scan; 0 (or unset) uses the default", Kind: "int", Default: func() string { return strconv.Itoa(worktree.DefaultScanDepth()) }, Get: func(gc *config.GlobalConfig) (string, bool) { return strconv.Itoa(gc.Worktree.ScanDepth), gc.Worktree.ScanDepth != 0 }, Set: func(gc *config.GlobalConfig, raw []string) error { value, err := requireExactlyOne("worktree.scan_depth", raw) if err != nil { return err } depth, err := strconv.Atoi(value) if err != nil { return fmt.Errorf("worktree.scan_depth must be an integer, got %q: %w", value, err) } if depth < 0 { return fmt.Errorf("worktree.scan_depth must be non-negative, got %d", depth) } gc.Worktree.ScanDepth = depth return nil }, Unset: func(gc *config.GlobalConfig) { gc.Worktree.ScanDepth = 0 }, }, { Key: "worktree.default_layout", Description: "Default tmux window layout for `dg ws` create when no --layout/--ai applies", Kind: "string", Default: resolvedAIFallbackName, Get: func(gc *config.GlobalConfig) (string, bool) { return gc.Worktree.DefaultLayout, gc.Worktree.DefaultLayout != "" }, Set: func(gc *config.GlobalConfig, raw []string) error { value, err := requireExactlyOne("worktree.default_layout", raw) if err != nil { return err } if _, err := worktree.ResolveLayout(value, "", gc); err != nil { return err } gc.Worktree.DefaultLayout = value return nil }, Unset: func(gc *config.GlobalConfig) { gc.Worktree.DefaultLayout = "" }, Effective: func(gc *config.GlobalConfig) string { layout, err := worktree.ResolveLayout("", "", gc) if err != nil { return resolvedAIFallbackName() } return layout.Name }, }, { Key: "worktree.attach_after_create", Description: "Whether `dg ws` create's n/N attaches into the new window (true) or stays in the dashboard (false)", Kind: "bool", Default: func() string { return strconv.FormatBool((*config.GlobalConfig)(nil).ShouldAttachAfterCreate()) }, Get: func(gc *config.GlobalConfig) (string, bool) { if gc.Worktree.AttachAfterCreate == nil { return "", false } return strconv.FormatBool(*gc.Worktree.AttachAfterCreate), true }, Set: func(gc *config.GlobalConfig, raw []string) error { value, err := requireExactlyOne("worktree.attach_after_create", raw) if err != nil { return err } parsed, err := strconv.ParseBool(value) if err != nil { return fmt.Errorf( "worktree.attach_after_create must be a boolean (true/false), got %q: %w", value, err, ) } gc.Worktree.AttachAfterCreate = &parsed return nil }, Unset: func(gc *config.GlobalConfig) { gc.Worktree.AttachAfterCreate = nil }, }, { Key: "integrations.output_budget", Description: "Whether the output-budget hook caps verbose Bash tool output at write time (docs/guides/output-budget-runner.md)", Kind: "bool", Default: func() string { return strconv.FormatBool((*config.GlobalConfig)(nil).OutputBudgetEnabled()) }, Get: func(gc *config.GlobalConfig) (string, bool) { if gc.Integrations.OutputBudget == nil { return "", false } return strconv.FormatBool(*gc.Integrations.OutputBudget), true }, Set: func(gc *config.GlobalConfig, raw []string) error { value, err := requireExactlyOne("integrations.output_budget", raw) if err != nil { return err } parsed, err := strconv.ParseBool(value) if err != nil { return fmt.Errorf( "integrations.output_budget must be a boolean (true/false), got %q: %w", value, err, ) } gc.Integrations.OutputBudget = &parsed return nil }, Unset: func(gc *config.GlobalConfig) { gc.Integrations.OutputBudget = nil }, }, { Key: "worktree.notify_sound", Description: "Whether an agent pane finishing/blocking/erroring plays a sound while its window is unattended (ADR-0009)", Kind: "bool", Default: func() string { return strconv.FormatBool(false) }, Get: func(gc *config.GlobalConfig) (string, bool) { return strconv.FormatBool(gc.Worktree.NotifySound), gc.Worktree.NotifySound }, Set: func(gc *config.GlobalConfig, raw []string) error { value, err := requireExactlyOne("worktree.notify_sound", raw) if err != nil { return err } parsed, err := strconv.ParseBool(value) if err != nil { return fmt.Errorf( "worktree.notify_sound must be a boolean (true/false), got %q: %w", value, err, ) } gc.Worktree.NotifySound = parsed return nil }, Unset: func(gc *config.GlobalConfig) { gc.Worktree.NotifySound = false }, }, { Key: "worktree.location", Description: "Where worktrees are created, and looked for by remove/repair/state checks: " + "`shared` (default) or `in-repo`", Kind: "string", Default: func() string { return config.WorktreeLocationShared }, Get: func(gc *config.GlobalConfig) (string, bool) { return gc.Worktree.Location, gc.Worktree.Location != "" }, Set: func(gc *config.GlobalConfig, raw []string) error { value, err := requireExactlyOne("worktree.location", raw) if err != nil { return err } switch value { case config.WorktreeLocationShared, config.WorktreeLocationInRepo: gc.Worktree.Location = value return nil default: return fmt.Errorf( "unknown worktree.location %q. Valid values: %s, %s", value, config.WorktreeLocationShared, config.WorktreeLocationInRepo, ) } }, Unset: func(gc *config.GlobalConfig) { gc.Worktree.Location = "" }, }, { Key: "review.reviewers", Description: "AI reviewer agents `dg task review-run` runs headless, each as provider/model (e.g. anthropic/claude-opus-4-6)", Kind: "stringlist", Default: func() string { return "" }, Get: func(gc *config.GlobalConfig) (string, bool) { return strings.Join(gc.Review.Reviewers, ", "), len(gc.Review.Reviewers) > 0 }, Set: func(gc *config.GlobalConfig, raw []string) error { if err := requireAtLeastOne("review.reviewers", "reviewer", raw); err != nil { return err } reviewers := make([]string, len(raw)) for i, r := range raw { if !isProviderModelShaped(r) { return fmt.Errorf( "review.reviewers entries must look like provider/model, got %q", r, ) } reviewers[i] = r } gc.Review.Reviewers = reviewers return nil }, Unset: func(gc *config.GlobalConfig) { gc.Review.Reviewers = nil }, }, { Key: "review.rounds", Description: "Max review rounds the /review-loop command performs before reporting to you (1-5)", Kind: "int", Default: func() string { return strconv.Itoa(config.DefaultReviewRounds) }, Get: func(gc *config.GlobalConfig) (string, bool) { return strconv.Itoa(gc.Review.Rounds), gc.Review.Rounds != 0 }, Set: func(gc *config.GlobalConfig, raw []string) error { value, err := requireExactlyOne("review.rounds", raw) if err != nil { return err } rounds, err := strconv.Atoi(value) if err != nil { return fmt.Errorf("review.rounds must be an integer, got %q: %w", value, err) } if rounds < config.ReviewRoundsMin || rounds > config.ReviewRoundsMax { return fmt.Errorf( "review.rounds must be between %d and %d, got %d", config.ReviewRoundsMin, config.ReviewRoundsMax, rounds, ) } gc.Review.Rounds = rounds return nil }, Unset: func(gc *config.GlobalConfig) { gc.Review.Rounds = 0 }, }, }
Settings is the registry of every user-settable devgeta configuration key.
Functions ¶
func Execute ¶
func Execute()
Execute runs the root command after wiring a process-level context that SIGINT and SIGTERM cancel. That context is what every command the shared executor runs (internal/commands.BaseCommand.ExecCommand) roots its own context in — see commands.SetRootContext — so a Ctrl-C or a SIGTERM takes the same path a per-call Timeout already does: it cancels the context, which fires exec.CommandContext's Cancel hook and, for a NoStdin command, group-kills the whole child tree instead of leaving forks (a brew invoking curl, an opencode invoking a model call) running unattended.
The handling is two-stage, and both stages matter:
- The first SIGINT/SIGTERM cancels the context. Whatever is running gets the same treatment a timeout gives it.
- context.AfterFunc runs stop (signal.NotifyContext's own unregister function) once the context is done, restoring the default signal disposition. That is what lets a *second* Ctrl-C or SIGTERM terminate devgeta itself the normal way — without it, the handler would stay installed and swallow every later signal, leaving a user facing a wedged command with no way out.
Types ¶
type Setting ¶ added in v1.6.0
type Setting struct {
Key string // dotted path, e.g. "worktree.attach_after_create"
Description string
Kind string // "bool" | "int" | "string" | "stringlist"
// Default returns the live default by calling whatever owns it, so the
// registry cannot drift from the runtime behavior it describes. It must
// never restate a default as a literal.
Default func() string
// Get reads the setting's current value off gc. isSet is false when
// nothing has been configured (the zero value) - callers should show
// Default() in that case rather than value, which is meaningless then.
Get func(gc *config.GlobalConfig) (value string, isSet bool)
// Set parses and validates raw, then assigns it on gc. Validation
// delegates to the same resolver the rest of devgeta uses for this
// value, so an invalid value produces the identical error a user would
// hit elsewhere (e.g. `dg wt create --ai <bad-alias>`).
Set func(gc *config.GlobalConfig, raw []string) error
// Unset clears the setting back to its zero value, letting Default()
// govern again.
Unset func(gc *config.GlobalConfig)
// Effective, when non-nil, resolves this setting's real effective value
// against the actual loaded gc when it's unset - for the one kind of
// setting whose "what applies when unset" answer depends on another live
// setting, not just this entry's own context-free Default() (e.g.
// worktree.default_layout falling back through worktree.default_ai before
// falling back to "opencode"). EffectiveDefault is the single place
// list/get/set/unset all read this through, so they can never disagree
// about it the way list's DEFAULT column once did with get/unset. Nil for
// every setting whose effective-when-unset value is simply Default().
Effective func(gc *config.GlobalConfig) string
}
Setting describes one user-settable devgeta configuration key: how to read its live default, read/write it on a loaded config, and validate a new value before assigning it.
func FindSetting ¶ added in v1.6.0
FindSetting looks up a registered setting by its dotted key.
func (*Setting) EffectiveDefault ¶ added in v1.6.0
func (s *Setting) EffectiveDefault(gc *config.GlobalConfig) string
EffectiveDefault returns what s resolves to when unset: s.Effective(gc) if the registry entry supplies cross-setting resolution, otherwise s's context-free Default(). This is the one code path `dg config list`'s DEFAULT column and get/set/unset's "value in effect when unset" logic both go through.