package main import ( "encoding/json" "fmt" "os" "strings" "text/tabwriter" "time" "fn-registry/registry" ) func cmdProposal(args []string) { if len(args) < 1 { printProposalUsage() os.Exit(1) } switch args[0] { case "add": cmdProposalAdd(args[1:]) case "list": cmdProposalList(args[1:]) case "show": cmdProposalShow(args[1:]) case "update": cmdProposalUpdate(args[1:]) case "help", "-h", "--help": printProposalUsage() default: fmt.Fprintf(os.Stderr, "unknown proposal command: %s\n", args[0]) printProposalUsage() os.Exit(1) } } func printProposalUsage() { fmt.Println(`fn proposal — gestiona proposals Usage: fn proposal add --kind --title [options] fn proposal list [-k kind] [-s status] fn proposal show <id> fn proposal update <id> --status <status> [--reviewed-by <who>] Kinds: new_function, new_type, improve_function, improve_type, new_pipeline Status: pending, approved, rejected, implemented`) } func cmdProposalAdd(args []string) { var id, kind, targetID, title, description, evidenceStr, createdBy string i := 0 for i < len(args) { switch args[i] { case "--id": i++ id = args[i] case "--kind": i++ kind = args[i] case "--target-id": i++ targetID = args[i] case "--title": i++ title = args[i] case "--description": i++ description = args[i] case "--evidence": i++ evidenceStr = args[i] case "--created-by": i++ createdBy = args[i] } i++ } if kind == "" || title == "" { fmt.Fprintln(os.Stderr, "error: --kind and --title are required") os.Exit(1) } if id == "" { id = fmt.Sprintf("proposal_%d", time.Now().UnixNano()) } var evidence map[string]any if evidenceStr != "" { if err := json.Unmarshal([]byte(evidenceStr), &evidence); err != nil { fmt.Fprintf(os.Stderr, "error: invalid evidence JSON: %v\n", err) os.Exit(1) } } p := ®istry.Proposal{ ID: id, Kind: registry.ProposalKind(kind), TargetID: targetID, Title: title, Description: description, Evidence: evidence, Status: registry.ProposalPending, CreatedBy: createdBy, } if err := registry.ValidateProposal(p); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } db := openDB() defer db.Close() if err := db.InsertProposal(p); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } fmt.Printf("Created proposal: %s\n", p.ID) } func cmdProposalList(args []string) { var kind, status string i := 0 for i < len(args) { switch args[i] { case "-k": i++ kind = args[i] case "-s": i++ status = args[i] } i++ } db := openDB() defer db.Close() proposals, err := db.ListProposals(registry.ProposalKind(kind), registry.ProposalStatus(status)) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } if len(proposals) == 0 { fmt.Println("No proposals.") return } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintln(w, "ID\tKIND\tSTATUS\tTITLE\tCREATED_BY") for _, p := range proposals { fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", p.ID, p.Kind, p.Status, truncate(p.Title, 40), p.CreatedBy) } w.Flush() } func cmdProposalShow(args []string) { if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: fn proposal show <id>") os.Exit(1) } db := openDB() defer db.Close() p, err := db.GetProposal(args[0]) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } fmt.Printf("ID: %s\n", p.ID) fmt.Printf("Kind: %s\n", p.Kind) fmt.Printf("Status: %s\n", p.Status) fmt.Printf("Title: %s\n", p.Title) fmt.Printf("Description: %s\n", p.Description) if p.TargetID != "" { fmt.Printf("Target ID: %s\n", p.TargetID) } if len(p.Evidence) > 0 { ev, _ := json.MarshalIndent(p.Evidence, " ", " ") fmt.Printf("Evidence: %s\n", string(ev)) } fmt.Printf("Created by: %s\n", p.CreatedBy) if p.ReviewedBy != "" { fmt.Printf("Reviewed by: %s\n", p.ReviewedBy) } fmt.Printf("Created: %s\n", p.CreatedAt.Format(time.RFC3339)) fmt.Printf("Updated: %s\n", p.UpdatedAt.Format(time.RFC3339)) } func cmdProposalUpdate(args []string) { if len(args) < 1 { fmt.Fprintln(os.Stderr, "usage: fn proposal update <id> --status <status> [--reviewed-by <who>]") os.Exit(1) } id := args[0] var status, reviewedBy string i := 1 for i < len(args) { switch args[i] { case "--status": i++ status = args[i] case "--reviewed-by": i++ reviewedBy = args[i] } i++ } db := openDB() defer db.Close() p, err := db.GetProposal(id) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } if status != "" { p.Status = registry.ProposalStatus(status) } if reviewedBy != "" { p.ReviewedBy = reviewedBy } // Validate updated proposal validKinds := map[string]bool{ "pending": true, "approved": true, "rejected": true, "implemented": true, } if status != "" && !validKinds[status] { fmt.Fprintf(os.Stderr, "error: invalid status %q\n", status) os.Exit(1) } if err := db.UpdateProposal(p); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } fmt.Printf("Updated proposal: %s (status: %s)\n", p.ID, p.Status) } func formatEvidence(evidence map[string]any) string { if len(evidence) == 0 { return "{}" } b, _ := json.MarshalIndent(evidence, "", " ") return string(b) } // formatStrings joins a slice for display, handling nil/empty. func formatStrings(ss []string) string { if len(ss) == 0 { return "" } return strings.Join(ss, ", ") }