Files
2026-05-17 02:44:02 +02:00

307 lines
7.1 KiB
Go

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 <today|dashboard> [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)
}