auto-update-daily-20260202
1// Package output provides report formatting.
2package output
3
4import (
5 "fmt"
6 "io"
7 "sort"
8 "strings"
9
10 "github.com/vdemeester/home/tools/review-tool/internal/activity"
11)
12
13// WriteMarkdown writes the report in markdown format.
14func WriteMarkdown(w io.Writer, report *activity.ReviewReport) error {
15 fmt.Fprintf(w, "# Activity Review: %s\n\n", report.TimeRange.Description)
16 fmt.Fprintf(w, "**Period:** %s to %s\n",
17 report.TimeRange.Start.Format("2006-01-02"),
18 report.TimeRange.End.Format("2006-01-02"))
19 fmt.Fprintf(w, "**Generated:** %s\n\n", report.GeneratedAt.Format("2006-01-02 15:04"))
20
21 // Summary section
22 if report.Summary != nil && report.Summary.TotalItems > 0 {
23 fmt.Fprintln(w, "## Summary")
24 fmt.Fprintf(w, "- **Total activities:** %d\n", report.Summary.TotalItems)
25
26 // Sort categories for consistent output
27 cats := make([]string, 0, len(report.Summary.ItemsByCategory))
28 for cat := range report.Summary.ItemsByCategory {
29 cats = append(cats, cat)
30 }
31 sort.Strings(cats)
32
33 for _, cat := range cats {
34 fmt.Fprintf(w, "- **%s:** %d\n", strings.Title(cat), report.Summary.ItemsByCategory[cat])
35 }
36 fmt.Fprintln(w)
37 }
38
39 // Activities by source
40 sourceOrder := []string{"github", "org", "jira", "claude"}
41 for _, source := range sourceOrder {
42 act, ok := report.Activities[source]
43 if !ok || len(act.Items) == 0 {
44 continue
45 }
46
47 fmt.Fprintf(w, "## %s\n\n", formatSourceName(source))
48
49 if act.Error != "" {
50 fmt.Fprintf(w, "> Error: %s\n\n", act.Error)
51 continue
52 }
53
54 // Group by type
55 byType := groupByType(act.Items)
56 types := make([]string, 0, len(byType))
57 for t := range byType {
58 types = append(types, t)
59 }
60 sort.Strings(types)
61
62 for _, typ := range types {
63 items := byType[typ]
64 fmt.Fprintf(w, "### %s (%d)\n\n", formatTypeName(typ), len(items))
65
66 // Sort by timestamp descending
67 sort.Slice(items, func(i, j int) bool {
68 return items[i].Timestamp.After(items[j].Timestamp)
69 })
70
71 for _, item := range items {
72 writeMarkdownItem(w, item)
73 }
74 fmt.Fprintln(w)
75 }
76 }
77
78 return nil
79}
80
81func writeMarkdownItem(w io.Writer, item activity.ActivityItem) {
82 timestamp := item.Timestamp.Format("2006-01-02 15:04")
83 if item.URL != "" {
84 fmt.Fprintf(w, "- [%s](%s) (%s)\n", item.Title, item.URL, timestamp)
85 } else {
86 fmt.Fprintf(w, "- %s (%s)\n", item.Title, timestamp)
87 }
88
89 // Show metadata for org items
90 if item.Category == activity.CategoryOrg {
91 var meta []string
92 if section := item.Metadata["section"]; section != "" {
93 meta = append(meta, section)
94 }
95 if file := item.Metadata["file"]; file != "" && !strings.HasSuffix(file, "todos.org") {
96 // Show shortened file path for archive items
97 if strings.Contains(file, "/archive/") {
98 parts := strings.Split(file, "/archive/")
99 if len(parts) > 1 {
100 meta = append(meta, "archive/"+parts[1])
101 }
102 }
103 }
104 if len(meta) > 0 {
105 fmt.Fprintf(w, " _(%s)_\n", strings.Join(meta, " | "))
106 }
107 }
108
109 if item.Description != "" {
110 fmt.Fprintf(w, " > %s\n", item.Description)
111 }
112}
113
114func groupByType(items []activity.ActivityItem) map[string][]activity.ActivityItem {
115 result := make(map[string][]activity.ActivityItem)
116 for _, item := range items {
117 result[item.Type] = append(result[item.Type], item)
118 }
119 return result
120}
121
122func formatSourceName(source string) string {
123 names := map[string]string{
124 "github": "GitHub",
125 "org": "Org-mode",
126 "jira": "Jira",
127 "claude": "Claude Sessions",
128 }
129 if name, ok := names[source]; ok {
130 return name
131 }
132 return strings.Title(source)
133}
134
135func formatTypeName(typ string) string {
136 names := map[string]string{
137 "pr_merged": "PRs Merged",
138 "pr_reviewed": "PRs Reviewed",
139 "issue_created": "Issues Created",
140 "issue_closed": "Issues Closed",
141 "issue_updated": "Issues Updated",
142 "comment": "Comments",
143 "discussion": "Discussions Started",
144 "discussion_comment": "Discussion Comments",
145 "todo_done": "Tasks Completed",
146 "state_change": "State Changes",
147 "clock_entry": "Time Logged",
148 "session": "Sessions",
149 "learning": "Learnings",
150 "research": "Research",
151 }
152 if name, ok := names[typ]; ok {
153 return name
154 }
155 return strings.Title(strings.ReplaceAll(typ, "_", " "))
156}