orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1package main
2
3import (
4 "fmt"
5 "regexp"
6 "strings"
7 "time"
8)
9
10// A passage describes the future as of the day it was written, and a model
11// repeating it says a thing "is scheduled for April 2026" in September. Two
12// rounds of telling it not to did not hold, which is the usual result of asking
13// a 4B to compare dates in prose, so the check happens here instead.
14//
15// This warns rather than rewrites. The sentence is the model's and may be a
16// fair report of what a stale page said, so the honest thing is to say which
17// line is out of date rather than to quietly edit an answer whose whole claim
18// is that it was checked.
19
20var (
21 // The ways an answer says a thing has not happened yet.
22 futureTense = regexp.MustCompile(`(?i)\b(is|are|was|were|remains?|stays?)\s+(scheduled|planned|slated|expected|due|set)\b|` +
23 `\b(will|is going to|are going to)\s+\w+|` +
24 `\b(is|are)\s+(targeting|upcoming|forthcoming)\b|` +
25 `\b(targets?|targeted for|scheduled for|planned for|due in|expected in)\b`)
26
27 // Month and year, which is as precise as these sentences get.
28 monthYear = regexp.MustCompile(`(?i)\b(january|february|march|april|may|june|july|august|september|october|november|december)\s+(\d{4})\b`)
29
30 // The other half of the same bug. A live tracker page saying a mission "is
31 // currently underway" was right the day it was written, and a model
32 // repeating it months later says the mission is still flying.
33 presentTense = regexp.MustCompile(`(?i)\b(is|are)\s+(currently|now|presently)\b|` +
34 `\b(is|are)\s+(underway|under way|ongoing|active|in progress|in flight|tracking)\b|` +
35 `\bcurrently\s+(active|underway|executing|running|flying)\b`)
36
37 // "April 2, 2026" as well as a bare month and year, since a tracker page
38 // dates things to the day.
39 dayMonthYear = regexp.MustCompile(`(?i)\b(january|february|march|april|may|june|july|august|september|october|november|december)\s+\d{1,2},?\s+(\d{4})\b`)
40
41 // A bare quarter or year on its own is too coarse to call stale, since
42 // "early 2028" said in 2026 is fine.
43 sentenceSplit = regexp.MustCompile(`(?m)[^.!?\n]+[.!?]?`)
44)
45
46// staleNow flags a sentence saying something is happening now while dating it
47// to a month that has already finished.
48func staleNow(text string, now time.Time) []string {
49 var out []string
50 seen := map[string]bool{}
51 for _, raw := range sentenceSplit.FindAllString(text, -1) {
52 s := strings.TrimSpace(raw)
53 if s == "" || !presentTense.MatchString(s) {
54 continue
55 }
56 for _, re := range []*regexp.Regexp{dayMonthYear, monthYear} {
57 var hit bool
58 for _, m := range re.FindAllStringSubmatch(s, -1) {
59 when, ok := monthStart(m[1], m[len(m)-1])
60 if !ok {
61 continue
62 }
63 if !when.AddDate(0, 1, 0).Before(truncMonth(now)) {
64 continue
65 }
66 key := m[1] + m[len(m)-1]
67 if seen[key] {
68 hit = true
69 continue
70 }
71 seen[key] = true
72 hit = true
73 out = append(out, fmt.Sprintf(
74 "one line says this is happening now but dates it to %s %s, so it is describing what a source said at the time rather than today",
75 strings.ToUpper(m[1][:1])+strings.ToLower(m[1][1:]), m[len(m)-1]))
76 }
77 if hit {
78 break
79 }
80 }
81 }
82 return out
83}
84
85// staleFutures returns a warning for each sentence claiming something is still
86// ahead on a month that has already finished.
87func staleFutures(text string, now time.Time) []string {
88 var out []string
89 seen := map[string]bool{}
90 for _, raw := range sentenceSplit.FindAllString(text, -1) {
91 s := strings.TrimSpace(raw)
92 if s == "" || !futureTense.MatchString(s) {
93 continue
94 }
95 for _, m := range monthYear.FindAllStringSubmatch(s, -1) {
96 when, ok := monthStart(m[1], m[2])
97 if !ok {
98 continue
99 }
100 // The month has to be fully behind us. Something "scheduled for
101 // September" on the 5th of September is not wrong.
102 if !when.AddDate(0, 1, 0).Before(truncMonth(now)) {
103 continue
104 }
105 key := m[1] + m[2]
106 if seen[key] {
107 continue
108 }
109 seen[key] = true
110 out = append(out, fmt.Sprintf(
111 "one line calls %s %s still upcoming, and it has passed, so the sources are older than the event",
112 strings.ToUpper(m[1][:1])+strings.ToLower(m[1][1:]), m[2]))
113 }
114 }
115 return out
116}
117
118func monthStart(name, year string) (time.Time, bool) {
119 t, err := time.Parse("January 2006", strings.ToUpper(name[:1])+strings.ToLower(name[1:])+" "+year)
120 if err != nil {
121 return time.Time{}, false
122 }
123 return t, true
124}
125
126func truncMonth(t time.Time) time.Time {
127 return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
128}