RunList prints all PRDs with their progress. Returns nil on success, error otherwise. Exit code should be 0 on success.
(opts ListOptions)
| 95 | // RunList prints all PRDs with their progress. |
| 96 | // Returns nil on success, error otherwise. Exit code should be 0 on success. |
| 97 | func RunList(opts ListOptions) error { |
| 98 | // Set defaults |
| 99 | if opts.BaseDir == "" { |
| 100 | cwd, err := os.Getwd() |
| 101 | if err != nil { |
| 102 | return fmt.Errorf("failed to get current directory: %w", err) |
| 103 | } |
| 104 | opts.BaseDir = cwd |
| 105 | } |
| 106 | |
| 107 | // Find all PRDs in .chief/prds/ |
| 108 | prdsDir := filepath.Join(opts.BaseDir, ".chief", "prds") |
| 109 | entries, err := os.ReadDir(prdsDir) |
| 110 | if err != nil { |
| 111 | if os.IsNotExist(err) { |
| 112 | fmt.Println("No PRDs found. Run 'chief new' to create one.") |
| 113 | return nil |
| 114 | } |
| 115 | return fmt.Errorf("failed to read PRDs directory: %w", err) |
| 116 | } |
| 117 | |
| 118 | // Collect PRD info |
| 119 | var prds []PRDInfo |
| 120 | for _, entry := range entries { |
| 121 | if !entry.IsDir() { |
| 122 | continue |
| 123 | } |
| 124 | |
| 125 | name := entry.Name() |
| 126 | prdPath := filepath.Join(prdsDir, name, "prd.md") |
| 127 | |
| 128 | // Try to load the PRD |
| 129 | p, err := prd.LoadPRD(prdPath) |
| 130 | if err != nil { |
| 131 | // Skip PRDs that can't be loaded (might be partially created) |
| 132 | continue |
| 133 | } |
| 134 | |
| 135 | // Count completed stories |
| 136 | total := len(p.UserStories) |
| 137 | completed := 0 |
| 138 | for _, story := range p.UserStories { |
| 139 | if story.Passes { |
| 140 | completed++ |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | percentage := 0 |
| 145 | if total > 0 { |
| 146 | percentage = (completed * 100) / total |
| 147 | } |
| 148 | |
| 149 | prds = append(prds, PRDInfo{ |
| 150 | Name: name, |
| 151 | Title: p.Project, |
| 152 | Completed: completed, |
| 153 | Total: total, |
| 154 | Percentage: percentage, |