Monorepo management for opam overlays
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge monopam_changes into monopam library and update poe

Consolidate the separate monopam_changes library into the main monopam
library as submodules (Changes.Aggregated, Changes.Daily, Changes.Query).
This simplifies the dependency graph and provides a cleaner interface.

Update poe to use the new Monopam.Changes interface and add:
- Git pull before checking for changes in the polling loop
- Detailed logging of git pull results (up-to-date vs new changes)
- --requests-verbose flag to control HTTP request logging (off by default)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+81 -103
-12
dune-project
··· 33 33 (ptime (>= 1.0.0)) 34 34 (odoc :with-doc))) 35 35 36 - (package 37 - (name monopam-changes) 38 - (synopsis "Parse and query aggregated daily changes from monopam") 39 - (description "Library for parsing and querying the aggregated daily changes format produced by monopam, suitable for broadcasting via Zulip or other channels.") 40 - (depends 41 - (ocaml (>= 5.2.0)) 42 - (dune (>= 3.20)) 43 - (eio (>= 1.2)) 44 - (jsont (>= 0.2.0)) 45 - (ptime (>= 1.0.0)) 46 - (fpath (>= 0.7.0)) 47 - (odoc :with-doc)))
+22 -10
lib/changes.ml
··· 5 5 6 6 Changes are stored in a .changes directory at the monorepo root: 7 7 - .changes/<repo_name>.json - weekly changelog entries 8 - - .changes/<repo_name>-<YYYY-MM-DD>.json - daily changelog entries (one file per day per repo) *) 8 + - .changes/<repo_name>-<YYYY-MM-DD>.json - daily changelog entries (one file per day per repo) 9 + - .changes/YYYYMMDD.json - aggregated daily changes for broadcasting 10 + 11 + {1 Submodules} 12 + 13 + - {!Aggregated} - Types and I/O for aggregated daily changes (YYYYMMDD.json) 14 + - {!Daily} - Types and I/O for per-day-per-repo changes (repo-YYYY-MM-DD.json) 15 + - {!Query} - High-level query interface for changes *) 16 + 17 + (** Re-export submodules for querying changes *) 18 + module Aggregated = Changes_aggregated 19 + module Daily = Changes_daily 20 + module Query = Changes_query 9 21 10 22 type commit_range = { 11 23 from_hash : string; ··· 740 752 if String.starts_with ~prefix:"initial import" summary_lower || 741 753 String.starts_with ~prefix:"added as subtree" summary_lower || 742 754 String.starts_with ~prefix:"added" summary_lower && String.ends_with ~suffix:"library" summary_lower then 743 - Monopam_changes.Aggregated.New_library 755 + Changes_aggregated.New_library 744 756 else if List.exists (fun kw -> string_contains_s summary_lower kw) 745 757 ["fix"; "bugfix"; "bug fix"; "repair"; "patch"; "resolve"; "correct"] then 746 - Monopam_changes.Aggregated.Bugfix 758 + Changes_aggregated.Bugfix 747 759 else if List.exists (fun kw -> string_contains_s summary_lower kw) 748 760 ["refactor"; "cleanup"; "clean up"; "reorganize"; "restructure"; "simplify"] then 749 - Monopam_changes.Aggregated.Refactor 761 + Changes_aggregated.Refactor 750 762 else if List.exists (fun kw -> string_contains_s summary_lower kw) 751 763 ["doc"; "documentation"; "readme"; "comment"; "tutorial"; "guide"] then 752 - Monopam_changes.Aggregated.Documentation 764 + Changes_aggregated.Documentation 753 765 else if List.exists (fun kw -> string_contains_s summary_lower kw) 754 766 ["add"; "new"; "feature"; "implement"; "support"; "introduce"; "enable"] then 755 - Monopam_changes.Aggregated.Feature 767 + Changes_aggregated.Feature 756 768 else 757 - Monopam_changes.Aggregated.Unknown 769 + Changes_aggregated.Unknown 758 770 759 771 (** Generate an aggregated daily file from individual daily json files. 760 772 This creates a YYYYMMDD.json file in the .changes directory. *) ··· 795 807 let now = Ptime_clock.now () in 796 808 let agg_entries = List.map (fun (repo_name, (e : daily_entry)) -> 797 809 let change_type = infer_change_type e.summary in 798 - Monopam_changes.Aggregated.{ 810 + Changes_aggregated.{ 799 811 repository = repo_name; 800 812 hour = e.hour; 801 813 timestamp = e.timestamp; ··· 820 832 in 821 833 822 834 (* Create the aggregated structure *) 823 - let aggregated : Monopam_changes.Aggregated.t = { 835 + let aggregated : Changes_aggregated.t = { 824 836 date; 825 837 generated_at = now; 826 838 git_head; ··· 830 842 831 843 (* Save to YYYYMMDD.json *) 832 844 let changes_dir_fpath = Fpath.(v (Fpath.to_string monorepo) / ".changes") in 833 - Monopam_changes.Aggregated.save ~fs ~changes_dir:changes_dir_fpath aggregated 845 + Changes_aggregated.save ~fs ~changes_dir:changes_dir_fpath aggregated
+15 -1
lib/changes.mli
··· 5 5 6 6 Changes are stored in a .changes directory at the monorepo root: 7 7 - .changes/<repo_name>.json - weekly changelog entries 8 - - .changes/<repo_name>-<YYYY-MM-DD>.json - daily changelog entries (one file per day per repo) *) 8 + - .changes/<repo_name>-<YYYY-MM-DD>.json - daily changelog entries (one file per day per repo) 9 + - .changes/YYYYMMDD.json - aggregated daily changes for broadcasting 10 + 11 + {1 Submodules} 12 + 13 + These modules provide types and I/O for querying the generated changes files. *) 14 + 15 + (** Aggregated daily changes format (YYYYMMDD.json files). *) 16 + module Aggregated = Changes_aggregated 17 + 18 + (** Daily changes with per-day-per-repo structure (repo-YYYY-MM-DD.json files). *) 19 + module Daily = Changes_daily 20 + 21 + (** High-level query interface for changes. *) 22 + module Query = Changes_query 9 23 10 24 (** {1 Types} *) 11 25
+1 -1
lib/dune
··· 1 1 (library 2 2 (name monopam) 3 3 (public_name monopam) 4 - (libraries eio tomlt tomlt.eio xdge opam-file-format fmt logs uri fpath claude jsont jsont.bytesrw ptime ptime.clock.os monopam_changes)) 4 + (libraries eio tomlt tomlt.eio xdge opam-file-format fmt logs uri fpath claude jsont jsont.bytesrw ptime ptime.clock.os))
+7
lib_changes/aggregated.ml lib/changes_aggregated.ml
··· 3 3 SPDX-License-Identifier: ISC 4 4 ---------------------------------------------------------------------------*) 5 5 6 + (** Aggregated daily changes format. 7 + 8 + This module provides types and JSON codecs for the aggregated daily changes 9 + format stored in [.changes/YYYYMMDD.json] files. These files combine all 10 + repository changes for a single day into a structured format suitable for 11 + broadcasting. *) 12 + 6 13 type change_type = 7 14 | Feature 8 15 | Bugfix
+2 -2
lib_changes/aggregated.mli lib/changes_aggregated.mli
··· 66 66 67 67 val load : fs:_ Eio.Path.t -> changes_dir:Fpath.t -> date:string -> (t, string) result 68 68 (** Load aggregated changes for a specific date. 69 - [date] should be in YYYYMMDD format. *) 69 + [date] should be in YYYY-MM-DD format. *) 70 70 71 71 val load_range : 72 72 fs:_ Eio.Path.t -> ··· 75 75 to_date:string -> 76 76 (t list, string) result 77 77 (** Load all aggregated changes files in date range. 78 - Dates should be in YYYYMMDD format. *) 78 + Dates should be in YYYY-MM-DD format. *) 79 79 80 80 val latest : fs:_ Eio.Path.t -> changes_dir:Fpath.t -> (t option, string) result 81 81 (** Load the most recent aggregated changes file. *)
+7
lib_changes/daily.ml lib/changes_daily.ml
··· 3 3 SPDX-License-Identifier: ISC 4 4 ---------------------------------------------------------------------------*) 5 5 6 + (** Daily changes with per-day-per-repo structure. 7 + 8 + This module provides an immutable data structure for loading and querying 9 + daily changes from per-day-per-repo JSON files. Files are named 10 + [<repo>-<YYYY-MM-DD>.json] and contain timestamped entries for real-time 11 + tracking. *) 12 + 6 13 type commit_range = { 7 14 from_hash : string; 8 15 to_hash : string;
lib_changes/daily.mli lib/changes_daily.mli
-4
lib_changes/dune
··· 1 - (library 2 - (name monopam_changes) 3 - (public_name monopam-changes) 4 - (libraries jsont jsont.bytesrw eio ptime ptime.clock.os fpath))
-16
lib_changes/monopam_changes.ml
··· 1 - (*--------------------------------------------------------------------------- 2 - Copyright (c) 2026 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 - SPDX-License-Identifier: ISC 4 - ---------------------------------------------------------------------------*) 5 - 6 - (** Library for parsing and querying aggregated daily changes. 7 - 8 - This library provides types and functions for working with the aggregated 9 - daily changes format used by the monopam tool and the poe Zulip bot. 10 - 11 - The {!Daily} module provides an immutable data structure for loading 12 - per-day-per-repo JSON files ([<repo>-<YYYY-MM-DD>.json]). *) 13 - 14 - module Aggregated = Aggregated 15 - module Daily = Daily 16 - module Query = Query
+21 -16
lib_changes/query.ml lib/changes_query.ml
··· 3 3 SPDX-License-Identifier: ISC 4 4 ---------------------------------------------------------------------------*) 5 5 6 + (** High-level query interface for changes. 7 + 8 + This module provides convenient functions for querying changes since a 9 + specific timestamp and formatting them for broadcast. *) 10 + 6 11 let changes_since ~fs ~changes_dir ~since = 7 12 (* Get the date part of since for filtering *) 8 13 let since_date = ··· 15 20 let (y, m, d), _ = Ptime.to_date_time now in 16 21 Printf.sprintf "%04d-%02d-%02d" y m d 17 22 in 18 - match Aggregated.load_range ~fs ~changes_dir ~from_date:since_date ~to_date:now_date with 23 + match Changes_aggregated.load_range ~fs ~changes_dir ~from_date:since_date ~to_date:now_date with 19 24 | Error e -> Error e 20 25 | Ok aggregated_files -> 21 26 (* Filter to files generated after 'since' and collect entries *) 22 - let entries = List.concat_map (fun (agg : Aggregated.t) -> 27 + let entries = List.concat_map (fun (agg : Changes_aggregated.t) -> 23 28 if Ptime.compare agg.generated_at since > 0 then 24 29 agg.entries 25 30 else ··· 49 54 end; 50 55 (* Group by change type *) 51 56 let by_type = [ 52 - (Aggregated.New_library, "New Libraries", []); 53 - (Aggregated.Feature, "Features", []); 54 - (Aggregated.Bugfix, "Bug Fixes", []); 55 - (Aggregated.Documentation, "Documentation", []); 56 - (Aggregated.Refactor, "Improvements", []); 57 - (Aggregated.Unknown, "Other Changes", []); 57 + (Changes_aggregated.New_library, "New Libraries", []); 58 + (Changes_aggregated.Feature, "Features", []); 59 + (Changes_aggregated.Bugfix, "Bug Fixes", []); 60 + (Changes_aggregated.Documentation, "Documentation", []); 61 + (Changes_aggregated.Refactor, "Improvements", []); 62 + (Changes_aggregated.Unknown, "Other Changes", []); 58 63 ] in 59 64 let grouped = List.map (fun (ct, title, _) -> 60 - let matching = List.filter (fun (e : Aggregated.entry) -> e.change_type = ct) entries in 65 + let matching = List.filter (fun (e : Changes_aggregated.entry) -> e.change_type = ct) entries in 61 66 (ct, title, matching)) by_type 62 67 in 63 68 List.iter (fun (_ct, title, entries) -> 64 69 if entries <> [] then begin 65 70 Buffer.add_string buf (Printf.sprintf "### %s\n\n" title); 66 - List.iter (fun (entry : Aggregated.entry) -> 71 + List.iter (fun (entry : Changes_aggregated.entry) -> 67 72 let repo_link = format_repo_link entry.repository entry.repo_url in 68 73 Buffer.add_string buf (Printf.sprintf "**%s**: %s\n" repo_link entry.summary); 69 74 List.iter (fun change -> ··· 82 87 else 83 88 let count = List.length entries in 84 89 let repos = List.sort_uniq String.compare 85 - (List.map (fun (e : Aggregated.entry) -> e.repository) entries) in 90 + (List.map (fun (e : Changes_aggregated.entry) -> e.repository) entries) in 86 91 Printf.sprintf "%d change%s across %d repositor%s: %s" 87 92 count (if count = 1 then "" else "s") 88 93 (List.length repos) (if List.length repos = 1 then "y" else "ies") ··· 91 96 (** {1 Daily Changes (Real-time)} *) 92 97 93 98 let daily_changes_since ~fs ~changes_dir ~since = 94 - Daily.entries_since ~fs ~changes_dir ~since 99 + Changes_daily.entries_since ~fs ~changes_dir ~since 95 100 96 101 let has_new_daily_changes ~fs ~changes_dir ~since = 97 102 daily_changes_since ~fs ~changes_dir ~since <> [] ··· 108 113 end; 109 114 (* Group by repository *) 110 115 let repos = List.sort_uniq String.compare 111 - (List.map (fun (e : Daily.entry) -> e.repository) entries) in 116 + (List.map (fun (e : Changes_daily.entry) -> e.repository) entries) in 112 117 List.iter (fun repo -> 113 - let repo_entries = List.filter (fun (e : Daily.entry) -> e.repository = repo) entries in 118 + let repo_entries = List.filter (fun (e : Changes_daily.entry) -> e.repository = repo) entries in 114 119 if repo_entries <> [] then begin 115 120 let first_entry = List.hd repo_entries in 116 121 let repo_link = format_repo_link repo first_entry.repo_url in 117 122 Buffer.add_string buf (Printf.sprintf "### %s\n\n" repo_link); 118 - List.iter (fun (entry : Daily.entry) -> 123 + List.iter (fun (entry : Changes_daily.entry) -> 119 124 Buffer.add_string buf (Printf.sprintf "**%s**\n" entry.summary); 120 125 List.iter (fun change -> 121 126 Buffer.add_string buf (Printf.sprintf "- %s\n" change)) entry.changes; ··· 133 138 else 134 139 let count = List.length entries in 135 140 let repos = List.sort_uniq String.compare 136 - (List.map (fun (e : Daily.entry) -> e.repository) entries) in 141 + (List.map (fun (e : Changes_daily.entry) -> e.repository) entries) in 137 142 Printf.sprintf "%d change%s across %d repositor%s: %s" 138 143 count (if count = 1 then "" else "s") 139 144 (List.length repos) (if List.length repos = 1 then "y" else "ies")
+6 -6
lib_changes/query.mli lib/changes_query.mli
··· 14 14 fs:_ Eio.Path.t -> 15 15 changes_dir:Fpath.t -> 16 16 since:Ptime.t -> 17 - (Aggregated.entry list, string) result 17 + (Changes_aggregated.entry list, string) result 18 18 (** Get all change entries from aggregated files created after [since]. 19 19 Returns entries from all days after the timestamp. *) 20 20 ··· 28 28 (** {1 Formatting} *) 29 29 30 30 val format_for_zulip : 31 - entries:Aggregated.entry list -> 31 + entries:Changes_aggregated.entry list -> 32 32 include_date:bool -> 33 33 date:string option -> 34 34 string ··· 37 37 [date] is used for the header if provided. *) 38 38 39 39 val format_summary : 40 - entries:Aggregated.entry list -> 40 + entries:Changes_aggregated.entry list -> 41 41 string 42 42 (** Format a brief summary of the changes. *) 43 43 ··· 47 47 fs:_ Eio.Path.t -> 48 48 changes_dir:Fpath.t -> 49 49 since:Ptime.t -> 50 - Daily.entry list 50 + Changes_daily.entry list 51 51 (** Get all daily change entries created after [since] timestamp. 52 52 Uses the per-day-per-repo files for real-time access. *) 53 53 ··· 59 59 (** Check if there are any new daily changes since the given timestamp. *) 60 60 61 61 val format_daily_for_zulip : 62 - entries:Daily.entry list -> 62 + entries:Changes_daily.entry list -> 63 63 include_date:bool -> 64 64 date:string option -> 65 65 string ··· 67 67 Groups entries by repository. *) 68 68 69 69 val format_daily_summary : 70 - entries:Daily.entry list -> 70 + entries:Changes_daily.entry list -> 71 71 string 72 72 (** Format a brief summary of daily changes. *)
-35
monopam-changes.opam
··· 1 - # This file is generated by dune, edit dune-project instead 2 - opam-version: "2.0" 3 - synopsis: "Parse and query aggregated daily changes from monopam" 4 - description: 5 - "Library for parsing and querying the aggregated daily changes format produced by monopam, suitable for broadcasting via Zulip or other channels." 6 - maintainer: ["Anil Madhavapeddy <anil@recoil.org>"] 7 - authors: ["Anil Madhavapeddy <anil@recoil.org>"] 8 - license: "ISC" 9 - homepage: "https://tangled.org/@anil.recoil.org/monopam" 10 - bug-reports: "https://tangled.org/@anil.recoil.org/monopam/issues" 11 - depends: [ 12 - "ocaml" {>= "5.2.0"} 13 - "dune" {>= "3.20" & >= "3.20"} 14 - "eio" {>= "1.2"} 15 - "jsont" {>= "0.2.0"} 16 - "ptime" {>= "1.0.0"} 17 - "fpath" {>= "0.7.0"} 18 - "odoc" {with-doc} 19 - ] 20 - build: [ 21 - ["dune" "subst"] {dev} 22 - [ 23 - "dune" 24 - "build" 25 - "-p" 26 - name 27 - "-j" 28 - jobs 29 - "@install" 30 - "@runtest" {with-test} 31 - "@doc" {with-doc} 32 - ] 33 - ] 34 - dev-repo: "git+https://tangled.org/@anil.recoil.org/monopam.git" 35 - x-maintenance-intent: ["(latest)"]