gabriel / musehub public
feat BREAKING feat/fork-repo #1 / 1
gabriel · 167 days ago · Apr 12, 2026 · Diff

feat: add fork-a-repo feature with full MCP tooling and test coverage

- MusehubFork ORM model (musehub_forks table) with unique(source_repo_id, forked_by) constraint - ForkRepoRequest Pydantic model for fork creation request body - fork_repo() service: validates public-only, no self-fork, no duplicate; creates fork repo + fork record - list_repo_forks_flat() service: flat UserForksResponse for GET /repos/{id}/forks - list_repo_forks() service: ForkNetworkResponse tree for GET /repos/{id}/fork-network - get_user_forks() service: all repos forked by a given identity handle - POST /api/repos/{repo_id}/fork — 201 on success, 403/404/409 on error, requires repo:write scope - GET /api/repos/{repo_id}/forks — public, returns flat fork list - GET /api/repos/{repo_id}/fork-network — public, returns fork tree - GET /api/users/{username}/forks — public, returns user's forked repos - 4 MCP tools: musehub_fork_repo, musehub_list_repo_forks, musehub_get_fork_network, musehub_get_user_forks - MCP dispatcher wiring for all 4 tools - 3 MCP executor functions (list_repo_forks, get_fork_network, get_user_forks) - 24 tests: happy paths, 5 error cases, empty states, public access, shape validation, multi-fork - type-contracts.md regenerated (301 types, 43 ORM models)

