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}