chore: sync from fn-registry agent
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user