diff --git a/internal/report/report.go b/internal/report/report.go index 88bec04..920d283 100644 --- a/internal/report/report.go +++ b/internal/report/report.go @@ -21,6 +21,11 @@ const ( EndMarker = "" ) +const ( + aiFieldNotesHeading = "### AI Field Notes" + aiDigestPlaceholder = "_AI digest not generated in this run. The daily report job fills this in when `OPENAI_API_KEY` is configured._" +) + type Options struct { Now time.Time AI AIOptions @@ -272,14 +277,18 @@ func RenderMarkdown(report ActivityReport) (string, error) { } func UpdateReadme(readme []byte, section string) []byte { - replacement := StartMarker + "\n" + strings.TrimSpace(section) + "\n" + EndMarker + section = strings.TrimSpace(section) text := string(readme) start := strings.Index(text, StartMarker) end := strings.Index(text, EndMarker) if start >= 0 && end >= start { + existingSection := text[start+len(StartMarker) : end] + section = preserveAIFieldNotes(existingSection, section) end += len(EndMarker) + replacement := StartMarker + "\n" + section + "\n" + EndMarker return []byte(text[:start] + replacement + text[end:]) } + replacement := StartMarker + "\n" + section + "\n" + EndMarker text = strings.TrimRight(text, "\n") if text == "" { return []byte(replacement + "\n") @@ -287,6 +296,43 @@ func UpdateReadme(readme []byte, section string) []byte { return []byte(text + "\n\n" + replacement + "\n") } +func preserveAIFieldNotes(existingSection string, nextSection string) string { + if !strings.Contains(nextSection, aiDigestPlaceholder) { + return nextSection + } + existingNotes := extractAIFieldNotes(existingSection) + if existingNotes == "" || strings.Contains(existingNotes, aiDigestPlaceholder) { + return nextSection + } + return replaceAIFieldNotes(nextSection, existingNotes) +} + +func extractAIFieldNotes(section string) string { + idx := strings.Index(section, aiFieldNotesHeading) + if idx < 0 { + return "" + } + notes := section[idx+len(aiFieldNotesHeading):] + if next := strings.Index(notes, "\n### "); next >= 0 { + notes = notes[:next] + } + return strings.TrimSpace(notes) +} + +func replaceAIFieldNotes(section string, notes string) string { + idx := strings.Index(section, aiFieldNotesHeading) + if idx < 0 { + return section + } + start := idx + len(aiFieldNotesHeading) + tail := section[start:] + end := len(tail) + if next := strings.Index(tail, "\n### "); next >= 0 { + end = next + } + return section[:start] + "\n\n" + strings.TrimSpace(notes) + strings.TrimRight(tail[end:], "\n") +} + func WriteReadme(path string, section string) error { current, err := os.ReadFile(path) if err != nil && !errors.Is(err, os.ErrNotExist) { @@ -388,6 +434,6 @@ Archive size: {{ formatInt .TotalMessages }} messages, {{ formatInt .TotalChanne {{- if .AISummary }} {{ .AISummary }} {{- else }} -_AI digest not generated in this run. The daily report job fills this in when ` + "`OPENAI_API_KEY`" + ` is configured._ +` + aiDigestPlaceholder + ` {{- end }} `)) diff --git a/internal/report/report_test.go b/internal/report/report_test.go index 22715f7..ebce498 100644 --- a/internal/report/report_test.go +++ b/internal/report/report_test.go @@ -61,6 +61,56 @@ func TestBuildRenderAndUpdateReadme(t *testing.T) { require.NotContains(t, string(updated), "Latest archived message") } +func TestUpdateReadmePreservesAIFieldNotes(t *testing.T) { + existing := []byte(`# Backup + + +## Discord Activity Report + +Last updated: 2026-04-21 05:00 UTC + +### AI Field Notes + +- Funny: Build logs learned patience. +- Trend: Activity clustered around launch prep. + +`) + next := `## Discord Activity Report + +Last updated: 2026-04-21 06:00 UTC + +### AI Field Notes + +` + aiDigestPlaceholder + ` +` + updated := string(UpdateReadme(existing, next)) + require.Contains(t, updated, "Last updated: 2026-04-21 06:00 UTC") + require.Contains(t, updated, "- Funny: Build logs learned patience.") + require.NotContains(t, updated, aiDigestPlaceholder) +} + +func TestUpdateReadmeLetsAIReportReplaceFieldNotes(t *testing.T) { + existing := []byte(`# Backup + + +## Discord Activity Report + +### AI Field Notes + +- Old: keep only until a new AI report lands. + +`) + next := `## Discord Activity Report + +### AI Field Notes + +- New: fresh report. +` + updated := string(UpdateReadme(existing, next)) + require.Contains(t, updated, "- New: fresh report.") + require.NotContains(t, updated, "- Old: keep only until") +} + func TestWriteReadmeCreatesFile(t *testing.T) { path := filepath.Join(t.TempDir(), "README.md") require.NoError(t, WriteReadme(path, "## Report\n"))