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
4 changes: 2 additions & 2 deletions cmd/entire/cli/agent/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func Register(name AgentName, factory Factory) {

// Get retrieves an agent by name.
//
//nolint:ireturn // Factory pattern requires returning the interface

func Get(name AgentName) (Agent, error) {
registryMu.RLock()
defer registryMu.RUnlock()
Expand Down Expand Up @@ -52,7 +52,7 @@ func List() []AgentName {
// Detect attempts to auto-detect which agent is being used.
// Checks each registered agent's DetectPresence method.
//
//nolint:ireturn // Factory pattern requires returning the interface

func Detect() (Agent, error) {
registryMu.RLock()
defer registryMu.RUnlock()
Expand Down
56 changes: 36 additions & 20 deletions cmd/entire/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,6 @@ type EntireSettings struct {
// Returns default settings if neither file exists.
// Works correctly from any subdirectory within the repository.
func LoadEntireSettings() (*EntireSettings, error) {
settings := &EntireSettings{
Strategy: strategy.DefaultStrategyName,
Enabled: true, // Default to enabled
}

// Get absolute paths for settings files
settingsFileAbs, err := paths.AbsPath(EntireSettingsFile)
if err != nil {
Expand All @@ -84,16 +79,9 @@ func LoadEntireSettings() (*EntireSettings, error) {
}

// Load base settings
data, err := os.ReadFile(settingsFileAbs) //nolint:gosec // path is from AbsPath or constant
settings, err := loadSettingsFromFile(settingsFileAbs)
if err != nil {
if !os.IsNotExist(err) {
return nil, fmt.Errorf("reading settings file: %w", err)
}
// File doesn't exist, continue with defaults
} else {
if err := json.Unmarshal(data, settings); err != nil {
return nil, fmt.Errorf("parsing settings file: %w", err)
}
return nil, fmt.Errorf("reading settings file: %w", err)
}

// Apply local overrides if they exist
Expand All @@ -109,11 +97,7 @@ func LoadEntireSettings() (*EntireSettings, error) {
}
}

// Apply defaults if not set
if settings.Strategy == "" {
settings.Strategy = strategy.DefaultStrategyName
}
settings.Strategy = strategy.NormalizeStrategyName(settings.Strategy)
applyDefaultStrategy(settings)

return settings, nil
}
Expand Down Expand Up @@ -239,6 +223,38 @@ func SaveEntireSettingsLocal(settings *EntireSettings) error {
return saveSettingsToFile(settings, EntireSettingsLocalFile)
}

// loadSettingsFromFile loads settings from a specific file path.
// Returns default settings if the file doesn't exist.
func loadSettingsFromFile(filePath string) (*EntireSettings, error) {
settings := &EntireSettings{
Strategy: strategy.DefaultStrategyName,
Enabled: true, // Default to enabled
}

data, err := os.ReadFile(filePath) //nolint:gosec // path is from caller
if err != nil {
if os.IsNotExist(err) {
return settings, nil
}
return nil, fmt.Errorf("%w", err)
}

if err := json.Unmarshal(data, settings); err != nil {
return nil, fmt.Errorf("parsing settings file: %w", err)
}
applyDefaultStrategy(settings)

return settings, nil
}

func applyDefaultStrategy(settings *EntireSettings) {
// Apply defaults if not set
if settings.Strategy == "" {
settings.Strategy = strategy.DefaultStrategyName
}
settings.Strategy = strategy.NormalizeStrategyName(settings.Strategy)
}

func saveSettingsToFile(settings *EntireSettings, filePath string) error {
// Get absolute path for the file
filePathAbs, err := paths.AbsPath(filePath)
Expand Down Expand Up @@ -305,7 +321,7 @@ func GetStrategy() strategy.Strategy {
// 2. Auto-detect if enabled (default)
// 3. Fall back to default agent
//
//nolint:ireturn // Factory pattern requires returning the interface

func GetAgent() (agent.Agent, error) {
settings, err := LoadEntireSettings()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/entire/cli/hook_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ func newAgentHookVerbCmdWithLogging(agentName agent.AgentName, hookName string)
RunE: func(_ *cobra.Command, _ []string) error {
// Skip silently if not in a git repository - hooks shouldn't prevent the agent from working
if _, err := paths.RepoRoot(); err != nil {
return nil //nolint:nilerr // intentional silent skip when no git repo
return nil
}

start := time.Now()
Expand Down
20 changes: 10 additions & 10 deletions cmd/entire/cli/integration_test/setup_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ func TestEnableDisable(t *testing.T) {
RunForAllStrategiesWithBasicEnv(t, func(t *testing.T, env *TestEnv, strategyName string) {
// Initially should be enabled (default)
stdout := env.RunCLI("status")
if !strings.Contains(stdout, "enabled") {
t.Errorf("Expected status to show 'enabled', got: %s", stdout)
if !strings.Contains(stdout, "Enabled") {
t.Errorf("Expected status to show 'Enabled', got: %s", stdout)
}

// Disable
Expand All @@ -59,8 +59,8 @@ func TestEnableDisable(t *testing.T) {

// Check status is now disabled
stdout = env.RunCLI("status")
if !strings.Contains(stdout, "disabled") {
t.Errorf("Expected status to show 'disabled', got: %s", stdout)
if !strings.Contains(stdout, "Disabled") {
t.Errorf("Expected status to show 'Disabled', got: %s", stdout)
}

// Re-enable (using --strategy flag for non-interactive mode)
Expand All @@ -71,8 +71,8 @@ func TestEnableDisable(t *testing.T) {

// Check status is now enabled
stdout = env.RunCLI("status")
if !strings.Contains(stdout, "enabled") {
t.Errorf("Expected status to show 'enabled', got: %s", stdout)
if !strings.Contains(stdout, "Enabled") {
t.Errorf("Expected status to show 'Enabled', got: %s", stdout)
}
})
}
Expand Down Expand Up @@ -128,8 +128,8 @@ func TestStatusWhenDisabled(t *testing.T) {

// Status command should still work and show disabled
stdout := env.RunCLI("status")
if !strings.Contains(stdout, "disabled") {
t.Errorf("Expected status to show 'disabled', got: %s", stdout)
if !strings.Contains(stdout, "Disabled") {
t.Errorf("Expected status to show 'Disabled', got: %s", stdout)
}
})
}
Expand All @@ -148,8 +148,8 @@ func TestEnableWhenDisabled(t *testing.T) {

// Verify it's now enabled
stdout = env.RunCLI("status")
if !strings.Contains(stdout, "enabled") {
t.Errorf("Expected status to show 'enabled' after re-enabling, got: %s", stdout)
if !strings.Contains(stdout, "Enabled") {
t.Errorf("Expected status to show 'Enabled' after re-enabling, got: %s", stdout)
}
})
}
77 changes: 60 additions & 17 deletions cmd/entire/cli/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,20 @@ Use --uninstall to completely remove Entire from this repository, including:
}

func newStatusCmd() *cobra.Command {
return &cobra.Command{
var detailed bool

cmd := &cobra.Command{
Use: "status",
Short: "Show Entire status",
Long: "Show whether Entire is currently enabled or disabled",
RunE: func(cmd *cobra.Command, _ []string) error {
return runStatus(cmd.OutOrStdout())
return runStatus(cmd.OutOrStdout(), detailed)
},
}

cmd.Flags().BoolVar(&detailed, "detailed", false, "Show detailed status for each settings file")

return cmd
}

// runEnableWithStrategy enables Entire with a specified strategy (non-interactive).
Expand Down Expand Up @@ -455,7 +461,7 @@ func runDisable(w io.Writer, useProjectSettings bool) error {
return nil
}

func runStatus(w io.Writer) error {
func runStatus(w io.Writer, detailed bool) error {
// Check if we're in a git repository
if _, repoErr := paths.RepoRoot(); repoErr != nil {
fmt.Fprintln(w, "✕ not a git repository")
Expand All @@ -472,7 +478,7 @@ func runStatus(w io.Writer) error {
localSettingsPath = EntireSettingsLocalFile
}

// Check which settings files exist (for source label)
// Check which settings files exist
_, projectErr := os.Stat(settingsPath)
if projectErr != nil && !errors.Is(projectErr, fs.ErrNotExist) {
return fmt.Errorf("cannot access project settings file: %w", projectErr)
Expand All @@ -489,29 +495,66 @@ func runStatus(w io.Writer) error {
return nil
}

// Load merged settings
if detailed {
return runStatusDetailed(w, settingsPath, localSettingsPath, projectExists, localExists)
}

// Short output: just show the effective/merged state
settings, err := LoadEntireSettings()
if err != nil {
return fmt.Errorf("failed to load settings: %w", err)
}
settings.Strategy = strategy.NormalizeStrategyName(settings.Strategy)

// Determine source label
var sourceLabel string
switch {
case projectExists && localExists:
sourceLabel = "Project + Local"
case localExists:
sourceLabel = "Local"
default:
sourceLabel = "Project"
fmt.Fprintln(w, formatSettingsStatusShort(settings))
return nil
}

// runStatusDetailed shows the effective status plus detailed status for each settings file.
func runStatusDetailed(w io.Writer, settingsPath, localSettingsPath string, projectExists, localExists bool) error {
// First show the effective/merged status
settings, err := LoadEntireSettings()
if err != nil {
return fmt.Errorf("failed to load settings: %w", err)
}
fmt.Fprintln(w, formatSettingsStatusShort(settings))
fmt.Fprintln(w) // blank line

// Show project settings if it exists
if projectExists {
projectSettings, err := loadSettingsFromFile(settingsPath)
if err != nil {
return fmt.Errorf("failed to load project settings: %w", err)
}
fmt.Fprintln(w, formatSettingsStatus("Project", projectSettings))
}

// Show local settings if it exists
if localExists {
localSettings, err := loadSettingsFromFile(localSettingsPath)
if err != nil {
return fmt.Errorf("failed to load local settings: %w", err)
}
fmt.Fprintln(w, formatSettingsStatus("Local", localSettings))
}

fmt.Fprintln(w, formatSettingsStatus(sourceLabel, settings))
return nil
}

// formatSettingsStatus formats a settings status line.
// formatSettingsStatusShort formats a short settings status line.
// Output format: "Enabled (manual-commit)" or "Disabled (auto-commit)"
func formatSettingsStatusShort(settings *EntireSettings) string {
displayName := settings.Strategy
if dn, ok := strategyInternalToDisplay[settings.Strategy]; ok {
displayName = dn
}

if settings.Enabled {
return fmt.Sprintf("Enabled (%s)", displayName)
}
return fmt.Sprintf("Disabled (%s)", displayName)
}

// formatSettingsStatus formats a settings status line with source prefix.
// Output format: "Project, enabled (manual-commit)" or "Local, disabled (auto-commit)"
func formatSettingsStatus(prefix string, settings *EntireSettings) string {
displayName := settings.Strategy
Expand Down
Loading
Loading