My aggregated monorepo of OCaml code, automaintained
1# Monopam CLI Improvements Plan
2
3## Design Decisions (Clarified)
4
51. **Verse remotes**: Auto-add on `monopam sync`, remove outdated ones for all verse members
62. **Add/remove commands**: Removed from CLI - use agent skills instead
73. **Doctor output**: Structured JSON with per-repo recommendations, rendered to text by CLI with `--json` option
84. **Verse remote URL**: Point to `src/` checkout (individual repo)
95. **Opam sync direction**: Local metadata always trumps opam-repo metadata
106. **Claude usage**: Always use Claude (via ocaml-claude library) for doctor command
11
12---
13
14## Current CLI Commands
15
16After removing add/remove:
17- `monopam status` - Show sync status and verse fork analysis
18- `monopam sync [--remote] [--skip-push] [--skip-pull] [package]` - Primary sync command
19- `monopam changes` - Generate changelogs with Claude
20- `monopam verse init` - Initialize workspace
21- `monopam verse members` - List registry members
22- `monopam verse pull` - Pull verse member repos
23- `monopam verse sync` - Sync verse workspace
24
25---
26
27## Implementation Plan
28
29### Phase 1: Verse Remotes (Auto-managed)
30
31#### Changes to `monopam sync`
32
33Add verse remote management to the sync process:
34
351. **During fetch phase**: For each verse member in registry:
36 - Scan their monorepo for subtrees
37 - For matching subtrees in our `src/`:
38 - Add git remote named `verse/<handle>` pointing to their `src/` checkout
39 - If remote exists but URL changed, update it
40 - Fetch from the remote
41
422. **Cleanup**: Remove verse remotes for:
43 - Members no longer in registry
44 - Repos we no longer have
45
46#### Data Flow
47
48```
49sync starts
50 ├── push phase (existing)
51 ├── fetch phase (existing)
52 │ └── NEW: for each verse member with matching repos:
53 │ └── ensure git remote in src/<repo> → verse/<member>/src/<repo>
54 │ └── git fetch verse/<handle>
55 ├── merge phase (existing)
56 ├── subtree phase (existing)
57 ├── finalize phase (existing)
58 └── remote phase (existing)
59```
60
61#### Implementation in monopam.ml
62
63```ocaml
64(* New function to manage verse remotes for a repo *)
65let ensure_verse_remotes ~proc ~fs ~config ~verse_config pkg =
66 let checkouts_root = Config.Paths.checkouts config in
67 let checkout_dir = Package.checkout_dir ~checkouts_root pkg in
68 let repo_name = Package.repo_name pkg in
69
70 (* Get all verse members who have this repo *)
71 let verse_subtrees = Verse.get_verse_subtrees ~proc ~fs ~config:verse_config () in
72 let members_with_repo =
73 Hashtbl.find_opt verse_subtrees repo_name
74 |> Option.value ~default:[]
75 in
76
77 (* For each member, ensure remote exists *)
78 List.iter (fun (handle, verse_mono_path) ->
79 let remote_name = "verse/" ^ handle in
80 let verse_src = Fpath.(verse_mono_path / ".." / "src" / repo_name) in
81 (* Add or update remote *)
82 Git.ensure_remote ~proc ~fs ~name:remote_name ~url:verse_src checkout_dir
83 ) members_with_repo
84```
85
86---
87
88### Phase 2: Opam Metadata Sync
89
90#### New Command: `monopam opam sync`
91
92Synchronize `.opam` files from monorepo subtrees to opam-repo.
93
94```bash
95monopam opam sync # Sync all packages
96monopam opam sync eio # Sync specific package
97```
98
99**Behavior:**
100- For each package in monorepo:
101 - Read `.opam` file from subtree
102 - Compare with opam-repo version
103 - If different, copy monorepo → opam-repo (local always wins)
104 - Stage changes in opam-repo
105
106#### Integration with `monopam sync`
107
108Add `--opam` flag:
109
110```bash
111monopam sync --opam --remote # Full sync including opam metadata
112```
113
114Or make it part of finalize phase (always sync opam).
115
116#### Implementation
117
118```ocaml
119let sync_opam_files ~proc ~fs ~config pkgs =
120 let monorepo = Config.Paths.monorepo config in
121 let opam_repo = Config.Paths.opam_repo config in
122
123 List.iter (fun pkg ->
124 let name = Package.name pkg in
125 let subtree_opam = Fpath.(monorepo / Package.subtree_prefix pkg / (name ^ ".opam")) in
126 let repo_opam = Fpath.(opam_repo / "packages" / name / (name ^ ".dev") / "opam") in
127
128 (* Read both files *)
129 let subtree_content = read_file_opt ~fs subtree_opam in
130 let repo_content = read_file_opt ~fs repo_opam in
131
132 match subtree_content with
133 | None -> () (* No opam file in subtree, skip *)
134 | Some content when Some content <> repo_content ->
135 (* Copy to opam-repo *)
136 write_file ~fs repo_opam content;
137 Git.add ~proc ~fs opam_repo [Fpath.to_string repo_opam]
138 | _ -> () (* Already in sync *)
139 ) pkgs
140```
141
142---
143
144### Phase 3: Doctor Command
145
146#### New Command: `monopam doctor`
147
148Claude-powered workspace health analysis.
149
150```bash
151monopam doctor # Full analysis, text output
152monopam doctor --json # JSON output for tooling
153monopam doctor eio # Analyze specific repo
154```
155
156#### Output Structure (JSON)
157
158```json
159{
160 "timestamp": "2026-01-21T12:00:00Z",
161 "workspace": "/home/user/tangled",
162 "summary": {
163 "repos_total": 39,
164 "repos_need_sync": 2,
165 "repos_behind_upstream": 3,
166 "verse_divergences": 5
167 },
168 "repos": [
169 {
170 "name": "eio",
171 "local_sync": "in_sync",
172 "remote_sync": { "ahead": 0, "behind": 0 },
173 "verse_analysis": [
174 {
175 "handle": "alice.bsky.social",
176 "their_commits": [
177 {
178 "hash": "abc1234",
179 "subject": "Add Eio.Path.symlink support",
180 "category": "feature",
181 "priority": "medium",
182 "recommendation": "review-first",
183 "conflict_risk": "low",
184 "summary": "Adds symlink creation support to Eio.Path module"
185 },
186 {
187 "hash": "def5678",
188 "subject": "Fix race condition in Eio.Fiber.fork",
189 "category": "bug-fix",
190 "priority": "high",
191 "recommendation": "merge-now",
192 "conflict_risk": "none",
193 "summary": "Fixes potential deadlock when forking fibers under load"
194 }
195 ],
196 "suggested_action": "git fetch verse/alice.bsky.social && git cherry-pick def5678"
197 }
198 ]
199 }
200 ],
201 "recommendations": [
202 {
203 "priority": "high",
204 "action": "Merge alice's bug fix for eio (def5678)",
205 "command": "cd src/eio && git cherry-pick def5678"
206 },
207 {
208 "priority": "medium",
209 "action": "Run monopam sync to resolve local sync issues",
210 "command": "monopam sync"
211 }
212 ],
213 "warnings": [
214 "opam-repo has uncommitted changes",
215 "verse/alice.bsky.social/ is 10 commits behind"
216 ]
217}
218```
219
220#### Text Rendering
221
222```
223=== Monopam Doctor Report ===
224Generated: 2026-01-21 12:00:00
225
226Summary:
227 39 repos tracked
228 2 need local sync
229 3 behind upstream
230 5 verse divergences
231
232─────────────────────────────────────────
233
234eio (diverged from alice.bsky.social)
235
236 Their commits (2):
237
238 [HIGH] def5678 Fix race condition in Eio.Fiber.fork
239 Category: bug-fix | Risk: none | Action: merge-now
240 → Fixes potential deadlock when forking fibers under load
241
242 [MED] abc1234 Add Eio.Path.symlink support
243 Category: feature | Risk: low | Action: review-first
244 → Adds symlink creation support to Eio.Path module
245
246 Suggested: git fetch verse/alice.bsky.social && git cherry-pick def5678
247
248─────────────────────────────────────────
249
250Recommendations:
251 1. [HIGH] Merge alice's bug fix for eio (def5678)
252 $ cd src/eio && git cherry-pick def5678
253
254 2. [MED] Run monopam sync to resolve local sync issues
255 $ monopam sync
256
257Warnings:
258 • opam-repo has uncommitted changes
259 • verse/alice.bsky.social/ is 10 commits behind
260```
261
262#### Claude Integration
263
264Use the existing `claude` OCaml library for analysis:
265
266```ocaml
267module Doctor = struct
268 type commit_analysis = {
269 hash: string;
270 subject: string;
271 category: [`Security_fix | `Bug_fix | `Feature | `Refactor | `Docs | `Test];
272 priority: [`Critical | `High | `Medium | `Low];
273 recommendation: [`Merge_now | `Review_first | `Skip | `Needs_discussion];
274 conflict_risk: [`None | `Low | `Medium | `High];
275 summary: string;
276 }
277
278 let analyze_commits ~commits ~our_branch ~their_handle =
279 let prompt = Format.asprintf {|
280You are analyzing git commits from a collaborator's repository.
281
282Repository context:
283- Our branch: %s
284- Their handle: %s
285
286Commits to analyze:
287%s
288
289For each commit, provide JSON with:
290- category: security-fix, bug-fix, feature, refactor, docs, test
291- priority: critical, high, medium, low
292- recommendation: merge-now, review-first, skip, needs-discussion
293- conflict_risk: none, low, medium, high
294- summary: one-line description of what the commit does
295
296Respond with a JSON array of analyses.
297|} our_branch their_handle (format_commits commits) in
298
299 Claude.chat ~model:"claude-sonnet-4-20250514" ~messages:[
300 Claude.Message.user prompt
301 ] ()
302 |> parse_analysis_response
303end
304```
305
306---
307
308## Implementation Order
309
3101. **Phase 1a**: Add `ensure_verse_remotes` function to monopam.ml
3112. **Phase 1b**: Integrate verse remote management into sync fetch phase
3123. **Phase 2a**: Add `sync_opam_files` function
3134. **Phase 2b**: Add `monopam opam sync` command (or integrate into sync)
3145. **Phase 3a**: Create `doctor.ml` module with types and Claude integration
3156. **Phase 3b**: Add `monopam doctor` command with JSON/text output
316
317---
318
319## Files to Modify/Create
320
321### Modify
322- `monopam/lib/monopam.ml` - Add verse remote management, opam sync
323- `monopam/lib/monopam.mli` - Export new functions
324- `monopam/bin/main.ml` - Add doctor command, opam subcommand
325
326### Create
327- `monopam/lib/doctor.ml` - Doctor analysis logic
328- `monopam/lib/doctor.mli` - Doctor interface
329- `monopam/lib/opam_sync.ml` - Opam metadata sync logic (optional, could be in monopam.ml)