package main import ( "fmt" "os" "sort" "strings" ) // cmdWork dispatches work subcommands. func cmdWork(args []string, flags Flags) { if len(args) == 0 { fmt.Fprintln(os.Stderr, "usage: dev_console work [args]") os.Exit(1) } sub := args[0] switch sub { case "today": workToday(flags) case "dashboard": workDashboard(flags) // v2 stubs case "weekly", "search": fmt.Fprintf(os.Stderr, "TODO v2: work %s not yet implemented\n", sub) os.Exit(2) default: fmt.Fprintf(os.Stderr, "unknown work subcommand: %s\n", sub) os.Exit(1) } } type workItem struct { Kind string `json:"kind"` ID string `json:"id"` Title string `json:"title"` Prio string `json:"prio"` Status string `json:"status"` Next string `json:"next"` } // workToday shows a prioritized list of up to 10 items to work on today. func workToday(flags Flags) { root := mustRegistryRoot() // Load issues issues, err := LoadOpenIssues(root) if err != nil { fatalf("load issues: %v", err) } ComputeDepsResolved(issues) // Load flows flows, err := LoadAllFlows(root) if err != nil { fatalf("load flows: %v", err) } var items []workItem // Priority order: alta > media > baja prioPriority := map[string]int{ "alta": 0, "media": 1, "baja": 2, "high": 0, "medium": 1, "low": 2, "": 3, } statusPriority := map[string]int{ "in-progress": 0, "pendiente": 1, "pending": 1, "bloqueado": 2, "blocked": 2, "deferred": 3, } // Filter issues: only pendiente or in-progress for _, iss := range issues { st := strings.ToLower(iss.Status) if st == "completado" || st == "done" || st == "completed" || st == "deferred" { continue } next := issueNext(iss) items = append(items, workItem{ Kind: "issue", ID: iss.ID, Title: iss.Title, Prio: iss.Priority, Status: st, Next: next, }) } // Add flows that are not completed for _, fl := range flows { st := strings.ToLower(fl.Status) if st == "completado" || st == "done" || st == "completed" { continue } next := flowNext(fl) items = append(items, workItem{ Kind: "flow", ID: fl.ID, Title: fl.Name, Prio: fl.Priority, Status: st, Next: next, }) } // Sort: first by status priority, then by prio priority, then by ID sort.Slice(items, func(i, j int) bool { si := statusPriority[items[i].Status] sj := statusPriority[items[j].Status] if si != sj { return si < sj } pi := prioPriority[strings.ToLower(items[i].Prio)] pj := prioPriority[strings.ToLower(items[j].Prio)] if pi != pj { return pi < pj } return items[i].ID < items[j].ID }) // Take top 10 if len(items) > 10 { items = items[:10] } if flags.JSON { printJSON(items) return } headers := []string{"KIND", "ID", "TITLE", "PRIO", "STATUS", "NEXT"} var rows [][]string for _, item := range items { rows = append(rows, []string{ item.Kind, item.ID, truncate(item.Title, 40), item.Prio, item.Status, truncate(item.Next, 30), }) } printTable(os.Stdout, headers, rows) } // issueNext returns a human-readable "next action" for an issue. func issueNext(iss Issue) string { st := strings.ToLower(iss.Status) if st == "in-progress" { return "iterate" } if !iss.DepsResolved { return "blocked: resolve deps" } if iss.AcceptancePct == 0 { return "implement (deps ok)" } if iss.AcceptancePct < 100 { return fmt.Sprintf("implement (%d%% done)", iss.AcceptancePct) } return "review DoD" } // flowNext returns a human-readable "next action" for a flow. func flowNext(fl Flow) string { st := strings.ToLower(fl.Status) if st == "in-progress" { return "iterate" } if fl.DoDPct == 100 { return "mark done" } if fl.UserFacingPct < 100 && fl.UserFacingPct > 0 { return fmt.Sprintf("DoD user-facing %d%%", fl.UserFacingPct) } if fl.AcceptancePct < 100 { return fmt.Sprintf("acceptance %d%%", fl.AcceptancePct) } return "check DoD" } // workDashboard prints a JSON dashboard for the work tab. func workDashboard(flags Flags) { root := mustRegistryRoot() issues, err := LoadAllIssues(root) if err != nil { fatalf("load issues: %v", err) } ComputeDepsResolved(issues) flows, err := LoadAllFlows(root) if err != nil { fatalf("load flows: %v", err) } // Build stats type stats struct { Total int `json:"total"` Pendiente int `json:"pendiente"` InProgress int `json:"in_progress"` Bloqueado int `json:"bloqueado"` Completado int `json:"completado"` } var ist stats for _, iss := range issues { ist.Total++ switch strings.ToLower(iss.Status) { case "pendiente", "pending": ist.Pendiente++ case "in-progress": ist.InProgress++ case "bloqueado", "blocked": ist.Bloqueado++ case "completado", "done", "completed": ist.Completado++ } } var fst stats for _, fl := range flows { fst.Total++ switch strings.ToLower(fl.Status) { case "pendiente", "pending": fst.Pendiente++ case "in-progress": fst.InProgress++ case "completado", "done", "completed": fst.Completado++ } } // Top priority issues var topIssues []Issue for _, iss := range issues { if iss.Priority == "alta" { st := strings.ToLower(iss.Status) if st != "completado" && st != "done" && st != "completed" && st != "deferred" { topIssues = append(topIssues, iss) } } } sort.Slice(topIssues, func(i, j int) bool { return topIssues[i].ID < topIssues[j].ID }) if len(topIssues) > 10 { topIssues = topIssues[:10] } type issueSlim struct { ID string `json:"id"` Title string `json:"title"` Status string `json:"status"` Type string `json:"type"` Domain []string `json:"domain"` Priority string `json:"priority"` Depends []string `json:"depends"` DepsResolved bool `json:"deps_resolved"` AcceptancePct int `json:"acceptance_pct"` } type flowSlim struct { ID string `json:"id"` Name string `json:"name"` Status string `json:"status"` Pattern string `json:"pattern"` Risk string `json:"risk"` Priority string `json:"priority"` Apps []string `json:"apps"` AcceptancePct int `json:"acceptance_pct"` DoDPct int `json:"dod_pct"` UserFacingPct int `json:"user_facing_pct"` } slimIssues := make([]issueSlim, 0, len(topIssues)) for _, iss := range topIssues { slimIssues = append(slimIssues, issueSlim{ ID: iss.ID, Title: iss.Title, Status: iss.Status, Type: iss.Type, Domain: iss.Domain, Priority: iss.Priority, Depends: iss.Depends, DepsResolved: iss.DepsResolved, AcceptancePct: iss.AcceptancePct, }) } slimFlows := make([]flowSlim, 0, len(flows)) for _, fl := range flows { slimFlows = append(slimFlows, flowSlim{ ID: fl.ID, Name: fl.Name, Status: fl.Status, Pattern: fl.Pattern, Risk: fl.Risk, Priority: fl.Priority, Apps: fl.Apps, AcceptancePct: fl.AcceptancePct, DoDPct: fl.DoDPct, UserFacingPct: fl.UserFacingPct, }) } out := map[string]any{ "issue_stats": ist, "flow_stats": fst, "top_issues": slimIssues, "flows": slimFlows, } printJSON(out) }