gabriel / musehub public
feat BREAKING main #17 / 34
AI Agent gabriel · 131 days ago · May 9, 2026 · Diff

feat: cross-repo and ref-pinned symbol anchors

Anchors previously only resolved to the issue's own repo at main. Filing issue #38 exposed this — the bridge.py fix lived in gabriel/muse but the anchor was stored in gabriel/musehub, generating a broken URL.

New anchor grammar: owner/repo::file/path.py::Symbol@ref cross-repo, ref-pinned file/path.py::Symbol@ref same-repo, ref-pinned file/path.py::Symbol same-repo (unchanged)

Disambiguation: 'owner/repo' is exactly one slash, no dots in either segment. File paths always have an extension or multiple path components, so 'gabriel/muse' is a repo slug but 'src/billing.py' is a file path.

New service: musehub/services/symbol_anchor.py SymbolAnchorRef dataclass — owner, repo, file_path, symbol_name, ref parse_symbol_anchor(raw) — pure parser, no I/O blob_url(base, default_owner, default_repo) — resolves to correct repo display_label() — cross-repo anchors prefix with 'owner/repo::'

Template wiring: parse_symbol_anchor registered as Jinja2 global in _templates.py issue_detail.html uses parser instead of raw split('::') Cross-repo anchors get isd-anchor--cross-repo class and owner/repo prefix

45 tests passing (27 unit + 18 SSR integration).

sha256:fc451e2dc8f414c220717efbc79cbb5dcacd39f8fa131fa068ba62a7399687a4 sha
+50 symbols
sha256:4c154bcb185fddbcae1446f51df21653dc7991663cfc025f6910df8bcaf34de8 snapshot
+50
symbols added
0
dead code introduced
Semantic Changes 50 symbols
~ musehub/services/symbol_anchor.py .py 9 symbols added
+ SymbolAnchorRef class class SymbolAnchorRef L33–68
+ blob_url method method blob_url L44–57
+ display_label method method display_label L59–68
+ is_cross_repo method method is_cross_repo L41–42
+ _looks_like_owner_repo function function _looks_like_owner_repo L71–81
+ _strip_ref function function _strip_ref L84–89
+ annotations import import annotations L27–27
+ dataclass class import dataclass L29–29
+ parse_symbol_anchor function function parse_symbol_anchor L92–127
~ tests/test_symbol_anchor_parser.py .py 35 symbols added
+ BASE variable variable BASE L61–61
+ OWN_OWNER variable variable OWN_OWNER L62–62
+ OWN_REPO variable variable OWN_REPO L63–63
+ _url function function _url L66–67
+ SymbolAnchorRef import import SymbolAnchorRef L54–54
+ annotations import import annotations L50–50
+ parse_symbol_anchor import import parse_symbol_anchor L54–54
+ pytest import import pytest L52–52
+ test_blob_url_commit_ref_in_url function function test_blob_url_commit_ref_in_url L244–247
+ test_blob_url_cross_repo_uses_own_ref function function test_blob_url_cross_repo_uses_own_ref L234–236
+ test_blob_url_cross_repo_with_symbol function function test_blob_url_cross_repo_with_symbol L224–226
+ test_blob_url_default_ref_is_main function function test_blob_url_default_ref_is_main L239–241
+ test_blob_url_same_repo_file_only function function test_blob_url_same_repo_file_only L214–216
+ test_blob_url_same_repo_with_symbol function function test_blob_url_same_repo_with_symbol L219–221
+ test_blob_url_uses_explicit_ref_same_repo function function test_blob_url_uses_explicit_ref_same_repo L229–231
+ test_cross_repo_deeply_nested_file function function test_cross_repo_deeply_nested_file L155–160
+ test_cross_repo_file_and_symbol function function test_cross_repo_file_and_symbol L130–136
+ test_cross_repo_file_only function function test_cross_repo_file_only L121–127
+ test_cross_repo_with_branch_ref function function test_cross_repo_with_branch_ref L139–145
+ test_cross_repo_with_commit_ref function function test_cross_repo_with_commit_ref L148–152
+ test_deeply_nested_path_not_mistaken_for_owner_repo function function test_deeply_nested_path_not_mistaken_for_owner_repo L175–179
+ test_display_label_cross_repo_shows_prefix function function test_display_label_cross_repo_shows_prefix L265–269
+ test_display_label_cross_repo_with_ref function function test_display_label_cross_repo_with_ref L272–275
+ test_display_label_same_repo_file_and_symbol function function test_display_label_same_repo_file_and_symbol L255–257
+ test_display_label_same_repo_file_only function function test_display_label_same_repo_file_only L260–262
+ test_file_with_extension_not_mistaken_for_owner_repo function function test_file_with_extension_not_mistaken_for_owner_repo L168–172
+ test_is_cross_repo_false_for_same_repo_anchor function function test_is_cross_repo_false_for_same_repo_anchor L204–206
+ test_is_cross_repo_true_for_cross_repo_anchor function function test_is_cross_repo_true_for_cross_repo_anchor L199–201
+ test_same_repo_file_and_symbol function function test_same_repo_file_and_symbol L84–89
+ test_same_repo_file_only function function test_same_repo_file_only L75–81
+ test_same_repo_file_only_with_ref function function test_same_repo_file_only_with_ref L109–113
+ test_same_repo_with_branch_ref function function test_same_repo_with_branch_ref L92–97
+ test_same_repo_with_commit_ref function function test_same_repo_with_commit_ref L100–106
+ test_single_segment_file_is_same_repo function function test_single_segment_file_is_same_repo L182–185
+ test_symbol_with_dots_preserved function function test_symbol_with_dots_preserved L188–191
+ parse_symbol_anchor import import parse_symbol_anchor L30–30
+ test_cross_repo_anchor_shows_repo_prefix_in_label function async_function test_cross_repo_anchor_shows_repo_prefix_in_label L268–281
+ test_cross_repo_anchor_url_points_to_other_repo function async_function test_cross_repo_anchor_url_points_to_other_repo L220–235
+ test_cross_repo_anchor_with_ref_in_url function async_function test_cross_repo_anchor_with_ref_in_url L238–250
+ test_same_repo_anchor_still_resolves_to_own_repo function async_function test_same_repo_anchor_still_resolves_to_own_repo L253–265
+ test_same_repo_anchor_with_ref_in_url function async_function test_same_repo_anchor_with_ref_in_url L284–296

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:fc451e2dc8f414c220717efbc79cbb5dcacd39f8fa131fa068ba62a7399687a4 --body "your comment"