A monorepo management tool for the agentic ages

Add tangled.org tree links to README.md generation

Parse origin remote URL to generate hyperlinks to branch trees on
tangled.org. Links are URL-encoded (/ -> %2F) and added to:
- Project names (linking to project/* branches)
- Package names (linking to opam/patches/* branches)
- Git repo names (linking to git/patches/* branches)
- Merged Into columns (linking to project branches)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

+44 -5
+44 -5
bin/main.ml
··· 1670 1670 tm.Unix.tm_hour tm.Unix.tm_min tm.Unix.tm_sec 1671 1671 in 1672 1672 1673 + (* Get tangled.org base URL from origin remote *) 1674 + let tangled_base = 1675 + match Unpac.Git.remote_url ~proc_mgr ~cwd:git "origin" with 1676 + | None -> None 1677 + | Some url -> 1678 + (* Parse git@git.recoil.org:user/repo or similar *) 1679 + let url = String.trim url in 1680 + (* Handle git@host:user/repo format *) 1681 + if String.starts_with ~prefix:"git@" url then 1682 + match String.index_opt url ':' with 1683 + | None -> None 1684 + | Some colon_pos -> 1685 + let path = String.sub url (colon_pos + 1) (String.length url - colon_pos - 1) in 1686 + (* Strip .git suffix if present *) 1687 + let path = if String.ends_with ~suffix:".git" path then 1688 + String.sub path 0 (String.length path - 4) else path in 1689 + Some (Printf.sprintf "https://tangled.org/%s" path) 1690 + else None 1691 + in 1692 + 1693 + (* URL encode a branch name for tree URLs *) 1694 + let url_encode s = 1695 + let buf = Buffer.create (String.length s * 2) in 1696 + String.iter (fun c -> 1697 + match c with 1698 + | '/' -> Buffer.add_string buf "%2F" 1699 + | ' ' -> Buffer.add_string buf "%20" 1700 + | c -> Buffer.add_char buf c 1701 + ) s; 1702 + Buffer.contents buf 1703 + in 1704 + 1705 + (* Create a markdown link to a branch tree, or just the name if no base URL *) 1706 + let branch_link name branch = 1707 + match tangled_base with 1708 + | None -> name 1709 + | Some base -> Printf.sprintf "[%s](%s/tree/%s)" name base (url_encode branch) 1710 + in 1711 + 1673 1712 add "# Unpac Workspace Status\n\n"; 1674 1713 addf "_Last updated: %s_\n\n" timestamp; 1675 1714 ··· 1706 1745 else if wt_exists then "📂 worktree active" 1707 1746 else "✓" in 1708 1747 addf "| %s | %d | %d | %s |\n" 1709 - proj (List.length merged_opam) (List.length merged_git) status 1748 + (branch_link proj proj_branch) (List.length merged_opam) (List.length merged_git) status 1710 1749 ) project_names; 1711 1750 add "\n" 1712 1751 end; ··· 1732 1771 ) project_names in 1733 1772 1734 1773 let merged_str = if merged_into = [] then "-" 1735 - else String.concat ", " merged_into in 1774 + else String.concat ", " (List.map (fun p -> branch_link p ("project/" ^ p)) merged_into) in 1736 1775 1737 1776 let status = 1738 1777 if dirty then "⚠️ uncommitted" 1739 1778 else if has_wt then "📂 editing" 1740 1779 else "✓" in 1741 1780 1742 - addf "| %s | %d | %s | %s |\n" pkg patch_count merged_str status 1781 + addf "| %s | %d | %s | %s |\n" (branch_link pkg patches_branch) patch_count merged_str status 1743 1782 ) opam_packages; 1744 1783 add "\n" 1745 1784 end; ··· 1765 1804 ) project_names in 1766 1805 1767 1806 let merged_str = if merged_into = [] then "-" 1768 - else String.concat ", " merged_into in 1807 + else String.concat ", " (List.map (fun p -> branch_link p ("project/" ^ p)) merged_into) in 1769 1808 1770 1809 let status = 1771 1810 if dirty then "⚠️ uncommitted" 1772 1811 else if has_wt then "📂 editing" 1773 1812 else "✓" in 1774 1813 1775 - addf "| %s | %d | %s | %s |\n" repo patch_count merged_str status 1814 + addf "| %s | %d | %s | %s |\n" (branch_link repo patches_branch) patch_count merged_str status 1776 1815 ) git_repos; 1777 1816 add "\n" 1778 1817 end;