symlog follow-up: analytics, remote sharing, and ergonomic extensions
muse symlog — Follow-up Items
Background
Issue #51 delivered the full symlog foundation: per-symbol journaling at commit time,
muse symlog read CLI with filters, lifecycle management (expire/delete/GC), and
@{N} ref resolution for muse code cat, muse symlog resolve, and
muse symlog diff. All 55 test IDs (SL_01–SL_55) are green.
This issue captures the items explicitly deferred from #51 plus useful extensions that emerged during implementation.
Deferred from #51
1. Analytics commands — muse symlog coupling and muse symlog hotspots
The live symlog journal is a rich, already-written dataset. Two analytics commands can read it in O(1) per symbol without touching commit history:
muse symlog hotspots [--top N] [--file path] [--json]
— ranks symbols by churn (number of symbol-modified entries in the journal).
Complements muse code hotspots (which reads commit history) with a zero-scan
alternative that is always up to date.
muse symlog coupling [--file path] [--json]
— finds symbol pairs that share commit IDs across their logs (i.e. they always
change together). Surfaced purely from symlog data, no AST traversal needed.
2. muse blame integration
muse blame "file.py::Symbol" currently reconstructs authorship by scanning
commit history. With symlogs in place, the per-symbol change record already
contains author and commit ID at each index. blame should prefer the symlog
when available — O(1) per symbol instead of O(commits).
3. muse code narrative rewrite
muse code narrative "file.py::Symbol" generates a plain-English description of
a symbol's lifecycle. It currently works from AST diffs across commits. The symlog
journal is the canonical source of that history now — narrative should read from
symlog entries, gaining richer metadata (operation, author, born-from chains).
4. Remote symlog sharing
Symlogs are currently local-only — they are not pushed or pulled. For teams sharing a MuseHub remote, symlogs are silently missing on clones. The correct design:
muse pushbundles.muse/symlogs/alongside commits.muse pullmerges incoming symlog entries (append-only, no conflicts).muse bundle createincludes symlogs in the msgpack payload.
5. Symlog entries for non-code domains
The extractor returns {} for non-parseable files, so MIDI tracks, binary assets,
and schema files produce no symlog entries. For music repos this is the wrong
trade-off: a MIDI symbol (track::chorus) should produce a symlog entry when
the Harmony domain plugin identifies a change. Deferred until the domain plugin
API supports content-ID extraction for non-code artifacts.
6. Relative time syntax (@{1.day.ago}, @{yesterday})
@{N} addresses work by index. Git's reflog also supports relative time syntax
(HEAD@{1.hour.ago}). Symlog --since/--until cover the filtering use case,
but @{1.day.ago} as a direct ref specifier is more ergonomic in agent prompts.
New items from implementation
7. muse checkout-symbol "file.py::Symbol@{N}"
Restore a prior version of a single symbol to disk without touching any other
file. The natural extension of muse code cat "addr@{N}" — cat reads, checkout
restores. Critically useful for surgical recovery: "undo the change to this one
function without reverting the whole commit."
8. muse symlog grep PATTERN [--file path] [--json]
Search symbol bodies across their full symlog history. Answers: "when did this
symbol last contain the string deprecated?" or "which version of this function
called session.add?" Reads object store bodies via resolve_symbol_body for
each entry.
9. muse symlog show "file.py::Symbol@{N}"
Convenience alias for muse code cat "file.py::Symbol@{N}" with human-focused
output: operation, author, timestamp header, then the full symbol body. Useful for
interactive exploration ("show me what this function looked like two changes ago").
10. muse symlog stat [--file path] [--json]
Summary statistics for a symbol or file: total entries, operation breakdown (created/modified/deleted/renamed), most active author, first and last commit IDs, date range. Useful for code review context and agent decision-making.
11. Symlog entries for muse revert
muse revert records a reflog entry for the branch ref move, but writes no symlog
entries for the symbols it touches. A revert of a commit that modified compute_total
should produce a symbol-modified: revert <commit_id> entry in that symbol's log.
12. muse symlog prune-orphans [--dry-run] [--json]
Find symlog files whose symbol address no longer exists in the current snapshot
and remove them. Analogous to muse gc for unreachable objects — keeps the
.muse/symlogs/ tree clean after large-scale renames or file deletions.
All 12 items from this follow-up are now resolved — 11 implemented and shipped on
main(both~/ecosystem/museand~/ecosystem/musehub, pushed to local/staging/production), plus item 5 confirmed still genuinely blocked with a documented decision.Deferred from #51
muse symlog hotspots [--top N] [--file path](ranks symbols bysymbol-modifiedchurn) andmuse symlog coupling [--file path] [--min N] [--top N](finds symbol pairs that share commit IDs), both reading purely from the already-written journal — zero commit-history scan.muse code blameintegration — now prefers the symlog journal (O(1) per symbol) over the full BFS commit-DAG walk, falling back only when no journal exists,--from REFis given, or--kind moved/signatureis requested (categories the journal can't produce).muse code narrativerewrite — uses the journal as an index narrowing down which commits to examine, rather than a literal data-source swap (the journal's classification is coarser than narrative's impl-vs-signature distinction). Incidentally fixed a real latent bug: the original fixed-address BFS walk silently stopped at rename boundaries; the journal'sborn_fromchain now crosses them correctly.muse push --symlog/push.symlog=trueconfig,muse pullalways best-effort merges,muse bundle create/unbundlecarry symlogs too (caught and fixed a real bug here:_load_bundle()'s defensive field reconstruction was silently dropping the newsymlogskey). Server side: newmusehub_symlog_entriestable (migration 0081) +wire_push_symlog/wire_get_symlogroutes, mirroring the reflog remote-sharing precedent from #54.extract_symbols()imports the code AST parser directly with no domain-plugin dispatch hook. Documented as a confirmed decision rather than silently left alone.@{1.hour.ago},@{yesterday}) — mirrors #54 item 1's reflog equivalent.New items from implementation
muse code checkout-symbol+ symlog ref —ADDRESScan now embed@{N}directly instead of requiring--commit REF.muse symlog grep PATTERN [--file path]— searches reconstructed symbol bodies across full history for a case-insensitive substring match.muse symlog show "file.py::Symbol@{N}"— human-focused header + full body view.muse symlog stat— summary statistics (operation breakdown, most active author, first/last commit).muse revert— reverts now write symlog entries for the symbols they touch, matching normal commits.muse symlog prune-orphans [--dry-run]— removes journals for symbols that no longer exist at HEAD.Every item shipped with full TDD — and this ticket surfaced a real process lesson along the way: two early test drafts (for blame and narrative's symlog fast paths) initially asserted only on final output, which the pre-existing code already produced correctly on its own — reverting the fix left those tests passing, a false-negative RED. Both were rewritten to prove the actual mechanism (monkeypatching the old code path to raise, confirming the new path is what runs) before being accepted. Every item also got a targeted regression suite and live verification via
muse-devagainst disposable/tmpsandboxes; item 4 was additionally verified end-to-end against the real local MuseHub hub.Closing.