Skip to content

Commit 9d2693a

Browse files
author
jcleira
committed
UX Improvements with Parallel Processing and JSON Output
Objective Enhance the CLI user experience with new features and improve code quality through pointer semantics and expanded test coverage. Why These changes improve usability, performance, and maintainability. They provide better feedback during operations, structured output, safer memory handling, and stronger test reliability. How - Introduce CLI output improvements. - Improve performance. - Enhance dashboard behavior. - Extend command functionality
1 parent 9aa1936 commit 9d2693a

37 files changed

+1849
-276
lines changed

cmd/branch/branch.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import (
88
)
99

1010
var BranchCmd = &cobra.Command{
11-
Use: "branch",
12-
Short: "Manage branches in main repositories",
13-
Long: `Manage branches in main repositories, including listing and cleaning up orphaned branches.`,
11+
Use: "branch",
12+
Aliases: []string{"b"},
13+
Short: "Manage branches in main repositories",
14+
Long: `Manage branches in main repositories, including listing and cleaning up orphaned branches.`,
1415
}
1516

1617
func init() {

cmd/branch/cleanup.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import (
1111
)
1212

1313
var cleanupCmd = &cobra.Command{
14-
Use: "cleanup",
15-
Short: "Delete orphaned branches",
16-
Long: `Delete branches that don't have an associated workspace (orphaned branches).`,
14+
Use: "cleanup",
15+
Short: "Delete orphaned branches",
16+
Long: `Delete branches that don't have an associated workspace (orphaned branches).`,
17+
Example: ` workspace branch cleanup`,
1718
Run: func(_ *cobra.Command, _ []string) {
1819
cleanupBranches()
1920
},
@@ -28,25 +29,29 @@ func cleanupBranches() {
2829

2930
plan, err := svc.PlanCleanup()
3031
if err != nil {
31-
commands.PrintError(fmt.Sprintf("Failed to plan cleanup: %v", err))
32+
commands.PrintErrorf("Failed to plan cleanup: %v", err)
3233
return
3334
}
3435

3536
if len(plan.OrphanedBranches) == 0 {
3637
if plan.SkippedIgnored > 0 {
37-
commands.PrintSuccess(fmt.Sprintf("No orphaned branches found! (%d ignored branches skipped)", plan.SkippedIgnored))
38+
commands.PrintSuccessf("No orphaned branches found! (%d ignored branches skipped)", plan.SkippedIgnored)
3839
} else {
3940
commands.PrintSuccess("No orphaned branches found!")
4041
}
4142
return
4243
}
4344

44-
commands.PrintWarning(fmt.Sprintf("Found %d orphaned branch(es):", len(plan.OrphanedBranches)))
45+
commands.PrintWarningf("Found %d orphaned branch(es):", len(plan.OrphanedBranches))
4546
if plan.SkippedIgnored > 0 {
46-
commands.PrintInfo(fmt.Sprintf("(%d ignored branches skipped)", plan.SkippedIgnored))
47+
commands.PrintInfof("(%d ignored branches skipped)", plan.SkippedIgnored)
4748
}
4849
for _, ob := range plan.OrphanedBranches {
49-
fmt.Printf(" - %s: %s\n", ob.RepoName, ob.BranchName)
50+
if ob.HasUnpushed {
51+
fmt.Printf(" - %s: %s %s\n", ob.RepoName, ob.BranchName, commands.ColorWarning(fmt.Sprintf("(%d unpushed)", ob.UnpushedCount)))
52+
} else {
53+
fmt.Printf(" - %s: %s\n", ob.RepoName, ob.BranchName)
54+
}
5055
}
5156

5257
if !commands.PromptYesNo("\nDo you want to delete these branches? (y/n): ") {
@@ -58,7 +63,7 @@ func cleanupBranches() {
5863

5964
result, err := svc.ExecuteCleanup(plan, skipBranches)
6065
if err != nil {
61-
commands.PrintError(fmt.Sprintf("Failed to execute cleanup: %v", err))
66+
commands.PrintErrorf("Failed to execute cleanup: %v", err)
6267
return
6368
}
6469

@@ -70,9 +75,9 @@ func promptForUnpushedBranches(plan *branch.CleanupPlan) []string {
7075

7176
for _, ob := range plan.OrphanedBranches {
7277
if ob.HasUnpushed {
73-
commands.PrintWarning(fmt.Sprintf("Branch '%s' in %s has %d unpushed commit(s)", ob.BranchName, ob.RepoName, ob.UnpushedCount))
78+
commands.PrintWarningf("Branch '%s' in %s has %d unpushed commit(s)", ob.BranchName, ob.RepoName, ob.UnpushedCount)
7479
if !commands.PromptYesNo("Delete anyway? (y/n): ") {
75-
commands.PrintInfo(fmt.Sprintf("Skipping branch '%s'", ob.BranchName))
80+
commands.PrintInfof("Skipping branch '%s'", ob.BranchName)
7681
skipBranches = append(skipBranches, fmt.Sprintf("%s:%s", ob.RepoName, ob.BranchName))
7782
}
7883
}
@@ -85,17 +90,17 @@ func displayCleanupResult(result *branch.CleanupResult) {
8590
fmt.Printf("\n")
8691

8792
for _, d := range result.Deleted {
88-
commands.PrintSuccess(fmt.Sprintf("Deleted %s in %s", d.BranchName, d.RepoName))
93+
commands.PrintSuccessf("Deleted %s in %s", d.BranchName, d.RepoName)
8994
}
9095

9196
for _, f := range result.Failed {
92-
commands.PrintError(fmt.Sprintf("Failed to delete %s in %s: %v", f.BranchName, f.RepoName, f.Error))
97+
commands.PrintErrorf("Failed to delete %s in %s: %v", f.BranchName, f.RepoName, f.Error)
9398
}
9499

95100
if len(result.Deleted) > 0 {
96-
commands.PrintSuccess(fmt.Sprintf("Deleted %d branch(es)", len(result.Deleted)))
101+
commands.PrintSuccessf("Deleted %d branch(es)", len(result.Deleted))
97102
}
98103
if len(result.Failed) > 0 {
99-
commands.PrintWarning(fmt.Sprintf("Failed to delete %d branch(es)", len(result.Failed)))
104+
commands.PrintWarningf("Failed to delete %d branch(es)", len(result.Failed))
100105
}
101106
}

cmd/branch/ignore.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ var ignoreAddCmd = &cobra.Command{
2222
Run: func(_ *cobra.Command, args []string) {
2323
pattern := args[0]
2424
if err := cmd.ConfigManager.AddIgnoredBranch(pattern); err != nil {
25-
commands.PrintError(fmt.Sprintf("Failed to add pattern: %v", err))
25+
commands.PrintErrorf("Failed to add pattern: %v", err)
2626
return
2727
}
28-
commands.PrintSuccess(fmt.Sprintf("Added pattern '%s' to ignore list", pattern))
28+
commands.PrintSuccessf("Added pattern '%s' to ignore list", pattern)
2929
},
3030
}
3131

@@ -36,10 +36,10 @@ var ignoreRemoveCmd = &cobra.Command{
3636
Run: func(_ *cobra.Command, args []string) {
3737
pattern := args[0]
3838
if err := cmd.ConfigManager.RemoveIgnoredBranch(pattern); err != nil {
39-
commands.PrintError(fmt.Sprintf("Failed to remove pattern: %v", err))
39+
commands.PrintErrorf("Failed to remove pattern: %v", err)
4040
return
4141
}
42-
commands.PrintSuccess(fmt.Sprintf("Removed pattern '%s' from ignore list", pattern))
42+
commands.PrintSuccessf("Removed pattern '%s' from ignore list", pattern)
4343
},
4444
}
4545

@@ -65,7 +65,7 @@ var ignoreClearCmd = &cobra.Command{
6565
Short: "Clear all ignored branch patterns",
6666
Run: func(_ *cobra.Command, _ []string) {
6767
if err := cmd.ConfigManager.ClearIgnoredBranches(); err != nil {
68-
commands.PrintError(fmt.Sprintf("Failed to clear ignored patterns: %v", err))
68+
commands.PrintErrorf("Failed to clear ignored patterns: %v", err)
6969
return
7070
}
7171
commands.PrintSuccess("Cleared all ignored branch patterns")

cmd/branch/list.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ import (
1212
)
1313

1414
var listCmd = &cobra.Command{
15-
Use: "list",
16-
Short: "List all branches and their associated workspaces",
17-
Long: `List all branches in main repositories, showing which workspaces they belong to and if they have unpushed commits.`,
15+
Use: "list",
16+
Aliases: []string{"ls"},
17+
Short: "List all branches and their associated workspaces",
18+
Long: `List all branches in main repositories, showing which workspaces they belong to and if they have unpushed commits.`,
19+
Example: ` workspace branch list
20+
workspace b ls`,
1821
Run: func(_ *cobra.Command, _ []string) {
1922
listBranches()
2023
},
@@ -29,7 +32,7 @@ func listBranches() {
2932

3033
output, err := svc.List()
3134
if err != nil {
32-
commands.PrintError(fmt.Sprintf("Failed to list branches: %v", err))
35+
commands.PrintErrorf("Failed to list branches: %v", err)
3336
return
3437
}
3538

cmd/config/init.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ func initShellIntegration() {
4646
functionContent = shell.GenerateFishFunction()
4747

4848
if err := os.MkdirAll(fishConfigDir, 0o755); err != nil {
49-
commands.PrintError(fmt.Sprintf("Failed to create fish config directory: %v", err))
49+
commands.PrintErrorf("Failed to create fish config directory: %v", err)
5050
return
5151
}
5252
default:
53-
commands.PrintError(fmt.Sprintf("Unsupported shell: %s", shellName))
53+
commands.PrintErrorf("Unsupported shell: %s", shellName)
5454
fmt.Println("Supported shells: bash, zsh, fish")
5555
return
5656
}
@@ -61,16 +61,16 @@ func initShellIntegration() {
6161

6262
if shellName == "fish" {
6363
if err := os.WriteFile(rcFile, []byte(functionContent), 0o644); err != nil {
64-
commands.PrintError(fmt.Sprintf("Failed to write fish function: %v", err))
64+
commands.PrintErrorf("Failed to write fish function: %v", err)
6565
return
6666
}
67-
commands.PrintSuccess(fmt.Sprintf("Fish function written to: %s", rcFile))
67+
commands.PrintSuccessf("Fish function written to: %s", rcFile)
6868
commands.PrintInfo("Restart your shell or run 'source ~/.config/fish/config.fish' to use the 'w' command")
6969
} else {
7070
fmt.Printf("Add this function to your %s:\n\n", commands.InfoStyle.Render(rcFile))
7171
fmt.Println(functionContent)
7272
fmt.Println()
73-
commands.PrintInfo("After adding, restart your shell or run 'source " + rcFile + "' to use the 'w' command")
73+
commands.PrintInfof("After adding, restart your shell or run 'source %s' to use the 'w' command", rcFile)
7474
}
7575

7676
fmt.Println()

cmd/config/set.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ var setCmd = &cobra.Command{
1414
Use: "set <key> <value>",
1515
Short: "Set configuration value",
1616
Long: `Set a configuration value. Available keys: workspaces-dir, repos-dir, claude-dir`,
17-
Args: cobra.ExactArgs(2),
17+
Example: ` workspace config set repos-dir ~/Projects/repos
18+
workspace config set workspaces-dir ~/dev/workspaces
19+
workspace config set claude-dir ~/shared/.claude`,
20+
Args: cobra.ExactArgs(2),
1821
Run: func(_ *cobra.Command, args []string) {
1922
setConfigValue(args[0], args[1])
2023
},
@@ -28,33 +31,33 @@ func setConfigValue(key, value string) {
2831
switch key {
2932
case "workspaces-dir":
3033
if err := cmd.ConfigManager.SetWorkspacesDir(value); err != nil {
31-
commands.PrintError(fmt.Sprintf("Failed to set workspaces directory: %v", err))
34+
commands.PrintErrorf("Failed to set workspaces directory: %v", err)
3235
return
3336
}
3437
cfg := cmd.ConfigManager.GetConfig()
3538
cmd.WorkspaceManager = workspace.NewManager(cfg.WorkspacesDir, cfg.ReposDir, cfg.ClaudeDir)
36-
commands.PrintSuccess(fmt.Sprintf("Workspaces directory set to: %s", cfg.WorkspacesDir))
39+
commands.PrintSuccessf("Workspaces directory set to: %s", cfg.WorkspacesDir)
3740

3841
case "repos-dir":
3942
if err := cmd.ConfigManager.SetReposDir(value); err != nil {
40-
commands.PrintError(fmt.Sprintf("Failed to set repos directory: %v", err))
43+
commands.PrintErrorf("Failed to set repos directory: %v", err)
4144
return
4245
}
4346
cfg := cmd.ConfigManager.GetConfig()
4447
cmd.WorkspaceManager = workspace.NewManager(cfg.WorkspacesDir, cfg.ReposDir, cfg.ClaudeDir)
45-
commands.PrintSuccess(fmt.Sprintf("Repos directory set to: %s", cfg.ReposDir))
48+
commands.PrintSuccessf("Repos directory set to: %s", cfg.ReposDir)
4649

4750
case "claude-dir":
4851
if err := cmd.ConfigManager.SetClaudeDir(value); err != nil {
49-
commands.PrintError(fmt.Sprintf("Failed to set claude directory: %v", err))
52+
commands.PrintErrorf("Failed to set claude directory: %v", err)
5053
return
5154
}
5255
cfg := cmd.ConfigManager.GetConfig()
5356
cmd.WorkspaceManager = workspace.NewManager(cfg.WorkspacesDir, cfg.ReposDir, cfg.ClaudeDir)
54-
commands.PrintSuccess(fmt.Sprintf("Claude directory set to: %s", cfg.ClaudeDir))
57+
commands.PrintSuccessf("Claude directory set to: %s", cfg.ClaudeDir)
5558

5659
default:
57-
commands.PrintError(fmt.Sprintf("Unknown configuration key: %s", key))
60+
commands.PrintErrorf("Unknown configuration key: %s", key)
5861
fmt.Println("Available keys: workspaces-dir, repos-dir, claude-dir")
5962
}
6063
}

cmd/config/setup.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func runInteractiveSetup() {
3535
cfg.ClaudeDir,
3636
)
3737
if err != nil {
38-
commands.PrintError(fmt.Sprintf("Setup wizard failed: %v", err))
38+
commands.PrintErrorf("Setup wizard failed: %v", err)
3939
os.Exit(1)
4040
}
4141

cmd/config/show.go

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"fmt"
5+
"os"
56

67
"github.com/spf13/cobra"
78

@@ -12,7 +13,7 @@ import (
1213
var showCmd = &cobra.Command{
1314
Use: "show",
1415
Short: "Show current configuration",
15-
Long: `Display the current workspace configuration.`,
16+
Long: `Display the current workspace configuration with health check information.`,
1617
Run: func(_ *cobra.Command, _ []string) {
1718
showConfig()
1819
},
@@ -32,8 +33,50 @@ func showConfig() {
3233
fmt.Printf("Config file: %s\n", commands.InfoStyle.Render(cmd.ConfigManager.GetConfigPath()))
3334
fmt.Println()
3435

35-
fmt.Printf("Workspaces directory: %s\n", commands.SuccessStyle.Render(config.WorkspacesDir))
36-
fmt.Printf("Repos directory: %s\n", commands.SuccessStyle.Render(config.ReposDir))
37-
fmt.Printf("Claude directory: %s\n", commands.SuccessStyle.Render(config.ClaudeDir))
36+
workspacesStatus := checkDirStatus(config.WorkspacesDir, "workspaces")
37+
reposStatus := checkDirStatus(config.ReposDir, "repos")
38+
claudeStatus := checkDirStatus(config.ClaudeDir, "")
39+
40+
fmt.Printf("Workspaces directory: %s %s\n", commands.SuccessStyle.Render(config.WorkspacesDir), workspacesStatus)
41+
fmt.Printf("Repos directory: %s %s\n", commands.SuccessStyle.Render(config.ReposDir), reposStatus)
42+
fmt.Printf("Claude directory: %s %s\n", commands.SuccessStyle.Render(config.ClaudeDir), claudeStatus)
3843
fmt.Println()
3944
}
45+
46+
func checkDirStatus(path, dirType string) string {
47+
info, err := os.Stat(path)
48+
if os.IsNotExist(err) {
49+
return commands.ColorWarning("(missing)")
50+
}
51+
if err != nil {
52+
return commands.ColorWarning("(error)")
53+
}
54+
if !info.IsDir() {
55+
return commands.ColorWarning("(not a directory)")
56+
}
57+
58+
entries, err := os.ReadDir(path)
59+
if err != nil {
60+
return commands.ColorSuccess("(exists)")
61+
}
62+
63+
count := 0
64+
for _, e := range entries {
65+
if e.IsDir() && e.Name() != ".claude" && !isHiddenDir(e.Name()) {
66+
count++
67+
}
68+
}
69+
70+
switch dirType {
71+
case "workspaces":
72+
return commands.ColorSuccess(fmt.Sprintf("(exists, %d workspace(s))", count))
73+
case "repos":
74+
return commands.ColorSuccess(fmt.Sprintf("(exists, %d repo(s))", count))
75+
default:
76+
return commands.ColorSuccess("(exists)")
77+
}
78+
}
79+
80+
func isHiddenDir(name string) bool {
81+
return name != "" && name[0] == '.'
82+
}

0 commit comments

Comments
 (0)