# Monopam CLI Improvements Plan ## Design Decisions (Clarified) 1. **Verse remotes**: Auto-add on `monopam sync`, remove outdated ones for all verse members 2. **Add/remove commands**: Removed from CLI - use agent skills instead 3. **Doctor output**: Structured JSON with per-repo recommendations, rendered to text by CLI with `--json` option 4. **Verse remote URL**: Point to `src/` checkout (individual repo) 5. **Opam sync direction**: Local metadata always trumps opam-repo metadata 6. **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 analysis - `monopam sync [--remote] [--skip-push] [--skip-pull] [package]` - Primary sync command - `monopam changes` - Generate changelogs with Claude - `monopam verse init` - Initialize workspace - `monopam verse members` - List registry members - `monopam verse pull` - Pull verse member repos - `monopam 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: 1. **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/` pointing to their `src/` checkout - If remote exists but URL changed, update it - Fetch from the remote 2. **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/ → verse//src/ │ └── git fetch verse/ ├── merge phase (existing) ├── subtree phase (existing) ├── finalize phase (existing) └── remote phase (existing) ``` #### Implementation in monopam.ml ```ocaml (* 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. ```bash monopam opam sync # Sync all packages monopam opam sync eio # Sync specific package ``` **Behavior:** - For each package in monorepo: - Read `.opam` file from subtree - Compare with opam-repo version - If different, copy monorepo → opam-repo (local always wins) - Stage changes in opam-repo #### Integration with `monopam sync` Add `--opam` flag: ```bash monopam sync --opam --remote # Full sync including opam metadata ``` Or make it part of finalize phase (always sync opam). #### Implementation ```ocaml 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. ```bash monopam doctor # Full analysis, text output monopam doctor --json # JSON output for tooling monopam doctor eio # Analyze specific repo ``` #### Output Structure (JSON) ```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: ```ocaml 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 1. **Phase 1a**: Add `ensure_verse_remotes` function to monopam.ml 2. **Phase 1b**: Integrate verse remote management into sync fetch phase 3. **Phase 2a**: Add `sync_opam_files` function 4. **Phase 2b**: Add `monopam opam sync` command (or integrate into sync) 5. **Phase 3a**: Create `doctor.ml` module with types and Claude integration 6. **Phase 3b**: Add `monopam doctor` command with JSON/text output --- ## Files to Modify/Create ### Modify - `monopam/lib/monopam.ml` - Add verse remote management, opam sync - `monopam/lib/monopam.mli` - Export new functions - `monopam/bin/main.ml` - Add doctor command, opam subcommand ### Create - `monopam/lib/doctor.ml` - Doctor analysis logic - `monopam/lib/doctor.mli` - Doctor interface - `monopam/lib/opam_sync.ml` - Opam metadata sync logic (optional, could be in monopam.ml)