sha256:fcbd65cac80cb90481487b9e2e10aa002e210d6626fa649a9ba04c57e6ca05aa sha
+75 ~148 −19 symbols
sha256:f30c4142fb33315686d586917735be87e2cc5eaa34f5a5300c6b287b3b95e7b2 snapshot
+75
symbols added
~148
symbols modified
−19
symbols removed
0
dead code introduced
Semantic Changes 242 symbols
~ tests/test_musehub_forks.py .py 35 symbols added
+ _TEST_HANDLE variable variable _TEST_HANDLE L51–51
+ _create_private_repo function async_function _create_private_repo L69–81
+ _create_public_repo function async_function _create_public_repo L54–66
+ _seed_identity function async_function _seed_identity L84–94
+ AsyncClient import import AsyncClient L40–40
+ AsyncSession import import AsyncSession L41–41
+ MusehubIdentity import import MusehubIdentity L43–43
+ MusehubRepo import import MusehubRepo L43–43
+ StrDict import import StrDict L44–44
+ annotations import import annotations L37–37
+ pytest import import pytest L39–39
+ test_fork_network_empty_children_when_no_forks function async_function test_fork_network_empty_children_when_no_forks L541–564
+ test_fork_network_has_root_and_children function async_function test_fork_network_has_root_and_children L497–538
+ test_fork_network_no_auth_required function async_function test_fork_network_no_auth_required L575–595
+ test_fork_network_returns_404_for_missing_repo function async_function test_fork_network_returns_404_for_missing_repo L567–572
+ test_fork_nonexistent_repo_returns_404 function async_function test_fork_nonexistent_repo_returns_404 L272–282
+ test_fork_own_repo_returns_403 function async_function test_fork_own_repo_returns_403 L314–328
+ test_fork_private_repo_returns_403 function async_function test_fork_private_repo_returns_403 L285–311
+ test_fork_public_repo_returns_201 function async_function test_fork_public_repo_returns_201 L102–140
+ test_fork_requires_auth function async_function test_fork_requires_auth L368–385
+ test_fork_response_contains_all_required_fields function async_function test_fork_response_contains_all_required_fields L680–720
+ test_fork_same_repo_twice_returns_409 function async_function test_fork_same_repo_twice_returns_409 L331–365
+ test_fork_sets_description_with_attribution function async_function test_fork_sets_description_with_attribution L143–173
+ test_fork_with_custom_description function async_function test_fork_with_custom_description L237–264
+ test_fork_with_custom_name function async_function test_fork_with_custom_name L176–204
+ test_fork_with_private_visibility function async_function test_fork_with_private_visibility L207–234
+ test_get_user_forks_404_for_unknown_user function async_function test_get_user_forks_404_for_unknown_user L658–663
+ test_get_user_forks_empty_for_new_user function async_function test_get_user_forks_empty_for_new_user L603–612
+ test_get_user_forks_no_auth_required function async_function test_get_user_forks_no_auth_required L666–672
+ test_get_user_forks_shows_fork_after_creation function async_function test_get_user_forks_shows_fork_after_creation L615–655
+ test_list_forks_empty_when_no_forks function async_function test_list_forks_empty_when_no_forks L393–417
+ test_list_forks_no_auth_required function async_function test_list_forks_no_auth_required L460–481
+ test_list_forks_returns_404_for_missing_repo function async_function test_list_forks_returns_404_for_missing_repo L484–489
+ test_list_forks_shows_fork_after_creation function async_function test_list_forks_shows_fork_after_creation L420–457
+ test_multiple_forks_appear_in_list function async_function test_multiple_forks_appear_in_list L728–786
~ docs/reference/type-contracts.md .md 23 symbols added, 18 symbols removed, 76 symbols modified
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
− py`@L1064 section ActivityEventResponse — `musehub/models/musehub.py` L1064–1080
− py` section MCPInputSchemaWire — `musehub/contracts/mcp_types.py` L911–923
− py` section MCPPropertyDefWire — `musehub/contracts/mcp_types.py` L924–941
− py` section MCPToolDefWire — `musehub/contracts/mcp_types.py` L942–956
− py` section MilestoneCreate — `musehub/models/musehub.py` L1806–1818
− py` section MilestoneListResponse — `musehub/models/musehub.py` L1819–1829
− py` section MilestoneResponse — `musehub/models/musehub.py` L1830–1849
− py` section MilestoneUpdate — `musehub/api/routes/musehub/milestones.py` L741–754
− py` section MusehubIssueMilestone — `musehub/db/musehub_models.py` L4197–4212
− py` section MusehubMilestone — `musehub/db/musehub_models.py` L4213–4237
− py` section DAWToolCallMessage — `musehub/contracts/mcp_types.py` L91–104
− py` section ElicitationAction — `musehub/contracts/mcp_types.py` L118–129
− py` section ElicitationRequest — `musehub/contracts/mcp_types.py` L130–144
− py` section ElicitationResponse — `musehub/contracts/mcp_types.py` L145–156
− py` section MCPInitializeParams — `musehub/contracts/mcp_types.py` L243–255
− py` section MCPInputSchema — `musehub/contracts/mcp_types.py` L282–294
− py` section MCPPropertyDef — `musehub/contracts/mcp_types.py` L295–312
− py` section SessionInfo — `musehub/contracts/mcp_types.py` L437–455
+ py` section _S3Client — `musehub/storage/backends.py` L4649–4655
+ py` section _S3Response — `musehub/storage/backends.py` L4656–4662
+ py` section _StreamingBody — `musehub/storage/backends.py` L4663–4669
+ py` section CreateIdentityRequest — `musehub/api/routes/api/identities.py` L633–649
+ py` section ForkRepoRequest — `musehub/models/musehub.py` L1554–1566
+ py` section MCPInputSchemaWire — `musehub/muse_contracts/mcp_types.py` L3622–3634
+ py` section MCPPropertyDefWire — `musehub/muse_contracts/mcp_types.py` L3635–3652
+ py` section MCPToolDefWire — `musehub/muse_contracts/mcp_types.py` L3653–3667
+ py`@L3700 section MyModel — `tests/test_api_contracts_section37.py` L3700–3708
+ py`@L3709 section MyModel — `tests/test_api_contracts_section37.py` L3709–3717
+ py`@L3718 section MyModel — `tests/test_api_contracts_section37.py` L3718–3726
+ py` section UpdateIdentityRequest — `musehub/api/routes/api/identities.py` L650–664
+ py` section MusehubBackgroundJob — `musehub/db/musehub_models.py` L4034–4055
+ py` section MusehubFork — `musehub/db/musehub_models.py` L4115–4131
+ py` section DAWToolCallMessage — `musehub/muse_contracts/mcp_types.py` L266–279
+ py` section ElicitationAction — `musehub/muse_contracts/mcp_types.py` L293–304
+ py` section ElicitationRequest — `musehub/muse_contracts/mcp_types.py` L305–319
+ py` section ElicitationResponse — `musehub/muse_contracts/mcp_types.py` L320–331
+ py` section MCPInitializeParams — `musehub/muse_contracts/mcp_types.py` L418–430
+ py` section MCPInputSchema — `musehub/muse_contracts/mcp_types.py` L457–469
+ py` section MCPPropertyDef — `musehub/muse_contracts/mcp_types.py` L470–487
+ py` section SessionInfo — `musehub/muse_contracts/mcp_types.py` L612–630
+ py` section _IdentityDict — `musehub/api/routes/api/identities.py` L91–113
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ py`
~ musehub/api/routes/musehub/repos.py .py 7 symbols added, 30 symbols modified
~ musehub/db/musehub_models.py .py 1 symbol added
+ MusehubFork class class MusehubFork L1067–1103
~ musehub/mcp/dispatcher.py .py 1 symbol modified
~ musehub/mcp/write_tools/repos.py .py 1 symbol added
+ execute_fork_repo function async_function execute_fork_repo L372–453
~ musehub/models/musehub.py .py 1 symbol added
+ ForkRepoRequest class class ForkRepoRequest L2497–2524
+ execute_get_fork_network function async_function execute_get_fork_network L2693–2729
+ execute_get_user_forks function async_function execute_get_user_forks L2732–2768
+ execute_list_repo_forks function async_function execute_list_repo_forks L2654–2690
~ tests/test_mcp_dispatcher.py .py 1 symbol added, 1 symbol removed, 1 symbol modified
− test_tool_catalogue_has_89_tools function function test_tool_catalogue_has_89_tools L170–172
+ test_tool_catalogue_has_93_tools function function test_tool_catalogue_has_93_tools L170–172
← Older Oldest on feat/fork-repo
All commits
Newer → Latest on feat/fork-repo

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:fcbd65cac80cb90481487b9e2e10aa002e210d6626fa649a9ba04c57e6ca05aa --body "your comment"