Monorepo management for opam overlays
at main 252 lines 9.1 kB view raw
1(** Changelog generation for monopam. 2 3 This module handles generating weekly and daily changelog entries using 4 Claude AI to analyze git commit history and produce user-facing change 5 summaries. 6 7 Changes are stored in a .changes directory at the monorepo root: 8 - .changes/<repo_name>.json - weekly changelog entries 9 - .changes/<repo_name>-<YYYY-MM-DD>.json - daily changelog entries (one file 10 per day per repo) 11 - .changes/YYYYMMDD.json - aggregated daily changes for broadcasting 12 13 {1 Submodules} 14 15 These modules provide types and I/O for querying the generated changes 16 files. *) 17 18module Aggregated = Changes_aggregated 19(** Aggregated daily changes format (YYYYMMDD.json files). *) 20 21module Daily = Changes_daily 22(** Daily changes with per-day-per-repo structure (repo-YYYY-MM-DD.json files). 23*) 24 25module Query = Changes_query 26(** High-level query interface for changes. *) 27 28(** {1 Types} *) 29 30type commit_range = { from_hash : string; to_hash : string; count : int } 31(** Range of commits included in a changelog entry. *) 32 33type weekly_entry = { 34 week_start : string; (** ISO date YYYY-MM-DD, Monday *) 35 week_end : string; (** ISO date YYYY-MM-DD, Sunday *) 36 summary : string; (** One-line summary *) 37 changes : string list; (** Bullet points *) 38 commit_range : commit_range; 39} 40(** A single week's changelog entry. *) 41 42type daily_entry = { 43 date : string; (** ISO date YYYY-MM-DD *) 44 hour : int; (** Hour of day 0-23 for filtering *) 45 timestamp : Ptime.t; (** RFC3339 timestamp for precise ordering *) 46 summary : string; (** One-line summary *) 47 changes : string list; (** Bullet points *) 48 commit_range : commit_range; 49 contributors : string list; (** List of contributors for this entry *) 50 repo_url : string option; (** Upstream repository URL *) 51} 52(** A single day's changelog entry with hour tracking for real-time updates. *) 53 54type changes_file = { repository : string; entries : weekly_entry list } 55(** Contents of a weekly changes JSON file for a repository. *) 56 57type daily_changes_file = { repository : string; entries : daily_entry list } 58(** Contents of a daily changes JSON file for a repository. *) 59 60(** Mode for changelog generation. *) 61type mode = Weekly | Daily 62 63(** {1 JSON Codecs} *) 64 65val commit_range_jsont : commit_range Jsont.t 66(** JSON codec for commit ranges. *) 67 68val weekly_entry_jsont : weekly_entry Jsont.t 69(** JSON codec for weekly entries. *) 70 71val changes_file_jsont : changes_file Jsont.t 72(** JSON codec for weekly changes files. *) 73 74val daily_entry_jsont : daily_entry Jsont.t 75(** JSON codec for daily entries. *) 76 77val daily_changes_file_jsont : daily_changes_file Jsont.t 78(** JSON codec for daily changes files. *) 79 80(** {1 File I/O} *) 81 82val load : 83 fs:_ Eio.Path.t -> monorepo:Fpath.t -> string -> (changes_file, string) result 84(** [load ~fs ~monorepo repo_name] loads weekly changes from 85 .changes/<repo_name>.json. Returns an empty changes file if the file does 86 not exist. *) 87 88val save : 89 fs:_ Eio.Path.t -> monorepo:Fpath.t -> changes_file -> (unit, string) result 90(** [save ~fs ~monorepo cf] saves the changes file to .changes/<repo_name>.json. 91*) 92 93val daily_exists : 94 fs:_ Eio.Path.t -> monorepo:Fpath.t -> date:string -> string -> bool 95(** [daily_exists ~fs ~monorepo ~date repo_name] checks if a daily changes file 96 exists. 97 @param date Date in YYYY-MM-DD format *) 98 99val load_daily : 100 fs:_ Eio.Path.t -> 101 monorepo:Fpath.t -> 102 date:string -> 103 string -> 104 (daily_changes_file, string) result 105(** [load_daily ~fs ~monorepo ~date repo_name] loads daily changes from 106 .changes/<repo_name>-<date>.json. Returns an empty changes file if the file 107 does not exist. 108 @param date Date in YYYY-MM-DD format *) 109 110val save_daily : 111 fs:_ Eio.Path.t -> 112 monorepo:Fpath.t -> 113 date:string -> 114 daily_changes_file -> 115 (unit, string) result 116(** [save_daily ~fs ~monorepo ~date cf] saves the changes file to 117 .changes/<repo_name>-<date>.json. 118 @param date Date in YYYY-MM-DD format *) 119 120(** {1 Markdown Generation} *) 121 122val to_markdown : changes_file -> string 123(** [to_markdown cf] generates markdown from a single weekly changes file. *) 124 125val aggregate : history:int -> changes_file list -> string 126(** [aggregate ~history cfs] generates combined markdown from multiple weekly 127 changes files. 128 @param history Number of weeks to include (0 for all) *) 129 130val aggregate_daily : history:int -> daily_changes_file list -> string 131(** [aggregate_daily ~history cfs] generates combined markdown from multiple 132 daily changes files. Only includes repos with actual changes (filters out 133 empty entries). 134 @param history Number of days to include (0 for all) *) 135 136(** {1 Date Calculation} *) 137 138val format_date : int * int * int -> string 139(** [format_date (year, month, day)] formats a date as YYYY-MM-DD. *) 140 141val week_of_date : int * int * int -> string * string 142(** [week_of_date (year, month, day)] returns (week_start, week_end) as ISO date 143 strings. week_start is Monday, week_end is Sunday. *) 144 145val week_of_ptime : Ptime.t -> string * string 146(** [week_of_ptime t] returns (week_start, week_end) for the given timestamp. *) 147 148val date_of_ptime : Ptime.t -> string 149(** [date_of_ptime t] returns the date as YYYY-MM-DD for the given timestamp. *) 150 151val has_week : changes_file -> week_start:string -> bool 152(** [has_week cf ~week_start] returns true if the changes file already has an 153 entry for the week starting on the given date. *) 154 155val has_day : daily_changes_file -> date:string -> bool 156(** [has_day cf ~date] returns true if the daily changes file already has an 157 entry for the given date. *) 158 159(** {1 Claude Integration} *) 160 161type claude_response = { summary : string; changes : string list } 162(** Response from Claude analysis. *) 163 164val generate_prompt : 165 repository:string -> 166 week_start:string -> 167 week_end:string -> 168 Git.log_entry list -> 169 string 170(** [generate_prompt ~repository ~week_start ~week_end commits] creates the 171 prompt to send to Claude for weekly changelog generation. *) 172 173val generate_weekly_prompt : 174 repository:string -> 175 week_start:string -> 176 week_end:string -> 177 Git.log_entry list -> 178 string 179(** [generate_weekly_prompt ~repository ~week_start ~week_end commits] creates 180 the prompt to send to Claude for weekly changelog generation. *) 181 182val generate_daily_prompt : 183 repository:string -> date:string -> Git.log_entry list -> string 184(** [generate_daily_prompt ~repository ~date commits] creates the prompt to send 185 to Claude for daily changelog generation. *) 186 187val parse_claude_response : string -> (claude_response option, string) result 188(** [parse_claude_response text] parses Claude's response. Returns [Ok None] if 189 the response is empty (blank summary and changes) or "NO_CHANGES". Returns 190 [Ok (Some r)] if valid JSON was parsed with actual changes. Returns 191 [Error msg] if parsing failed. *) 192 193val analyze_commits : 194 sw:Eio.Switch.t -> 195 process_mgr:_ Eio.Process.mgr -> 196 clock:float Eio.Time.clock_ty Eio.Resource.t -> 197 repository:string -> 198 week_start:string -> 199 week_end:string -> 200 Git.log_entry list -> 201 (claude_response option, string) result 202(** [analyze_commits ~sw ~process_mgr ~clock ~repository ~week_start ~week_end 203 commits] sends commits to Claude for weekly analysis and returns the parsed 204 response. *) 205 206val analyze_commits_daily : 207 sw:Eio.Switch.t -> 208 process_mgr:_ Eio.Process.mgr -> 209 clock:float Eio.Time.clock_ty Eio.Resource.t -> 210 repository:string -> 211 date:string -> 212 Git.log_entry list -> 213 (claude_response option, string) result 214(** [analyze_commits_daily ~sw ~process_mgr ~clock ~repository ~date commits] 215 sends commits to Claude for daily analysis and returns the parsed response. 216*) 217 218val refine_daily_changelog : 219 sw:Eio.Switch.t -> 220 process_mgr:_ Eio.Process.mgr -> 221 clock:float Eio.Time.clock_ty Eio.Resource.t -> 222 string -> 223 (string, string) result 224(** [refine_daily_changelog ~sw ~process_mgr ~clock markdown] sends the raw 225 daily changelog markdown through Claude to produce a more narrative, 226 well-organized version. Groups related changes together and orders them by 227 significance. Ensures all repository names are formatted as markdown links 228 using the pattern 229 [[repo-name](https://tangled.org/@anil.recoil.org/repo-name.git)]. Returns 230 the refined markdown or the original on error. *) 231 232(** {1 Aggregated Files} *) 233 234val generate_aggregated : 235 fs:_ Eio.Path.t -> 236 monorepo:Fpath.t -> 237 date:string -> 238 git_head:string -> 239 now:Ptime.t -> 240 (unit, string) result 241(** [generate_aggregated ~fs ~monorepo ~date ~git_head ~now] generates an 242 aggregated JSON file from all daily JSON files. 243 244 This creates a .changes/YYYYMMDD.json file containing all repository entries 245 for the specified date, with change type classification and author 246 aggregation. 247 248 @param fs Filesystem path 249 @param monorepo Path to the monorepo root 250 @param date Date in YYYY-MM-DD format 251 @param git_head Short git hash of the monorepo HEAD at generation time 252 @param now Current time for the generated_at field *)