Monorepo management for opam overlays
Monopam CLI Improvements Plan#
Design Decisions (Clarified)#
- Verse remotes: Auto-add on
monopam sync, remove outdated ones for all verse members - Add/remove commands: Removed from CLI - use agent skills instead
- Doctor output: Structured JSON with per-repo recommendations, rendered to text by CLI with
--jsonoption - Verse remote URL: Point to
src/checkout (individual repo) - Opam sync direction: Local metadata always trumps opam-repo metadata
- Claude usage: Always use Claude (via ocaml-claude library) for doctor command
Current CLI Commands#
After removing add/remove:
monopam status- Show sync status and verse fork analysismonopam sync [--remote] [--skip-push] [--skip-pull] [package]- Primary sync commandmonopam changes- Generate changelogs with Claudemonopam verse init- Initialize workspacemonopam verse members- List registry membersmonopam verse pull- Pull verse member reposmonopam verse sync- Sync verse workspace
Implementation Plan#
Phase 1: Verse Remotes (Auto-managed)#
Changes to monopam sync#
Add verse remote management to the sync process:
-
During fetch phase: For each verse member in registry:
- Scan their monorepo for subtrees
- For matching subtrees in our
src/:- Add git remote named
verse/<handle>pointing to theirsrc/checkout - If remote exists but URL changed, update it
- Fetch from the remote
- Add git remote named
-
Cleanup: Remove verse remotes for:
- Members no longer in registry
- Repos we no longer have
Data Flow#
sync starts
├── push phase (existing)
├── fetch phase (existing)
│ └── NEW: for each verse member with matching repos:
│ └── ensure git remote in src/<repo> → verse/<member>/src/<repo>
│ └── git fetch verse/<handle>
├── merge phase (existing)
├── subtree phase (existing)
├── finalize phase (existing)
└── remote phase (existing)
Implementation in monopam.ml#
(* New function to manage verse remotes for a repo *)
let ensure_verse_remotes ~proc ~fs ~config ~verse_config pkg =
let checkouts_root = Config.Paths.checkouts config in
let checkout_dir = Package.checkout_dir ~checkouts_root pkg in
let repo_name = Package.repo_name pkg in
(* Get all verse members who have this repo *)
let verse_subtrees = Verse.get_verse_subtrees ~proc ~fs ~config:verse_config () in
let members_with_repo =
Hashtbl.find_opt verse_subtrees repo_name
|> Option.value ~default:[]
in
(* For each member, ensure remote exists *)
List.iter (fun (handle, verse_mono_path) ->
let remote_name = "verse/" ^ handle in
let verse_src = Fpath.(verse_mono_path / ".." / "src" / repo_name) in
(* Add or update remote *)
Git.ensure_remote ~proc ~fs ~name:remote_name ~url:verse_src checkout_dir
) members_with_repo
Phase 2: Opam Metadata Sync#
New Command: monopam opam sync#
Synchronize .opam files from monorepo subtrees to opam-repo.
monopam opam sync # Sync all packages
monopam opam sync eio # Sync specific package
Behavior:
- For each package in monorepo:
- Read
.opamfile from subtree - Compare with opam-repo version
- If different, copy monorepo → opam-repo (local always wins)
- Stage changes in opam-repo
- Read
Integration with monopam sync#
Add --opam flag:
monopam sync --opam --remote # Full sync including opam metadata
Or make it part of finalize phase (always sync opam).
Implementation#
let sync_opam_files ~proc ~fs ~config pkgs =
let monorepo = Config.Paths.monorepo config in
let opam_repo = Config.Paths.opam_repo config in
List.iter (fun pkg ->
let name = Package.name pkg in
let subtree_opam = Fpath.(monorepo / Package.subtree_prefix pkg / (name ^ ".opam")) in
let repo_opam = Fpath.(opam_repo / "packages" / name / (name ^ ".dev") / "opam") in
(* Read both files *)
let subtree_content = read_file_opt ~fs subtree_opam in
let repo_content = read_file_opt ~fs repo_opam in
match subtree_content with
| None -> () (* No opam file in subtree, skip *)
| Some content when Some content <> repo_content ->
(* Copy to opam-repo *)
write_file ~fs repo_opam content;
Git.add ~proc ~fs opam_repo [Fpath.to_string repo_opam]
| _ -> () (* Already in sync *)
) pkgs
Phase 3: Doctor Command#
New Command: monopam doctor#
Claude-powered workspace health analysis.
monopam doctor # Full analysis, text output
monopam doctor --json # JSON output for tooling
monopam doctor eio # Analyze specific repo
Output Structure (JSON)#
{
"timestamp": "2026-01-21T12:00:00Z",
"workspace": "/home/user/tangled",
"summary": {
"repos_total": 39,
"repos_need_sync": 2,
"repos_behind_upstream": 3,
"verse_divergences": 5
},
"repos": [
{
"name": "eio",
"local_sync": "in_sync",
"remote_sync": { "ahead": 0, "behind": 0 },
"verse_analysis": [
{
"handle": "alice.bsky.social",
"their_commits": [
{
"hash": "abc1234",
"subject": "Add Eio.Path.symlink support",
"category": "feature",
"priority": "medium",
"recommendation": "review-first",
"conflict_risk": "low",
"summary": "Adds symlink creation support to Eio.Path module"
},
{
"hash": "def5678",
"subject": "Fix race condition in Eio.Fiber.fork",
"category": "bug-fix",
"priority": "high",
"recommendation": "merge-now",
"conflict_risk": "none",
"summary": "Fixes potential deadlock when forking fibers under load"
}
],
"suggested_action": "git fetch verse/alice.bsky.social && git cherry-pick def5678"
}
]
}
],
"recommendations": [
{
"priority": "high",
"action": "Merge alice's bug fix for eio (def5678)",
"command": "cd src/eio && git cherry-pick def5678"
},
{
"priority": "medium",
"action": "Run monopam sync to resolve local sync issues",
"command": "monopam sync"
}
],
"warnings": [
"opam-repo has uncommitted changes",
"verse/alice.bsky.social/ is 10 commits behind"
]
}
Text Rendering#
=== Monopam Doctor Report ===
Generated: 2026-01-21 12:00:00
Summary:
39 repos tracked
2 need local sync
3 behind upstream
5 verse divergences
─────────────────────────────────────────
eio (diverged from alice.bsky.social)
Their commits (2):
[HIGH] def5678 Fix race condition in Eio.Fiber.fork
Category: bug-fix | Risk: none | Action: merge-now
→ Fixes potential deadlock when forking fibers under load
[MED] abc1234 Add Eio.Path.symlink support
Category: feature | Risk: low | Action: review-first
→ Adds symlink creation support to Eio.Path module
Suggested: git fetch verse/alice.bsky.social && git cherry-pick def5678
─────────────────────────────────────────
Recommendations:
1. [HIGH] Merge alice's bug fix for eio (def5678)
$ cd src/eio && git cherry-pick def5678
2. [MED] Run monopam sync to resolve local sync issues
$ monopam sync
Warnings:
• opam-repo has uncommitted changes
• verse/alice.bsky.social/ is 10 commits behind
Claude Integration#
Use the existing claude OCaml library for analysis:
module Doctor = struct
type commit_analysis = {
hash: string;
subject: string;
category: [`Security_fix | `Bug_fix | `Feature | `Refactor | `Docs | `Test];
priority: [`Critical | `High | `Medium | `Low];
recommendation: [`Merge_now | `Review_first | `Skip | `Needs_discussion];
conflict_risk: [`None | `Low | `Medium | `High];
summary: string;
}
let analyze_commits ~commits ~our_branch ~their_handle =
let prompt = Format.asprintf {|
You are analyzing git commits from a collaborator's repository.
Repository context:
- Our branch: %s
- Their handle: %s
Commits to analyze:
%s
For each commit, provide JSON with:
- category: security-fix, bug-fix, feature, refactor, docs, test
- priority: critical, high, medium, low
- recommendation: merge-now, review-first, skip, needs-discussion
- conflict_risk: none, low, medium, high
- summary: one-line description of what the commit does
Respond with a JSON array of analyses.
|} our_branch their_handle (format_commits commits) in
Claude.chat ~model:"claude-sonnet-4-20250514" ~messages:[
Claude.Message.user prompt
] ()
|> parse_analysis_response
end
Implementation Order#
- Phase 1a: Add
ensure_verse_remotesfunction to monopam.ml - Phase 1b: Integrate verse remote management into sync fetch phase
- Phase 2a: Add
sync_opam_filesfunction - Phase 2b: Add
monopam opam synccommand (or integrate into sync) - Phase 3a: Create
doctor.mlmodule with types and Claude integration - Phase 3b: Add
monopam doctorcommand with JSON/text output
Files to Modify/Create#
Modify#
monopam/lib/monopam.ml- Add verse remote management, opam syncmonopam/lib/monopam.mli- Export new functionsmonopam/bin/main.ml- Add doctor command, opam subcommand
Create#
monopam/lib/doctor.ml- Doctor analysis logicmonopam/lib/doctor.mli- Doctor interfacemonopam/lib/opam_sync.ml- Opam metadata sync logic (optional, could be in monopam.ml)