Monorepo management for opam overlays
0
fork

Configure Feed

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

monopam: generate llms.txt alongside README.md

Add write_llms_txt following the spec at https://llmstxt.org/. Called
from both push and pull so the LLM-friendly index stays in sync.

The llms.txt complements README.md:
- README.md: humans browsing the forge, links to upstream repos
- llms.txt: LLMs, links to relative per-package README paths so an LLM
can fetch only what it needs without pulling the whole catalog

+79 -1
+66
lib/init.ml
··· 173 173 (List.length pkgs) (List.length grouped)); 174 174 Buffer.contents buf 175 175 176 + (** {1 llms.txt Generation} 177 + 178 + Generates a /llms.txt file at the monorepo root following the spec at 179 + https://llmstxt.org/. This gives LLMs an efficient entry point for 180 + navigating the monorepo: a structured index that links to each package's 181 + README.md via a relative path, so an LLM can fetch only the packages it 182 + needs without pulling the whole catalog into context. *) 183 + 184 + let generate_llms_txt pkgs = 185 + let grouped = Ctx.group_by_repo pkgs in 186 + let buf = Buffer.create 4096 in 187 + Buffer.add_string buf "# Blacksun Monorepo\n\n"; 188 + Buffer.add_string buf 189 + "> OCaml packages for space systems, cryptography, protocols, and monorepo \ 190 + tooling.\n\n"; 191 + Buffer.add_string buf 192 + "This monorepo aggregates OCaml libraries as git subtrees. Each package \ 193 + has its own README with API documentation and usage examples at the \ 194 + linked path.\n\n"; 195 + Buffer.add_string buf "## Packages\n\n"; 196 + List.iter 197 + (fun (repo, pkgs) -> 198 + List.iter 199 + (fun pkg -> 200 + let name = Package.name pkg in 201 + let synopsis = Option.value ~default:"" (Package.synopsis pkg) in 202 + Buffer.add_string buf 203 + (Fmt.str "- [%s](%s/README.md): %s\n" name repo synopsis)) 204 + pkgs) 205 + grouped; 206 + Buffer.add_string buf "\n## Optional\n\n"; 207 + Buffer.add_string buf 208 + "- [CLAUDE.md](CLAUDE.md): Development workflow and monorepo conventions\n"; 209 + Buffer.add_string buf 210 + "- [README.md](README.md): Human-facing package index with upstream repo \ 211 + links\n"; 212 + Buffer.contents buf 213 + 176 214 (** {1 dune-project Generation} *) 177 215 178 216 let collect_external_deps ~fs ~config pkgs = ··· 297 335 in 298 336 ignore (Eio.Process.await child)); 299 337 Log.app (fun m -> m "Updated README.md with %d packages" (List.length pkgs)) 338 + end 339 + 340 + let write_llms_txt ~proc ~fs ~config pkgs = 341 + let monorepo = Config.Paths.monorepo config in 342 + let monorepo_eio = Eio.Path.(fs / Fpath.to_string monorepo) in 343 + let llms_path = Eio.Path.(monorepo_eio / "llms.txt") in 344 + let content = generate_llms_txt pkgs in 345 + let needs_update = 346 + match Eio.Path.load llms_path with 347 + | existing -> existing <> content 348 + | exception Eio.Io _ -> true 349 + in 350 + if needs_update then begin 351 + Log.info (fun m -> m "Updating llms.txt in monorepo"); 352 + Eio.Path.save ~create:(`Or_truncate 0o644) llms_path content; 353 + Eio.Switch.run (fun sw -> 354 + let child = 355 + Eio.Process.spawn proc ~sw ~cwd:monorepo_eio 356 + [ "git"; "add"; "llms.txt" ] 357 + in 358 + ignore (Eio.Process.await child)); 359 + Eio.Switch.run (fun sw -> 360 + let child = 361 + Eio.Process.spawn proc ~sw ~cwd:monorepo_eio 362 + [ "git"; "commit"; "-m"; "Update llms.txt package index" ] 363 + in 364 + ignore (Eio.Process.await child)); 365 + Log.app (fun m -> m "Updated llms.txt with %d packages" (List.length pkgs)) 300 366 end 301 367 302 368 let write_claude_md ~proc ~fs ~config =
+10
lib/init.mli
··· 26 26 unit 27 27 (** [write_readme ~proc ~fs ~config pkgs] updates README.md if changed. *) 28 28 29 + val write_llms_txt : 30 + proc:_ Eio.Process.mgr -> 31 + fs:Eio.Fs.dir_ty Eio.Path.t -> 32 + config:Config.t -> 33 + Package.t list -> 34 + unit 35 + (** [write_llms_txt ~proc ~fs ~config pkgs] updates llms.txt if changed. Follows 36 + the spec at https://llmstxt.org/ — an LLM-friendly index with relative paths 37 + to each package's README.md. *) 38 + 29 39 val write_claude_md : 30 40 proc:_ Eio.Process.mgr -> 31 41 fs:Eio.Fs.dir_ty Eio.Path.t ->
+1
lib/pull.ml
··· 549 549 let finalize_pull ~proc ~fs_t ~config ~all_pkgs unresolved = 550 550 if unresolved = [] then begin 551 551 Init.write_readme ~proc ~fs:fs_t ~config all_pkgs; 552 + Init.write_llms_txt ~proc ~fs:fs_t ~config all_pkgs; 552 553 Init.write_claude_md ~proc ~fs:fs_t ~config; 553 554 Init.write_dune_project ~proc ~fs:fs_t ~config all_pkgs; 554 555 Ok ()
+2 -1
lib/push.ml
··· 718 718 719 719 let export_and_push ~sw ~proc ~fs ~fs_t ~config ~sources ~upstream ~push_mono 720 720 ~clean ~force ~all_pkgs repos = 721 - (* Update README before push *) 721 + (* Update README and llms.txt before push *) 722 722 Init.write_readme ~proc ~fs:fs_t ~config all_pkgs; 723 + Init.write_llms_txt ~proc ~fs:fs_t ~config all_pkgs; 723 724 let n_repos = List.length repos in 724 725 let total = if upstream then n_repos * 2 else n_repos in 725 726 (* Pre-compute all subtree splits in a single history walk *)