cmd

package
v1.23.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: MIT Imports: 33 Imported by: 0

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/

Index

Constants

This section is empty.

Variables

View Source
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:

  1. The first SIGINT/SIGTERM cancels the context. Whatever is running gets the same treatment a timeout gives it.
  2. 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

func FindSetting(key string) (*Setting, bool)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL